Kirill Simonov avatar Kirill Simonov committed 952b6ec

Extract the extension from HTSQL repository, document, clean up.

Comments (0)

Files changed (12)

+syntax: glob
+*.pyc
+*.pyo
+*.orig
+.*.sw?
+*.html
+*.egg-info
+_build
+build
+dist
+sandbox
+Copyright (c) 2013, Prometheus Research, LLC
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+* Redistributions of source code must retain the above copyright
+  notice, this list of conditions and the following disclaimer.
+
+* Redistributions in binary form must reproduce the above copyright
+  notice, this list of conditions and the following disclaimer in the
+  documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+include README NEWS LICENSE demo/Makefile demo/conf.py demo/*.rst
+
+List of Changes
+===============
+
+
+0.0.1 (2013-XX-XX)
+------------------
+
+* Initial release.
+
+
+*********************************************************
+  ``sphinxcontrib-htsql`` -- HTSQL extension for Sphinx
+*********************************************************
+
+Overview
+========
+
+``sphinxcontrib-htsql`` is an extension for embedding HTSQL_ queries
+in Sphinx_ documents.
+
+You can see this extension in action at
+http://htsql.org/doc/overview.html#htsql-in-a-nutshell.
+
+This software is written by Kirill Simonov (`Prometheus Research, LLC`_)
+and released under BSD license.
+
+
+Usage
+=====
+
+To use this extension with your Sphinx website, add the following line
+to ``conf.py``::
+
+    extensions.append('sphinxcontrib.htsql')
+
+You also need to specify the address of the HTSQL service::
+
+    htsql_root = 'http://demo.htsql.org'
+
+Now you can add HTSQL queries to any Sphinx document using ``htsql``
+directive::
+
+    .. htsql:: /school?campus='old'
+
+This directive executes the query and inserts a composite block
+consisting of the query string and the query output in tabular form.
+
+If a query spans many lines, it could be given as a content of the
+directive::
+
+    .. htsql::
+
+       /school.define(num_dept := count(department))
+              {code, num_dept}?num_dept>3
+
+If you want to display one query with the output of another query, use
+``output`` option.  It is useful for describing destructive operations,
+not-yet-implemented features or escaping rules.  You need to quote
+whitespace and special characters manually::
+
+    .. htsql:: /school?campus='north'
+       :output: /school?campus='south'
+
+Normally, the ``htsql`` directive expects the query to be valid.  Use
+``error`` option to indicate that the query is invalid and you want to
+show the error message::
+
+    .. htsql:: /school{code, title}
+       :error:
+
+Normally, the query is rendered with a link leading to the HTSQL
+service.  Use ``no-link`` option to disable this feature::
+
+    .. htsql:: /school?exists(department)
+       :no-link:
+
+Use ``no-output`` option to render the query, but not the output::
+
+    .. htsql:: /school[ns]
+       :no-output:
+
+Use ``no-input`` option to render the query output, but not the query
+itself::
+
+    .. htsql:: /school[ns]
+       :no-input:
+
+Normally, query output is rendered as a table.  Use option ``raw`` to
+render the output unformatted::
+
+    .. htsql:: /school[ns]/:json
+       :raw:
+
+Use ``cut`` option to truncate the query output up to the given number
+of lines.  This option works both with tabular and raw output::
+
+    .. htsql:: /school
+       :cut: 3
+
+
+Reference
+=========
+
+Directives
+----------
+
+``htsql-root``
+    Specifies the address of the HTSQL service.
+    
+    This directive overrides the ``htsql_root`` configuration parameter
+    for the rest of the current document.
+
+    This directive has no body and no options.
+
+``htsql``
+    Inserts an output of an HTSQL query.
+
+    The query could be specified as a parameter of the directive or (for
+    multi-line queries) as the directive content.
+
+    This directive is rendered as a composite block with two entries:
+
+    * A literal block with the query string and a link to the HTSQL
+      service.
+
+    * A table with the query output.
+
+    Options:
+
+    ``output``
+        Render the output of a query different from the one specified
+        in the directive.
+
+    ``error``
+        Accept invalid queries and render the error message in the
+        output block.
+
+    ``no-link``
+        Do not link the query block to the HTSQL service.
+
+    ``no-input``
+        Do not render the query block.
+
+    ``no-output``
+        Do not render the output block.
+
+    ``raw``
+        Render the output unformatted.
+
+    ``cut``
+        Truncate the output to the given number of lines.
+
+Configuration parameters
+------------------------
+
+``htsql_root``
+    The address of HTSQL service.
+
+CSS classes
+-----------
+
+``htsql-io``
+    Wraps the output of ``htsql`` directive.
+
+``htsql-input``
+    Wraps the query block.
+
+``htsql-output``
+    Wraps the output block.
+
+``htsql-link``
+    Wraps a link to the HTSQL service.
+
+``htsql-arrow-link``
+    Wraps an arrow symbol with a link to the HTSQL service.
+
+
+.. _Sphinx: http://sphinx-doc.org/
+.. _HTSQL: http://htsql.org/
+.. _Prometheus Research, LLC: http://prometheusresearch.com/
+
+
+.. vim: set spell spelllang=en textwidth=72:
+
+# You can set these variables from the command line.
+SPHINXBUILD   = sphinx-build
+BUILDDIR      = _build
+
+ifeq ($(shell $(SPHINXBUILD) 2> /dev/null; echo $$?), 127)
+define MSG
+
+
+The 'sphinx-build' command was not found. Make sure you have Sphinx
+installed, then set the SPHINXBUILD environment variable to point
+to the full path of the 'sphinx-build' executable. Alternatively you
+may add the Sphinx directory to PATH.
+
+If you don't have Sphinx installed, grab it from
+http://sphinx-doc.org/
+endef
+$(error $(MSG))
+endif
+
+html:
+	$(SPHINXBUILD) -b html . $(BUILDDIR)/html
+	@echo
+	@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
+
+clean:
+	-rm -rf $(BUILDDIR)
+
+
+# Enable `sphinxcontrib-htsql` extension.
+extensions = ['sphinxcontrib.htsql']
+
+# The URL for HTSQL service.
+htsql_root = 'http://demo.htsql.org'
+
+# Standard configuration.
+project = u'An HTSQL-based Project'
+master_doc = 'index'
+highlight_language = 'htsql'
+exclude_patterns = ['_build']
+
+HTSQL Demo
+==========
+
+This document demonstrate different options of the ``htsql`` directive.
+
+Simple query
+------------
+
+The ``htsql`` directive takes a query as a parameter.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school?campus='old'
+
+.. htsql:: /school?campus='old'
+
+Multiline query
+---------------
+
+If a query spans many lines, it could be given as a content of the directive.
+
+.. sourcecode:: rst
+
+   .. htsql::
+
+      /school.define(num_dept := count(department))
+             {code, num_dept}?num_dept>3
+
+.. htsql::
+
+   /school.define(num_dept := count(department))
+          {code, num_dept}?num_dept>3
+
+Custom output
+-------------
+
+If you want to display one query with the output of another query, use
+``output`` option.  It is useful for describing destructive operations,
+not-yet-implemented features or escaping rules.  You need to quote
+whitespace and special characters manually.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school?campus='north'
+      :output: /school?campus='south'
+
+.. htsql:: /school?campus='north'
+   :output: /school?campus='south'
+
+Errors
+------
+
+Normally, the ``htsql`` directive expects the query to be valid.  Use ``error``
+option to indicate that the query is invalid and you want to show the error
+message.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school{code, title}
+      :error:
+
+.. htsql:: /school{code, title}
+   :error:
+
+Disable linking
+---------------
+
+Normally, the query is rendered with a link leading to the HTSQL service.  Use
+``no-link`` option to disable this feature.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school?exists(department)
+      :no-link:
+
+.. htsql:: /school?exists(department)
+   :no-link:
+
+Hiding query output
+-------------------
+
+Use ``no-output`` option to render the query, but not the output.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school[ns]
+      :no-output:
+
+.. htsql:: /school[ns]
+   :no-output:
+
+Hiding query
+------------
+
+Use ``no-input`` option to render the query output, but not the query itself.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school[ns]
+      :no-input:
+
+.. htsql:: /school[ns]
+   :no-input:
+
+Raw output
+----------
+
+Normally, query output is rendered as a table.  Use option ``raw`` to render
+the output unformatted.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school[ns]/:json
+      :raw:
+
+.. htsql:: /school[ns]/:json
+   :raw:
+
+Truncating output
+-----------------
+
+Use ``cut`` option to truncate the query output up to the given number
+of lines.  This option works both with tabular and raw output.
+
+.. sourcecode:: rst
+
+   .. htsql:: /school
+      :cut: 3
+
+.. htsql:: /school
+   :cut: 3
+
+#
+# Copyright (c) 2013, Prometheus Research, LLC
+#
+
+
+from setuptools import setup
+
+
+NAME = "sphinxcontrib-htsql"
+VERSION = "0.1"
+DESCRIPTION = "A Sphinx HTSQL Extension"
+LONG_DESCRIPTION = open('README').read()
+AUTHOR = "Kirill Simonov (Prometheus Research, LLC)"
+AUTHOR_EMAIL = "xi@resolvent.net"
+LICENSE = "BSD"
+URL = "http://bitbucket.org/prometheus/sphinxcontrib-htsql"
+DOWNLOAD_URL = "http://pypi.python.org/pypi/sphinxcontrib-htsql"
+CLASSIFIERS = [
+        'Development Status :: 4 - Beta',
+        'Intended Audience :: Developers',
+        'License :: OSI Approved :: BSD License',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Topic :: Documentation',
+        'Topic :: Text Processing',
+]
+PLATFORMS = 'any'
+REQUIRES = ['Sphinx']
+PACKAGES = ['sphinxcontrib']
+ZIP_SAFE = False
+INCLUDE_PACKAGE_DATA = True
+NAMESPACE_PACKAGES = ['sphinxcontrib']
+
+
+setup(name=NAME,
+      version=VERSION,
+      description=DESCRIPTION,
+      long_description=LONG_DESCRIPTION,
+      author=AUTHOR,
+      author_email=AUTHOR_EMAIL,
+      license=LICENSE,
+      url=URL,
+      download_url=DOWNLOAD_URL,
+      classifiers=CLASSIFIERS,
+      platforms=PLATFORMS,
+      requires=REQUIRES,
+      packages=PACKAGES,
+      zip_safe=ZIP_SAFE,
+      include_package_data=INCLUDE_PACKAGE_DATA,
+      namespace_packages=NAMESPACE_PACKAGES)
+
+

sphinxcontrib/__init__.py

+#
+# Copyright (c) 2013, Prometheus Research, LLC
+#
+
+
+__import__('pkg_resources').declare_namespace(__name__)
+
+

sphinxcontrib/htsql.css

+
+div.htsql-input a.htsql-link {
+  color: inherit;
+  background-color: inherit;
+  text-decoration: none;
+}
+
+div.htsql-input a.htsql-arrow-link {
+  float: right;
+  font-size: 105%;
+  text-decoration: none;
+}
+
+div.htsql-input a.htsql-arrow-link:after {
+  content: "\25E5";
+}
+
+div.htsql-output {
+  overflow: auto;
+}
+
+div.htsql-output table.docutils tbody tr td.htsql-cut {
+  border-bottom: 0;
+}
+
+div.htsql-output table.docutils tbody tr td.htsql-cut:after {
+  content: "\2026";
+}
+
+div.htsql-output table.docutils tbody tr td.htsql-false-val {
+  font-style: italic;
+}
+
+div.htsql-output table.docutils tbody tr td.htsql-null-val:after,
+div.htsql-output table.docutils tbody tr td.htsql-empty-val:after {
+  content: "\A0";
+}
+

sphinxcontrib/htsql.py

+#
+# Copyright (c) 2013, Prometheus Research, LLC
+#
+
+
+from docutils import nodes
+from docutils.parsers.rst import Directive, directives
+from sphinx.util.osutil import copyfile
+from pygments.lexer import RegexLexer
+from pygments.token import (Punctuation, Text, Operator, Name, String, Number,
+        Comment)
+
+import re
+import os, os.path
+from urllib2 import quote, urlopen, Request, HTTPError, URLError
+from cgi import escape
+from json import loads
+
+
+class HtsqlLexer(RegexLexer):
+
+    name = 'HTSQL'
+    aliases = ['htsql']
+    filenames = ['*.htsql']
+    mimetypes = ['text/x-htsql', 'application/x-htsql']
+
+    escape_regexp = re.compile(r'%(?P<code>[0-9A-Fa-f]{2})')
+
+    tokens = {
+        'root': [
+            (r'\s+', Text),
+            (r'\#[^\r\n]*', Comment.Single),
+            (r'\'(?:[^\']|\'\')*\'', String),
+            (r'(?:\d+(?:\.\d*)?|\.\d+)[eE][+-]?\d+', Number),
+            (r'\d+\.\d*|\.\d+', Number),
+            (r'\d+', Number),
+            (r'(?<=:)\w+', Name.Function),
+            (r'\w+(?=\s*\()', Name.Function),
+            (r'\w+', Name.Builtin),
+            (r'~|!~|<=|<|>=|>|==|=|!==|!=|'
+             r'\^|\?|->|@|:=|!|&|\||\+|-|\*|/', Operator),
+            (r'\(|\)|\{|\}|\.|,|:|;|\$', Punctuation),
+            (r'\[', Punctuation, 'identity'),
+        ],
+        'identity': [
+            (r'\s+', Text),
+            (r'\(|\[', Punctuation, '#push'),
+            (r'\)|\]', Punctuation, '#pop'),
+            (r'\.', Punctuation),
+            (r'[\w-]+', String),
+            (r'\'(?:[^\']|\'\')*\'', String),
+            (r'\$', Punctuation, 'name'),
+        ],
+        'name': [
+            (r'\s+', Text),
+            (r'\w+', Name.Builtin, '#pop'),
+        ],
+    }
+
+    def get_tokens_unprocessed(self, text):
+        octets = text.encode('utf-8')
+        quotes = []
+        for match in self.escape_regexp.finditer(octets):
+            quotes.append(match.start())
+        octets = self.escape_regexp.sub(lambda m: chr(int(m.group('code'), 16)),
+                                        octets)
+        try:
+            text = octets.decode('utf-8')
+        except UnicodeDecodeError:
+            quotes = []
+        token_stream = super(HtsqlLexer, self).get_tokens_unprocessed(text)
+        pos_inc = 0
+        for pos, token, value in token_stream:
+            pos += pos_inc
+            while quotes and pos <= quotes[0] < pos+len(value):
+                idx = quotes.pop(0)-pos
+                octets = value[idx].encode('utf-8')
+                repl = u''.join(u'%%%02X' % ord(octet) for octet in octets)
+                value = value[:idx]+repl+value[idx+1:]
+                pos_inc += len(repl)-1
+            yield (pos, token, value)
+
+
+class HTSQLRootDirective(Directive):
+
+    required_arguments = 1
+    has_content = False
+
+    def run(self):
+        env = self.state.document.settings.env
+        env.htsql_root = self.arguments[0]
+        return []
+
+
+class HTSQLDirective(Directive):
+
+    optional_arguments = 1
+    has_content = True
+    final_argument_whitespace = True
+    option_spec = {
+            'output': directives.path,
+            'error': directives.flag,
+            'no-link': directives.flag,
+            'no-input': directives.flag,
+            'no-output': directives.flag,
+            'raw': directives.flag,
+            'cut': directives.positive_int,
+    }
+
+    def run(self):
+        doc = self.state.document
+        env = doc.settings.env
+        if self.arguments:
+            if self.content:
+                return [doc.reporter.error("htsql directive cannot have both"
+                                           " an argument and a body",
+                                           lineno=self.lineno)]
+            query  = " ".join(line.strip()
+                              for line in self.arguments[0].split("\n"))
+        elif self.content:
+            query = "\n".join(self.content).strip()
+        else:
+            return [doc.reporter.error("htsql directive expects an argument"
+                                       " or a body", lineno=self.lineno)]
+        query_node = htsql_block(query, query)
+        query_node['language'] = 'htsql'
+        if not hasattr(env, 'htsql_root') or not env.htsql_root:
+            return [doc.reporter.error("config option `htsql_root`"
+                                       " is not set", lineno=self.lineno)]
+        if 'output' not in self.options:
+            query = quote(query.encode('utf-8'),
+                          safe="~`!@$^&*()={[}]|:;\"'<,>?/")
+        else:
+            query = self.options['output'].encode('utf-8')
+        uri = env.htsql_root+query
+        if 'no-link' not in self.options:
+            query_node['uri'] = uri
+        if not hasattr(env, 'htsql_uris'):
+            env.htsql_uris = {}
+        if uri not in env.htsql_uris:
+            result = load_uri(uri, 'error' in self.options)
+            if not result:
+                return [doc.reporter.error("failed to execute an HTSQL query:"
+                                           " %s" % uri, line=self.lineno)]
+            env.htsql_uris[uri] = result
+        htsql_container = nodes.container(classes=['htsql-io'])
+        if 'no-input' not in self.options:
+            query_container = nodes.container('', query_node,
+                                              classes=['htsql-input'])
+            htsql_container += query_container
+        if 'no-output' in self.options:
+            return [htsql_container]
+        content_type, content = env.htsql_uris[uri]
+        if 'raw' in self.options:
+            content_type = 'text/plain'
+        result_node = build_result(self.content_offset, content_type, content,
+                                   self.options.get('cut'))
+        result_container = nodes.container('', result_node,
+                                           classes=['htsql-output'])
+        htsql_container += result_container
+        return [htsql_container]
+
+
+class htsql_block(nodes.literal_block):
+    pass
+
+
+def visit_htsql_block(self, node):
+    # Adapted from `visit_literal_block()`
+    if node.rawsource != node.astext():
+        return self.visit_literal_block(self, node)
+    lang = self.highlightlang
+    linenos = node.rawsource.count('\n') >= \
+              self.highlightlinenothreshold - 1
+    highlight_args = node.get('highlight_args', {})
+    if node.has_key('language'):
+        lang = node['language']
+        highlight_args['force'] = True
+    if node.has_key('linenos'):
+        linenos = node['linenos']
+    highlight_args['nowrap'] = True
+    def warner(msg):
+        self.builder.warn(msg, (self.builder.current_docname, node.line))
+    highlighted = self.highlighter.highlight_block(
+        node.rawsource, lang, warn=warner, linenos=linenos,
+        **highlight_args)
+    if node.has_key('uri'):
+        highlighted = '<a href="%s" target="_new" class="htsql-link">%s</a>' \
+                % (escape(node['uri'], True), highlighted)
+        highlighted = '<a href="%s" target="_new" class="htsql-arrow-link"></a>%s' \
+                % (escape(node['uri'], True), highlighted)
+    highlighted = '<pre>%s</pre>' % highlighted
+    highlighted = '<div class="highlight">%s</div>' % highlighted
+    starttag = self.starttag(node, 'div', suffix='',
+                             CLASS='highlight-%s' % lang)
+    self.body.append(starttag + highlighted + '</div>\n')
+    raise nodes.SkipNode
+
+
+def depart_htsql_block(self, node):
+    self.depart_literal_block(node)
+
+
+def purge_htsql_root(app, env, docname):
+    if hasattr(env, 'htsql_root'):
+        del env.htsql_root
+    if env.config.htsql_root:
+        env.htsql_root = env.config.htsql_root
+
+
+def copy_static(app, exception):
+    if app.builder.name != 'html' or exception:
+        return
+    src = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'htsql.css')
+    dst = os.path.join(app.builder.outdir, '_static', 'htsql.css')
+    copyfile(src, dst)
+
+
+def load_uri(uri, error=False):
+    try:
+        headers = { 'Accept': 'x-htsql/raw' }
+        request = Request(uri, headers=headers)
+        response = urlopen(request)
+        content_type = response.info().gettype()
+        content = response.read()
+    except HTTPError, response:
+        if not error:
+            return None
+        content_type = response.headers.gettype()
+        content = response.read()
+    except URLError:
+        return None
+    return (content_type, content)
+
+
+def build_result(line, content_type, content, cut=None):
+    if content_type == 'application/javascript':
+        product = loads(content)
+        if isinstance(product, dict) and 'meta' in product:
+            return build_result_table(product, cut)
+    content = content.decode('utf-8', 'replace')
+    if cut and content.count('\n') > cut:
+        start = 0
+        while cut:
+            start = content.find('\n', start)+1
+            cut -= 1
+        content = content[:start]+u"\u2026\n"
+    result_node = nodes.literal_block(content, content)
+    result_node['language'] = 'text'
+    return result_node
+
+
+def build_result_table(product, cut):
+    meta = product.get('meta')
+    data = product.get('data')
+    if 'domain' not in meta:
+        return
+    build = get_build_by_domain(meta['domain'])
+    if not build.span:
+        return
+    table_node = nodes.table()
+    measures = build.measures(data, cut)
+    group_node = nodes.tgroup(cols=build.span)
+    table_node += group_node
+    for measure in measures:
+        colspec_node = nodes.colspec(colwidth=measure)
+        group_node += colspec_node
+    head_node = nodes.thead()
+    group_node += head_node
+    head_rows = build.head(build.head_height())
+    if head_rows:
+        for row in head_rows:
+            row_node = nodes.row()
+            head_node += row_node
+            for cell, rowspan, colspan, classes in row:
+                entry_node = nodes.entry(classes=classes)
+                if rowspan > 1:
+                    entry_node['morerows'] = rowspan-1
+                if colspan > 1:
+                    entry_node['morecols'] = colspan-1
+                row_node += entry_node
+                para_node = nodes.paragraph()
+                entry_node += para_node
+                text_node = nodes.Text(cell)
+                para_node += text_node
+                #entry_node += text_node
+    body_node = nodes.tbody()
+    group_node += body_node
+    body_rows = build.body(build.body_height(data, cut), data, cut)
+    if body_rows:
+        for row in body_rows:
+            row_node = nodes.row()
+            body_node += row_node
+            for cell, rowspan, colspan, classes in row:
+                entry_node = nodes.entry(classes=classes)
+                if rowspan > 1:
+                    entry_node['morerows'] = rowspan-1
+                if colspan > 1:
+                    entry_node['morecols'] = colspan-1
+                row_node += entry_node
+                para_node = nodes.paragraph()
+                entry_node += para_node
+                text_node = nodes.Text(cell)
+                para_node += text_node
+                #entry_node += text_node
+    return table_node
+
+
+def get_build_by_domain(domain):
+    if domain['type'] == 'list':
+        return ListBuild(domain)
+    elif domain['type'] == 'record':
+        return RecordBuild(domain)
+    else:
+        return ScalarBuild(domain)
+
+
+class MetaBuild(object):
+
+    def __init__(self, profile):
+        self.profile = profile
+        self.header = profile.get('header')
+        if not self.header:
+            self.header = u""
+        self.domain_build = get_build_by_domain(profile['domain'])
+        self.span = self.domain_build.span
+
+    def head_height(self):
+        if not self.span:
+            return 0
+        height = self.domain_build.head_height()
+        if self.header:
+            height += 1
+        return height
+
+    def head(self, height):
+        rows = [[] for idx in range(height)]
+        if not self.span or not height:
+            return rows
+        is_last = (not self.domain_build.head_height())
+        if not is_last:
+            rows = [[]] + self.domain_build.head(height-1)
+        rowspan = 1
+        if is_last:
+            rowspan = height
+        colspan = self.span
+        classes = []
+        if not self.header:
+            classes.append(u'htsql-dummy')
+        rows[0].append((self.header.replace(u" ", u"\xA0"),
+                        rowspan, colspan, classes))
+        return rows
+
+    def body_height(self, data, cut):
+        return self.domain_build.body_height(data, cut)
+
+    def body(self, height, data, cut):
+        return self.domain_build.body(height, data, cut)
+
+    def cut(self, height):
+        return self.domain_build.cut(height)
+
+    def measures(self, data, cut):
+        measures = self.domain_build.measures(data, cut)
+        if len(measures) == 1:
+            measures[0] = max(measures[0], len(self.header))
+        return measures
+
+
+class ListBuild(object):
+
+    def __init__(self, domain):
+        self.item_build = get_build_by_domain(domain['item']['domain'])
+        self.span = self.item_build.span
+
+    def head_height(self):
+        if not self.span:
+            return []
+        return self.item_build.head_height()
+
+    def head(self, height):
+        if not self.span or not height:
+            return [[] for idx in range(height)]
+        return self.item_build.head(height)
+
+    def body_height(self, data, cut):
+        if not self.span or not data:
+            return 0
+        height = 0
+        for item in data:
+            item_height = self.item_build.body_height(item, None)
+            if cut and height+item_height > cut:
+                return height+1
+            height += item_height
+        return height
+
+    def body(self, height, data, cut):
+        if not self.span or not height:
+            return [[] for idx in range(height)]
+        if not data:
+            rows = [[] for idx in range(height)]
+            rows[0].append((u"", height, self.span, [u'htsql-dummy']))
+            return rows
+        rows = []
+        for idx, item in enumerate(data):
+            item_height = self.item_build.body_height(item, None)
+            if cut and len(rows)+item_height > cut:
+                rows += self.item_build.cut(height)
+                break
+            if idx == len(data)-1 and item_height < height:
+                item_height = height
+            height -= item_height
+            rows += self.item_build.body(item_height, item, None)
+        return rows
+
+    def cut(self, height):
+        if not self.span or not height:
+            return [[] for idx in range(height)]
+        return self.item_build.cut(height)
+
+    def measures(self, data, cut):
+        measures = [1 for idx in range(self.span)]
+        if not self.span or not data:
+            return measures
+        height = 0
+        for idx, item in enumerate(data):
+            height += self.item_build.body_height(item, None)
+            if cut and height > cut:
+                break
+            item_measures = self.item_build.measures(item, None)
+            measures = [max(measure, item_measure)
+                        for measure, item_measure
+                            in zip(measures, item_measures)]
+        return measures
+
+
+class RecordBuild(object):
+
+    def __init__(self, domain):
+        self.field_builds = [MetaBuild(field) for field in domain['fields']]
+        self.span = sum(field_build.span for field_build in self.field_builds)
+
+    def head_height(self):
+        if not self.span:
+            return 0
+        return max(field_build.head_height()
+                   for field_build in self.field_builds)
+
+    def head(self, height):
+        rows = [[] for idx in range(height)]
+        if not self.span or not height:
+            return rows
+        for field_build in self.field_builds:
+            field_rows = field_build.head(height)
+            rows = [row+field_row
+                    for row, field_row in zip(rows, field_rows)]
+        return rows
+
+    def body_height(self, data, cut):
+        if not self.span:
+            return 0
+        if not data:
+            data = [None]*len(self.field_builds)
+        return max(field_build.body_height(item, cut)
+                   for field_build, item in zip(self.field_builds, data))
+
+    def body(self, height, data, cut):
+        rows = [[] for idx in range(height)]
+        if not self.span:
+            return rows
+        if not data:
+            data = [None]*len(self.field_builds)
+        for field_build, item in zip(self.field_builds, data):
+            field_rows = field_build.body(height, item, cut)
+            rows = [row+field_row
+                    for row, field_row in zip(rows, field_rows)]
+        return rows
+
+    def cut(self, height):
+        rows = [[] for idx in range(height)]
+        if not self.span or not height:
+            return rows
+        for field_build in self.field_builds:
+            field_rows = field_build.cut(height)
+            rows = [row+field_row
+                    for row, field_row in zip(rows, field_rows)]
+        return rows
+
+    def measures(self, data, cut):
+        if not data:
+            data = [None]*self.span
+        measures = []
+        for field_build, item in zip(self.field_builds, data):
+            measures += field_build.measures(item, cut)
+        return measures
+
+
+class ScalarBuild(object):
+
+    def __init__(self, domain):
+        self.domain = domain
+        self.span = 1
+
+    def head_height(self):
+        return 0
+
+    def head(self, height):
+        rows = [[] for idx in range(height)]
+        if not height:
+            return rows
+        rows[0].append((u"", height, 1, [u'htsql-dummy']))
+        return rows
+
+    def body_height(self, data, cut):
+        return 1
+
+    def body(self, height, data, cut):
+        rows = [[] for idx in range(height)]
+        if not height:
+            return rows
+        classes = [u'htsql-%s-type' % self.domain['type']]
+        if data is None:
+            classes.append(u'htsql-null-val')
+            data = u""
+        elif data is True:
+            classes.append(u'htsql-true-val')
+            data = u"true"
+        elif data is False:
+            classes.append(u'htsql-false-val')
+            data = u"false"
+        else:
+            data = unicode(data)
+            if not data:
+                classes.append(u'htsql-empty-val')
+        rows[0].append((data, height, 1, classes))
+        return rows
+
+    def cut(self, height):
+        rows = [[] for idx in range(height)]
+        if not height:
+            return rows
+        classes = [u'htsql-%s-type' % self.domain['type'], u'htsql-cut']
+        rows[0].append((u"", height, 1, classes))
+        return rows
+
+    def measures(self, data, cut):
+        if data is None:
+            return [1]
+        return [max(1, len(unicode(data)))]
+
+
+def setup(app):
+    app.add_config_value('htsql_root', None, 'env')
+    app.add_directive('htsql-root', HTSQLRootDirective)
+    app.add_directive('htsql', HTSQLDirective)
+    app.connect('env-purge-doc', purge_htsql_root)
+    app.connect('build-finished', copy_static)
+    app.add_node(htsql_block,
+                 html=(visit_htsql_block, depart_htsql_block))
+    app.add_stylesheet('htsql.css')
+    app.add_lexer('htsql', HtsqlLexer())
+
+
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.