Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Better Documentation Through Automation: Creating docutils & Sphinx Extensions

Better Documentation Through Automation: Creating docutils & Sphinx Extensions

Sphinx is an incredibly useful tool for creating attractive documentation for your project, but if all you ever use it for is converting reStructuredText files to HTML you are barely scratching the surface of its power. This presentation shows how easy it is to extend Sphinx by defining new markup processors, allowing you to take your documentation to the next level.

PyCon 2013

doughellmann

March 16, 2013
Tweet

More Decks by doughellmann

Other Decks in Programming

Transcript

  1. Better Documentation Through Automation:
    Creating docutils & Sphinx Extensions
    Doug Hellmann
    @doughellmann
    PyCon 2013
    Saturday, March 16, 13

    View Slide

  2. Markup Language
    Saturday, March 16, 13

    View Slide

  3. Markup Language
    Extensible
    Saturday, March 16, 13

    View Slide

  4. Saturday, March 16, 13

    View Slide

  5. Sphinx
    Application
    Saturday, March 16, 13

    View Slide

  6. Build
    Environment
    Sphinx
    Application
    .rst
    Saturday, March 16, 13

    View Slide

  7. Build
    Environment
    docutils
    parser
    Sphinx
    Application
    .rst
    Saturday, March 16, 13

    View Slide

  8. Build
    Environment
    docutils
    parser
    Sphinx
    Builder
    Sphinx
    Application
    .rst .pdf .html
    Saturday, March 16, 13

    View Slide

  9. section
    title paragraph
    #text emphasis #text literal
    #text
    #text
    #text
    Saturday, March 16, 13

    View Slide

  10. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam
    vulputate elementum lectus a viverra. Vivamus hendrerit egestas
    lacinia. Proin tellus lectus, scelerisque vitae rutrum eget,
    ultrices vel metus. Quisque :ref:`target-name` pharetra lorem
    vehicula lorem posuere molestie cursus ipsum ornare. Class
    aptent taciti sociosqu ad litora torquent per conubia nostra,
    per inceptos himenaeos. Maecenas eu sapien at tellus suscipit
    aliquet :pep:`8` ut non tellus. Nulla facilisis bibendum dolor,
    quis mattis urna posuere eget. Nulla quis dui id augue faucibus
    varius. Nam eu leo quam. Fusce :term:`condimentum` placerat nisi
    nec vestibulum. Duis tortor sapien, commodo ac faucibus
    nec :rfc:`1822`, rutrum quis urna. Pellentesque cursus facilisis
    odio, id aliquam sem commodo vestibulum. Proin eget velit sed
    justo ultricies vulputate.
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam
    vulputate elementum lectus a viverra. Vivamus hendrerit egestas
    lacinia. Proin tellus lectus, scelerisque vitae rutrum eget,
    ultrices vel metus. Quisque :ref:`target-name` pharetra lorem
    vehicula lorem posuere molestie cursus ipsum ornare. Class
    aptent taciti sociosqu ad litora torquent per conubia nostra,
    per inceptos himenaeos. Maecenas eu sapien at tellus suscipit
    aliquet :pep:`8` ut non tellus. Nulla facilisis bibendum dolor,
    quis mattis urna posuere eget. Nulla quis dui id augue faucibus
    varius. Nam eu leo quam. Fusce :term:`condimentum` placerat nisi
    nec vestibulum. Duis tortor sapien, commodo ac faucibus
    nec :rfc:`1822`, rutrum quis urna. Pellentesque cursus facilisis
    odio, id aliquam sem commodo vestibulum. Proin eget velit sed
    justo ultricies vulputate.
    Saturday, March 16, 13

    View Slide

  11. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam
    vulputate elementum lectus a viverra. Vivamus hendrerit egestas
    lacinia. Proin tellus lectus, scelerisque vitae rutrum eget,
    ultrices vel metus. Quisque :ref:`target-name` pharetra lorem
    vehicula lorem posuere molestie cursus ipsum ornare. Class
    aptent taciti sociosqu ad litora torquent per conubia nostra,
    per inceptos himenaeos. Maecenas eu sapien at tellus suscipit
    aliquet :pep:`8` ut non tellus. Nulla facilisis bibendum dolor,
    quis mattis urna posuere eget. Nulla quis dui id augue faucibus
    varius. Nam eu leo quam. Fusce :term:`condimentum` placerat nisi
    nec vestibulum. Duis tortor sapien, commodo ac faucibus
    nec :rfc:`1822`, rutrum quis urna. Pellentesque cursus facilisis
    odio, id aliquam sem commodo vestibulum. Proin eget velit sed
    justo ultricies vulputate.
    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam
    vulputate elementum lectus a viverra. Vivamus hendrerit egestas
    lacinia. Proin tellus lectus, scelerisque vitae rutrum eget,
    ultrices vel metus. Quisque :ref:`target-name` pharetra lorem
    vehicula lorem posuere molestie cursus ipsum ornare. Class
    aptent taciti sociosqu ad litora torquent per conubia nostra,
    per inceptos himenaeos. Maecenas eu sapien at tellus suscipit
    aliquet :pep:`8` ut non tellus. Nulla facilisis bibendum dolor,
    quis mattis urna posuere eget. Nulla quis dui id augue faucibus
    varius. Nam eu leo quam. Fusce :term:`condimentum` placerat nisi
    nec vestibulum. Duis tortor sapien, commodo ac faucibus
    nec :rfc:`1822`, rutrum quis urna. Pellentesque cursus facilisis
    odio, id aliquam sem commodo vestibulum. Proin eget velit sed
    justo ultricies vulputate.
    Saturday, March 16, 13

    View Slide

  12. .. seealso::
    PyEnchant_
    Python interface to enchant_.
    :ref:`project-sphinxcontrib-spelling`
    Project home page for the spelling checker.
    sphinxcontrib_
    BitBucket repository for sphinxcontrib-spelling and
    several other Sphinx extensions.
    .. include:: example.py
    :literal:
    :start-after: # end-of-header-comment
    .. image:: figure.png
    Saturday, March 16, 13

    View Slide

  13. 2.6
    - Fixed a problem with hook script line endings under Cygwin
    (`Issue 68 virtualenvwrapper/issue/68>`_).
    - Updated documentation to include a list of the compatible
    shells
    (:ref:`supported-shells`) and Python versions
    (:ref:`supported-versions`) (`Issue 70 bitbucket.org/dhellmann/virtualenvwrapper/issue/70>`_).
    - Fixed installation dependency on virtualenv (`Issue 60
    `_).
    - Fixed the method for determining the Python version so it
    works
    under Python 2.4 (`Issue 61 dhellmann/virtualenvwrapper/issue/60>`_).
    - Converted the test infrastructure to use `tox
    `_ instead of home-
    grown
    scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  14. 2.6
    - Fixed a problem with hook script line endings under Cygwin
    (`Issue 68 virtualenvwrapper/issue/68>`_).
    - Updated documentation to include a list of the compatible
    shells
    (:ref:`supported-shells`) and Python versions
    (:ref:`supported-versions`) (`Issue 70 bitbucket.org/dhellmann/virtualenvwrapper/issue/70>`_).
    - Fixed installation dependency on virtualenv (`Issue 60
    `_).
    - Fixed the method for determining the Python version so it
    works
    under Python 2.4 (`Issue 61 dhellmann/virtualenvwrapper/issue/60>`_).
    - Converted the test infrastructure to use `tox
    `_ instead of home-
    grown
    scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  15. 2.6
    - Fixed a problem with hook script line endings under Cygwin
    (`Issue 68 virtualenvwrapper/issue/68>`_).
    - Updated documentation to include a list of the compatible
    shells
    (:ref:`supported-shells`) and Python versions
    (:ref:`supported-versions`) (`Issue 70 bitbucket.org/dhellmann/virtualenvwrapper/issue/70>`_).
    - Fixed installation dependency on virtualenv (`Issue 60
    `_).
    - Fixed the method for determining the Python version so it
    works
    under Python 2.4 (`Issue 61 dhellmann/virtualenvwrapper/issue/60>`_).
    - Converted the test infrastructure to use `tox
    `_ instead of home-
    grown
    scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  16. `Issue 68 `_
    Saturday, March 16, 13

    View Slide

  17. `Issue 68 `_
    Saturday, March 16, 13

    View Slide

  18. `Issue 68 `_
    Saturday, March 16, 13

    View Slide

  19. `68`
    `Issue 68 `_
    :bbissue:
    Saturday, March 16, 13

    View Slide

  20. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  21. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  22. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  23. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  24. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  25. from docutils import nodes, utils
    from docutils.parsers.rst.roles import set_classes
    def bbissue_role(name, rawtext, text, lineno, inliner, options={},
    content=[]):
    "Link to a BitBucket issue."
    try:
    issue_num = int(text)
    if issue_num <= 0:
    raise ValueError
    except ValueError:
    msg = inliner.reporter.error(
    '"%s" is an invalid bug id.' % text,
    line=lineno)
    prb = inliner.problematic(rawtext, rawtext, msg)
    return [prb], [msg]
    app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    Saturday, March 16, 13

    View Slide

  26. app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    raise AttributeError
    except AttributeError, err:
    raise ValueError('bitbucket_project_url not set (%s)' %
    str(err))
    slash = '/' if base[-1] != '/' else ''
    ref = base + slash + type + '/' + slug + '/'
    set_classes(options)
    node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    Saturday, March 16, 13

    View Slide

  27. app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    raise AttributeError
    except AttributeError, err:
    raise ValueError('bitbucket_project_url not set (%s)' %
    str(err))
    slash = '/' if base[-1] != '/' else ''
    ref = base + slash + type + '/' + slug + '/'
    set_classes(options)
    node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    Saturday, March 16, 13

    View Slide

  28. app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    raise AttributeError
    except AttributeError, err:
    raise ValueError('bitbucket_project_url not set (%s)' %
    str(err))
    slash = '/' if base[-1] != '/' else ''
    ref = base + slash + type + '/' + slug + '/'
    set_classes(options)
    node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    Saturday, March 16, 13

    View Slide

  29. app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    raise AttributeError
    except AttributeError, err:
    raise ValueError('bitbucket_project_url not set (%s)' %
    str(err))
    slash = '/' if base[-1] != '/' else ''
    ref = base + slash + type + '/' + slug + '/'
    set_classes(options)
    node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    Saturday, March 16, 13

    View Slide

  30. app = inliner.document.settings.env.app
    node = make_link_node(rawtext, app, 'issue',
    text, options)
    return [node], []
    def make_link_node(rawtext, app, type, slug, options):
    "Create a link to a BitBucket resource."
    try:
    base = app.config.bitbucket_project_url
    if not base:
    raise AttributeError
    except AttributeError, err:
    raise ValueError('bitbucket_project_url not set (%s)' %
    str(err))
    slash = '/' if base[-1] != '/' else ''
    ref = base + slash + type + '/' + slug + '/'
    set_classes(options)
    node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    Saturday, March 16, 13

    View Slide

  31. node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    app.add_config_value('bitbucket_project_url', None, 'env')
    return
    Saturday, March 16, 13

    View Slide

  32. node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    app.add_config_value('bitbucket_project_url', None, 'env')
    return
    Saturday, March 16, 13

    View Slide

  33. node = nodes.reference(rawtext,
    type + ' ' + utils.unescape(slug),
    refuri=ref,
    **options)
    return node
    def setup(app):
    "Install the plugin."
    app.info('Initializing BitBucket plugin')
    app.add_role('bbissue', bbissue_role)
    app.add_config_value('bitbucket_project_url', None, 'env')
    return
    Saturday, March 16, 13

    View Slide

  34. 2.6
    - Fixed a problem with hook script line endings under Cygwin
    (:bbissue:`68`).
    - Updated documentation to include a list of the
    compatible shells
    (:ref:`supported-shells`) and Python versions
    (:ref:`supported-versions`) (:bbissue:`70`).
    - Fixed installation dependency on virtualenv (:bbissue:`60`).
    - Fixed the method for determining the Python version so
    it works under Python 2.4 (:bbissue:`61`).
    - Converted the test infrastructure to use `tox
    `_ instead of
    home-grown
    scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  35. 2.6
    - Fixed a problem with hook script line endings under Cygwin
    (:bbissue:`68`).
    - Updated documentation to include a list of the
    compatible shells
    (:ref:`supported-shells`) and Python versions
    (:ref:`supported-versions`) (:bbissue:`70`).
    - Fixed installation dependency on virtualenv (:bbissue:`60`).
    - Fixed the method for determining the Python version so
    it works under Python 2.4 (:bbissue:`61`).
    - Converted the test infrastructure to use `tox
    `_ instead of
    home-grown
    scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  36. 2.6
    • Fixed a problem with hook script line endings under
    Cygwin (issue 68).
    • Updated documentation to include a list of the
    compatible shells (Supported Shells) and Python
    versions (Python Versions) (issue 70).
    • Fixed installation dependency on virtualenv (issue 60).
    • Fixed the method for determining the Python version so
    it works under Python 2.4 (issue 61).
    • Converted the test infrastructure to use tox instead of
    home-grown scripts in the Makefile.
    Saturday, March 16, 13

    View Slide

  37. .. name:: arguments
    :option: value
    :option: another-value
    body line
    body line
    Saturday, March 16, 13

    View Slide

  38. .. name:: arguments
    :option: value
    :option: another-value
    body line
    body line
    Saturday, March 16, 13

    View Slide

  39. .. name:: arguments
    :option: value
    :option: another-value
    body line
    body line
    Saturday, March 16, 13

    View Slide

  40. .. name:: arguments
    :option: value
    :option: another-value
    body line
    body line
    Saturday, March 16, 13

    View Slide

  41. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  42. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  43. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  44. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  45. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  46. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  47. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  48. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  49. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  50. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  51. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  52. from docutils.parsers.rst.directives.tables import Table
    class SQLTable(Table):
    option_spec = {'widths': directives.positive_int_list,
    'class': directives.class_option,
    'name': directives.unchanged,
    'connection_string':directives.unchanged,
    }
    def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    Saturday, March 16, 13

    View Slide

  53. def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    Saturday, March 16, 13

    View Slide

  54. def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    Saturday, March 16, 13

    View Slide

  55. def run(self):
    env = self.state.document.settings.env
    app = env.app
    config = app.config
    # Make sure we have some content, which for now we
    # assume is a query.
    if not self.content:
    error = self.state_machine.reporter.error(
    'No query in sqltable directive',
    nodes.literal_block(self.block_text,
    self.block_text),
    line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    Saturday, March 16, 13

    View Slide

  56. line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    Saturday, March 16, 13

    View Slide

  57. line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    Saturday, March 16, 13

    View Slide

  58. line=self.lineno)
    return [error]
    # Connect to the database
    connection_string = self.options.get(
    'connection_string',
    config.sqltable_connection_string,
    )
    app.info('Connecting to %s' % connection_string)
    engine = sqlalchemy.create_engine(connection_string)
    # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    Saturday, March 16, 13

    View Slide

  59. # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    Saturday, March 16, 13

    View Slide

  60. # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    Saturday, March 16, 13

    View Slide

  61. # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    Saturday, March 16, 13

    View Slide

  62. # Run the query
    query = '\n'.join(self.content)
    app.info('Running query %r' % query)
    results = engine.execute(query)
    # Extract some values we need for building the table.
    table_headers = results.keys()
    table_body = results
    max_cols = len(table_headers)
    max_header_cols = max_cols
    # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    Saturday, March 16, 13

    View Slide

  63. # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    Saturday, March 16, 13

    View Slide

  64. # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    Saturday, March 16, 13

    View Slide

  65. # Handle the width settings and title
    col_widths = self.get_column_widths(max_cols)
    title, messages = self.make_title()
    # Build the node containing the table content
    table_node = self.build_table(table_body,
    col_widths,
    table_headers)
    table_node['classes'] += self.options.get('class', [])
    self.add_name(table_node)
    if title:
    table_node.insert(0, title)
    return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    Saturday, March 16, 13

    View Slide

  66. Table Node Hierarchy
    Saturday, March 16, 13

    View Slide

  67. Table Node Hierarchy
    table
    Saturday, March 16, 13

    View Slide

  68. Table Node Hierarchy
    tgroup
    table
    Saturday, March 16, 13

    View Slide

  69. Table Node Hierarchy
    tgroup
    colspec
    table
    Saturday, March 16, 13

    View Slide

  70. Table Node Hierarchy
    tgroup
    colspec thead
    table
    Saturday, March 16, 13

    View Slide

  71. Table Node Hierarchy
    tgroup
    colspec thead
    row
    table
    Saturday, March 16, 13

    View Slide

  72. Table Node Hierarchy
    tgroup
    colspec thead
    row
    entry
    table
    Saturday, March 16, 13

    View Slide

  73. Table Node Hierarchy
    tgroup
    colspec thead
    row
    entry
    paragraph
    table
    Saturday, March 16, 13

    View Slide

  74. Table Node Hierarchy
    tgroup
    colspec tbody
    thead
    row
    entry
    paragraph
    table
    Saturday, March 16, 13

    View Slide

  75. Table Node Hierarchy
    tgroup
    colspec tbody
    thead
    row
    entry
    paragraph
    row
    entry
    paragraph
    table
    Saturday, March 16, 13

    View Slide

  76. return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    tgroup
    colspec
    table
    Saturday, March 16, 13

    View Slide

  77. return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    tgroup
    colspec
    table
    Saturday, March 16, 13

    View Slide

  78. return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    tgroup
    colspec
    table
    Saturday, March 16, 13

    View Slide

  79. return [table_node] + messages
    def build_table(self, table_data, col_widths, headers):
    table = nodes.table()
    # Set up the column specifications
    # based on the widths.
    tgroup = nodes.tgroup(cols=len(col_widths))
    table += tgroup
    tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    tgroup
    colspec
    table
    Saturday, March 16, 13

    View Slide

  80. tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    thead
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  81. tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    thead
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  82. tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    thead
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  83. tgroup.extend(nodes.colspec(colwidth=col_width)
    for col_width in col_widths)
    # Set the headers
    thead = nodes.thead()
    tgroup += thead
    row_node = nodes.row()
    thead += row_node
    row_node.extend(
    nodes.entry(h, nodes.paragraph(text=h))
    for h in headers
    )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    thead
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  84. )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    rows.append(trow)
    tbody.extend(rows)
    #print table
    return table
    def setup(app):
    app.info('Initializing SQLTable')
    app.add_config_value('sqltable_connection_string',
    '',
    'env')
    tbody
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  85. )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    rows.append(trow)
    tbody.extend(rows)
    #print table
    return table
    def setup(app):
    app.info('Initializing SQLTable')
    app.add_config_value('sqltable_connection_string',
    '',
    'env')
    tbody
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  86. )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    rows.append(trow)
    tbody.extend(rows)
    #print table
    return table
    def setup(app):
    app.info('Initializing SQLTable')
    app.add_config_value('sqltable_connection_string',
    '',
    'env')
    tbody
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  87. )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    rows.append(trow)
    tbody.extend(rows)
    #print table
    return table
    def setup(app):
    app.info('Initializing SQLTable')
    app.add_config_value('sqltable_connection_string',
    '',
    'env')
    tbody
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  88. )
    # The body of the table is made up of rows.
    # Each row contains a series of entries,
    # and each entry contains a paragraph of text.
    tbody = nodes.tbody()
    tgroup += tbody
    rows = []
    for row in table_data:
    trow = nodes.row()
    for cell in row:
    entry = nodes.entry()
    para = nodes.paragraph(
    text=unicode(cell))
    entry += para
    trow += entry
    rows.append(trow)
    tbody.extend(rows)
    #print table
    return table
    def setup(app):
    app.info('Initializing SQLTable')
    app.add_config_value('sqltable_connection_string',
    '',
    'env')
    tbody
    row
    entry
    paragraph
    Saturday, March 16, 13

    View Slide

  89. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  90. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  91. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  92. .. sqltable:: List of Users
    :connection_string: sqlite:///sampledata.db
    select
    name as 'Name',
    email as 'E-mail'
    from
    users
    order by
    Name asc
    Saturday, March 16, 13

    View Slide

  93. Build
    Environment
    docutils
    parser
    Sphinx
    Builder
    Sphinx
    Application
    .rst .pdf .html
    Saturday, March 16, 13

    View Slide

  94. Saturday, March 16, 13

    View Slide

  95. .rst
    Saturday, March 16, 13

    View Slide

  96. .rst
    Saturday, March 16, 13

    View Slide

  97. .rst
    word
    list
    check
    Saturday, March 16, 13

    View Slide

  98. .rst
    word
    list
    check
    Saturday, March 16, 13

    View Slide

  99. section
    title paragraph
    #text emphasis #text literal
    #text
    #text
    #text
    Saturday, March 16, 13

    View Slide

  100. section
    title paragraph
    #text emphasis #text literal
    #text
    #text
    #text
    Saturday, March 16, 13

    View Slide

  101. from sphinx.builders import Builder
    class SpellingBuilder(Builder):
    name = 'spelling'
    def init(self):
    self.docnames = []
    self.document_data = []
    project_words = os.path.join(self.srcdir,
    self.config.spelling_word_list_filename)
    self.checker = SpellingChecker(
    lang=self.config.spelling_lang,
    suggest=self.config.spelling_show_suggestions,
    word_list_filename=project_words,
    )
    self.output_filename = os.path.join(self.outdir,
    'output.txt')
    self.output = codecs.open(self.output_filename,
    'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    Saturday, March 16, 13

    View Slide

  102. from sphinx.builders import Builder
    class SpellingBuilder(Builder):
    name = 'spelling'
    def init(self):
    self.docnames = []
    self.document_data = []
    project_words = os.path.join(self.srcdir,
    self.config.spelling_word_list_filename)
    self.checker = SpellingChecker(
    lang=self.config.spelling_lang,
    suggest=self.config.spelling_show_suggestions,
    word_list_filename=project_words,
    )
    self.output_filename = os.path.join(self.outdir,
    'output.txt')
    self.output = codecs.open(self.output_filename,
    'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    Saturday, March 16, 13

    View Slide

  103. from sphinx.builders import Builder
    class SpellingBuilder(Builder):
    name = 'spelling'
    def init(self):
    self.docnames = []
    self.document_data = []
    project_words = os.path.join(self.srcdir,
    self.config.spelling_word_list_filename)
    self.checker = SpellingChecker(
    lang=self.config.spelling_lang,
    suggest=self.config.spelling_show_suggestions,
    word_list_filename=project_words,
    )
    self.output_filename = os.path.join(self.outdir,
    'output.txt')
    self.output = codecs.open(self.output_filename,
    'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    Saturday, March 16, 13

    View Slide

  104. from sphinx.builders import Builder
    class SpellingBuilder(Builder):
    name = 'spelling'
    def init(self):
    self.docnames = []
    self.document_data = []
    project_words = os.path.join(self.srcdir,
    self.config.spelling_word_list_filename)
    self.checker = SpellingChecker(
    lang=self.config.spelling_lang,
    suggest=self.config.spelling_show_suggestions,
    word_list_filename=project_words,
    )
    self.output_filename = os.path.join(self.outdir,
    'output.txt')
    self.output = codecs.open(self.output_filename,
    'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    Saturday, March 16, 13

    View Slide

  105. from sphinx.builders import Builder
    class SpellingBuilder(Builder):
    name = 'spelling'
    def init(self):
    self.docnames = []
    self.document_data = []
    project_words = os.path.join(self.srcdir,
    self.config.spelling_word_list_filename)
    self.checker = SpellingChecker(
    lang=self.config.spelling_lang,
    suggest=self.config.spelling_show_suggestions,
    word_list_filename=project_words,
    )
    self.output_filename = os.path.join(self.outdir,
    'output.txt')
    self.output = codecs.open(self.output_filename,
    'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    Saturday, March 16, 13

    View Slide

  106. 'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    filename = self.env.doc2path(docname, base=None)
    for node in doctree.traverse(docutils.nodes.Text):
    if (node.tagname == '#text' and
    node.parent and
    node.parent.tagname in TEXT_NODES):
    # Determine the line number for this node
    lineno = get_line_number(node)
    # Check the text of the node.
    for word, suggestions in
    self.checker.check(node.astext()):
    msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    Saturday, March 16, 13

    View Slide

  107. 'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    filename = self.env.doc2path(docname, base=None)
    for node in doctree.traverse(docutils.nodes.Text):
    if (node.tagname == '#text' and
    node.parent and
    node.parent.tagname in TEXT_NODES):
    # Determine the line number for this node
    lineno = get_line_number(node)
    # Check the text of the node.
    for word, suggestions in
    self.checker.check(node.astext()):
    msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    Saturday, March 16, 13

    View Slide

  108. 'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    filename = self.env.doc2path(docname, base=None)
    for node in doctree.traverse(docutils.nodes.Text):
    if (node.tagname == '#text' and
    node.parent and
    node.parent.tagname in TEXT_NODES):
    # Determine the line number for this node
    lineno = get_line_number(node)
    # Check the text of the node.
    for word, suggestions in
    self.checker.check(node.astext()):
    msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    Saturday, March 16, 13

    View Slide

  109. 'wt', encoding='UTF-8')
    def write_doc(self, docname, doctree):
    filename = self.env.doc2path(docname, base=None)
    for node in doctree.traverse(docutils.nodes.Text):
    if (node.tagname == '#text' and
    node.parent and
    node.parent.tagname in TEXT_NODES):
    # Determine the line number for this node
    lineno = get_line_number(node)
    # Check the text of the node.
    for word, suggestions in
    self.checker.check(node.astext()):
    msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    Saturday, March 16, 13

    View Slide

  110. msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    msg = ' '.join(msg_parts)
    self.info(msg)
    self.output.write(u"%s:%s: (%s) %s\n" % (
    self.env.doc2path(docname, None),
    lineno, word,
    self.format_suggestions(suggestions),
    ))
    # Found at least one bad spelling
    self.app.statuscode = 1
    return
    def finish(self):
    self.output.close()
    self.info('Output written to %s' % self.output_filename)
    return
    Saturday, March 16, 13

    View Slide

  111. msg_parts = [ docname ]
    if lineno:
    msg_parts.append(
    darkgreen('(line %3d)' % lineno)
    )
    msg_parts.append(red(word))
    msg_parts.append(
    self.format_suggestions(suggestions)
    )
    msg = ' '.join(msg_parts)
    self.info(msg)
    self.output.write(u"%s:%s: (%s) %s\n" % (
    self.env.doc2path(docname, None),
    lineno, word,
    self.format_suggestions(suggestions),
    ))
    # Found at least one bad spelling
    self.app.statuscode = 1
    return
    def finish(self):
    self.output.close()
    self.info('Output written to %s' % self.output_filename)
    return
    Saturday, March 16, 13

    View Slide

  112. self.env.doc2path(docname, None),
    lineno, word,
    self.format_suggestions(suggestions),
    ))
    # Found at least one bad spelling
    self.app.statuscode = 1
    return
    def finish(self):
    self.output.close()
    self.info('Output written to %s' % self.output_filename)
    return
    def setup(app):
    app.info('Initializing Spelling Checker')
    app.add_builder(SpellingBuilder)
    return
    Saturday, March 16, 13

    View Slide

  113. self.env.doc2path(docname, None),
    lineno, word,
    self.format_suggestions(suggestions),
    ))
    # Found at least one bad spelling
    self.app.statuscode = 1
    return
    def finish(self):
    self.output.close()
    self.info('Output written to %s' % self.output_filename)
    return
    def setup(app):
    app.info('Initializing Spelling Checker')
    app.add_builder(SpellingBuilder)
    return
    Saturday, March 16, 13

    View Slide

  114. self.env.doc2path(docname, None),
    lineno, word,
    self.format_suggestions(suggestions),
    ))
    # Found at least one bad spelling
    self.app.statuscode = 1
    return
    def finish(self):
    self.output.close()
    self.info('Output written to %s' % self.output_filename)
    return
    def setup(app):
    app.info('Initializing Spelling Checker')
    app.add_builder(SpellingBuilder)
    return
    Saturday, March 16, 13

    View Slide

  115. $ sphinx-build -b spelling -d build/doctrees \
    source build/spelling
    Running Sphinx v1.1.2
    Initializing Spelling Checker
    loading reprint directive
    loading books directives
    loading pickled environment... done
    building [spelling]: all documents
    updating environment: 0 added, 0 changed, 0 removed
    looking for now-outdated files... none found
    preparing documents... done
    index (line 13) blarg ["blare", "blarney", "Blair", "blamer",
    "blazer", "blog"]
    writing output... [100%]
    Spelling checker messages written to /Users/dhellmann/Devel/
    website/website/build/spelling/output.txt
    build finished with problems.
    Saturday, March 16, 13

    View Slide

  116. $ sphinx-build -b spelling -d build/doctrees \
    source build/spelling
    Running Sphinx v1.1.2
    Initializing Spelling Checker
    loading reprint directive
    loading books directives
    loading pickled environment... done
    building [spelling]: all documents
    updating environment: 0 added, 0 changed, 0 removed
    looking for now-outdated files... none found
    preparing documents... done
    index (line 13) blarg ["blare", "blarney", "Blair", "blamer",
    "blazer", "blog"]
    writing output... [100%]
    Spelling checker messages written to /Users/dhellmann/Devel/
    website/website/build/spelling/output.txt
    build finished with problems.
    Saturday, March 16, 13

    View Slide

  117. $ sphinx-build -b spelling -d build/doctrees \
    source build/spelling
    Running Sphinx v1.1.2
    Initializing Spelling Checker
    loading reprint directive
    loading books directives
    loading pickled environment... done
    building [spelling]: all documents
    updating environment: 0 added, 0 changed, 0 removed
    looking for now-outdated files... none found
    preparing documents... done
    index (line 13) blarg ["blare", "blarney", "Blair", "blamer",
    "blazer", "blog"]
    writing output... [100%]
    Spelling checker messages written to /Users/dhellmann/Devel/
    website/website/build/spelling/output.txt
    build finished with problems.
    Saturday, March 16, 13

    View Slide

  118. Better Documentation Through Automation:
    Creating docutils & Sphinx Extensions
    Doug Hellmann
    @doughellmann
    PyCon 2013
    http://docutils.sourceforge.net/
    https://bitbucket.org/birkenfeld/sphinx-contrib/
    http://packages.python.org/pyenchant/
    http://doughellmann.com
    Saturday, March 16, 13

    View Slide