diff options
Diffstat (limited to 'docs/sphinx/hieroglyph')
-rw-r--r-- | docs/sphinx/hieroglyph/LICENSE.txt | 26 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/README.txt | 10 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/__init__.py | 6 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/errors.py | 10 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/hieroglyph.py | 404 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/nodes.py | 267 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/test/__init__.py | 2 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/test/test_comments.py | 586 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/test/test_hierglyph.py | 264 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/test/test_nodes.py | 386 | ||||
-rw-r--r-- | docs/sphinx/hieroglyph/version.py | 3 |
11 files changed, 1964 insertions, 0 deletions
diff --git a/docs/sphinx/hieroglyph/LICENSE.txt b/docs/sphinx/hieroglyph/LICENSE.txt new file mode 100644 index 000000000..3f7a63830 --- /dev/null +++ b/docs/sphinx/hieroglyph/LICENSE.txt @@ -0,0 +1,26 @@ +Copyright (c) 2011, Robert Smallshire +All rights reserved. + +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. + * Neither the name of Robert Smallshire nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +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. diff --git a/docs/sphinx/hieroglyph/README.txt b/docs/sphinx/hieroglyph/README.txt new file mode 100644 index 000000000..c26409d89 --- /dev/null +++ b/docs/sphinx/hieroglyph/README.txt @@ -0,0 +1,10 @@ +Sphinx is a popular tool for documenting Python APIs which uses reStructuredText +as a its lightweight markup language. Sphinx extends restructured text with +semantic markup elements for documenting Python APIs but once these are used the +ratio of markup to content becomes too high and readability is compromised +enough that the docstring becomes unsuitable for use with standard Python +introspection mechanisms like help() or IDEs. + +Hieroglyph is an a Sphinx extension which automatically converts a highly +readable docstring format suitable for use with help() and IDEs to the +reStructuredText hieroglyphics required by Sphinx.
\ No newline at end of file diff --git a/docs/sphinx/hieroglyph/__init__.py b/docs/sphinx/hieroglyph/__init__.py new file mode 100644 index 000000000..25dea27fb --- /dev/null +++ b/docs/sphinx/hieroglyph/__init__.py @@ -0,0 +1,6 @@ +# We only need to expose the setup function to Sphinx + +from .hieroglyph import setup +from .version import __version__ + +__author__ = 'Robert Smallshire'
\ No newline at end of file diff --git a/docs/sphinx/hieroglyph/errors.py b/docs/sphinx/hieroglyph/errors.py new file mode 100644 index 000000000..334b097d8 --- /dev/null +++ b/docs/sphinx/hieroglyph/errors.py @@ -0,0 +1,10 @@ +
+from sphinx.errors import ExtensionError
+
+__author__ = 'rjs'
+
+class HieroglyphError(ExtensionError):
+ '''
+ An exception type specific to the Hieroglyph Sphinx extension.
+ '''
+ pass
\ No newline at end of file diff --git a/docs/sphinx/hieroglyph/hieroglyph.py b/docs/sphinx/hieroglyph/hieroglyph.py new file mode 100644 index 000000000..0056d9ab8 --- /dev/null +++ b/docs/sphinx/hieroglyph/hieroglyph.py @@ -0,0 +1,404 @@ +from __future__ import print_function + +import re + +from errors import HieroglyphError +from nodes import (Node, Raises, Except, Note, Warning, Returns, Arg, + ensure_terminal_blank) + +__author__ = 'Robert Smallshire' + +def parse_hieroglyph_text(lines): + '''Parse text in hieroglyph format and return a reStructuredText equivalent + + Args: + lines: A sequence of strings representing the lines of a single + docstring as read from the source by Sphinx. This string should be + in a format that can be parsed by hieroglyph. + + Returns: + A list of lines containing the transformed docstring as + reStructuredText as produced by hieroglyph. + + Raises: + RuntimeError: If the docstring cannot be parsed. + ''' + indent_lines = unindent(lines) + indent_lines = pad_blank_lines(indent_lines) + indent_lines = first_paragraph_indent(indent_lines) + indent_paragraphs = gather_lines(indent_lines) + parse_tree = group_paragraphs(indent_paragraphs) + syntax_tree = extract_structure(parse_tree) + result = syntax_tree.render_rst() + ensure_terminal_blank(result) + return result + + +def unindent(lines): + '''Convert an iterable of indented lines into a sequence of tuples. + + The first element of each tuple is the indent in number of characters, and + the second element is the unindented string. + + Args: + lines: A sequence of strings representing the lines of text in a docstring. + + Returns: + A list of tuples where each tuple corresponds to one line of the input + list. Each tuple has two entries - the first is an integer giving the + size of the indent in characters, the second is the unindented text. + ''' + unindented_lines = [] + for line in lines: + unindented_line = line.lstrip() + indent = len(line) - len(unindented_line) + unindented_lines.append((indent, unindented_line)) + return unindented_lines + + +def pad_blank_lines(indent_texts): + '''Give blank (empty) lines the same indent level as the preceding line. + + Args: + indent_texts: An iterable of tuples each containing an integer in the + first element and a string in the second element. + + Returns: + A list of tuples each containing an integer in the first element and a + string in the second element. + ''' + current_indent = 0 + result = [] + for indent, text in indent_texts: + if len(text) > 0: + current_indent = indent + result.append((current_indent, text)) + return result + + +def extract_structure(parse_tree): + '''Create an Abstract Syntax Tree representing the semantics of a parse tree. + + Args: + parse_tree: TODO + + Returns: + A Node with is the result of an Abstract Syntax Tree representing the + docstring. + + Raises: + HieroglyphError: In the event that the parse tree cannot be understood. + ''' + return convert_node(parse_tree) + + +def convert_node(node): + if node.indent == 0 and len(node.lines) == 0: + return convert_children(node) + if node.lines[0].startswith('Args:'): + return convert_args(node) + if node.lines[0].startswith('Returns:'): + return convert_returns(node) + if node.lines[0].startswith('Raises:'): + return convert_raises(node) + if node.lines[0].startswith('Note:'): + return convert_note(node) + if node.lines[0].startswith('Warning:'): + return convert_warning(node) + result = convert_children(node) + result.lines = node.lines + result.indent = node.indent + return result + + +def convert_children(node): + converted_children = [convert_node(child) for child in node.children] + result = Node() + result.children = converted_children + return result + + +ARG_REGEX = re.compile(r'(\*{0,2}\w+)(\s+\((\w+)\))?\s*:\s*(.*)') + +def append_child_to_args_group_node(child, group_node, indent): + arg = None + non_empty_lines = (line for line in child.lines if line) + for line in non_empty_lines: + m = ARG_REGEX.match(line) + if m is None: + raise HieroglyphError("Invalid hieroglyph argument syntax: {0}".format(line)) + param_name = m.group(1) + param_type = m.group(3) + param_text = m.group(4) + + arg = Arg(indent, child.indent, param_name) + group_node.children.append(arg) + arg.type = param_type + + if param_text is not None: + arg.children.append(Node(indent, [param_text], arg)) + if arg is not None: + last_child = arg.children[-1] if len(arg.children) != 0 else arg + for grandchild in child.children: + last_child.children.append(grandchild) + + +def convert_args(node): + assert node.lines[0].startswith('Args:') + group_node = Node() + for child in node.children: + append_child_to_args_group_node(child, group_node, node.indent) + return group_node + + +def convert_returns(node): + assert node.lines[0].startswith('Returns:') + returns = Returns(node.indent) + returns.line = node.lines[0][8:].strip() + returns.children = node.children + return returns + + +def convert_note(node): + assert node.lines[0].startswith('Note:') + note = Note(node.indent) + note.line = node.lines[0][5:].strip() + note.children = node.children + return note + + +def convert_warning(node): + assert node.lines[0].startswith('Warning:') + warning = Warning(node.indent) + warning.line = node.lines[0][8:].strip() + warning.children = node.children + return warning + + +def convert_raises(node): + assert node.lines[0].startswith('Raises:') + group_node = Raises(node.indent) + for child in node.children: + append_child_to_raise_node(child, group_node) + return group_node + + +RAISE_REGEX = re.compile(r'(\w+)\s*:\s*(.*)') + +def extract_exception_type_and_text(line): + m = RAISE_REGEX.match(line) + if m is None: + raise HieroglyphError("Invalid hieroglyph exception syntax: {0}".format(line)) + return (m.group(2), m.group(1)) + + +def append_child_to_raise_node(child, group_node): + exception = None + non_empty_lines = (line for line in child.lines if line) + for line in non_empty_lines: + exception_text, exception_type = extract_exception_type_and_text(line) + + exception = Except(child.indent, exception_type) + group_node.children.append(exception) # TODO: Could use parent here. + + if exception_text is not None: + exception.children.append( Node(child.indent, + [exception_text], exception)) + if exception is not None: + last_child = exception.children[-1] if len(exception.children) != 0 else exception + for grandchild in child.children: + last_child.children.append(grandchild) + + +def group_paragraphs(indent_paragraphs): + ''' + Group paragraphs so that more indented paragraphs become children of less + indented paragraphs. + ''' + # The tree consists of tuples of the form (indent, [children]) where the + # children may be strings or other tuples + + root = Node(0, [], None) + current_node = root + + previous_indent = -1 + for indent, lines in indent_paragraphs: + if indent > previous_indent: + current_node = create_child_node(current_node, indent, lines) + elif indent == previous_indent: + current_node = create_sibling_node(current_node, indent, lines) + elif indent < previous_indent: + current_node = create_uncle_node(current_node, indent, lines) + previous_indent = indent + return root + + +def create_sibling_node(current_node, indent, lines): + sibling = Node(indent, lines, current_node.parent) + current_node.parent.add_child(sibling) + current_node = sibling + return current_node + + +def create_child_node(current_node, indent, lines): + child = Node(indent, lines, current_node) + current_node.add_child(child) + current_node = child + return current_node + + +def create_uncle_node(current_node, indent, lines): + ancestor = current_node + while ancestor.indent >= indent: + if ancestor.parent is None: + break + ancestor = ancestor.parent + uncle = Node(indent, lines, ancestor) + ancestor.add_child(uncle) + current_node = uncle + return current_node + + +def gather_lines(indent_lines): + '''Split the list of (int, str) tuples into a list of (int, [str]) tuples + to group the lines into paragraphs of consistent indent. + ''' + return remove_empty_paragraphs(split_separated_lines(gather_lines_by_indent(indent_lines))) + +def gather_lines_by_indent(indent_lines): + result = [] + previous_indent = -1 + for indent, line in indent_lines: + if indent != previous_indent: + paragraph = (indent, []) + result.append(paragraph) + else: + paragraph = result[-1] + paragraph[1].append(line) + previous_indent = indent + return result + +def split_separated_lines(indent_paragraphs): + result = [] + for indent, paragraph in indent_paragraphs: + result.append((indent, [])) + + if len(paragraph) > 0: + result[-1][1].append(paragraph[0]) + + if len(paragraph) > 2: + for line in paragraph[1: -1]: + result[-1][1].append(line) + if len(line) == 0: + result.append((indent, [])) + + if len(paragraph) > 1: + result[-1][1].append(paragraph[-1]) + + return result + +def remove_empty_paragraphs(indent_paragraphs): + return [(indent, paragraph) for indent, paragraph in indent_paragraphs if len(paragraph)] + +def first_paragraph_indent(indent_texts): + '''Fix the indentation on the first paragraph. + + This occurs because the first line of a multi-line docstring following the + opening quote usually has no indent. + + Args: + indent_texts: The lines of the docstring as an iterable over 2-tuples + each containing an integer indent level as the first element and + the text as the second element. + + Return: + A list of 2-tuples, each containing an integer indent level as the + first element and the text as the second element. + ''' + opening_indent = determine_opening_indent(indent_texts) + + result = [] + input = iter(indent_texts) + for indent, text in input: + if indent == 0: + result.append((opening_indent, text)) + else: + result.append((indent, text)) + break + + for indent, text in input: + result.append((indent, text)) + + return result + + +def determine_opening_indent(indent_texts): + '''Determine the opening indent level for a docstring. + + The opening indent level is the indent level is the first non-zero indent + level of a non-empty line in the docstring. + + Args: + indent_texts: The lines of the docstring as an iterable over 2-tuples + each containing an integer indent level as the first element and + the text as the second element. + + Returns: + The opening indent level as an integer. + ''' + num_lines = len(indent_texts) + + if num_lines < 1: + return 0 + + assert num_lines >= 1 + + first_line_indent = indent_texts[0][0] + + if num_lines == 1: + return first_line_indent + + assert num_lines >= 2 + + second_line_indent = indent_texts[1][0] + second_line_text = indent_texts[1][1] + + if len(second_line_text) == 0: + return first_line_indent + + return second_line_indent + + + +def rewrite_autodoc(app, what, name, obj, options, lines): + '''Convert lines from Hieroglyph to Sphinx format. + + The function to be called by the Sphinx autodoc extension when autodoc + has read and processed a docstring. This function modified its + ``lines`` argument *in place* replacing Hieroglyph syntax input into + Sphinx reStructuredText output. + + Args: + apps: The Sphinx application object. + + what: The type of object which the docstring belongs to. One of + 'module', 'class', 'exception', 'function', 'method', 'attribute' + + name: The fully qualified name of the object. + + obj: The object itself. + + options: The options given to the directive. An object with attributes + ``inherited_members``, ``undoc_members``, ``show_inheritance`` and + ``noindex`` that are ``True`` if the flag option of the same name + was given to the auto directive. + + lines: The lines of the docstring. Will be modified *in place*. + ''' + lines[:] = parse_hieroglyph_text(lines) + + +def setup(app): + app.connect('autodoc-process-docstring', rewrite_autodoc) + + diff --git a/docs/sphinx/hieroglyph/nodes.py b/docs/sphinx/hieroglyph/nodes.py new file mode 100644 index 000000000..e583ce04d --- /dev/null +++ b/docs/sphinx/hieroglyph/nodes.py @@ -0,0 +1,267 @@ +__author__ = 'Robert Smallshire'
+
+class Node(object):
+
+ def __init__(self, indent=None, lines=None, parent=None):
+ if indent is not None:
+ self.indent = indent
+ else:
+ self.indent = 0
+
+ if lines is not None:
+ self.lines = lines
+ else:
+ self.lines = []
+
+ self._parent = parent
+
+ self.children = []
+
+ parent = property(lambda self: self._parent)
+
+ def add_child(self, child):
+ assert(child.parent is self)
+ self.children.append(child)
+
+
+ def __repr__(self):
+ return "Node(" + repr(self.indent) + ", " + repr(self.lines) + ", children=" + repr(self.children) + ")"
+
+
+ def render_rst(self, *args, **kwargs):
+ result = []
+ prefix = ' ' * self.indent
+ result.extend(prefix + line for line in self.lines)
+ for child in self.children:
+ result.extend(child.render_rst())
+ return result
+
+
+
+class Arg(Node):
+
+ def __init__(self, indent, child_indent, name):
+ super(Arg, self).__init__(indent)
+ self.child_indent = child_indent
+ self.name = name
+ self.type = None
+
+
+ def __repr__(self):
+ return "Arg(" + repr(self.name) + ", " + repr(self.type) + ", children=" + repr(self.children) + ")"
+
+
+ def render_rst(self, *args, **kwargs):
+ result = []
+ indent = ' ' * self.indent
+
+ # Render the param description
+ description = []
+ for child in self.children:
+ child_lines = child.render_rst()
+ description.extend(child_lines)
+
+ dedent = self.child_indent - self.indent
+
+ name = self.name.replace('*', r'\*')
+
+ first_description = description[0].lstrip() if len(description) else ''
+ if not first_description:
+ # TODO: Emit a warning about a missing argument description
+ pass
+
+ result.append("{indent}:param {name}: {first_description}".format(indent=indent, name=name,
+ first_description=first_description))
+
+ dedented_body = [line[dedent:] for line in description[1:]]
+
+ result.extend(dedented_body)
+
+ # If a type was specified render the type
+ if self.type is not None:
+ result.append("{indent}:type {name}: {type}".format(indent=indent, name=self.name, type=self.type))
+ result.append('')
+
+ ensure_terminal_blank(result)
+
+ return result
+
+
+
+class Raises(Node):
+
+ def __init__(self, indent=None):
+ super(Raises, self).__init__(indent=indent)
+
+ def __repr__(self):
+ return "Raises(" + repr(self.indent) + ", children=" + repr(self.children) + ")"
+
+
+ def render_rst(self, *args, **kwargs):
+ result = []
+ indent = ' ' * self.indent
+ result.append(indent + ':raises:')
+ for child in self.children:
+ result.extend(child.render_rst(only_child=len(self.children) == 1))
+
+ ensure_terminal_blank(result)
+
+ return result
+
+
+class Except(Node):
+
+ def __init__(self, indent, type):
+ super(Except, self).__init__(indent=indent)
+ #self.child_indent = child_indent
+ self.type = type
+
+
+ def __repr__(self):
+ return "Except(" + repr(self.type) + ", children=" + repr(self.children) + ")"
+
+
+ def render_rst(self, only_child=False, *args, **kwargs):
+ result = []
+ indent = ' ' * self.indent
+
+ # Render the param description
+ description = []
+ for child in self.children:
+ child_lines = child.render_rst()
+ description.extend(child_lines)
+
+ #dedent = self.child_indent - self.indent
+ bullet = '* ' if not only_child else ''
+
+ first_description = description[0].lstrip() if len(description) else ''
+ result.append("{indent}{bullet}{type} - {first_description}".format(indent=indent,
+ bullet=bullet, type=self.type,
+ first_description=first_description))
+
+ #dedented_body = [' ' * len(bullet) + line[dedent:] for line in description[1:]]
+ #result.extend(dedented_body)
+ result.extend(description[1:])
+ ensure_terminal_blank(result)
+
+ return result
+
+
+
+class Returns(Node):
+
+ def __init__(self, indent):
+ super(Returns, self).__init__(indent=indent)
+ self.title = 'Returns'
+ self.line = ''
+
+
+ def __repr__(self):
+ return "Returns(" + str(self.indent) + ", children=" + str(self.children) + ")"
+
+
+ def render_rst(self, *args, **kwargs):
+ result = []
+ indent = ' ' * self.indent
+
+ # Render the param description
+ description = [self.line] if self.line else []
+ for child in self.children:
+ child_lines = child.render_rst()
+ description.extend(child_lines)
+
+ self.render_title(description, indent, result)
+
+ result.extend(description[1:])
+
+ ensure_terminal_blank(result)
+ return result
+
+
+ def render_title(self, description, indent, result):
+ result.append(
+ "{indent}:returns: {first_description}".format(indent=indent,
+ first_description=description[0].lstrip()))
+
+
+
+class Warning(Node):
+
+ def __init__(self, indent):
+ super(Warning, self).__init__(indent=indent)
+
+ def __repr__(self):
+ return "Warning(" + repr(self.indent) + ", children=" + str(self.children) + ")"
+
+ def render_rst(self, *args, **kwargs):
+ # TODO: Factor out the commonality between this and Note below
+ result = []
+ indent = ' ' * self.indent
+
+ # Render the param description
+ description = [self.line] if self.line else []
+ for child in self.children:
+ child_lines = child.render_rst()
+ description.extend(child_lines)
+
+ # Fix the indent on the first line
+ if len(description) > 1 and len(description[1].strip()) != 0:
+ body_indent = len(description[1]) - len(description[1].strip())
+ else:
+ body_indent = self.indent + 4
+
+ if len(description) > 0:
+ description[0] = ' ' * body_indent + description[0]
+
+ result.append(indent + ".. warning::")
+ result.append(indent + '')
+ result.extend(description)
+
+ ensure_terminal_blank(result)
+ return result
+
+
+class Note(Node):
+
+ def __init__(self, indent):
+ super(Note, self).__init__(indent=indent)
+ self.line = ''
+
+
+ def __repr__(self):
+ return "Note(" + repr(self.indent) + ", children=" + str(self.children) + ")"
+
+
+ def render_rst(self, *args, **kwargs):
+ # TODO: Factor out the commonality between this and Warning above
+ result = []
+ indent = ' ' * self.indent
+
+ # Render the param description
+ description = [self.line] if self.line else []
+ for child in self.children:
+ child_lines = child.render_rst()
+ description.extend(child_lines)
+
+ # Fix the indent on the first line
+ if len(description) > 1 and len(description[1].strip()) != 0:
+ body_indent = len(description[1]) - len(description[1].strip())
+ else:
+ body_indent = self.indent + 4
+
+ if len(description) > 0:
+ description[0] = ' ' * body_indent + description[0]
+
+ result.append(indent + ".. note::")
+ result.append(indent + '')
+ result.extend(description)
+
+ ensure_terminal_blank(result)
+ return result
+
+
+def ensure_terminal_blank(result):
+ '''If the description didn't end with a blank line add one here.'''
+ if len(result) > 0:
+ if len(result[-1].strip()) != 0:
+ result.append('')
diff --git a/docs/sphinx/hieroglyph/test/__init__.py b/docs/sphinx/hieroglyph/test/__init__.py new file mode 100644 index 000000000..fd249423f --- /dev/null +++ b/docs/sphinx/hieroglyph/test/__init__.py @@ -0,0 +1,2 @@ +__author__ = 'rjs' +
\ No newline at end of file diff --git a/docs/sphinx/hieroglyph/test/test_comments.py b/docs/sphinx/hieroglyph/test/test_comments.py new file mode 100644 index 000000000..d1a1453ee --- /dev/null +++ b/docs/sphinx/hieroglyph/test/test_comments.py @@ -0,0 +1,586 @@ +import unittest + +from hieroglyph.hieroglyph import parse_hieroglyph_text +from hieroglyph.errors import HieroglyphError + +class CommentTests(unittest.TestCase): + + def test_comment1(self): + source = """Fetches rows from a Bigtable. + This is a continuation of the opening paragraph. + + Retrieves rows pertaining to the given keys from the Table instance + represented by big_table. Silly things may happen if + other_silly_variable is not None. + + Args: + big_table: An open Bigtable Table instance. + keys: A sequence of strings representing the key of each table row + to fetch. + other_silly_variable (str): Another optional variable, that has a much + longer name than the other args, and which does nothing. + + Returns: + A dict mapping keys to the corresponding table row data + fetched. Each row is represented as a tuple of strings. For + example: + + {'Serak': ('Rigel VII', 'Preparer'), + 'Zim': ('Irk', 'Invader'), + 'Lrrr': ('Omicron Persei 8', 'Emperor')} + + If a key from the keys argument is missing from the dictionary, + then that row was not found in the table. + + Raises: + IOError: An error occurred accessing the bigtable.Table object. + """ + + expected = """ Fetches rows from a Bigtable. + This is a continuation of the opening paragraph. + + Retrieves rows pertaining to the given keys from the Table instance + represented by big_table. Silly things may happen if + other_silly_variable is not None. + + :param big_table: An open Bigtable Table instance. + + :param keys: A sequence of strings representing the key of each table row + to fetch. + + :param other_silly_variable: Another optional variable, that has a much + longer name than the other args, and which does nothing. + + :type other_silly_variable: str + + :returns: A dict mapping keys to the corresponding table row data + fetched. Each row is represented as a tuple of strings. For + example: + + {'Serak': ('Rigel VII', 'Preparer'), + 'Zim': ('Irk', 'Invader'), + 'Lrrr': ('Omicron Persei 8', 'Emperor')} + + If a key from the keys argument is missing from the dictionary, + then that row was not found in the table. + + :raises: + IOError - An error occurred accessing the bigtable.Table object. + """ + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment2(self): + source = """Determine if all elements in the source sequence satisfy a condition. + + All of the source sequence will be consumed. + + Note: This method uses immediate execution. + + Args: + predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + + Returns: + True if all elements in the sequence meet the predicate condition, + otherwise False. + + Raises: + ValueError: If the Queryable is closed() + TypeError: If predicate is not callable. + """ + + expected = """Determine if all elements in the source sequence satisfy a condition. + + All of the source sequence will be consumed. + + .. note:: + + This method uses immediate execution. + + :param predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + + :returns: True if all elements in the sequence meet the predicate condition, + otherwise False. + + :raises: + * ValueError - If the Queryable is closed() + + * TypeError - If predicate is not callable. + """ + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment3(self): + source = """Determine if all elements in the source sequence satisfy a condition. + + All of the source sequence will be consumed. + + Note: This method uses immediate execution. + + Args: + predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + + Returns: + True if all elements in the sequence meet the predicate condition, + otherwise False. + + Raises: + ValueError: If the Queryable is closed() + TypeError: If predicate is not callable. + """ + + expected = """Determine if all elements in the source sequence satisfy a condition. + + All of the source sequence will be consumed. + + .. note:: + + This method uses immediate execution. + + :param predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + + :returns: True if all elements in the sequence meet the predicate condition, + otherwise False. + + :raises: + * ValueError - If the Queryable is closed() + + * TypeError - If predicate is not callable. + """ + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment4(self): + source_lines = [u'Determine if all elements in the source sequence satisfy a condition.', + u'', + u'All of the source sequence will be consumed.', + u'', + u'Note: This method uses immediate execution.', + u'', + u'Args:', + u' predicate: An optional single argument function used to test each', + u' elements. If omitted, the bool() function is used resulting in', + u' the elements being tested directly.', + u'', + u'Returns:', + u' True if all elements in the sequence meet the predicate condition,', + u' otherwise False.', + u'', + u'Raises:', + u' ValueError: If the Queryable is closed()', + u' TypeError: If predicate is not callable.', + u''] + + expected = """Determine if all elements in the source sequence satisfy a condition. + +All of the source sequence will be consumed. + +.. note:: + + This method uses immediate execution. + +:param predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + +:returns: True if all elements in the sequence meet the predicate condition, + otherwise False. + +:raises: + * ValueError - If the Queryable is closed() + + * TypeError - If predicate is not callable. + +""" + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment5(self): + source_lines = [u'An empty Queryable.', + u'', + u'Note: The same empty instance will be returned each time.', + u'', + u'Returns: A Queryable over an empty sequence.', + u''] + + expected = """An empty Queryable. + +.. note:: + + The same empty instance will be returned each time. + +:returns: A Queryable over an empty sequence. + +""" + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment6(self): + source_lines = [u'A convenience factory for creating Records.', + u'', + u'Args:', + u' **kwargs: Each keyword argument will be used to initialise an', + u' attribute with the same name as the argument and the given', + u' value.', + u'', + u'Returns:', + u' A Record which has a named attribute for each of the keyword arguments.', + u''] + + expected = """A convenience factory for creating Records. + +:param \*\*kwargs: Each keyword argument will be used to initialise an + attribute with the same name as the argument and the given + value. + +:returns: A Record which has a named attribute for each of the keyword arguments. + +""" + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment7(self): + source = """Projects each element of a sequence to an intermediate new sequence, + flattens the resulting sequences into one sequence and optionally + transforms the flattened sequence using a selector function. + + Note: This method uses deferred execution. + + Args: + collection_selector: A unary function mapping each element of the + source iterable into an intermediate sequence. The single + argument of the collection_selector is the value of an element + from the source sequence. The return value should be an + iterable derived from that element value. The default + collection_selector, which is the identity function, assumes + that each element of the source sequence is itself iterable. + + result_selector: An optional unary function mapping the elements in + the flattened intermediate sequence to corresponding elements + of the result sequence. The single argument of the + result_selector is the value of an element from the flattened + intermediate sequence. The return value should be the + corresponding value in the result sequence. The default + result_selector is the identity function. + + Returns: + A Queryable over a generated sequence whose elements are the result + of applying the one-to-many collection_selector to each element of + the source sequence, concatenating the results into an intermediate + sequence, and then mapping each of those elements through the + result_selector into the result sequence. + + Raises: + ValueError: If this Queryable has been closed. + TypeError: If either collection_selector or result_selector are not + callable. + """ + + expected = """ Projects each element of a sequence to an intermediate new sequence, + flattens the resulting sequences into one sequence and optionally + transforms the flattened sequence using a selector function. + + .. note:: + + This method uses deferred execution. + + :param collection_selector: A unary function mapping each element of the + source iterable into an intermediate sequence. The single + argument of the collection_selector is the value of an element + from the source sequence. The return value should be an + iterable derived from that element value. The default + collection_selector, which is the identity function, assumes + that each element of the source sequence is itself iterable. + + :param result_selector: An optional unary function mapping the elements in + the flattened intermediate sequence to corresponding elements + of the result sequence. The single argument of the + result_selector is the value of an element from the flattened + intermediate sequence. The return value should be the + corresponding value in the result sequence. The default + result_selector is the identity function. + + :returns: A Queryable over a generated sequence whose elements are the result + of applying the one-to-many collection_selector to each element of + the source sequence, concatenating the results into an intermediate + sequence, and then mapping each of those elements through the + result_selector into the result sequence. + + :raises: + * ValueError - If this Queryable has been closed. + + * TypeError - If either collection_selector or result_selector are not + callable. + """ + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment8(self): + source = """A convenience factory for creating Records. + + Args: + **kwargs: Each keyword argument will be used to initialise an + attribute with the same name as the argument and the given + value. + + Returns: + A Record which has a named attribute for each of the keyword arguments. + """ + + expected = """A convenience factory for creating Records. + + :param \*\*kwargs: Each keyword argument will be used to initialise an + attribute with the same name as the argument and the given + value. + + :returns: A Record which has a named attribute for each of the keyword arguments. + +""" + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment9(self): + source_lines = [u'Parse a single line of a tree to determine depth and node.', + u'', + u'Args:', + u' This line is missing an argument name.', + u' ', + u'Returns:', + u' A 2-tuple containing the tree 0 based tree depth as the first', + u' element and the node description as the second element.', + u'', + u'Raises:', + u' ValueError: If line does not have the expected form.', + u''] + + self.assertRaises(HieroglyphError, lambda: parse_hieroglyph_text(source_lines)) + + def test_comment10(self): + source = """ + Execute the command described by concatenating the string function arguments + with the p4 -s global scripting flag and return the results in a dictionary. + + For example, to run the command:: + + p4 -s fstat -T depotFile foo.h + + call:: + + p4('fstat', '-T', 'depotFile', 'foo.h') + + Args: + args: The arguments to the p4 command as a list of objects which will + be converted to strings. + + Returns: + A dictionary of lists where each key in the dictionary is the field name + from the command output, and each value is a list of output lines in + order. + + Raises: + PerforceError: If the command could not be run or if the command + reported an error. + """ + + expected = """ + Execute the command described by concatenating the string function arguments + with the p4 -s global scripting flag and return the results in a dictionary. + + For example, to run the command:: + + p4 -s fstat -T depotFile foo.h + + call:: + + p4('fstat', '-T', 'depotFile', 'foo.h') + + :param args: The arguments to the p4 command as a list of objects which will + be converted to strings. + + :returns: A dictionary of lists where each key in the dictionary is the field name + from the command output, and each value is a list of output lines in + order. + + :raises: + PerforceError - If the command could not be run or if the command + reported an error. + +""" + + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment11(self): + source = """Projects each element of a sequence to an intermediate new sequence, + flattens the resulting sequences into one sequence and optionally + transforms the flattened sequence using a selector function. + + Warning: This method may explode at short notice. + + Args: + collection_selector: A unary function mapping each element of the + source iterable into an intermediate sequence. The single + argument of the collection_selector is the value of an element + from the source sequence. The return value should be an + iterable derived from that element value. The default + collection_selector, which is the identity function, assumes + that each element of the source sequence is itself iterable. + + result_selector: An optional unary function mapping the elements in + the flattened intermediate sequence to corresponding elements + of the result sequence. The single argument of the + result_selector is the value of an element from the flattened + intermediate sequence. The return value should be the + corresponding value in the result sequence. The default + result_selector is the identity function. + + Returns: + A Queryable over a generated sequence whose elements are the result + of applying the one-to-many collection_selector to each element of + the source sequence, concatenating the results into an intermediate + sequence, and then mapping each of those elements through the + result_selector into the result sequence. + + Raises: + ValueError: If this Queryable has been closed. + TypeError: If either collection_selector or result_selector are not + callable. + """ + + expected = """ Projects each element of a sequence to an intermediate new sequence, + flattens the resulting sequences into one sequence and optionally + transforms the flattened sequence using a selector function. + + .. warning:: + + This method may explode at short notice. + + :param collection_selector: A unary function mapping each element of the + source iterable into an intermediate sequence. The single + argument of the collection_selector is the value of an element + from the source sequence. The return value should be an + iterable derived from that element value. The default + collection_selector, which is the identity function, assumes + that each element of the source sequence is itself iterable. + + :param result_selector: An optional unary function mapping the elements in + the flattened intermediate sequence to corresponding elements + of the result sequence. The single argument of the + result_selector is the value of an element from the flattened + intermediate sequence. The return value should be the + corresponding value in the result sequence. The default + result_selector is the identity function. + + :returns: A Queryable over a generated sequence whose elements are the result + of applying the one-to-many collection_selector to each element of + the source sequence, concatenating the results into an intermediate + sequence, and then mapping each of those elements through the + result_selector into the result sequence. + + :raises: + * ValueError - If this Queryable has been closed. + + * TypeError - If either collection_selector or result_selector are not + callable. + """ + source_lines = source.splitlines() + actual_lines = parse_hieroglyph_text(source_lines) + expected_lines = expected.splitlines() + self.assertEqual(len(actual_lines), len(expected_lines)) + for actual_line, result_line in zip(actual_lines, expected_lines): + if len(actual_line.strip()) == 0: + self.assertTrue(len(result_line.strip()) == 0) + else: + self.assertEqual(actual_line, result_line) + + def test_comment12(self): + source = """Determine if all elements in the source sequence satisfy a condition. + + All of the source sequence will be consumed. + + Note: This method uses immediate execution. + + Args: + predicate: An optional single argument function used to test each + elements. If omitted, the bool() function is used resulting in + the elements being tested directly. + + Returns: + True if all elements in the sequence meet the predicate condition, + otherwise False. + + Raises: + This is not a proper exception description + """ + + source_lines = source.splitlines() + self.assertRaises(HieroglyphError, lambda: parse_hieroglyph_text(source_lines)) + diff --git a/docs/sphinx/hieroglyph/test/test_hierglyph.py b/docs/sphinx/hieroglyph/test/test_hierglyph.py new file mode 100644 index 000000000..42947cb0c --- /dev/null +++ b/docs/sphinx/hieroglyph/test/test_hierglyph.py @@ -0,0 +1,264 @@ +import unittest
+from hieroglyph.hieroglyph import first_paragraph_indent, gather_lines, unindent
+
+__author__ = 'Robert Smallshire'
+
+class UnindentTests(unittest.TestCase):
+
+ def test_zero_lines(self):
+ source = []
+ expected = []
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+ def test_one_zero_indent_line(self):
+ source = ["First line"]
+ expected = [(0, "First line")]
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+ def test_two_zero_indent_lines(self):
+ source = ["First line",
+ "Second line"]
+ expected = [(0, "First line"),
+ (0, "Second line")]
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+ def test_two_indented_lines(self):
+ source = [" First line",
+ " Second line"]
+ expected = [(4, "First line"),
+ (6, "Second line")]
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+ def test_whitespace_line(self):
+ source = [" "]
+ expected = [(4, "")]
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+ def test_tab_line(self):
+ source = ["\tHello"]
+ expected = [(1, "Hello")]
+ actual = unindent(source)
+ self.assertEqual(actual, expected)
+
+
+class FirstParagraphIndentTests(unittest.TestCase):
+
+ def test_zero_lines(self):
+ source = []
+ expected = []
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_single_line_non_indented_comment(self):
+ source = [(0, "A single line comment")]
+ expected = [(0, "A single line comment")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_single_line_indented_comment(self):
+ source = [(4, "A single line comment")]
+ expected = [(4, "A single line comment")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_double_line_non_indented_comment(self):
+ source = [(0, "The first line"),
+ (0, "The second line")]
+ expected = [(0, "The first line"),
+ (0, "The second line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_double_line_indented_comment(self):
+ source = [(4, "The first line"),
+ (4, "The second line")]
+ expected = [(4, "The first line"),
+ (4, "The second line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_first_line_indent(self):
+ source = [(4, "The first line"),
+ (0, "The second line")]
+ expected = [(4, "The first line"),
+ (0, "The second line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_first_line_non_indent(self):
+ source = [(0, "The first line"),
+ (4, "The second line")]
+ expected = [(4, "The first line"),
+ (4, "The second line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_increasing_indent(self):
+ source = [(0, "The first line"),
+ (4, "The second line"),
+ (8, "The third line")]
+ expected = [(4, "The first line"),
+ (4, "The second line"),
+ (8, "The third line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_separate_paragraphs(self):
+ source = [(0, "This is the first paragraph"),
+ (0, ""),
+ (4, "This is the second paragraph")]
+ expected = [(0, "This is the first paragraph"),
+ (0, ""),
+ (4, "This is the second paragraph")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_separate_paragraphs_indented(self):
+ source = [(4, "This is the first paragraph"),
+ (4, ""),
+ (8, "This is the second paragraph")]
+ expected = [(4, "This is the first paragraph"),
+ (4, ""),
+ (8, "This is the second paragraph")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_separated_lines_first_line_non_indented(self):
+ source = [(0, "The first line"),
+ (0, ""),
+ (4, "The third line")]
+ expected = [(0, "The first line"),
+ (0, ""),
+ (4, "The third line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+ def test_separated_lines_first_line_indented(self):
+ source = [(4, "The first line"),
+ (4, ""),
+ (4, "The third line")]
+ expected = [(4, "The first line"),
+ (4, ""),
+ (4, "The third line")]
+ actual = first_paragraph_indent(source)
+ self.assertEqual(actual, expected)
+
+class GatherLinesTests(unittest.TestCase):
+
+ def test_empty(self):
+ source = []
+ expected = []
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_one_liner(self):
+ source = [(0, 'One liner')]
+ expected = [(0, ['One liner'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_two_liner(self):
+ source = [(0, 'First line'),
+ (0, 'Second line')]
+ expected = [(0, ['First line',
+ 'Second line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_separated_lines(self):
+ source = [(0, 'First line'),
+ (0, ''),
+ (0, 'Third line')]
+ expected = [(0, ['First line',
+ '']),
+ (0, ['Third line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_separated_multi_lines(self):
+ source = [(0, 'First line'),
+ (0, 'Second line'),
+ (0, ''),
+ (0, 'Fourth line'),
+ (0, 'Fifth line')]
+ expected = [(0, ['First line',
+ 'Second line',
+ '']),
+ (0, ['Fourth line',
+ 'Fifth line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+
+ def test_indented_lines(self):
+ source = [(0, 'First line'),
+ (4, 'Second line')]
+ expected = [(0, ['First line']),
+ (4, ['Second line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_dedented_lines(self):
+ source = [(4, 'First line'),
+ (0, 'Second line')]
+ expected = [(4, ['First line']),
+ (0, ['Second line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_indented_multi_lines(self):
+ source = [(0, 'First line'),
+ (0, 'Second line'),
+ (4, 'Third line'),
+ (4, 'Fourth line')]
+ expected = [(0, ['First line',
+ 'Second line']),
+ (4, ['Third line',
+ 'Fourth line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_dedented_multi_lines(self):
+ source = [(4, 'First line'),
+ (4, 'Second line'),
+ (0, 'Third line'),
+ (0, 'Fourth line')]
+ expected = [(4, ['First line',
+ 'Second line']),
+ (0, ['Third line',
+ 'Fourth line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_indented_separated_multi_lines(self):
+ source = [(0, 'First line'),
+ (0, 'Second line'),
+ (0, ''),
+ (4, 'Fourth line'),
+ (4, 'Fifth line')]
+ expected = [(0, ['First line',
+ 'Second line',
+ '']),
+ (4, ['Fourth line',
+ 'Fifth line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
+
+ def test_dedented_separated_multi_lines(self):
+ source = [(4, 'First line'),
+ (4, 'Second line'),
+ (4, ''),
+ (0, 'Fourth line'),
+ (0, 'Fifth line')]
+ expected = [(4, ['First line',
+ 'Second line',
+ '']),
+ (0, ['Fourth line',
+ 'Fifth line'])]
+ actual = gather_lines(source)
+ self.assertEqual(actual, expected)
diff --git a/docs/sphinx/hieroglyph/test/test_nodes.py b/docs/sphinx/hieroglyph/test/test_nodes.py new file mode 100644 index 000000000..4cc17b477 --- /dev/null +++ b/docs/sphinx/hieroglyph/test/test_nodes.py @@ -0,0 +1,386 @@ +import unittest
+from hieroglyph.nodes import Node, Arg, Raises, Except, Returns, Warning, Note
+
+__author__ = 'Robert Smallshire'
+
+class NodeTests(unittest.TestCase):
+
+ def test_create_default_node(self):
+ node = Node()
+ self.assertEqual(node.indent, 0)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_create_with_indent(self):
+ node = Node(indent=4)
+ self.assertEqual(node.indent, 4)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_create_with_lines(self):
+ node = Node(lines= ['First', 'Second', 'Third'])
+ self.assertEqual(node.indent, 0)
+ self.assertEqual(node.lines, ['First', 'Second', 'Third'])
+ self.assertIsNone(node.parent)
+
+ def test_repr(self):
+ node = Node(5, ['One', 'Two', 'Three'])
+ actual = repr(node)
+ expected = "Node(5, ['One', 'Two', 'Three'], children=[])"
+ self.assertEqual(expected, actual)
+
+ def test_add_one_child(self):
+ node = Node()
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Node()
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_render_rst_empty(self):
+ node = Node()
+ rst = node.render_rst()
+ self.assertEqual(len(rst), 0)
+
+ def test_render_rst_indent(self):
+ node = Node(indent=4)
+ rst = node.render_rst()
+ self.assertEqual(len(rst), 0)
+
+ def test_render_rst_lines(self):
+ node = Node(lines= ['First',
+ 'Second',
+ 'Third'])
+ rst = node.render_rst()
+ self.assertEqual(rst, ['First',
+ 'Second',
+ 'Third'])
+
+ def test_render_rst_indented_lines(self):
+ node = Node(indent=3, lines= ['First',
+ 'Second',
+ 'Third'])
+ rst = node.render_rst()
+ self.assertEqual(rst, [' First',
+ ' Second',
+ ' Third'])
+
+ def test_render_rst_with_child(self):
+ node = Node(indent=4, lines=["Parent"])
+ child = Node(indent=8, lines=["Child"], parent=node)
+ node.add_child(child)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' Parent',
+ ' Child'])
+
+ def test_render_rst_with_children(self):
+ node = Node(indent=4, lines=["Parent"])
+ child_a = Node(indent=8, lines=["ChildA"], parent=node)
+ node.add_child(child_a)
+ child_b = Node(indent=6, lines=["ChildB"], parent=node)
+ node.add_child(child_b)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' Parent',
+ ' ChildA',
+ ' ChildB'])
+
+
+class ArgTests(unittest.TestCase):
+
+ def test_create(self):
+ node = Arg(5, 10, 'foo')
+ self.assertEqual(node.indent, 5)
+ self.assertEqual(node.child_indent, 10)
+ self.assertEqual(node.name, 'foo')
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_set_type(self):
+ node = Arg(5, 10, 'foo')
+ node.type = 'str'
+ self.assertEqual(node.type, 'str')
+
+ def test_add_one_child(self):
+ node = Arg(5, 10, 'foo')
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Arg(5, 10, 'foo')
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_repr(self):
+ node = Arg(5, 10, 'foo')
+ actual = repr(node)
+ expected = "Arg('foo', None, children=[])"
+ self.assertEqual(expected, actual)
+
+ def test_render_rst_empty(self):
+ node = Arg(5, 10, 'bar')
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :param bar: ',
+ ''])
+
+ def test_render_rst_with_child(self):
+ node = Arg(5, 10, 'bar')
+ child = Node(indent=10, lines=["Description"], parent=node)
+ node.add_child(child)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :param bar: Description',
+ ''])
+
+ def test_render_rst_with_children(self):
+ node = Arg(5, 10, 'bar')
+ child_a = Node(indent=10, lines=["ChildA"], parent=node)
+ node.add_child(child_a)
+ child_b = Node(indent=10, lines=["ChildB"], parent=node)
+ node.add_child(child_b)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :param bar: ChildA',
+ ' ChildB',
+ ''])
+
+ def test_render_rst_with_type(self):
+ node = Arg(5, 10, 'bar')
+ node.type = 'str'
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :param bar: ',
+ ' :type bar: str',
+ ''])
+
+
+class RaisesTests(unittest.TestCase):
+
+ def test_create_default_node(self):
+ node = Raises()
+ self.assertEqual(node.indent, 0)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_create_with_indent(self):
+ node = Raises(indent=4)
+ self.assertEqual(node.indent, 4)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_repr(self):
+ node = Raises(5)
+ actual = repr(node)
+ expected = "Raises(5, children=[])"
+ self.assertEqual(expected, actual)
+
+ def test_add_one_child(self):
+ node = Raises()
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Raises()
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_render_rst_empty(self):
+ node = Raises()
+ rst = node.render_rst()
+ self.assertEqual(rst, [':raises:',
+ ''])
+
+ def test_render_rst_indent(self):
+ node = Raises(indent=5)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :raises:',
+ ''])
+
+ def test_render_rst_with_child(self):
+ node = Raises(5)
+ child = Node(indent=10, lines=["Description"], parent=node)
+ node.add_child(child)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :raises:',
+ ' Description',
+ ''])
+
+ def test_render_rst_with_children(self):
+ node = Raises(5)
+ child_a = Node(indent=10, lines=["ChildA"], parent=node)
+ node.add_child(child_a)
+ child_b = Node(indent=10, lines=["ChildB"], parent=node)
+ node.add_child(child_b)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' :raises:',
+ ' ChildA',
+ ' ChildB',
+ ''])
+
+
+class ExceptTests(unittest.TestCase):
+
+ def test_create(self):
+ node = Except(5, 'FooError')
+ self.assertEqual(node.indent, 5)
+ self.assertEqual(node.type, 'FooError')
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_add_one_child(self):
+ node = Except(5, 'FooError')
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Except(5, 'FooError')
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_repr(self):
+ node = Except(5,'FooError')
+ actual = repr(node)
+ expected = "Except('FooError', children=[])"
+ self.assertEqual(expected, actual)
+
+ def test_render_rst_empty(self):
+ node = Except(5, 'FooError')
+ rst = node.render_rst()
+ self.assertEqual(rst, [' * FooError - ',
+ ''])
+
+ def test_render_rst_indent(self):
+ node = Except(5, 'FooError')
+ rst = node.render_rst()
+ self.assertEqual(rst, [' * FooError - ',
+ ''])
+
+ def test_render_rst_with_child(self):
+ node = Except(5, 'FooError')
+ child = Node(indent=10, lines=["Description"], parent=node)
+ node.add_child(child)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' * FooError - Description',
+ ''])
+
+ def test_render_rst_with_children(self):
+ node = Except(5, 'FooError')
+ child_a = Node(indent=10, lines=["ChildA"], parent=node)
+ node.add_child(child_a)
+ child_b = Node(indent=10, lines=["ChildB"], parent=node)
+ node.add_child(child_b)
+ rst = node.render_rst()
+ self.assertEqual(rst, [' * FooError - ChildA',
+ ' ChildB',
+ ''])
+
+class ReturnsTests(unittest.TestCase):
+
+ def test_create(self):
+ node = Returns(5)
+ self.assertEqual(node.indent, 5)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_add_one_child(self):
+ node = Returns(5)
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Returns(5)
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_repr(self):
+ node = Returns(5)
+ actual = repr(node)
+ expected = "Returns(5, children=[])"
+ self.assertEqual(expected, actual)
+
+ # TODO test_render_rst
+
+class WarningTests(unittest.TestCase):
+
+ def test_create(self):
+ node = Warning(5)
+ self.assertEqual(node.indent, 5)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_add_one_child(self):
+ node = Warning(5)
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Warning(5)
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_repr(self):
+ node = Warning(5)
+ actual = repr(node)
+ expected = "Warning(5, children=[])"
+ self.assertEqual(expected, actual)
+
+ # TODO test_render_rst
+
+class NoteTests(unittest.TestCase):
+
+ def test_create(self):
+ node = Note(5)
+ self.assertEqual(node.indent, 5)
+ self.assertEqual(node.lines, [])
+ self.assertIsNone(node.parent)
+
+ def test_add_one_child(self):
+ node = Note(5)
+ child = Node(parent=node)
+ node.add_child(child)
+ self.assertIs(node.children[0], child)
+
+ def test_add_two_children(self):
+ node = Note(5)
+ child0 = Node(parent=node)
+ child1 = Node(parent=node)
+ node.add_child(child0)
+ node.add_child(child1)
+ self.assertIs(node.children[0], child0)
+ self.assertIs(node.children[1], child1)
+
+ def test_repr(self):
+ node = Note(5)
+ actual = repr(node)
+ expected = "Note(5, children=[])"
+ self.assertEqual(expected, actual)
+
+ # TODO test_render_rst
diff --git a/docs/sphinx/hieroglyph/version.py b/docs/sphinx/hieroglyph/version.py new file mode 100644 index 000000000..d060125c0 --- /dev/null +++ b/docs/sphinx/hieroglyph/version.py @@ -0,0 +1,3 @@ +'''Specification of the hieroglyph version'''
+
+__version__ = '0.6'
|