diff --git a/LICENSE.txt b/LICENSE.txt index 64ef4ee..19c8982 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ This software is OSI Certified Open Source Software. OSI Certified is a certification mark of the Open Source Initiative. -Copyright (c) 2006, Enthought, Inc. +Copyright (c) 2012-2014, Enthought, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.rst b/README.rst index 1b37d3b..3e5f7a7 100644 --- a/README.rst +++ b/README.rst @@ -20,9 +20,11 @@ Key aims of **sectiondoc** are: - Do not change the order of sections. - Allow sphinx directives between (and inside) section blocks. + - Custom rendering styles - Easier to debug (native support for debugging) and extend (future versions). + Repository ---------- @@ -35,14 +37,18 @@ using:: Installation ------------ -1. Install ``sectiondoc`` from pypi using pip:: +Install ``sectiondoc`` from pypi using pip:: $ pip install sectiondoc -2. Add sectiondoc to the extensions variable of your sphinx ``conf.py``:: +Usage +----- + + +Styles can be selected by referencing in ``conf.py`` the module they are defined:: extensions = [ ..., - 'sectiondoc', + 'sectiondoc.styles.legacy', ..., ] diff --git a/docs/source/architecture.rst b/docs/source/architecture.rst deleted file mode 100644 index 9416b60..0000000 --- a/docs/source/architecture.rst +++ /dev/null @@ -1,58 +0,0 @@ -Architecture -************ - -The are three different parts in the pipeline of **refactordoc**. - -(i) The autodoc event hook and object refactoring dispatch; -(ii) The docstring section detection and method dispatching and; -(iii) The second component parsing and refactor of the detected sections; - -The entry function -################## - -The entry function ``setup`` is located in the ``__init__.py`` file. Through -the setup function refactor doc is loading the autodoc extention and hooks -the :func:`~refactordoc.refactor_docstring` function to the -``autodoc-process-docstring`` event. - -The :func:`~refactordoc.refactor_docstring` function receives the list of -lines that compose the dostrings and based on the ``object`` initializes a -new class instance to do the main work. The final item in the process is to -execute the parse method of the created class. - -The refactoring class -##################### - -The refactoring classes are responsible for doing the actual work. These classes -are derived from the :class:`~refactordoc.BaseDoc` class. After initialization -refactoring takes place by executing the :meth:`~refactordoc.BaseDoc.parse` -method. The method looks for section headers by parsing the lines of the -docstring. For each section that is discovered the -:meth:`~refactordoc.BaseDoc._refactor` method is called with the name of -the discovered section to dispatch processing to the associated refactoring -method. The dispatcher constructs the name of the refactoring function by -looking up the ``headers`` dictionary for a key equal to the header string -found. If a key is found then the refactoring method name is composed from the -prefix ``_header_`` and the retrieved value. If a key with the header name is -not found then the default :meth:`~refactordoc.BaseDoc._refactor_header` is -used. - -The refactoring methods -####################### - -Depending on the section the associated method parses and extracts the section -definition block using the provided by the :class:`~refactordoc.BaseDoc` class -utility methods. - -When the definition block is a paragraph the -:meth:`~refactordoc.BaseDoc.extract_paragraph` will return the paragraph for -further processing. When the definition block is a list of definition items. -These items are parsed and extracted (i.e removed from the docstring) with the -help of the :meth:`~refactordoc.BaseDoc.extract_items` and a -:class:`~refactordoc.defintion_items.DefintionItem` (or a subclass). The list -of items that is returned holds all the information to produce a sequence of -sphinx friendly rst. - -After collecting the information in the section the refactoring method is -ready to produce the updated rst and return a list of lines to the -dispatching method so that they can be re-inserted in the docstring. diff --git a/docs/source/components.rst b/docs/source/components.rst deleted file mode 100644 index 7ba23f4..0000000 --- a/docs/source/components.rst +++ /dev/null @@ -1,82 +0,0 @@ -Section components -****************** - -Each section is composed into a number of components these components are -described below. - -Section header -^^^^^^^^^^^^^^ - -The start of the section is designated with the section header, which is -a standard rst header. The underline is however restricted to using only -``-`` or ``=``:: - - Section - ------- - -and:: - - Section - ======= - -Each section header is followed by a section definition block which can be -either a list of items or one or more definition items. In general, The number -and format of these items depends on the type of section that is currently -parsed. - -Definition list -^^^^^^^^^^^^^^^ - -Two of the most common formats are described bellow: - -The *standard definition item* format is based on the item of a variation -of the definition list item as it defined in `restructured text -`_ - -:: - - +-------------------------------------------------+ - | term [ " : " classifier [ " or " classifier] ] | - +--+----------------------------------------------+---+ - | definition | - | (body elements)+ | - +--------------------------------------------------+ - -where ```` is the single word (e.g. my_field) and -```` is a indented block of rst code. The item header can -optionally include the ```` attribute. This type of item is -commonly used to describe class attributes and function arguments. In this -documentation we will refer to this format as `variable` item to avoid -confusion with sphinx directives. - -A similar definition item format is the method item where the item header is -composed of a function signature:: - - +------------------------------+ - | term "(" [ classifier ] ")" | - +--+---------------------------+---+ - | definition | - | (body elements)+ | - +-------------------------------+ - -This item is commonly used to describe provided functions (or methods) and -thus is referred to as the `method item`. The -```` in this case is a list of arguments as it appears in the -signature and ```` the method summary (one sentence). All -`method` fields should be separated by a single empty line. - -Paragraph -^^^^^^^^^ - -Instead of a list of items the section can contain a paragraph:: - - +-------------------------+ - | definition | - | (body elements)+ | - +-------------------------+ - -This type of field is used for information sections like ``Notes``. - -.. note:: Currently the ```` is a single unindented block with no - empty lines. However, this will probably should change in future - versions of RefactorDoc. diff --git a/docs/source/conf.py b/docs/source/conf.py index 6953621..dd84df4 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -29,10 +29,11 @@ extensions = ['sectiondoc', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', - 'sphinx.ext.viewcode'] + 'sphinx.ext.viewcode', + 'sphinx.ext.autosummary'] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = [] # The suffix of source filenames. source_suffix = '.rst' @@ -60,11 +61,14 @@ # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' +autodoc_member_order = 'groupwise' +autoclass_content = 'both' + # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. -html_theme = 'default' +html_theme = 'alabaster' # Output file base name for HTML help builder. htmlhelp_basename = 'SectionDocdoc' diff --git a/docs/source/extending.rst b/docs/source/extending.rst deleted file mode 100644 index 089401f..0000000 --- a/docs/source/extending.rst +++ /dev/null @@ -1,43 +0,0 @@ -Building your own suite -####################### - -While the default refactoring suite is enough for most cases. The user -might need to extent the section repertoire, process other object -types, allow more freedom in defining the definition list or restrict -the docstring style to improve consinstancy through his code. - -.. warning:: All the methods below require to change the refactordoc code and - even thought the changes might be small it is not considered the best way - since updating refactordoc becomes non-trivial. Future version will remove - this shortcoming. - - -Adding sections -############### - -New sections to be refactored can be simply added to the ``headers`` dictionary -when an appropriate refactoring method exists. For example in the default -suite that is shipped with refactordoc the -:class:`~refactordoc.function_doc.FunctionDoc` class sets the `Returns` -`Raises` and `Yields` section to use the `_refactor_as_item_list` method -in the class:: - - if headers is None: - headers = {'Returns': 'as_item_list', 'Arguments': 'arguments', - 'Parameters': 'arguments', 'Raises': 'as_item_list', - 'Yields': 'as_item_list', 'Notes':'notes'} - - -When such a method does not - -exist then the user has to augment the related class with that will -parse and extract the section definition block(s) and return the -refactored lines as a list of strings to replace the section in the -docstring. The signature of the method should be -``_header_(self, header)`` - -Where ```` is the value in the ``headers`` that corresponds to the -``header`` string that is found in the docstring. - - -.. note:: More to come diff --git a/docs/source/index.rst b/docs/source/index.rst index d9374b7..b3a08d1 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -6,12 +6,10 @@ Contents ======== .. toctree:: - :maxdepth: 2 + :maxdepth: 1 - usage - architecture - components - extending + rendering + styles reference authors todo @@ -25,4 +23,3 @@ Indices and tables * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff --git a/docs/source/legacy_style.rst b/docs/source/legacy_style.rst new file mode 100644 index 0000000..1dcbe21 --- /dev/null +++ b/docs/source/legacy_style.rst @@ -0,0 +1,129 @@ +Legacy +###### + +Previous versions of Sectiondoc (and the even older refactordoc +package) supported a single style for rendering sections in +function/method doc-strings. The old style is still supported in +recent versions as a **legacy** style. + +For class objects the **legacy** renders three types of sections: + +========== ================================ ============ === ===================== +Heading Description Item Max Rendered as +========== ================================ ============ === ===================== +Methods Class methods with summary MethodItem -- Table with links to + the method +Attributes Class attributes and their usage Attribute -- Sphinx attributes +Arguments function arguments and type ArgumentItem -- Parameters field list +Parameters function arguments and type ArgumentItem -- Parameters field list +Notes Useful notes paragraph 1 Note admonition +========== ================================ ============ === ===================== + +For functions the **legacy** renders four types of sections: + +========== =========================== ============ === ===================== +Heading Description Item Max Rendered as +========== =========================== ============ === ===================== +Arguments function arguments and type ArgumentItem -- Parameters field list +Parameters function arguments and type ArgumentItem -- Parameters field list +Returns Return value ListItem -- Unordered list +Raises Raised exceptions ListItem -- Unordered list +Yields Yield values ListItem -- Unordered list +Notes Useful notes paragraph 1 Note admonition +========== =========================== ============ === ===================== + +.. note:: + All other sections are rendered using the ``.. rubric::`` directive by + default. + +layout rules +************ + +To be able to detect and render the sections properly the docstrings should follow +the following rules: + +- Between the section header and the first section item there can be at + most only one empty line. + +- The end of the section is designated by one of the following: + + - The allowed number of items by the section has been parsed. + - Two consecutive empty lines are found. + - The line is not identified as a possible header of the section item. + + .. hint:: Please check the doc-string of the specific definition item + class to have more information regarding the valid item header + format. + +Examples +******** + +Argument sections +^^^^^^^^^^^^^^^^^ +:: + + Arguments + --------- + new_lines : list + The list of lines to insert + + index : int + Index to start the insertion + + +.. automethod:: sectiondoc.styles.doc_render.DocRender.insert_lines + :noindex: + + +Attribute sections +------------------ +:: + + Attributes + ---------- + docstring : list + A list of strings (lines) that holds docstrings. The lines are changed + inplace. + + index : int + The zero-based line number of the docstring that is currently + processed. + + sections : dict + The sections that will be detected and rendered. The dictionary + maps the section headers for detection to a tuple containing + the section rendering function and optional values for the item + renderer and parser. + +.. autoclass:: sectiondoc.styles.doc_render.DocRender + :noindex: + :no-members: + +Returns sections +---------------- +:: + + Returns + ------- + result : list + A new list of left striped strings. + +.. autofunction:: sectiondoc.util.remove_indent + :noindex: + +Raises section +-------------- + +.. todo:: Add example + + +Notes +----- +:: + + Notes + ----- + Empty strings are not changed. + +.. autofunction:: sectiondoc.util.add_indent + :noindex: diff --git a/docs/source/reference.rst b/docs/source/reference.rst index d36da91..740ff80 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -3,36 +3,32 @@ Library Reference The extension is separated into three main parts. -Sphinx extension ----------------- -.. automodule:: sectiondoc - :members: +.. default-role:: autolink -Refactor classes ----------------- +Styles +------ -.. automodule:: sectiondoc.base_doc - :members: +.. automodule:: sectiondoc.styles + :members: -.. automodule:: sectiondoc.function_doc - :members: - :private-members: -.. automodule:: sectiondoc.class_doc - :members: - :private-members: +Sections +-------- -Definition items ----------------- +.. automodule:: sectiondoc.sections + :members: -.. automodule:: sectiondoc.definition_items - :members: +Items +----- -Line functions --------------- +.. automodule:: sectiondoc.items + :members: -.. automodule:: sectiondoc.line_functions - :members: +Renderers +--------- + +.. automodule:: sectiondoc.renderers + :members: diff --git a/docs/source/rendering.rst b/docs/source/rendering.rst new file mode 100644 index 0000000..18a30f3 --- /dev/null +++ b/docs/source/rendering.rst @@ -0,0 +1,52 @@ +Docstring rendering +******************* + +The are five different parts in the pipeline of **sectiondoc** docstring rendering. + +Style +##### + +The rendering :class:`~Style` is hooked with autodoc to receive the docstring +of the various objects and maps the objects types provided by autodoc to +:class:`~DocRender` instances which are responsible for rendering the provided +docstring. + +The DocRender +############# + +The DocRender is responsible for doing the actual work. At initialization +The class receives a dictionary mapping sections to a rendering function +and optional section item parsing and rendering classes. The actual +rendering starts by executing :meth:`~.DocRender.parse` to detect sections +in the docstring. For each section that is discovered the +:meth:`~.DocRender._render` is called with the name of the discovered section +to further dispatch processing to the associated section rendering function. +If a associated fuctntion to the section does not exist the default is to use +:func:`~.rubric`. + +Section rendering function +########################## + +The rendering fuctions is will use the utility methods of the the DocRender +instance to extract the section block. Depedending on the implementation +:meth:`~DocRender.extract_paragraph` will call to return the +paragraph for further processing or will call +:meth:`~DocRender.extract_items` to return the list of :class:`~.Item` +instances. After collecting the information in the section the rendering +function is ready to produce the updated rst using the appropriate +:class:`~.Renderer` and return a list of lines that will be re-inserted into +the docstring. + +Item +#### + +:class:`Item` instances contain the ``term``, ``classfier(s)`` and +``definition`` information of items in a section. Each :class:`Item` type +knows how to parse a set of lines grouping and filtering the information +ready to ber rendered inot sphinx friendly rst. + +Renderer +######## + +The :class:`Renderer` is used by the section renderer functions to render +a previously contructed :class:`Item` into sphinx friently rst. diff --git a/docs/source/styles.rst b/docs/source/styles.rst new file mode 100644 index 0000000..0228371 --- /dev/null +++ b/docs/source/styles.rst @@ -0,0 +1,51 @@ +Styles +------ + +SectionDoc comes with the following predefined rendering styles + +.. toctree:: + :maxdepth: 1 + + legacy_style + +.. note:: + + The default rendering style is currently :mod:`~.legacy` + + +Extending +--------- + +Custom styles can be created by instanciating a :class:`~.Style` to +map a :class:`~.DocRender` factory for each type of object rendered by +autodoc. For example adding the following functions in you conf.py +defines a rendering style for functions and methods:: + + def function_section(lines): + return DocRender( + lines, + sections={ + 'Returns': (item_list, ListItem, OrDefinitionItem), + 'Arguments': (arguments, Argument, OrDefinitionItem), + 'Parameters': (arguments, Argument, OrDefinitionItem), + 'Raises': (item_list, ListItem, OrDefinitionItem), + 'Yields': (item_list, ListItem, OrDefinitionItem), + 'Notes': (notes_paragraph, None, None)}) + + + def setup(app): + style = Style({ + 'function': function_section, + 'method': function_section}) + app.setup_extension('sphinx.ext.autodoc') + app.connect('autodoc-process-docstring', style.render_docstring) + +Specifically the :class:`~.Style` instance will map the ``function`` +and ``method`` docstrings to the dostring rendering funtion +``function_section``. The :class:`~DocRender` will then detect the +sections ``Returns, Arguments, Parameters, Raises, Yields, Notes`` and +use the mapped combination of section rendering function, Item description +and item rendering type to render the detected section in-place. + +The rendering styles can be further extented by implemeting new Item, +Renderer instances or section rendering functions. diff --git a/docs/source/usage.rst b/docs/source/usage.rst deleted file mode 100644 index 55e1b01..0000000 --- a/docs/source/usage.rst +++ /dev/null @@ -1,151 +0,0 @@ -Default refactoring -******************* - -The base implementation of RefactorDoc provides refactoring for class and -function doc-strings. A number of known (i.e. predefined) sections are processed -by the ClassDoc and FunctionDoc classes and all unknown sections are re-factored -using the ``.. rubric::`` directive by default. - -For class objects the :class:`~refactordoc.class_doc.ClassDoc` includes code to -re-factor three types of sections. - -========== ================================ ========== === ==================== -Heading Description Item Max Rendered as -========== ================================ ========== === ==================== -Methods Class methods with summary MethodItem -- Table with links to - the method -Attributes Class attributes and their usage Attribute -- Sphinx attributes -Notes Useful notes paragraph 1 Note admonition -========== ================================ ========== === ==================== - -For function objects the :class:`~refactordoc.function_doc.FunctionDoc` includes -code to re-factor three types of sections. - -========= =========================== ============ === ===================== -Heading Description Item Max Rendered as -========= =========================== ============ === ===================== -Arguments function arguments and type ArgumentItem -- Parameters field list -Returns Return value ListItem -- Unordered list -Raises Raised exceptions ListItem -- Unordered list -Notes Useful notes paragraph 1 Note admonition -========= =========================== ============ === ===================== - - -Usage rules -*********** - -To be able to re-factor the sections properly the doc-strings should follow -theses rules: - -.. admonition:: Rules - - - Between the section header and the first section item there can be at - most only one empty line. - - - The end of the section is designated by one of the following: - - - The allowed number of items by the section has been parsed. - - Two consecutive empty lines are found. - - The line is not identified as a possible header of the section item. - - .. hint:: Please check the doc-string of the specific definition item - class to have more information regarding the valid item header - format. - -Examples -******** - -Argument sections -^^^^^^^^^^^^^^^^^ -:: - - Arguments - --------- - new_lines : list - The list of lines to insert - - index : int - Index to start the insertion - """ - -.. automethod:: refactordoc.base_doc.BaseDoc.insert_lines - :noindex: - - -Method sections -^^^^^^^^^^^^^^^ -:: - - Methods - ------- - _refactor_attributes(self, header): - Re-factor the attributes section to sphinx friendly format. - - _refactor_methods(self, header): - Re-factor the methods section to sphinx friendly format. - - _refactor_notes(self, header): - Re-factor the note section to use the rst ``.. note`` directive. - - -.. note:: The table that is created in this example does not have the links - enabled because the methods are not rendered by autodoc (the - ``:no-members`` option is set). - -.. autoclass:: refactordoc.class_doc.ClassDoc - :noindex: - :no-members: - -Attribute sections ------------------- -:: - - Attributes - ---------- - docstring : list - A list of strings (lines) that holds doc-strings - - index : int - The current zero-based line number of the doc-string that is currently - processed. - - headers : dict - The sections that the class re-factors. Each entry in the - dictionary should have as key the name of the section in the - form that it appears in the doc-strings. The value should be - the postfix of the method, in the subclasses, that is - responsible for refactoring (e.g. {'Methods': 'method'}). - -.. autoclass:: refactordoc.base_doc.BaseDoc - :noindex: - :no-members: - -Returns sections ----------------- -:: - - Returns - ------- - result : list - A new list of left striped strings. - -.. autofunction:: refactordoc.line_functions.remove_indent - :noindex: - -Raises section --------------- - -.. todo:: Add example - - -Notes ------ -:: - - Notes - ----- - Empty strings are not changed. - -.. autofunction:: refactordoc.line_functions.add_indent - :noindex: - diff --git a/sectiondoc/__init__.py b/sectiondoc/__init__.py index be231cf..1311c19 100644 --- a/sectiondoc/__init__.py +++ b/sectiondoc/__init__.py @@ -1,35 +1,15 @@ -#------------------------------------------------------------------------------ -# file: refactor_doc.py +# ----------------------------------------------------------------------------- +# file: __init__.py # License: LICENSE.TXT # -# Copyright (c) 2011, Enthought, Inc. +# Copyright (c) 2011-2014, Enthought, Inc. # All rights reserved. -#------------------------------------------------------------------------------ -from .function_doc import FunctionDoc -from .class_doc import ClassDoc - +# ----------------------------------------------------------------------------- try: # pragma: no cover - from ._version import full_version as __version__ + from sectiondoc._version import full_version as __version__ except ImportError: # pragma: no cover __version__ = "not-built" +from sectiondoc.styles.legacy import setup -#------------------------------------------------------------------------------ -# Extension definition -#------------------------------------------------------------------------------ - -def refactor_docstring(app, what, name, obj, options, lines): - - refactor = None - if 'class' in what: - refactor = ClassDoc(lines) - elif 'function' in what or 'method' in what: - refactor = FunctionDoc(lines) - - if refactor is not None: - refactor.parse() - - -def setup(app): - app.setup_extension('sphinx.ext.autodoc') - app.connect('autodoc-process-docstring', refactor_docstring) +__all__ = ['__version__', 'setup'] diff --git a/sectiondoc/class_doc.py b/sectiondoc/class_doc.py deleted file mode 100644 index e214e65..0000000 --- a/sectiondoc/class_doc.py +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: class_doc.py -# License: LICENSE.TXT -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from .base_doc import BaseDoc -from .line_functions import add_indent -from .definition_items import (MethodItem, AttributeItem, max_attribute_length, - max_attribute_index) - - -class ClassDoc(BaseDoc): - """ Docstring refactoring for classes. - - The class provides the following refactoring methods. - - Methods - ------- - _refactor_attributes(self, header): - Refactor the attributes section to sphinx friendly format. - - _refactor_methods(self, header): - Refactor the methods section to sphinx friendly format. - - _refactor_notes(self, header): - Refactor the note section to use the rst ``.. note`` directive. - - """ - - def __init__(self, lines, headers=None): - if headers is None: - headers = {'Attributes': 'attributes', 'Methods': 'methods', - 'Notes':'notes'} - - super(ClassDoc, self).__init__(lines, headers) - return - - def _refactor_attributes(self, header): - """Refactor the attributes section to sphinx friendly format. - - """ - items = self.extract_items(AttributeItem) - lines = [] - for item in items: - lines += item.to_rst() - return lines - - def _refactor_methods(self, header): - """Refactor the methods section to sphinx friendly format. - - """ - items = self.extract_items(MethodItem) - lines = [] - if len(items) > 0 : - columns = self._get_column_lengths(items) - border = '{0:=^{1}} {0:=^{2}}'.format('', columns[0], columns[1]) - heading = '{0:<{2}} {1:<{3}}'.format('Method', 'Description', - columns[0], columns[1]) - lines += [border] - lines += [heading] - lines += [border] - for items in items: - lines += items.to_rst(columns) - lines += [border] - lines += [''] - lines = [line.rstrip() for line in lines] - return lines - - def _refactor_notes(self, header): - """Refactor the note section to use the rst ``.. note`` directive. - - """ - paragraph = self.get_next_paragraph() - lines = ['.. note::'] - lines += add_indent(paragraph) - return lines - - def _get_column_lengths(self, items): - """ Helper function to estimate the column widths for the refactoring of - the ``Methods`` section. - - The method finds the index of the item that has the largest function - name (i.e. self.term) and the largest signature. If the indexes are not - the same then checks to see which of the two items have the largest - string sum (i.e. self.term + self.signature). - - """ - name_index = max_attribute_index(items, 'term') - signature_index = max_attribute_index(items, 'signature') - if signature_index != name_index: - index = signature_index - item1_width = len(items[index].term + items[index].signature) - index = name_index - item2_width = len(items[index].term + items[index].signature) - first_column = max(item1_width, item2_width) - else: - index = name_index - first_column = len(items[index].term + items[index].signature) - - first_column += 11 # Add boilerplate characters - second_column = max_attribute_length(items, 'definition') - return (first_column, second_column) diff --git a/sectiondoc/definition_items.py b/sectiondoc/definition_items.py deleted file mode 100644 index 7d8a3d2..0000000 --- a/sectiondoc/definition_items.py +++ /dev/null @@ -1,546 +0,0 @@ -# -*- coding: utf-8 -*- -#----------------------------------------------------------------------------- -# file: fields.py -# License: LICENSE.TXT -# Author: Ioannis Tziakos -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#----------------------------------------------------------------------------- -import collections -import re - -from .line_functions import (add_indent, fix_star, trim_indent, NEW_LINE, - fix_trailing_underscore) - -header_regex = re.compile(r'\s:\s?') -definition_regex = re.compile(r""" -\*{0,2} # no, one or two stars -\w+\s: # a word followed by a semicolumn and optionally a space -( - \s # just a space - | # OR - \s[\w.]+ # dot separated words - (\(.*\))? # with maybe a signature - | - \s[\w.]+ # dot separated words - (\(.*\))? - \sor # with an or in between - \s[\w.]+ - (\(.*\))? -)? -$ # match at the end of the line -""", re.VERBOSE) -function_regex = re.compile(r'\w+\(.*\)\s*') -signature_regex = re.compile('\((.*)\)') - - -class DefinitionItem(collections.namedtuple( - 'DefinitionItem', ('term', 'classifier', 'definition'))): - """ A docstring definition item - - Syntax diagram:: - - +-------------------------------------------------+ - | term [ " : " classifier [ " or " classifier] ] | - +--+----------------------------------------------+---+ - | definition | - | (body elements)+ | - +--------------------------------------------------+ - - The Definition class is based on the nametuple class and is responsible - to check, parse and refactor a docstring definition item into sphinx - friendly rst. - - Attributes - ---------- - term : str - The term usually reflects the name of a parameter or an attribute. - - classifier: str - The classifier of the definition. Commonly used to reflect the type - of an argument or the signature of a function. - - .. note:: Currently only one classifier is supported. - - definition : list - The list of strings that holds the description the definition item. - - .. note:: A Definition item is based on the item of a section definition - list as it defined in restructured text - (_http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections). - - """ - - @classmethod - def is_definition(cls, line): - """ Check if the line is describing a definition item. - - The method is used to check that a line is following the expected - format for the term and classifier attributes. - - The expected format is:: - - +-------------------------------------------------+ - | term [ " : " classifier [ " or " classifier] ] | - +-------------------------------------------------+ - - Subclasses can subclass to restrict or expand this format. - - """ - return definition_regex.match(line) is not None - - @classmethod - def parse(cls, lines): - """Parse a definition item from a set of lines. - - The class method parses the definition list item from the list of - docstring lines and produces a DefinitionItem with the term, - classifier and the definition. - - .. note:: The global indention in the definition lines is striped - - The term definition is assumed to be in one of the following formats:: - - term - Definition. - - :: - - term - Definition, paragraph 1. - - Definition, paragraph 2. - - :: - - term : classifier - Definition. - - Arguments - --------- - lines - docstring lines of the definition without any empty lines before or - after. - - Returns - ------- - definition : DefinitionItem - - """ - header = lines[0].strip() - term, classifier = header_regex.split(header, maxsplit=1) if \ - (' :' in header) else (header, '') - trimed_lines = trim_indent(lines[1:]) if (len(lines) > 1) else [''] - definition = [line.rstrip() for line in trimed_lines] - return cls(term.strip(), classifier.strip(), definition) - - def to_rst(self, **kwards): - """ Outputs the Definition in sphinx friendly rst. - - The method renders the definition into a list of lines that follow - the rst markup. The default behaviour is to render the definition - as an sphinx definition item:: - - - - () -- - - - Subclasses will usually override the method to provide custom made - behaviour. However the signature of the method should hold only - keyword arguments which have default values. The keyword arguments - can be used to pass addition rendering information to subclasses. - - Returns - ------- - lines : list - A list of string lines rendered in rst. - - Example - ------- - - :: - - >>> item = DefinitionItem('lines', 'list', - ['A list of string lines rendered in rst.']) - >>> item.to_rst() - lines - - *(list)* -- - A list of string lines rendered in rst. - - .. note:: An empty line is added at the end of the list of strings so - that the results can be concatenated directly and rendered properly - by sphinx. - - - """ - postfix = ' --' if (len(self.definition) > 0) else '' - lines = [] - lines += [self.term] - lines += [NEW_LINE] - lines += [' *({0})*{1}'.format(self.classifier, postfix)] - lines += add_indent(self.definition) # definition is all ready a list - lines += [NEW_LINE] - return lines - - -class AttributeItem(DefinitionItem): - """ Definition that renders the rst output using the attribute directive. - - """ - _normal = (".. attribute:: {0}\n" - " :annotation: = {1}\n" - "\n" - "{2}\n\n") - _no_definition = (".. attribute:: {0}\n" - " :annotation: = {1}\n\n") - _no_classifier = (".. attribute:: {0}\n\n" - "{2}\n\n") - _only_term = ".. attribute:: {0}\n\n" - - def to_rst(self, ): - """ Return the attribute info using the attribute sphinx markup. - - Examples - -------- - - :: - - >>> item = AttributeItem('indent', 'int', - ... ['The indent to use for the description block.']) - >>> item.to_rst() - .. attribute:: indent - :annotation: = int - - The indent to use for the description block - >>> - - :: - - >>> item = AttributeItem('indent', '', - ... ['The indent to use for the description block.']) - >>> item.to_rst() - .. attribute:: indent - - The indent to use for the description block - >>> - - .. note:: An empty line is added at the end of the list of strings so - that the results can be concatenated directly and rendered properly - by sphinx. - - """ - definition = '\n'.join(add_indent(self.definition)) - template = self.template.format(self.term, self.classifier, definition) - return template.splitlines() - - @property - def template(self): - if self.classifier == '' and self.definition == ['']: - template = self._only_term - elif self.classifier == '': - template = self._no_classifier - elif self.definition == ['']: - template = self._no_definition - else: - template = self._normal - return template - - -class ArgumentItem(DefinitionItem): - """ A definition item for function argument sections. - - """ - _normal = (":param {0}:\n" - "{2}\n" - ":type {0}: {1}") - _no_definition = (":param {0}:\n" - ":type {0}: {1}") - _no_classifier = (":param {0}:\n" - "{2}") - _only_term = ":param {0}:" - - def to_rst(self): - """ Render ArgumentItem in sphinx friendly rst using the ``:param:`` - role. - - Example - ------- - - :: - - >>> item = ArgumentItem('indent', 'int', - ... ['The indent to use for the description block.', - '' - 'This is the second paragraph of the argument definition.']) - >>> item.to_rst() - :param indent: - The indent to use for the description block. - - This is the second paragraph of the argument definition. - :type indent: int - - .. note:: - - There is no new line added at the last line of the :meth:`to_rst` - method. - - """ - argument = fix_star(self.term) - argument = fix_trailing_underscore(argument) - argument_type = self.classifier - definition = '\n'.join(add_indent(self.definition)) - template = self.template.format(argument, argument_type, definition) - return template.splitlines() - - @property - def template(self): - if self.classifier == '' and self.definition == ['']: - template = self._only_term - elif self.classifier == '': - template = self._no_classifier - elif self.definition == ['']: - template = self._no_definition - else: - template = self._normal - return template - - -class ListItem(DefinitionItem): - """ A definition item that is rendered as an ordered/unordered list - - """ - - _normal = ("**{0}** (*{1}*) --\n" - "{2}\n\n") - _only_term = "**{0}**\n\n" - _no_definition = "**{0}** (*{1}*)\n\n" - _no_classifier = ("**{0}** --\n" - "{2}\n\n") - - def to_rst(self, prefix=None): - """ Outputs ListItem in rst using as items in an list. - - Arguments - --------- - prefix : str - The prefix to use. For example if the item is part of a numbered - list then ``prefix='-'``. - - Example - ------- - - >>> item = ListItem('indent', 'int', - ... ['The indent to use for the description block.']) - >>> item.to_rst(prefix='-') - - **indent** (`int`) -- - The indent to use for the description block. - - >>> item = ListItem('indent', 'int', - ... ['The indent to use for' - 'the description block.']) - >>> item.to_rst(prefix='-') - - **indent** (`int`) -- - The indent to use for - the description block. - - - .. note:: An empty line is added at the end of the list of strings so - that the results can be concatenated directly and rendered properly - by sphinx. - - """ - indent = 0 if (prefix is None) else len(prefix) + 1 - definition = '\n'.join(add_indent(self.definition, indent)) - template = self.template.format(self.term, self.classifier, definition) - if prefix is not None: - template = prefix + ' ' + template - return template.splitlines() - - @property - def template(self): - if self.classifier == '' and self.definition == ['']: - template = self._only_term - elif self.classifier == '': - template = self._no_classifier - elif self.definition == ['']: - template = self._no_definition - else: - template = self._normal - return template - - -class TableLineItem(DefinitionItem): - """ A Definition Item that represents a table line. - - """ - - def to_rst(self, columns=(0, 0, 0)): - """ Outputs definition in rst as a line in a table. - - Arguments - --------- - columns : tuple - The three item tuple of column widths for the term, classifier - and definition fields of the TableLineItem. When the column width - is 0 then the field - - .. note:: - - The strings attributes are clipped to the column width. - - Example - ------- - - >>> item = TableLineItem('function(arg1, arg2)', '', - ... ['This is the best function ever.']) - >>> item.to_rst(columns=(22, 0, 20)) - function(arg1, arg2) This is the best fun - - """ - definition = ' '.join([line.strip() for line in self.definition]) - term = self.term[:columns[0]] - classifier = self.classifier[:columns[1]] - definition = definition[:columns[2]] - - first_column = '' if columns[0] == 0 else '{0:<{first}} ' - second_column = '' if columns[1] == 0 else '{1:<{second}} ' - third_column = '' if columns[2] == 0 else '{2:<{third}}' - table_line = ''.join((first_column, second_column, third_column)) - - lines = [] - lines += [table_line.format(term, classifier, definition, - first=columns[0], second=columns[1], third=columns[2])] - lines += [''] - return lines - - -class MethodItem(DefinitionItem): - """ A TableLineItem subclass to parse and render class methods. - - """ - @classmethod - def is_definition(cls, line): - """ Check if the definition header is a function signature. - - """ - match = function_regex.match(line) - return match - - @classmethod - def parse(cls, lines): - """Parse a method definition item from a set of lines. - - The class method parses the method signature and definition from the - list of docstring lines and produces a MethodItem where the term - is the method name and the classifier is arguments - - .. note:: The global indention in the definition lines is striped - - The method definition item is assumed to be as follows:: - - +------------------------------+ - | term "(" [ classifier ] ")" | - +--+---------------------------+---+ - | definition | - | (body elements)+ | - +--------------------- ---------+ - - Arguments - --------- - lines : - docstring lines of the method definition item without any empty - lines before or after. - - Returns - ------- - definition : MethodItem - - """ - header = lines[0].strip() - term, classifier, _ = signature_regex.split(header) - definition = trim_indent(lines[1:]) if (len(lines) > 1) else [''] - return cls(term, classifier, definition) - - def to_rst(self, columns=(0, 0)): - """ Outputs definition in rst as a line in a table. - - Arguments - --------- - columns : tuple - The two item tuple of column widths for the :meth: role column - and the definition (i.e. summary) of the MethodItem - - .. note:: The strings attributes are clipped to the column width. - - Example - ------- - - :: - - >>> item = MethodItem('function', 'arg1, arg2', - ... ['This is the best function ever.']) - >>> item.to_rst(columns=(40, 20)) - :meth:`function ` This is the best fun - - """ - definition = ' '.join([line.strip() for line in self.definition]) - method_role = ':meth:`{0}({1}) <{0}>`'.format(self.term, - self.classifier) - table_line = '{0:<{first}} {1:<{second}}' - - lines = [] - lines += [table_line.format(method_role[:columns[0]], - definition[:columns[1]], first=columns[0], - second=columns[1])] - return lines - - @property - def signature(self): - return '{0}({1})'.format(self.term, self.classifier) - - -#------------------------------------------------------------------------------ -# Functions to work with Definition Items -#------------------------------------------------------------------------------ - -def max_attribute_length(items, attr): - """ Find the max length of the attribute in a list of DefinitionItems. - - Arguments - --------- - items : list - The list of the DefinitionItem instances (or subclasses). - - attr : str - Attribute to look at. - - """ - if attr == 'definition': - maximum = max([len(' '.join(item.definition)) for item in items]) - else: - maximum = max([len(getattr(item, attr)) for item in items]) - return maximum - - -def max_attribute_index(items, attr): - """ Find the index of the attribute with the maximum length in a list of - DefinitionItems. - - Arguments - --------- - items : list - The list of the DefinitionItems (or subclasses). - - attr : str - Attribute to look at. - - """ - if attr == 'definition': - attributes = [len(' '.join(item.definition)) for item in items] - else: - attributes = [len(getattr(item, attr)) for item in items] - - maximum = max(attributes) - return attributes.index(maximum) diff --git a/sectiondoc/function_doc.py b/sectiondoc/function_doc.py deleted file mode 100644 index f761e17..0000000 --- a/sectiondoc/function_doc.py +++ /dev/null @@ -1,88 +0,0 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: function_doc.py -# License: LICENSE.TXT -# Author: Ioannis Tziakos -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from .base_doc import BaseDoc -from .line_functions import add_indent -from .definition_items import ArgumentItem, ListItem - - -class FunctionDoc(BaseDoc): - """Docstring refactoring for functions - - The class provides the following refactoring methods. - - Methods - ------- - _refactor_arguments(self, header): - Refactor the Arguments and Parameters section to sphinx friendly - format. - - _refactor_as_items_list(self, header): - Refactor the Returns, Raises and Yields sections to sphinx friendly - format. - - _refactor_notes(self, header): - Refactor the note section to use the rst ``.. note`` directive. - - """ - - def __init__(self, lines, headers=None): - - if headers is None: - headers = {'Returns': 'as_item_list', 'Arguments': 'arguments', - 'Parameters': 'arguments', 'Raises': 'as_item_list', - 'Yields': 'as_item_list', 'Notes': 'notes'} - - super(FunctionDoc, self).__init__(lines, headers) - return - - def _refactor_as_item_list(self, header): - """ Refactor the a section to sphinx friendly item list. - - Arguments - --------- - header : str - The header name that is used for the fields (i.e. ``:
:``). - - """ - items = self.extract_items(item_class=ListItem) - lines = [':{0}:'.format(header.lower())] - prefix = None if len(items) == 1 else '-' - for item in items: - lines += add_indent(item.to_rst(prefix)) - return lines - - def _refactor_arguments(self, header): - """ Refactor the argument section to sphinx friendly format. - - Arguments - --------- - header : unused - This parameter is ignored in thi method. - - """ - items = self.extract_items(item_class=ArgumentItem) - lines = [] - for item in items: - lines += item.to_rst() - return lines - - def _refactor_notes(self, header): - """ Refactor the notes section to sphinx friendly format. - - Arguments - --------- - header : unused - This parameter is ignored in this method. - - """ - paragraph = self.get_next_paragraph() - lines = ['.. note::'] - lines += add_indent(paragraph) - return lines diff --git a/sectiondoc/items/__init__.py b/sectiondoc/items/__init__.py new file mode 100644 index 0000000..b403aec --- /dev/null +++ b/sectiondoc/items/__init__.py @@ -0,0 +1,8 @@ +__all__ = [ + 'OrDefinitionItem', + 'MethodItem', + 'Item'] + +from sectiondoc.items.item import Item +from sectiondoc.items.or_definition_item import OrDefinitionItem +from sectiondoc.items.method_item import MethodItem diff --git a/sectiondoc/items/item.py b/sectiondoc/items/item.py new file mode 100644 index 0000000..1b0cd63 --- /dev/null +++ b/sectiondoc/items/item.py @@ -0,0 +1,86 @@ +import abc +from collections import namedtuple + + +class Item(namedtuple('Item', ['term', 'classifiers', 'definition'])): + """ A section item. + + The Item class is responsible to check, parse and render a docstring + item into sphinx friendly rst. + + Format diagram:: + + +-------------------------------------------------+ + | header | + +--+----------------------------------------------+---+ + | definition | + | (body elements)+ | + +--------------------------------------------------+ + + + Depending only in the type of the list item the header is split into a + term and one or more classifiers. + + Attributes + ---------- + term : str + The term usually reflects the name of a parameter or an attribute. + + classifiers : list + The classifier(s) of the term. Commonly used to reflect the type + of an argument or the signature of a function. + + definition : list + The list of strings that holds the description of the definition item. + + """ + + @property + def mode(self): + """ Property (`string`), the operational mode of the item based on the + available info. Possible values are ``{'only_term', 'no_classifiers', + 'no_definition', 'full'}``. + + + """ + if self.classifiers == [] and self.definition == ['']: + mode = 'only_term' + elif self.classifiers == []: + mode = 'no_classifiers' + elif self.definition == ['']: + mode = 'no_definition' + else: + mode = 'full' + return mode + + @classmethod + def is_item(cls, line): + """ Check if the line is describing an item. + + The method is used to check that a line is following the expected + format for the `term` and `classifiers` attributes. + + """ + raise NotImplementedError() + + @classmethod + def parse(cls, lines): + """ Parse a definition item from a set of lines. + + The class method parses the item from the list of docstring lines and + produces a Item with the term, classifier and the definition. + + .. note:: The global indention in the definition lines is striped + + Arguments + --------- + lines : + docstring lines of the definition without any empty lines before or + after. + + Returns + ------- + item : Item + + """ + raise NotImplementedError() diff --git a/sectiondoc/items/method_item.py b/sectiondoc/items/method_item.py new file mode 100644 index 0000000..3ec01c3 --- /dev/null +++ b/sectiondoc/items/method_item.py @@ -0,0 +1,62 @@ +from sectiondoc.items.item import Item +from sectiondoc.items.regex import function_regex, signature_regex +from sectiondoc.util import trim_indent + + +class MethodItem(Item): + """ A MethodItem for method descriptions. + + """ + + @property + def signature(self): + return '{0}({1})'.format(self.term, ', '.join(self.classifiers)) + + @classmethod + def is_item(cls, line): + """ Check if the definition header is a function signature. + + The expected header has the following format:: + + +------------------------------+ + | term "(" [ classifier ] ")" | + +------------------------------+ + + """ + return function_regex.match(line) + + @classmethod + def parse(cls, lines): + """ Parse a method definition item from a set of lines. + + Parse the method signature and definition from the list of docstring + lines and produce a MethodItem where the `term` is the method name and + the classifier is arguments. + + .. note:: The global indention in the definition lines is striped + + The format of the method definition item is expected to be as follows:: + + +------------------------------+ + | term "(" [ classifier ] ")" | + +--+---------------------------+---+ + | definition | + | (body elements)+ | + +-------------------------------+ + + Arguments + --------- + lines : + docstring lines of the method definition item without any empty + lines before or after. + + Returns + ------- + definition : MethodItem + + """ + header = lines[0].strip() + term, classifiers, _ = signature_regex.split(header) + classifiers = [classifiers.strip()] + definition = trim_indent(lines[1:]) if (len(lines) > 1) else [''] + return cls(term, classifiers, definition) diff --git a/sectiondoc/items/or_definition_item.py b/sectiondoc/items/or_definition_item.py new file mode 100644 index 0000000..4884a87 --- /dev/null +++ b/sectiondoc/items/or_definition_item.py @@ -0,0 +1,110 @@ +from sectiondoc.items.regex import definition_regex, header_regex +from sectiondoc.items.item import Item +from sectiondoc.util import trim_indent + + +class OrDefinitionItem(Item): + """ A docstring definition section item. + + In this section definition item there are two classifiers that are + separated by ``or``. + + Syntax diagram:: + + +-------------------------------------------------+ + | term [ " : " classifier [ " or " classifier] ] | + +--+----------------------------------------------+---+ + | definition | + | (body elements)+ | + +--------------------------------------------------+ + + Attributes + ---------- + term : str + The term usually reflects the name of a parameter or an attribute. + + classifiers : list + The classifiers of the definition. Commonly used to reflect the type + of an argument or the signature of a function. Only two classifiers + are accepted. + + definition : list + The list of strings that holds the description the definition item. + + .. note:: An Or Definition item is based on the item of a section definition + list as it defined in restructured text + (_http://docutils.sourceforge.net/docs/ref/rst/restructuredtext.html#sections). + + """ + + @classmethod + def is_item(cls, line): + """ Check if the line is describing a definition item. + + The method is used to check that a line is following the expected + format for the term and classifier attributes. + + The expected format is:: + + +-------------------------------------------------+ + | term [ " : " classifier [ " or " classifier] ] | + +-------------------------------------------------+ + + Subclasses can subclass to restrict or expand this format. + + """ + return definition_regex.match(line) is not None + + @classmethod + def parse(cls, lines): + """Parse a definition item from a set of lines. + + The class method parses the definition list item from the list of + docstring lines and produces a DefinitionItem with the term, + classifier and the definition. + + .. note:: The global indention in the definition lines is striped + + The term definition is assumed to be in one of the following formats:: + + term + Definition. + + :: + + term + Definition, paragraph 1. + + Definition, paragraph 2. + + :: + + term : classifier + Definition. + + :: + + term : classifier or classifier + Definition. + + Arguments + --------- + lines + docstring lines of the definition without any empty lines before or + after. + + Returns + ------- + definition : OrDefinitionItem + + """ + header = lines[0].strip() + term, classifiers = header_regex.split( + header, maxsplit=1) if (' :' in header) else (header, '') + classifiers = [ + classifier.strip() for classifier in classifiers.split('or')] + if classifiers == ['']: + classifiers = [] + trimed_lines = trim_indent(lines[1:]) if (len(lines) > 1) else [''] + definition = [line.rstrip() for line in trimed_lines] + return Item(term.strip(), classifiers, definition) diff --git a/sectiondoc/items/regex.py b/sectiondoc/items/regex.py new file mode 100644 index 0000000..5f6a287 --- /dev/null +++ b/sectiondoc/items/regex.py @@ -0,0 +1,22 @@ +import re + +function_regex = re.compile(r'\w+\(.*\)\s*') +signature_regex = re.compile('\((.*)\)') +header_regex = re.compile(r'\s:\s?') +definition_regex = re.compile(r""" +\*{0,2} # no, one or two stars +\w+\s: # a word followed by a semicolumn and optionally a space +( + \s # just a space + | # OR + \s[\w.]+ # dot separated words + (\(.*\))? # with maybe a signature + | + \s[\w.]+ # dot separated words + (\(.*\))? + \sor # with an or in between + \s[\w.]+ + (\(.*\))? +)? +$ # match at the end of the line +""", re.VERBOSE) diff --git a/sectiondoc/items/util.py b/sectiondoc/items/util.py new file mode 100644 index 0000000..0bf2047 --- /dev/null +++ b/sectiondoc/items/util.py @@ -0,0 +1,38 @@ +def max_attribute_length(items, attr): + """ Find the max length of the attribute in a list of Items. + + Arguments + --------- + items : list + The list of Item instances. + + attr : str + Attribute to look at. + + """ + if attr == 'definition': + maximum = max([len(' '.join(item.definition)) for item in items]) + else: + maximum = max([len(getattr(item, attr)) for item in items]) + return maximum + + +def max_attribute_index(items, attr): + """ Find the index of the field with the maximum length in a list of Items. + + Arguments + --------- + items : list + The list of Item instances. + + attr : str + Attribute to look at. + + """ + if attr == 'definition': + attributes = [len(' '.join(item.definition)) for item in items] + else: + attributes = [len(getattr(item, attr)) for item in items] + + maximum = max(attributes) + return attributes.index(maximum) diff --git a/sectiondoc/renderers/__init__.py b/sectiondoc/renderers/__init__.py new file mode 100644 index 0000000..2ef037c --- /dev/null +++ b/sectiondoc/renderers/__init__.py @@ -0,0 +1,16 @@ +__all__ = [ + 'Method', + 'Argument', + 'Renderer', + 'Attribute', + 'ListItem', + 'TableRow', + 'Definition'] + +from sectiondoc.renderers.method import Method +from sectiondoc.renderers.argument import Argument +from sectiondoc.renderers.renderer import Renderer +from sectiondoc.renderers.attribute import Attribute +from sectiondoc.renderers.list_item import ListItem +from sectiondoc.renderers.table_row import TableRow +from sectiondoc.renderers.definition import Definition diff --git a/sectiondoc/renderers/argument.py b/sectiondoc/renderers/argument.py new file mode 100644 index 0000000..36adc20 --- /dev/null +++ b/sectiondoc/renderers/argument.py @@ -0,0 +1,48 @@ +from sectiondoc.renderers.renderer import Renderer +from sectiondoc.util import add_indent, fix_star, fix_trailing_underscore + + +class Argument(Renderer): + """ Render an item as a sphinx parameter role. + + """ + templates = { + "full": ":param {0}:\n{2}\n:type {0}: {1}", + "no_definition": ":param {0}:\n:type {0}: {1}", + "no_classifiers": ":param {0}:\n{2}", + "only_term": ":param {0}:"} + + def to_rst(self): + """ Render an item as an argument using the ``:param:`` + role. + + Example + ------- + + :: + + >>> item = Item('indent', 'int', + ... ['The indent to use for the description block.', + '' + 'This is the second paragraph of the argument definition.']) + >>> renderer = Argument(item) + >>> renderer.to_rst() + :param indent: + The indent to use for the description block. + This is the second paragraph of the argument definition. + :type indent: int + + .. note:: + + There is no new line added at the last line of the :meth:`to_rst` + method. + + """ + item = self.item + argument = fix_star(item.term) + argument = fix_trailing_underscore(argument) + argument_types = ' or '.join(item.classifiers) + definition = '\n'.join(add_indent(item.definition)) + template = self.templates[item.mode].format( + argument, argument_types, definition) + return template.splitlines() diff --git a/sectiondoc/renderers/attribute.py b/sectiondoc/renderers/attribute.py new file mode 100644 index 0000000..8a045d0 --- /dev/null +++ b/sectiondoc/renderers/attribute.py @@ -0,0 +1,52 @@ +from sectiondoc.renderers.renderer import Renderer +from sectiondoc.util import add_indent + + +class Attribute(Renderer): + """ Render an Item instance using the sphinx attribute directive. + + """ + + templates = { + "full": ".. attribute:: {0}\n :annotation: = {1}\n\n{2}\n\n", + "no_definition": ".. attribute:: {0}\n :annotation: = {1}\n\n", + "no_classifiers": ".. attribute:: {0}\n\n{2}\n\n", + "only_term": ".. attribute:: {0}\n\n"} + + def to_rst(self): + """ Return the attribute info using the attribute sphinx markup. + + Examples + -------- + + :: + + >>> item = Item('indent', 'int', + ... ['The indent to use for the description block.']) + >>> Attribute(item).to_rst() + .. attribute:: indent + :annotation: = `int` + + The indent to use for the description block + >>> + + :: + + >>> item = Item('indent', '', + ... ['The indent to use for the description block.']) + >>> Attribute(item).to_rst() + .. attribute:: indent + + The indent to use for the description block + >>> + + .. note:: An empty line is added at the end of the list of strings so + that the results can be concatenated directly and rendered properly + by sphinx. + + """ + item = self.item + definition = '\n'.join(add_indent(item.definition)) + template = self.templates[item.mode].format( + item.term, ' or '.join(item.classifiers), definition) + return template.splitlines() diff --git a/sectiondoc/renderers/definition.py b/sectiondoc/renderers/definition.py new file mode 100644 index 0000000..86e0fc8 --- /dev/null +++ b/sectiondoc/renderers/definition.py @@ -0,0 +1,61 @@ +from sectiondoc.renderers.renderer import Renderer +from sectiondoc.util import add_indent, NEW_LINE + + +class Definition(Renderer): + """ Render an Item instance as a sphinx definition term + + """ + + def to_rst(self, **kwards): + """ Outputs the Item in sphinx friendly rst. + + The method renders the `definition` into a list of lines that + follow the rst markup of a sphinx definition item:: + + + + () -- + + + Returns + ------- + lines : list + A list of string lines rendered in rst. + + Example + ------- + + :: + + >>> item = Item( + 'lines', 'list', + ['A list of string lines rendered in rst.']) + >>> renderer = Definition(item) + >>> renderer.to_rst + lines + + *(list)* -- + A list of string lines rendered in rst. + + .. note:: An empty line is added at the end of the list of strings so + that the results can be concatenated directly and rendered properly + by sphinx. + + + """ + item = self.item + postfix = ' --' if (len(item.definition) > 0) else '' + lines = [] + lines += [item.term] + lines += [NEW_LINE] + number_of_classifiers = len(item.classifiers) + if number_of_classifiers == 1: + lines += [' *({0[0]})*{1}'.format(item.classifiers, postfix)] + elif number_of_classifiers == 2: + lines += [ + ' *({0[0]} or {0[1]})*{2}'.format( + item.classifiers, postfix)] + lines += add_indent(item.definition) # definition is already a list + lines += [NEW_LINE] + return lines diff --git a/sectiondoc/renderers/list_item.py b/sectiondoc/renderers/list_item.py new file mode 100644 index 0000000..5e89c05 --- /dev/null +++ b/sectiondoc/renderers/list_item.py @@ -0,0 +1,57 @@ +from sectiondoc.renderers import Renderer +from sectiondoc.util import add_indent + + +class ListItem(Renderer): + """ Rendered an item instance as an ordered/unordered list item. + + """ + + templates = { + "full": "**{0}** (*{1}*) --\n{2}\n\n", + "only_term": "**{0}**\n\n", + "no_definition": "**{0}** (*{1}*)\n\n", + "no_classifiers": "**{0}** --\n{2}\n\n"} + + def to_rst(self, prefix=None): + """ Renders an item as items in an rst list. + + Arguments + --------- + prefix : str + The prefix to use. For example if the item is part of an + unnumbered list then ``prefix='-'``. + + Example + ------- + + >>> item = Item('indent', 'int', + ... ['The indent to use for the description block.']) + >>> renderer = ListItem(item) + >>> renderer.to_rst(prefix='-') + - **indent** (`int`) -- + The indent to use for the description block. + + >>> item = Item('indent', 'int', + ... ['The indent to use for' + 'the description block.']) + >>> renderer = ListItem(item) + >>> renderer.to_rst(prefix='-') + - **indent** (`int`) -- + The indent to use for + the description block. + + + .. note:: An empty line is added at the end of the list of strings so + that the results can be concatenated directly and rendered properly + by sphinx. + + """ + item = self.item + indent = 0 if (prefix is None) else len(prefix) + 1 + definition = '\n'.join(add_indent(item.definition, indent)) + template = self.templates[item.mode].format( + item.term, ' or '.join(item.classifiers), definition) + if prefix is not None: + template = prefix + ' ' + template + return template.splitlines() diff --git a/sectiondoc/renderers/method.py b/sectiondoc/renderers/method.py new file mode 100644 index 0000000..4d09f6a --- /dev/null +++ b/sectiondoc/renderers/method.py @@ -0,0 +1,40 @@ +from sectiondoc.renderers.renderer import Renderer + + +class Method(Renderer): + """ Render method items as a table row. + """ + + def to_rst(self, columns=(0, 0)): + """ Outputs definition in rst as a line in a table. + + Arguments + --------- + columns : tuple + The two item tuple of column widths for the `:meth:` role column + and the definition (i.e. summary) of the MethodItem + + .. note:: The string attributes are clipped to the column width. + + Example + ------- + + :: + + >>> item = MethodItem('function', 'arg1, arg2', + ... ['This is the best function ever.']) + >>> renderer = Method(item) + >>> renderer.to_rst(columns=(40, 20)) + :meth:`function ` This is the best fun + + """ + item = self.item + definition = ' '.join([line.strip() for line in item.definition]) + method_role = ':meth:`{0}({1}) <{0}>`'.format( + item.term, ', '.join(item.classifiers)) + table_line = '{0:<{first}} {1:<{second}}' + lines = [] + lines += [table_line.format(method_role[:columns[0]], + definition[:columns[1]], first=columns[0], + second=columns[1])] + return lines diff --git a/sectiondoc/renderers/renderer.py b/sectiondoc/renderers/renderer.py new file mode 100644 index 0000000..b70b1ae --- /dev/null +++ b/sectiondoc/renderers/renderer.py @@ -0,0 +1,27 @@ +import abc + + +class Renderer(object): + """ An item renderer. + """ + + def __init__(self, item=None): + self.item = item + + @abc.abstractmethod + def to_rst(self, **kwards): + """ Outputs the `item` in sphinx friendly rst. + + The method renders the passed into a list of lines that follow + the rst markup. + + Subclasses need to override the method to provide their custom made + behaviour. However the signature of the method should hold only + keyword arguments which always have default values. + + Returns + ------- + lines : list + A list of string lines rendered in rst. + + """ diff --git a/sectiondoc/renderers/table_row.py b/sectiondoc/renderers/table_row.py new file mode 100644 index 0000000..b1efb13 --- /dev/null +++ b/sectiondoc/renderers/table_row.py @@ -0,0 +1,46 @@ +from sectiondoc.renderers.renderer import Renderer + + +class TableRow(Renderer): + """ Render an Item that represents a table line. + + """ + + def to_rst(self, columns=(0, 0, 0)): + """ Outputs definition in rst as a line in a table. + + Arguments + --------- + columns : tuple + The three item tuple of column widths for the term, classifiers + and definition fields of the TableLineItem. When the column width + is 0 then the field is ignored. + + .. note:: + - The strings attributes are clipped to the column width. + + Example + ------- + + >>> item = Item('function(arg1, arg2)', '', + ... ['This is the best function ever.']) + >>> TableRow(item).to_rst(columns=(22, 0, 20)) + function(arg1, arg2) This is the best fun + + """ + item = self.item + definition = ' '.join([line.strip() for line in item.definition]) + term = item.term[:columns[0]] + classifiers = ', '.join(item.classifiers)[:columns[1]] + definition = definition[:columns[2]] + + first_column = '' if columns[0] == 0 else '{0:<{first}} ' + second_column = '' if columns[1] == 0 else '{1:<{second}} ' + third_column = '' if columns[2] == 0 else '{2:<{third}}' + table_line = ''.join((first_column, second_column, third_column)) + + lines = [] + lines += [table_line.format(term, classifiers, definition, + first=columns[0], second=columns[1], third=columns[2])] + lines += [''] + return lines diff --git a/sectiondoc/sections/__init__.py b/sectiondoc/sections/__init__.py new file mode 100644 index 0000000..7fa74a5 --- /dev/null +++ b/sectiondoc/sections/__init__.py @@ -0,0 +1,14 @@ +__all__ = [ + 'attributes', + 'notes_paragraph', + 'methods_table', + 'rubric', + 'arguments', + 'item_list'] + +from sectiondoc.sections.attributes import attributes +from sectiondoc.sections.notes import notes_paragraph +from sectiondoc.sections.methods import methods_table +from sectiondoc.sections.rubric import rubric +from sectiondoc.sections.arguments import arguments +from sectiondoc.sections.item_list import item_list diff --git a/sectiondoc/sections/arguments.py b/sectiondoc/sections/arguments.py new file mode 100644 index 0000000..411d3d5 --- /dev/null +++ b/sectiondoc/sections/arguments.py @@ -0,0 +1,36 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# License: LICENSE.TXT +# Author: Ioannis Tziakos +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.items import OrDefinitionItem +from sectiondoc.renderers.argument import Argument + + +def arguments(doc, header, renderer=Argument, item_class=OrDefinitionItem): + """ Render the argument section to sphinx friendly format. + + Arguments + --------- + doc : DocRender + The docstring container. + header : string + This parameter is ignored in this method. + + renderer : Renderer + A renderer instance to render the items. + + item_class : type + The item parser class to use. Default is :class:`~.orDefinitionItem`. + + """ + items = doc.extract_items(item_class) + lines = [] + renderer = renderer() + for item in items: + renderer.item = item + lines += renderer.to_rst() + return lines diff --git a/sectiondoc/sections/attributes.py b/sectiondoc/sections/attributes.py new file mode 100644 index 0000000..1f31670 --- /dev/null +++ b/sectiondoc/sections/attributes.py @@ -0,0 +1,23 @@ +# -*- coding: utf-8 -*- +#----------------------------------------------------------------------------- +# License: LICENSE.TXT +# Author: Ioannis Tziakos +# +# Copyright (c) 2011-2014, Enthought, Inc. +# All rights reserved. +#----------------------------------------------------------------------------- +from sectiondoc.items import OrDefinitionItem +from sectiondoc.renderers import Attribute + + +def attributes(doc, header, renderer=Attribute, item_class=OrDefinitionItem): + """Render the attributes section to sphinx friendly format. + + """ + items = doc.extract_items(item_class) + lines = [] + renderer = renderer() + for item in items: + renderer.item = item + lines += renderer.to_rst() + return lines diff --git a/sectiondoc/sections/item_list.py b/sectiondoc/sections/item_list.py new file mode 100644 index 0000000..b0e6d6f --- /dev/null +++ b/sectiondoc/sections/item_list.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# License: LICENSE.TXT +# Author: Ioannis Tziakos +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.util import add_indent +from sectiondoc.items import OrDefinitionItem +from sectiondoc.renderers import ListItem + + +def item_list(doc, header, renderer=ListItem, item_class=OrDefinitionItem): + """ Render the section to sphinx friendly item list. + + Arguments + --------- + doc : DocRender + The docstring container. + + header : str + The header name that is used for the fields (i.e. ``:
:``). + + renderer : Renderer + A renderer instance to render the items. + + item_class : type + The item parser class to use. Default is :class:`~.OrDefinitionItem`. + + """ + items = doc.extract_items(item_class) + lines = [':{0}:'.format(header.lower())] + prefix = None if len(items) == 1 else '-' + renderer = renderer() + for item in items: + renderer.item = item + lines += add_indent(renderer.to_rst(prefix)) + return lines diff --git a/sectiondoc/sections/methods.py b/sectiondoc/sections/methods.py new file mode 100644 index 0000000..3aaefd5 --- /dev/null +++ b/sectiondoc/sections/methods.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# file: sections/methods.py +# License: LICENSE.TXT +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.items import MethodItem +from sectiondoc.sections.util import get_column_lengths +from sectiondoc.renderers import Method + + +def methods_table(doc, header, renderer=Method, item_class=MethodItem): + """ Render the methods section to sphinx friendly table format. + + """ + items = doc.extract_items(item_class) + lines = [] + if len(items) > 0: + columns = get_column_lengths(items) + border = '{0:=^{1}} {0:=^{2}}'.format('', columns[0], columns[1]) + heading = '{0:<{2}} {1:<{3}}'.format('Method', 'Description', + columns[0], columns[1]) + lines += [border] + lines += [heading] + lines += [border] + renderer = Method() + for item in items: + renderer.item = item + lines += renderer.to_rst(columns) + lines += [border] + lines += [''] + lines = [line.rstrip() for line in lines] + return lines diff --git a/sectiondoc/sections/notes.py b/sectiondoc/sections/notes.py new file mode 100644 index 0000000..ec3eb30 --- /dev/null +++ b/sectiondoc/sections/notes.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# file: sections/notes.py +# License: LICENSE.TXT +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.util import add_indent + + +def notes_paragraph(doc, header, renderer=None, item_class=None): + """Render the note section to use the rst ``.. note`` directive. + + The section is expected to be given as a paragraph. + + """ + paragraph = doc.get_next_paragraph() + lines = ['.. note::'] + lines += add_indent(paragraph) + return lines diff --git a/sectiondoc/sections/rubric.py b/sectiondoc/sections/rubric.py new file mode 100644 index 0000000..97e2a7e --- /dev/null +++ b/sectiondoc/sections/rubric.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# License: LICENSE.TXT +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.util import fix_backspace, NEW_LINE + + +def rubric(doc, header, renderer=None, item_class=None): + """ Refactor a header section using the rubric directive. + + The method supports refactoring of single word headers, two word headers + and headers that include a backslash ''\''. + + Arguments + --------- + header : string + The header string to use with the rubric directive. + + """ + header = fix_backspace(header) + directive = '.. rubric:: {0}'.format(header) + return [directive, NEW_LINE] diff --git a/sectiondoc/sections/util.py b/sectiondoc/sections/util.py new file mode 100644 index 0000000..4e718a0 --- /dev/null +++ b/sectiondoc/sections/util.py @@ -0,0 +1,47 @@ + +# -*- coding: utf-8 -*- +# ----------------------------------------------------------------------------- +# file: sections/util.py +# License: LICENSE.TXT +# +# Copyright (c) 2011-14, Enthought, Inc. +# All rights reserved. +# ----------------------------------------------------------------------------- +from sectiondoc.items.util import max_attribute_length, max_attribute_index + + +def get_column_lengths(items): + """ Helper function to estimate the column widths for the refactoring of + the ``Methods`` section. + + The method finds the index of the item that has the largest function + name (i.e. self.term) and the largest signature. If the indexes are not + the same then checks to see which of the two items have the largest + string sum (i.e. self.term + self.signature). + + Parameters + ---------- + items : list + A list of MethodItems + + Returns + ------- + widths : tuple + A tuple of the first_column and second_column maximum widths. + + """ + name_index = max_attribute_index(items, 'term') + signature_index = max_attribute_index(items, 'signature') + if signature_index != name_index: + index = signature_index + item1_width = len(items[index].term + items[index].signature) + index = name_index + item2_width = len(items[index].term + items[index].signature) + first_column = max(item1_width, item2_width) + else: + index = name_index + first_column = len(items[index].term + items[index].signature) + + first_column += 11 # Add boilerplate characters + second_column = max_attribute_length(items, 'definition') + return (first_column, second_column) diff --git a/sectiondoc/styles/__init__.py b/sectiondoc/styles/__init__.py new file mode 100644 index 0000000..fd69655 --- /dev/null +++ b/sectiondoc/styles/__init__.py @@ -0,0 +1,6 @@ +__all__ = [ + 'Style', + 'DocRender'] + +from sectiondoc.styles.style import Style +from sectiondoc.styles.doc_render import DocRender diff --git a/sectiondoc/base_doc.py b/sectiondoc/styles/doc_render.py similarity index 53% rename from sectiondoc/base_doc.py rename to sectiondoc/styles/doc_render.py index 5fa3e89..9bcf6ec 100644 --- a/sectiondoc/base_doc.py +++ b/sectiondoc/styles/doc_render.py @@ -1,153 +1,119 @@ # -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # file: base_doc.py # License: LICENSE.TXT # -# Copyright (c) 2011, Enthought, Inc. +# Copyright (c) 2011-14, Enthought, Inc. # All rights reserved. -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- import re -from .definition_items import DefinitionItem -from .line_functions import is_empty, get_indent, fix_backspace, NEW_LINE +from sectiondoc.items import OrDefinitionItem +from sectiondoc.util import is_empty, get_indent +from sectiondoc.sections import rubric underline_regex = re.compile(r'\s*\S+\s*\Z') -#------------------------------------------------------------------------------ -# Classes -#------------------------------------------------------------------------------ - -class BaseDoc(object): - """Base abstract docstring refactoring class. +class DocRender(object): + """ Docstring rendering class. The class' main purpose is to parse the docstring and find the - sections that need to be refactored. Subclasses should provide - the methods responsible for refactoring the sections. + sections that need to be refactored. The operation take place in + two stages: + + - The class is instanciated with the appropriate section renderers + - The ``parse`` method is called to parse and render the sections + inplace. Attributes ---------- docstring : list - A list of strings (lines) that holds docstrings + A list of strings (lines) that holds docstrings. The lines are changed + inplace. index : int - The current zero-based line number of the docstring that is currently + The zero-based line number of the docstring that is currently processed. - headers : dict - The sections that the class will refactor. Each entry in the - dictionary should have as key the name of the section in the - form that it appears in the docstrings. The value should be - the postfix of the method, in the subclasses, that is - responsible for refactoring (e.g. {'Methods': 'method'}). - - BaseDoc also provides a number of methods that operate on the docstring to - help with the refactoring. This is necessary because the docstring has to - change inplace and thus it is better to live the docstring manipulation to - the class methods instead of accessing the lines directly. + sections : dict + The sections that will be detected and rendered. The dictionary + maps the section headers for detection to a tuple containing + the section rendering function and optional values for the item + renderer and parser. """ - def __init__(self, lines, headers=None): - """ Initialize the class - - The method setups the class attributes and starts parsing the - docstring to find and refactor the sections. + def __init__(self, lines, sections=None): + """ Arguments --------- - lines : list of strings - The docstring to refactor + lines : list + The docstring as a list of strings where to render the sections - headers : dict - The sections for which the class has custom refactor methods. - Each entry in the dictionary should have as key the name of - the section in the form that it appears in the docstrings. - The value should be the postfix of the method, in the - subclasses, that is responsible for refactoring (e.g. - {'Methods': 'method'}). + sections : dict + The sections that will be detected and rendered. The dictionary + maps the section headers for detection to a tuple containing + the section rendering function and optional values for the item + renderer and parser. If on section rendering information is + provided the default behaviour of the class is to render + every section using the rubric rendering function. """ try: self._docstring = lines.splitlines() except AttributeError: self._docstring = lines - self.headers = {} if headers is None else headers + self.sections = {} if sections is None else sections self.bookmarks = [] def parse(self): """ Parse the docstring. The docstring is parsed for sections. If a section is found then - the corresponding refactoring method is called. + the corresponding section rendering method is called. """ self.index = 0 self.seek_to_next_non_empty_line() while not self.eod: - header = self.is_section() - if header: - self._refactor(header) + section = self.is_section() + if len(section) > 0: + self._render(section) else: self.index += 1 self.seek_to_next_non_empty_line() - def _refactor(self, header): - """Call the heading refactor method. - - The header is removed from the docstring and the docstring - refactoring is dispatched to the appropriate refactoring method. + def _render(self, section): + """ Call the section rendering function. - The name of the refactoring method is constructed using the form - _refactor_
. Where
is the value corresponding to - ``self.headers[header]``. If there is no custom method for the - section then the self._refactor_header() is called with the - found header name as input. + The header is removed from the docstring and the appropriate + rendering function is executed. """ self.remove_lines(self.index, 2) # Remove header self.remove_if_empty(self.index) # Remove space after header - refactor_postfix = self.headers.get(header, 'header') - method_name = ''.join(('_refactor_', refactor_postfix)) - method = getattr(self, method_name) - lines = method(header) + method, renderer, item_class = self.sections.get( + section, (rubric, None, None)) + lines = method(self, section, renderer, item_class) self.insert_and_move(lines, self.index) - def _refactor_header(self, header): - """ Refactor the header section using the rubric directive. - - The method has been tested and supports refactoring single word - headers, two word headers and headers that include a backslash - ''\''. - - Arguments - --------- - header : string - The header string to use with the rubric directive. - - """ - header = fix_backspace(header) - directive = '.. rubric:: {0}'.format(header) - lines = [] - lines += [directive, NEW_LINE] - return lines - - def extract_items(self, item_class=None): - """ Extract the definition items from a docstring. + def extract_items(self, item_type): + """ Extract the section items from a docstring. Parse the items in the description of a section into items of the - provided class time. Given a DefinitionItem or a subclass defined by - the ``item_class`` parameter. Staring from the current index position, - the method checks if in the next two lines a valid header exists. - If successful, then the lines that belong to the item description - block (i.e. header + definition) are popped out from the docstring - and passed to the ``item_class`` parser and create an instance of - ``item_class``. - - The process is repeated until there is no compatible ``item_class`` - found or we run out of docstring. Then the method returns a list of - item_class instances. + provided itme type. The method starts at the current line index + position and checks if in the next two lines contain a valid item of + the desired type. If successful, the lines that belong to the item + description block (i.e. item header + item body) are popped out from + the docstring and passed to the ``item_type.parser`` class method to + get a new instance of ``item_type``. + + The process is repeated until there are no compatible ``item_type`` + items found in the section or we run out of docstring lines, + The collected item instances are returned The exit conditions allow for two valid section item layouts: @@ -173,21 +139,19 @@ def extract_items(self, item_class=None): Arguments --------- - item_class : DefinitionItem - A DefinitionItem or a subclass. This argument is used to check + item_type : Item + An Item type or a subclass. This argument is used to check if a line in the docstring is a valid item and to parse the - individual list items in the section. When ``None`` (default) the - base DefinitionItem class is used. - + individual list items in the section. Returns ------- - parameters : list - List of the parsed item instances of ``item_class`` type. + items : list + List of the collected item instances of :class:`~.Item` type. """ - item_type = DefinitionItem if (item_class is None) else item_class - is_item = item_type.is_definition + item_type = OrDefinitionItem if (item_type is None) else item_type + is_item = item_type.is_item item_blocks = [] while (not self.eod) and \ (is_item(self.peek()) or is_item(self.peek(1))): @@ -200,8 +164,8 @@ def get_next_block(self): """ Get the next item block from the docstring. The method reads the next item block in the docstring. The first line - is assumed to be the DefinitionItem header and the following lines to - belong to the definition:: + is assumed to be the Item header and the following lines to + belong to the definition body::
@@ -216,9 +180,9 @@ def get_next_block(self): while not self.eod: peek_0 = self.peek() peek_1 = self.peek(1) - if is_empty(peek_0) and not peek_1.startswith(sub_indent) \ - or not is_empty(peek_0) \ - and not peek_0.startswith(sub_indent): + if is_empty(peek_0) and not peek_1.startswith(sub_indent): + break + elif not is_empty(peek_0) and not peek_0.startswith(sub_indent): break else: line = self.pop() @@ -240,34 +204,36 @@ def is_section(self): # check for underline type format underline = underline_regex.match(line2) if underline is None: - return False + return '' # is the next line an rst section underline? striped_header = header.rstrip() expected_underline1 = re.sub(r'[A-Za-z\\]|\b\s', '-', striped_header) expected_underline2 = re.sub(r'[A-Za-z\\]|\b\s', '=', striped_header) - if ((underline.group().rstrip() == expected_underline1) or - (underline.group().rstrip() == expected_underline2)): + if ( + (underline.group().rstrip() == expected_underline1) or + (underline.group().rstrip() == expected_underline2)): return header.strip() else: - return False + return '' def insert_lines(self, lines, index): - """ Insert refactored lines + """ Insert lines in the docstring. Arguments --------- - new_lines : list + lines : list The list of lines to insert index : int Index to start the insertion + """ docstring = self.docstring for line in reversed(lines): docstring.insert(index, line) def insert_and_move(self, lines, index): - """ Insert refactored lines and move current index to the end. + """ Insert lines and move the current index to the end. """ self.insert_lines(lines, index) @@ -325,9 +291,9 @@ def bookmark(self): def goto_bookmark(self, bookmark_index=-1): """ Move to bookmark. - Move the current index to the docstring line given my the - ``self.bookmarks[bookmark_index]`` and remove it from the bookmark - list. Default value will pop the last entry. + Move the current index to the docstring line given by the + ``self.bookmarks[bookmark_index]`` and remove it from the + bookmark list. Default value will pop the last entry. Returns ------- @@ -349,7 +315,6 @@ def peek(self, ahead=0): ahead : int The number of lines to look ahead. - """ position = self.index + ahead try: @@ -362,7 +327,7 @@ def pop(self, index=None): """ Pop a line from the dostrings. """ - index = self.index if (index is None) else index + index = self.index if index is None else index return self._docstring.pop(index) @property diff --git a/sectiondoc/styles/legacy.py b/sectiondoc/styles/legacy.py new file mode 100644 index 0000000..0839fe9 --- /dev/null +++ b/sectiondoc/styles/legacy.py @@ -0,0 +1,38 @@ +from sectiondoc.sections import ( + attributes, methods_table, notes_paragraph, item_list, arguments) +from sectiondoc.renderers import Attribute, Method, Argument, ListItem +from sectiondoc.items import OrDefinitionItem, MethodItem +from sectiondoc.styles.doc_render import DocRender +from sectiondoc.styles.style import Style + + +def class_section(lines): + return DocRender( + lines, + sections={ + 'Attributes': (attributes, Attribute, OrDefinitionItem), + 'Arguments': (arguments, Argument, OrDefinitionItem), + 'Parameters': (arguments, Argument, OrDefinitionItem), + 'Methods': (methods_table, Method, MethodItem), + 'Notes': (notes_paragraph, None, None)}) + + +def function_section(lines): + return DocRender( + lines, + sections={ + 'Returns': (item_list, ListItem, OrDefinitionItem), + 'Arguments': (arguments, Argument, OrDefinitionItem), + 'Parameters': (arguments, Argument, OrDefinitionItem), + 'Raises': (item_list, ListItem, OrDefinitionItem), + 'Yields': (item_list, ListItem, OrDefinitionItem), + 'Notes': (notes_paragraph, None, None)}) + + +def setup(app): + style = Style({ + 'class': class_section, + 'function': function_section, + 'method': function_section}) + app.setup_extension('sphinx.ext.autodoc') + app.connect('autodoc-process-docstring', style.render_docstring) diff --git a/sectiondoc/styles/style.py b/sectiondoc/styles/style.py new file mode 100644 index 0000000..891cef0 --- /dev/null +++ b/sectiondoc/styles/style.py @@ -0,0 +1,10 @@ +class Style(object): + + def __init__(self, rendering_map): + self.rendering_map = rendering_map + + def render_docstring(self, app, what, name, obj, options, lines): + renderer_factory = self.rendering_map.get(what, None) + if renderer_factory is not None: + docstring_renderer = renderer_factory(lines) + docstring_renderer.parse() diff --git a/tests/__init__.py b/sectiondoc/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to sectiondoc/tests/__init__.py diff --git a/tests/_compat.py b/sectiondoc/tests/_compat.py similarity index 100% rename from tests/_compat.py rename to sectiondoc/tests/_compat.py diff --git a/tests/test_base_doc.py b/sectiondoc/tests/test_base_doc.py similarity index 58% rename from tests/test_base_doc.py rename to sectiondoc/tests/test_base_doc.py index d2b3ce2..a60f344 100644 --- a/tests/test_base_doc.py +++ b/sectiondoc/tests/test_base_doc.py @@ -1,23 +1,15 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: test_base_doc.py -# License: LICENSE.TXT -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from sectiondoc.base_doc import BaseDoc -from ._compat import unittest +from sectiondoc.styles import DocRender +from sectiondoc.tests._compat import unittest -class TestBaseDoc(unittest.TestCase): +class TestDocRender(unittest.TestCase): def setUp(self): self.maxDiff = None def test_refactor_header(self): docstring =\ -""" This is a sample docstring. + """ This is a sample docstring. My Header --------- @@ -25,21 +17,21 @@ def test_refactor_header(self): """ rst =\ -""" This is a sample docstring. + """ This is a sample docstring. .. rubric:: My Header This is just some sample text. """ docstring_lines = docstring.splitlines() - base_doc = BaseDoc(docstring_lines) + base_doc = DocRender(docstring_lines) base_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) def test_refactor_complex_header(self): docstring =\ -""" This is a sample docstring. + """ This is a sample docstring. Input\\Output header ------------------- @@ -48,14 +40,14 @@ def test_refactor_complex_header(self): """ rst =\ -""" This is a sample docstring. + """ This is a sample docstring. .. rubric:: Input\\\\Output header This is just some sample text. """ docstring_lines = docstring.splitlines() - base_doc = BaseDoc(docstring_lines) + base_doc = DocRender(docstring_lines) base_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) diff --git a/sectiondoc/tests/test_definition_items.py b/sectiondoc/tests/test_definition_items.py new file mode 100644 index 0000000..ad40873 --- /dev/null +++ b/sectiondoc/tests/test_definition_items.py @@ -0,0 +1,245 @@ +from sectiondoc.items import OrDefinitionItem, MethodItem, Item +from sectiondoc.renderers import ( + Argument, Attribute, Definition, ListItem, Method, TableRow) +from sectiondoc.tests._compat import unittest + + +class TestOrDefinitionItem(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_is_item(self): + self.assertFalse(OrDefinitionItem.is_item("term")) + self.assertFalse(OrDefinitionItem.is_item("term ")) + self.assertTrue(OrDefinitionItem.is_item("term :")) + self.assertTrue(OrDefinitionItem.is_item("term : ")) + self.assertTrue(OrDefinitionItem.is_item("term : classifier")) + self.assertFalse(OrDefinitionItem.is_item(":term : classifier")) + self.assertFalse(OrDefinitionItem.is_item("term : classifier:")) + + # special cases + header_with_object = 'component : class.component.instance' + self.assertTrue(OrDefinitionItem.is_item(header_with_object)) + + header_with_trait = 'properies : Dict(Str, Any)' + self.assertTrue(OrDefinitionItem.is_item(header_with_trait)) + + header_with_or = 'item : ModelIndex or None' + self.assertTrue(OrDefinitionItem.is_item(header_with_or)) + + def test_parse(self): + item = OrDefinitionItem.parse(['term', ' Definition.']) + self.assertEqual(item, OrDefinitionItem('term', [], ['Definition.'])) + + item = OrDefinitionItem.parse([ + 'term', ' Definition, paragraph 1.', + '', ' Definition, paragraph 2.']) + self.assertEqual( + item, + OrDefinitionItem( + 'term', [], [ + 'Definition, paragraph 1.', + '', + 'Definition, paragraph 2.'])) + + item = OrDefinitionItem.parse(['term :', ' Definition.']) + self.assertEqual(item, OrDefinitionItem('term', [], ['Definition.'])) + + item = OrDefinitionItem.parse(['term : classifier', ' Definition.']) + self.assertEqual( + item, OrDefinitionItem('term', ['classifier'], ['Definition.'])) + + item = OrDefinitionItem.parse( + ['term : classifier or classifier', ' Definition.']) + self.assertEqual( + item, + OrDefinitionItem( + 'term', + ['classifier', 'classifier'], ['Definition.'])) + + item = OrDefinitionItem.parse( + ['term : classifier', ' Block.', ' Definition.']) + self.assertEqual( + item, OrDefinitionItem( + 'term', ['classifier'], ['Block.', ' Definition.'])) + + +class TestDefintionRenderer(unittest.TestCase): + + def test_to_rst(self): + rst = """\ +lines + + *(list)* -- + A list of string lines rendered in rst. +""" + item = Item( + 'lines', ['list'], ['A list of string lines rendered in rst.']) + rendered = '\n'.join(Definition(item).to_rst()) + self.assertMultiLineEqual(rst, rendered) + + +class TestAttributeRenderer(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_to_rst(self): + # with annotation + rst = """\ +.. attribute:: indent + :annotation: = int + + The indent to use for the description block. +""" + item = Item( + 'indent', + ['int'], + ['The indent to use for the description block.']) + rendered = '\n'.join(Attribute(item).to_rst()) + self.assertMultiLineEqual(rst, rendered) + + # without annotation + rst = """\ +.. attribute:: indent + + The indent to use for the description block. +""" + item = Item( + 'indent', [], ['The indent to use for the description block.']) + rendered = '\n'.join(Attribute(item).to_rst()) + self.assertMultiLineEqual(rst, rendered) + + +class TestArgumentRenderer(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_to_rst_with_one_classifier(self): + rst = """\ +:param indent: + The indent to use for the description block. + This is the second paragraph of the argument definition. +:type indent: int""" + + item = Item( + 'indent', ['int'], [ + 'The indent to use for the description block.', + 'This is the second paragraph of the argument definition.']) + rendered = '\n'.join(Argument(item).to_rst()) + self.assertMultiLineEqual(rst, rendered) + + def test_to_with_two_classifiers(self): + rst = """\ +:param indent: + The indent to use for the description block. + This is the second paragraph of the argument definition. +:type indent: int or float""" + + item = Item( + 'indent', ['int', 'float'], [ + 'The indent to use for the description block.', + 'This is the second paragraph of the argument definition.']) + rendered = '\n'.join(Argument(item).to_rst()) + self.assertMultiLineEqual(rst, rendered) + + +class TestListItemRenderer(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_to_rst_normal(self): + rst = """\ +- **indent** (*int*) -- + The indent to use for the description block. + + This is the second paragraph of the argument definition. +""" + item = Item( + 'indent', ['int'], [ + 'The indent to use for the description block.', '', + 'This is the second paragraph of the argument definition.']) + rendered = '\n'.join(ListItem(item).to_rst(prefix='-')) + self.assertMultiLineEqual(rst, rendered) + + def test_to_rst_no_classifier(self): + rst = """\ +- **indent** -- + The indent to use for the description block. +""" + item = Item( + 'indent', [], ['The indent to use for the description block.']) + rendered = '\n'.join(ListItem(item).to_rst(prefix='-')) + self.assertMultiLineEqual(rst, rendered) + + def test_to_rst_only_term(self): + rst = """\ +- **indent** +""" + item = Item('indent', [], ['']) + rendered = '\n'.join(ListItem(item).to_rst(prefix='-')) + self.assertMultiLineEqual(rst, rendered) + + def test_to_rst_no_defintition(self): + rst = """\ +- **indent** (*int*) +""" + item = Item('indent', ['int'], ['']) + rendered = '\n'.join(ListItem(item).to_rst(prefix='-')) + self.assertMultiLineEqual(rst, rendered) + + +class TestTableLineItem(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_to_rst(self): + # with annotation + rst = """\ +function(arg1, arg2) This is the best fun +""" + item = Item( + 'function(arg1, arg2)', 'and', ['This is the best function ever.']) + rendered = '\n'.join(TableRow(item).to_rst(columns=(22, 0, 20))) + self.assertMultiLineEqual(rst, rendered) + + +class TestMethodItem(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_is_item(self): + self.assertTrue(MethodItem.is_item("term()")) + self.assertTrue( + MethodItem.is_item("term(*args, my_keyword=None)")) + self.assertFalse(MethodItem.is_item("term")) + self.assertFalse(MethodItem.is_item("term : *args")) + + def test_parse(self): + item = MethodItem.parse( + ['method(arguments)', ' Definition in a single line']) + self.assertEqual(item, MethodItem( + 'method', ['arguments'], ['Definition in a single line'])) + + +class TestMethod(unittest.TestCase): + + def test_to_rst(self): + # with annotation + rst = """\ +:meth:`function(arg1, arg2) ` This is the best fun +""" + item = Item( + 'function', ['arg1', 'arg2'], + ['This is the best function ever.']) + rendered = '\n'.join(Method(item).to_rst(columns=(39, 20))) + '\n' + self.assertMultiLineEqual(rst, rendered) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_function_doc.py b/sectiondoc/tests/test_legacy_style.py similarity index 55% rename from tests/test_function_doc.py rename to sectiondoc/tests/test_legacy_style.py index 2a2b329..61ec1ca 100644 --- a/tests/test_function_doc.py +++ b/sectiondoc/tests/test_legacy_style.py @@ -1,23 +1,148 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: test_function_doc.py -# License: LICENSE.TXT -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from sectiondoc.function_doc import FunctionDoc -from ._compat import unittest +from sectiondoc.styles.legacy import class_section, function_section +from sectiondoc.tests._compat import unittest -class TestFunctionDoc(unittest.TestCase): +class TestLegacyStyleClass(unittest.TestCase): def setUp(self): self.maxDiff = None - def test_refactor_returns(self): + def test_refactor_attributes(self): docstring =\ -""" This is a sample function docstring. + """Base abstract docstring refactoring class. + +The class' main purpose is to parse the dosctring and find the +sections that need to be refactored. It also provides a number of +methods to help with the refactoring. Subclasses should provide +the methods responsible for refactoring the sections. + +Attributes +---------- +docstring : list + A list of strings (lines) that holds docstrings + +index : int + The current zero-based line number of the docstring that is + proccessed. +""" + + rst = \ + """Base abstract docstring refactoring class. + +The class' main purpose is to parse the dosctring and find the +sections that need to be refactored. It also provides a number of +methods to help with the refactoring. Subclasses should provide +the methods responsible for refactoring the sections. + +.. attribute:: docstring + :annotation: = list + + A list of strings (lines) that holds docstrings + +.. attribute:: index + :annotation: = int + + The current zero-based line number of the docstring that is + proccessed. +""" + + docstring_lines = docstring.splitlines() + class_doc = class_section(docstring_lines) + class_doc.parse() + output = '\n'.join(docstring_lines) + self.assertMultiLineEqual(rst, output) + + def test_refactor_methods(self): + docstring = \ + """ This is a sample class docstring + +Methods +------- +extract_fields(indent='', field_check=None) + Extract the fields from the docstring + +get_field() + Get the field description. + +get_next_paragraph() + Get the next paragraph designated by an empty line. + +""" + + rst = \ + """ This is a sample class docstring + +==================================================================== =================================================== +Method Description +==================================================================== =================================================== +:meth:`extract_fields(indent='', field_check=None) ` Extract the fields from the docstring +:meth:`get_field() ` Get the field description. +:meth:`get_next_paragraph() ` Get the next paragraph designated by an empty line. +==================================================================== =================================================== + +""" # noqa + + docstring_lines = docstring.splitlines() + class_doc = class_section(docstring_lines) + class_doc.parse() + output = '\n'.join(docstring_lines) + self.assertMultiLineEqual(rst, output) + + def test_refactor_notes(self): + docstring1 =\ + """ This is a sample class docstring + +Notes +----- +This is the test. +Wait we have not finished. + +This is not a note. +""" + + docstring2 =\ + """ This is a sample class docstring + +Notes +----- + +This is the test. +Wait we have not finished. + +This is not a note. +""" + + rst = \ + """ This is a sample class docstring + +.. note:: + This is the test. + Wait we have not finished. + +This is not a note. +""" + + docstring_lines = docstring1.splitlines() + class_doc = class_section(docstring_lines) + class_doc.parse() + output = '\n'.join(docstring_lines) + '\n' + self.assertMultiLineEqual(rst, output) + + docstring_lines = docstring2.splitlines() + class_doc = class_section(docstring_lines) + class_doc.parse() + output = '\n'.join(docstring_lines) + '\n' + self.assertMultiLineEqual(rst, output) + + +class TestOldStyleFunction(unittest.TestCase): + + def setUp(self): + self.maxDiff = None + + def test_refactor_returns(self): + docstring = \ + """ This is a sample function docstring. Returns ------- @@ -27,7 +152,7 @@ def test_refactor_returns(self): """ rst = \ -""" This is a sample function docstring. + """ This is a sample function docstring. :returns: **myvalue** (*list*) -- @@ -36,14 +161,14 @@ def test_refactor_returns(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) self.assertMultiLineEqual(rst, output) def test_refactor_raises(self): - docstring =\ -""" This is a sample function docstring. + docstring = \ + """ This is a sample function docstring. Raises ------ @@ -56,7 +181,7 @@ def test_refactor_raises(self): """ rst = \ -""" This is a sample function docstring. + """ This is a sample function docstring. :raises: - **TypeError** -- @@ -68,14 +193,14 @@ def test_refactor_raises(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) self.assertMultiLineEqual(rst, output) def test_refactor_arguments(self): docstring =\ -""" This is a sample function docstring + """ This is a sample function docstring Arguments --------- @@ -92,7 +217,7 @@ def test_refactor_arguments(self): """ rst = \ -""" This is a sample function docstring + """ This is a sample function docstring :param inputa: The first argument holds the first input!. @@ -108,14 +233,14 @@ def test_refactor_arguments(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) def test_refactor_strange_arguments(self): - docstring =\ -""" This is a sample function docstring + docstring = \ + """ This is a sample function docstring Parameters ---------- @@ -130,9 +255,8 @@ def test_refactor_strange_arguments(self): from_ : Arguments with trailing underscore. """ - rst = \ -""" This is a sample function docstring + """ This is a sample function docstring :param \*args: Positional arguments with which this constructor was called @@ -145,14 +269,14 @@ def test_refactor_strange_arguments(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) def test_refactor_notes(self): - docstring =\ -""" This is a sample function docstring. + docstring = \ + """ This is a sample function docstring. Notes ----- @@ -163,7 +287,7 @@ def test_refactor_notes(self): """ rst = \ -""" This is a sample function docstring. + """ This is a sample function docstring. .. note:: This is the test. @@ -173,14 +297,14 @@ def test_refactor_notes(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) def test_docstring_cases_1(self): - docstring1 =\ -""" Sets the selection to the bounds of start and end. + docstring1 = \ + """ Sets the selection to the bounds of start and end. If the indices are invalid, no selection will be made, and any current selection will be cleared. @@ -198,8 +322,8 @@ def test_docstring_cases_1(self): result : None """ - docstring2 =\ -""" Sets the selection to the bounds of start and end. + docstring2 = \ + """ Sets the selection to the bounds of start and end. If the indices are invalid, no selection will be made, and any current selection will be cleared. @@ -216,8 +340,8 @@ def test_docstring_cases_1(self): result : None """ - rst =\ -""" Sets the selection to the bounds of start and end. + rst = \ + """ Sets the selection to the bounds of start and end. If the indices are invalid, no selection will be made, and any current selection will be cleared. @@ -234,13 +358,13 @@ def test_docstring_cases_1(self): """ docstring_lines = docstring1.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) self.assertMultiLineEqual(rst, output) docstring_lines = docstring2.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) self.assertMultiLineEqual(rst, output) @@ -248,7 +372,7 @@ def test_docstring_cases_1(self): def test_docstring_cases_2(self): docstring = \ -""" Verify that the requested attribute is properly set + """ Verify that the requested attribute is properly set The method compares the attribute value in the Enaml object and check if it is synchronized with the toolkit widget. The component @@ -279,7 +403,7 @@ def test_docstring_cases_2(self): """ rst = \ -""" Verify that the requested attribute is properly set + """ Verify that the requested attribute is properly set The method compares the attribute value in the Enaml object and check if it is synchronized with the toolkit widget. The component @@ -308,7 +432,7 @@ def test_docstring_cases_2(self): """ docstring_lines = docstring.splitlines() - function_doc = FunctionDoc(docstring_lines) + function_doc = function_section(docstring_lines) function_doc.parse() output = '\n'.join(docstring_lines) + '\n' self.assertMultiLineEqual(rst, output) diff --git a/tests/test_line_functions.py b/sectiondoc/tests/test_line_functions.py similarity index 61% rename from tests/test_line_functions.py rename to sectiondoc/tests/test_line_functions.py index 03ca0cd..cabb478 100644 --- a/tests/test_line_functions.py +++ b/sectiondoc/tests/test_line_functions.py @@ -1,43 +1,33 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: test_line_functions.py -# License: LICENSE.TXT -# Author: Ioannis Tziakos -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from sectiondoc.line_functions import (add_indent, remove_indent, get_indent, - fix_star, fix_backspace, is_empty, - replace_at) -from ._compat import unittest +from sectiondoc.util import ( + add_indent, remove_indent, get_indent, fix_star, fix_backspace, is_empty, + replace_at) +from sectiondoc.tests._compat import unittest class TestLineFunctions(unittest.TestCase): def test_add_indent(self): - input = ["This is the first line", "", - " This is the third line"] - expected = [" This is the first line", "", - " This is the third line"] + input = ["This is the first line", "", " This is the third line"] + expected = [ + " This is the first line", "", " This is the third line"] output = add_indent(input, indent=3) self.assertEqual(output, expected) - expected = ["This is the first line", "", - " This is the third line"] + expected = [ + "This is the first line", "", " This is the third line"] output = add_indent(input, indent=0) self.assertEqual(output, expected) - expected = [" This is the first line", "", - " This is the third line"] + expected = [ + " This is the first line", "", " This is the third line"] output = add_indent(input) self.assertEqual(output, expected) def test_remove_indent(self): - input = [" This is the first line", "", - " This is the third line"] - expected = ["This is the first line", "", - "This is the third line"] + input = [ + " This is the first line", "", " This is the third line"] + expected = [ + "This is the first line", "", "This is the third line"] output = remove_indent(input) self.assertEqual(output, expected) diff --git a/sectiondoc/line_functions.py b/sectiondoc/util.py similarity index 99% rename from sectiondoc/line_functions.py rename to sectiondoc/util.py index 7032654..d041806 100644 --- a/sectiondoc/line_functions.py +++ b/sectiondoc/util.py @@ -1,6 +1,5 @@ # -*- coding: utf-8 -*- #----------------------------------------------------------------------------- -# file: line_functions.py # License: LICENSE.TXT # Author: Ioannis Tziakos # diff --git a/setup.py b/setup.py index 1753b3a..11cd97e 100644 --- a/setup.py +++ b/setup.py @@ -1,85 +1,37 @@ # -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ +# ----------------------------------------------------------------------------- # License: LICENSE.TXT # # Copyright (c) 2014, Enthought, Inc. # All rights reserved. -#------------------------------------------------------------------------------ -from setuptools import setup -import os -import subprocess +# ----------------------------------------------------------------------------- +from setuptools import setup, find_packages -MAJOR = 0 -MINOR = 4 +MAJOR = 1 +MINOR = 0 MICRO = 0 +DEV = 0 VERSION = '{0:d}.{1:d}.{2:d}'.format(MAJOR, MINOR, MICRO) +FULLVERSION = '{0:d}.{1:d}.{2:d}'.format(MAJOR, MINOR, MICRO, DEV) IS_RELEASED = False -# Return the git revision as a string -def git_version(): - def _minimal_ext_cmd(cmd): - # construct minimal environment - env = {} - for k in ['SYSTEMROOT', 'PATH']: - v = os.environ.get(k) - if v is not None: - env[k] = v - # LANGUAGE is used on win32 - env['LANGUAGE'] = 'C' - env['LANG'] = 'C' - env['LC_ALL'] = 'C' - out = subprocess.Popen( - cmd, stdout=subprocess.PIPE, env=env, - ).communicate()[0] - return out - - try: - out = _minimal_ext_cmd(['git', 'rev-parse', 'HEAD']) - git_revision = out.strip().decode('ascii') - except OSError: - git_revision = "Unknown" - - return git_revision - - def write_version_py(filename='sectiondoc/_version.py'): template = """\ # THIS FILE IS GENERATED FROM SECTIONDOC SETUP.PY version = '{version}' full_version = '{full_version}' -git_revision = '{git_revision}' is_released = {is_released} if not is_released: version = full_version """ - # Adding the git rev number needs to be done inside - # write_version_py(), otherwise the import of sectiondoc._version - # messes up the build under Python 3. - fullversion = VERSION - if os.path.exists('.git'): - git_rev = git_version() - elif os.path.exists('sectiondoc/_version.py'): - # must be a source distribution, use existing version file - try: - from sectiondoc._version import git_revision as git_rev - except ImportError: - raise ImportError("Unable to import git_revision. Try removing " - "sectiondoc/_version.py and the build " - "directory before building.") - else: - git_rev = "Unknown" - - if not IS_RELEASED: - fullversion += '.dev1-' + git_rev[:7] - with open(filename, "wt") as fp: - fp.write(template.format(version=VERSION, - full_version=fullversion, - git_revision=git_rev, - is_released=IS_RELEASED)) + fp.write(template.format( + version=VERSION, + full_version=FULLVERSION, + is_released=IS_RELEASED)) if __name__ == "__main__": @@ -89,7 +41,8 @@ def write_version_py(filename='sectiondoc/_version.py'): setup( name='sectiondoc', version=__version__, - packages=['sectiondoc'], + packages=find_packages(), author="Enthought Ltd", author_email="info@enthought.com", + test_suite='sectiondoc.tests', ) diff --git a/tests/test_class_doc.py b/tests/test_class_doc.py deleted file mode 100644 index c968b7c..0000000 --- a/tests/test_class_doc.py +++ /dev/null @@ -1,146 +0,0 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: test_class_doc.py -# License: LICENSE.TXT -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from sectiondoc.class_doc import ClassDoc -from ._compat import unittest - - -class TestClassDoc(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_refactor_attributes(self): - docstring =\ -"""Base abstract docstring refactoring class. - -The class' main purpose is to parse the dosctring and find the -sections that need to be refactored. It also provides a number of -methods to help with the refactoring. Subclasses should provide -the methods responsible for refactoring the sections. - -Attributes ----------- -docstring : list - A list of strings (lines) that holds docstrings - -index : int - The current zero-based line number of the docstring that is - proccessed. -""" - - rst = \ -"""Base abstract docstring refactoring class. - -The class' main purpose is to parse the dosctring and find the -sections that need to be refactored. It also provides a number of -methods to help with the refactoring. Subclasses should provide -the methods responsible for refactoring the sections. - -.. attribute:: docstring - :annotation: = list - - A list of strings (lines) that holds docstrings - -.. attribute:: index - :annotation: = int - - The current zero-based line number of the docstring that is - proccessed. -""" - - docstring_lines = docstring.splitlines() - class_doc = ClassDoc(docstring_lines) - class_doc.parse() - output = '\n'.join(docstring_lines) - self.assertMultiLineEqual(rst, output) - - def test_refactor_methods(self): - docstring =\ -""" This is a sample class docstring - -Methods -------- -extract_fields(indent='', field_check=None) - Extract the fields from the docstring - -get_field() - Get the field description. - -get_next_paragraph() - Get the next paragraph designated by an empty line. - -""" - - rst = \ -""" This is a sample class docstring - -==================================================================== =================================================== -Method Description -==================================================================== =================================================== -:meth:`extract_fields(indent='', field_check=None) ` Extract the fields from the docstring -:meth:`get_field() ` Get the field description. -:meth:`get_next_paragraph() ` Get the next paragraph designated by an empty line. -==================================================================== =================================================== - -""" - - docstring_lines = docstring.splitlines() - class_doc = ClassDoc(docstring_lines) - class_doc.parse() - output = '\n'.join(docstring_lines) - self.assertMultiLineEqual(rst, output) - - def test_refactor_notes(self): - docstring1 =\ -""" This is a sample class docstring - -Notes ------ -This is the test. -Wait we have not finished. - -This is not a note. -""" - - docstring2 =\ -""" This is a sample class docstring - -Notes ------ - -This is the test. -Wait we have not finished. - -This is not a note. -""" - - rst = \ -""" This is a sample class docstring - -.. note:: - This is the test. - Wait we have not finished. - -This is not a note. -""" - - docstring_lines = docstring1.splitlines() - class_doc = ClassDoc(docstring_lines) - class_doc.parse() - output = '\n'.join(docstring_lines) + '\n' - self.assertMultiLineEqual(rst, output) - - docstring_lines = docstring2.splitlines() - class_doc = ClassDoc(docstring_lines) - class_doc.parse() - output = '\n'.join(docstring_lines) + '\n' - self.assertMultiLineEqual(rst, output) - -if __name__ == '__main__': - unittest.main() diff --git a/tests/test_definition_items.py b/tests/test_definition_items.py deleted file mode 100644 index 5a461d3..0000000 --- a/tests/test_definition_items.py +++ /dev/null @@ -1,228 +0,0 @@ -# -*- coding: utf-8 -*- -#------------------------------------------------------------------------------ -# file: test_line_functions.py -# License: LICENSE.TXT -# Author: Ioannis Tziakos -# -# Copyright (c) 2011, Enthought, Inc. -# All rights reserved. -#------------------------------------------------------------------------------ -from sectiondoc.definition_items import (DefinitionItem, AttributeItem, - ArgumentItem, ListItem, - TableLineItem, - MethodItem) -from ._compat import unittest - - -class TestDefinitionItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_is_definition(self): - self.assertFalse(DefinitionItem.is_definition("term")) - self.assertFalse(DefinitionItem.is_definition("term ")) - self.assertTrue(DefinitionItem.is_definition("term :")) - self.assertTrue(DefinitionItem.is_definition("term : ")) - self.assertTrue(DefinitionItem.is_definition("term : classifier")) - self.assertFalse(DefinitionItem.is_definition(":term : classifier")) - self.assertFalse(DefinitionItem.is_definition("term : classifier:")) - - # special cases - header_with_object = 'component : class.component.instance' - self.assertTrue(DefinitionItem.is_definition(header_with_object)) - - header_with_trait = 'properies : Dict(Str, Any)' - self.assertTrue(DefinitionItem.is_definition(header_with_trait)) - - header_with_or = 'item : ModelIndex or None' - self.assertTrue(DefinitionItem.is_definition(header_with_or)) - - def test_parse(self): - item = DefinitionItem.parse(['term', - ' Definition.']) - self.assertEqual(item, DefinitionItem('term', '', - ['Definition.'])) - - item = DefinitionItem.parse(['term', - ' Definition, paragraph 1.', - '', - ' Definition, paragraph 2.']) - self.assertEqual(item, DefinitionItem('term', '', - ['Definition, paragraph 1.', - '', - 'Definition, paragraph 2.'])) - - item = DefinitionItem.parse(['term :', - ' Definition.']) - self.assertEqual(item, DefinitionItem('term', '', - ['Definition.'])) - - item = DefinitionItem.parse(['term : classifier', - ' Definition.']) - self.assertEqual(item, DefinitionItem('term', 'classifier', - ['Definition.'])) - - item = DefinitionItem.parse(['term : classifier', - ' Block.', - ' Definition.']) - self.assertEqual(item, DefinitionItem('term', 'classifier', - ['Block.', - ' Definition.'])) - - def test_to_rst(self): - rst = """\ -lines - - *(list)* -- - A list of string lines rendered in rst. -""" - item = DefinitionItem('lines', 'list', - ['A list of string lines rendered in rst.']) - rendered = '\n'.join(item.to_rst()) - self.assertMultiLineEqual(rst, rendered) - - -class TestAttributeItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_to_rst(self): - # with annotation - rst = """\ -.. attribute:: indent - :annotation: = int - - The indent to use for the description block. -""" - item = AttributeItem('indent', 'int', - ['The indent to use for the description block.']) - rendered = '\n'.join(item.to_rst()) - self.assertMultiLineEqual(rst, rendered) - - # without annotation - rst = """\ -.. attribute:: indent - - The indent to use for the description block. -""" - item = AttributeItem('indent', '', - ['The indent to use for the description block.']) - rendered = '\n'.join(item.to_rst()) - self.assertMultiLineEqual(rst, rendered) - - -class TestArgumentItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_to_rst(self): - rst = """\ -:param indent: - The indent to use for the description block. - This is the second paragraph of the argument definition. -:type indent: int""" - - item = ArgumentItem('indent', 'int', - ['The indent to use for the description block.', - '' - 'This is the second paragraph of the argument definition.']) - rendered = '\n'.join(item.to_rst()) - self.assertMultiLineEqual(rst, rendered) - - -class TestListItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_to_rst_normal(self): - rst = """\ -- **indent** (*int*) -- - The indent to use for the description block. - - This is the second paragraph of the argument definition. -""" - item = ListItem('indent', 'int', - ['The indent to use for the description block.', - '', - 'This is the second paragraph of the argument definition.']) - rendered = '\n'.join(item.to_rst(prefix='-')) - self.assertMultiLineEqual(rst, rendered) - - def test_to_rst_no_classifier(self): - rst = """\ -- **indent** -- - The indent to use for the description block. -""" - item = ListItem('indent', '', - ['The indent to use for the description block.']) - rendered = '\n'.join(item.to_rst(prefix='-')) - self.assertMultiLineEqual(rst, rendered) - - def test_to_rst_only_term(self): - rst = """\ -- **indent** -""" - item = ListItem('indent', '', ['']) - rendered = '\n'.join(item.to_rst(prefix='-')) - self.assertMultiLineEqual(rst, rendered) - - def test_to_rst_no_defintition(self): - rst = """\ -- **indent** (*int*) -""" - item = ListItem('indent', 'int', ['']) - rendered = '\n'.join(item.to_rst(prefix='-')) - self.assertMultiLineEqual(rst, rendered) - - -class TestTableLineItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_to_rst(self): - # with annotation - rst = """\ -function(arg1, arg2) This is the best fun -""" - item = TableLineItem('function(arg1, arg2)', 'and', - ['This is the best function ever.']) - rendered = '\n'.join(item.to_rst(columns=(22, 0, 20))) - self.assertMultiLineEqual(rst, rendered) - - -class TestMethodItem(unittest.TestCase): - - def setUp(self): - self.maxDiff = None - - def test_is_definition(self): - self.assertTrue(MethodItem.is_definition("term()")) - self.assertTrue(MethodItem.is_definition( - "term(*args, my_keyword=None)")) - self.assertFalse(MethodItem.is_definition("term")) - self.assertFalse(MethodItem.is_definition("term : *args")) - - def test_parse(self): - item = MethodItem.parse(['method(arguments)', - ' Definition in a single line']) - self.assertEqual(item, MethodItem('method', 'arguments', - ['Definition in a single line'])) - - def test_to_rst(self): - # with annotation - rst = """\ -:meth:`function(arg1, arg2) ` This is the best fun -""" - item = MethodItem('function', 'arg1, arg2', - ['This is the best function ever.']) - rendered = '\n'.join(item.to_rst(columns=(39, 20))) + '\n' - self.assertMultiLineEqual(rst, rendered) - - -if __name__ == '__main__': - unittest.main()