From: <jon...@us...> - 2010-09-16 21:41:14
|
Author: jonwaltman Date: 2010-09-16 23:41:05 +0200 (Thu, 16 Sep 2010) New Revision: 6421 Added: trunk/sandbox/texinfo-writer/ trunk/sandbox/texinfo-writer/Makefile trunk/sandbox/texinfo-writer/README.rst trunk/sandbox/texinfo-writer/rst2texinfo.py trunk/sandbox/texinfo-writer/setup.py trunk/sandbox/texinfo-writer/test_texinfo.py trunk/sandbox/texinfo-writer/texinfo.py trunk/sandbox/texinfo-writer/tools/ trunk/sandbox/texinfo-writer/tools/rst2texinfo.el Log: Initial commit for Texinfo writer Property changes on: trunk/sandbox/texinfo-writer ___________________________________________________________________ Added: svn:ignore + *.texi *.info *.egg-info build dist invocation.rst Added: trunk/sandbox/texinfo-writer/Makefile =================================================================== --- trunk/sandbox/texinfo-writer/Makefile (rev 0) +++ trunk/sandbox/texinfo-writer/Makefile 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,43 @@ + +infodir ?= /usr/share/info + +dir_entry = 'rst2texinfo' +dir_category = 'Documentation tools' +dir_description = 'Convert reStructuredText to Texinfo.' + +all: rst2texinfo.info + +rst2texinfo.info:: + python rst2texinfo.py --help > invocation.rst + python rst2texinfo.py \ + --texinfo-filename=rst2texinfo.info \ + --texinfo-dir-entry=$(dir_entry) \ + --texinfo-dir-category=$(dir_category) \ + --texinfo-dir-description=$(dir_description) \ + README.rst rst2texinfo.texi + makeinfo --no-split rst2texinfo.texi + +install-info: all + cp -t $(infodir) rst2texinfo.info + install-info --info-dir=$(infodir) rst2texinfo.info + +uninstall-info: all + -rm -f $(infodir)/rst2texinfo.info + install-info --delete --info-dir=$(infodir) rst2texinfo.info + +install:: + python setup.py install + +uninstall:: + -rm -rf /usr/local/lib/python2.6/dist-packages/rst2texinfo-0.2-py2.6.egg/ + -rm -f /usr/local/bin/rst2texinfo.py + +test:: + python test_texinfo.py -v + +clean: + -rm -f invocation.rst rst2texinfo.texi rst2texinfo.info + -rm -rf build rst2texinfo.egg-info dist + -rm -f *.pyc + +.PHONY: all install-info uninstall-info test clean Added: trunk/sandbox/texinfo-writer/README.rst =================================================================== --- trunk/sandbox/texinfo-writer/README.rst (rev 0) +++ trunk/sandbox/texinfo-writer/README.rst 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,189 @@ + +rst2texinfo +########### + +:Author: Jon Waltman +:Contact: jon...@gm... + +Convert reStructuredText to Texinfo + +``rst2texinfo`` is an extension of the Docutils text processing system +which adds support for generating Texinfo files from reStructuredText. + + +Introduction +************ + +The purpose of this program is to generate Info files from reST +documents. Info is the underlying format of the on-line help system +used by the GNU Project. This system provides a useful and convenient +method for reading manuals from the terminal or within Emacs. +Although, the focus of this program is to produce Info output, Texinfo +can also be used to generate a variety of different formats, including +HTML, Latex, and plain text. + +rst2texinfo is an extension of the Docutils text processing system +http://docutils.sourceforge.net/. + +The output of rst2texinfo is Texinfo, the official documentation +format of the GNU Project. Information on Texinfo can be found at +http://www.gnu.org/software/texinfo/. + + +Set up +****** + +Required packages: + +* Python (tested using v2.6) -- http://www.python.org/ +* Docutils (tested using v0.6) -- http://docutils.sourceforge.net/ + +To create Info files, you will need the "makeinfo" program which is +part of the Texinfo project located at +http://www.gnu.org/software/texinfo/. + + +Install +******* + +:: + + python setup.py install + + +Invoking rst2texinfo +******************** + +.. include:: invocation.rst + + +Converting Texinfo to Info +************************** + +Now that you can convert your reST document to Texinfo, the next step +is to convert the Texinfo file to Info. To do this, you will need to +use the ``makeinfo`` program. Refer to the documentation for more +details but its general usage is:: + + makeinfo --no-split FILENAME.texi + +This should create a file named FILENAME.info which can then be read +using an "Info reader". + +See `makeinfo`_ for more information. + +.. _makeinfo: info:texinfo#Invoking_makeinfo + + +Reading Info Files +****************** + +There are two main programs for reading Info files, ``info`` and GNU +Emacs. The ``info`` program has less features but is available on +most \*nix environments and can be quickly accessed at the terminal. +Emacs provides better font color displays and supports extensive +customization (of course). This document focuses on using Emacs for +reading Info files. + +Checkout the `manual`_ for help on reading Info files. + +.. _manual: info:info + + +Issues +****** + +The conversion of reST to Texinfo is not a seamless transition. Info +is not as sophisticated as HTML which creates several issues since +most reST documents are geared for HTML output. The following sections +describe some of these issues. + + +Displaying Links +================ + +One noticeable problem you may encounter with the generated Info files +is how references are displayed. If you read the source of an Info +file, a reference to this section would look like:: + + * note Displaying Links: target-id + +In the stand-alone reader, ``info``, references are displayed just as +they appear in the source. Emacs, on the other-hand, will by default +replace ``\*note:`` with ``see`` and hide the ``target-id``. For +example: + + `Displaying Links`_ + +The exact behavior of how Emacs displays references is dependent on +the variable ``Info-hide-note-references``. If set to the value of +``hide``, Emacs will hide both the ``\*note:`` part and the +``target-id``. This is generally the best way to view reST documents +since they often make frequent use of links and do not take this +limitation into account. However, changing this variable affects how +all Info documents are displayed and most due take this behavior into +account. + +If you want Emacs to display Info files produced by rst2texinfo using the +value ``hide`` for ``Info-hide-note-references`` and the default value +for all other Info files, try adding the following Emacs Lisp code to +your start-up file, ``~/.emacs.d/init.el``. + +:: + + (defadvice info-insert-file-contents (after + docutils-info-insert-file-contents + activate) + "Hack to make `Info-hide-note-references' buffer-local and + automatically set to `hide' iff it can be determined that this file + was created from a Texinfo file generated by Docutils or Sphinx." + (set (make-local-variable 'Info-hide-note-references) + (default-value 'Info-hide-note-references)) + (save-excursion + (save-restriction + (widen) (goto-char (point-min)) + (when (re-search-forward + "^Generated by \\(Sphinx\\|Docutils\\)" + (save-excursion (search-forward "" nil t)) t) + (set (make-local-variable 'Info-hide-note-references) + 'hide))))) + + +Miscellaneous +============= + +The following notes may be helpful if you want to create Texinfo +files: + +- Each section corresponds to a different ``node`` in the Info file. + +- Some characters cannot be properly escaped in menu entries and + xrefs. The following characters are replaced by spaces in these + contexts: ``@``, ``{``, ``}``, ``.``, ``,``, and ``:``. + +- In the HTML and Tex output, the word ``see`` is automatically + inserted before all xrefs. + +- Links to external Info files can be created using the somewhat + official URI scheme ``info``. For example:: + + info:Texinfo#makeinfo_options + + which produces: + + info:Texinfo#makeinfo_options + +- Inline markup appears as follows in Info: + + * strong -- \*strong\* + * emphasis -- _emphasis_ + * literal -- \`literal' + + +Copyright +********* + +rst2texinfo and this documentation should have the same license as the +Docutils project. + +Copyright © 2010 Jon Waltman. All rights reserved. Added: trunk/sandbox/texinfo-writer/rst2texinfo.py =================================================================== --- trunk/sandbox/texinfo-writer/rst2texinfo.py (rev 0) +++ trunk/sandbox/texinfo-writer/rst2texinfo.py 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,20 @@ +#!/usr/bin/env python + +import locale +try: + locale.setlocale(locale.LC_ALL, '') +except: + pass + +from docutils.core import publish_cmdline, default_description +from texinfo import Writer + + +def main(): + description = ("Converts reStructuredText to Texinfo. " + + default_description) + publish_cmdline(writer=Writer(), description=description) + + +if __name__ == '__main__': + main() Property changes on: trunk/sandbox/texinfo-writer/rst2texinfo.py ___________________________________________________________________ Added: svn:executable + * Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Added: trunk/sandbox/texinfo-writer/setup.py =================================================================== --- trunk/sandbox/texinfo-writer/setup.py (rev 0) +++ trunk/sandbox/texinfo-writer/setup.py 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,32 @@ + +# Usage: python setup.py {build | install} + +import os +from setuptools import setup, find_packages + +setup( + name = 'rst2texinfo', + version = '0.2', + author = 'Jon Waltman', + author_email = 'jon...@gm...', + description = 'Converts reStructuredText to Texinfo', + long_description = open(os.path.join(os.path.dirname(__file__), + 'README.rst')).read(), + license = 'TBD', + install_requires = ['docutils'], + scripts = ['rst2texinfo.py'], + py_modules = ['texinfo'], + include_package_data = True, + classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "License :: OSI Approved", + "Environment :: Console", + "Intended Audience :: Developers", + "Programming Language :: Python", + "Topic :: Documentation", + "Topic :: Terminals", + "Topic :: Text Editors", + "Topic :: Text Processing", + "Topic :: Utilities", + ], +) Property changes on: trunk/sandbox/texinfo-writer/setup.py ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Added: trunk/sandbox/texinfo-writer/test_texinfo.py =================================================================== --- trunk/sandbox/texinfo-writer/test_texinfo.py (rev 0) +++ trunk/sandbox/texinfo-writer/test_texinfo.py 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,274 @@ +#! /usr/bin/env python + +# Author: Jon Waltman <jon...@gm...> +# Copyright: This module has been placed in the public domain. + +""" +Tests for Texinfo writer. +""" + +import os +import os.path +import sys + +import texinfo + +sys.path.insert(0, os.path.abspath('../../docutils/test')) +import DocutilsTestSupport + +def suite(): + settings = {} + s = DocutilsTestSupport.PublishTestSuite('texinfo', suite_settings=settings) + s.generateTests(totest) + return s + +template = texinfo.TEMPLATE + +parts = dict( + filename='<string>.info', + direntry='', + title='<untitled>', + paragraphindent='2', + exampleindent='4', + preamble='', + body='\n', + ) + +totest = {} + +totest['blank'] = [ +# input +["", +## # expected output +template % dict(parts)], +] + + +totest['escaping'] = [ +# input +[r""" +Escaping -- @,{}: +***************** + +@commands{ use braces }. + +@ { } @@ {{ }} + +@} @@}} + +@{ @@{{ + +""", +## # expected output +template % dict(parts, + title='Escaping --', + body= +r"""@@commands@{ use braces @}. + +@@ @{ @} @@@@ @{@{ @}@} + +@@@} @@@@@}@} + +@@@{ @@@@@{@{ +""")], +] + + + +totest['table_with_markup'] = [ +# input +[r""" ++------------+---------------------+--------------------+ +| emphasis | strong emphasis | inline literal | ++============+=====================+====================+ +| *emphasis* | **strong emphasis** | ``inline literal`` | ++------------+---------------------+--------------------+ +""", +## # expected output +template % dict(parts, + body= +r"""@multitable {xxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxxxxxxxx} {xxxxxxxxxxxxxxxxxxxxxx} +@headitem emphasis +@tab strong emphasis +@tab inline literal +@item @emph{emphasis} +@tab @strong{strong emphasis} +@tab @code{inline literal} +@end multitable +""")], +] + +totest['sections'] = [ +# input +[r""" +Top +*** + +This tests sectioning. + +Section 1 +========= + +SubSection 1.1 +-------------- + +SubSubSection 1.1.1 +~~~~~~~~~~~~~~~~~~~ + +Section 2 +========= + +""", +## # expected output +template % dict(parts, + title='Top', + body= +r"""This tests sectioning. + + +@menu +* Section 1:: +* Section 2:: + +@detailmenu + --- The Detailed Node Listing --- + +Section 1 + +* SubSection 1 1:: +@end detailmenu +@end menu + + +@node Section 1,Section 2,Top,Top +@anchor{section 1}@anchor{0}@anchor{section-1} +@chapter Section 1 + +@menu +* SubSection 1 1:: +@end menu + + +@node SubSection 1 1,,,Section 1 +@anchor{subsection 1 1}@anchor{1}@anchor{subsection-1-1} +@section SubSection 1.1 + +@menu +* SubSubSection 1 1 1:: +@end menu + + +@node SubSubSection 1 1 1,,,SubSection 1 1 +@anchor{subsubsection 1 1 1}@anchor{2}@anchor{subsubsection-1-1-1} +@subsection SubSubSection 1.1.1 + +@node Section 2,,Section 1,Top +@anchor{section 2}@anchor{3}@anchor{section-2} +@chapter Section 2 +""")], +] + +totest['duplicate_sections'] = [ +# input +[r""" +Duplicate +========= + +Duplicate +========= + +Duplicate +========= + +""", + ## # expected output +template % dict(parts, + title='Duplicate', + body= +r"""@menu +* Duplicate: Duplicate<2>. +* Duplicate: Duplicate<3>. +@end menu + + +@node Duplicate<2>,Duplicate<3>,Top,Top +@anchor{duplicate<2>}@anchor{0}@anchor{1}@anchor{id1} +@unnumbered Duplicate + +@node Duplicate<3>,,Duplicate<2>,Top +@anchor{duplicate<3>}@anchor{2}@anchor{id2} +@unnumbered Duplicate +""")], +] + + +totest['comments'] = [ +# input +[r""" +.. Foo + bar + +""", + ## # expected output +template % dict(parts, + body= +r"""@c Foo +@c bar +""")], +] + +totest['docinfo'] = [ +# input +[r""" +Doc Info Test +============= + +:Author: Jon +:Copyright: This document has been placed in the public domain. + +Example... +""", + ## # expected output +template % dict(parts, + title='Doc Info Test', + body= +r"""@itemize @w +@item Author: +Jon + +@item Copyright: +This document has been placed in the public domain. +@end itemize + +Example... +""")], +] + + +totest['admonitions'] = [ +# input +[r""" +Admonitions +=========== + +.. Note:: + + Hooty hoo! + +""", + ## # expected output +template % dict(parts, + title='Admonitions', + body= +r"""@cartouche +@quotation Note +Hooty hoo! +@end quotation +@end cartouche +""")], +] + + +if __name__ == '__main__': + import unittest + unittest.main(defaultTest='suite') Property changes on: trunk/sandbox/texinfo-writer/test_texinfo.py ___________________________________________________________________ Added: svn:executable + * Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Added: trunk/sandbox/texinfo-writer/texinfo.py =================================================================== --- trunk/sandbox/texinfo-writer/texinfo.py (rev 0) +++ trunk/sandbox/texinfo-writer/texinfo.py 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,981 @@ +# -*- coding: utf-8 -*- +# Author: Jon Waltman <jon...@gm...> +# Copyright: This module is put into the public domain. + +""" +Texinfo writer for reStructuredText. + +Texinfo is the official documentation system of the GNU project and +can be used to generate multiple output formats. This writer focuses +on producing Texinfo that is compiled to Info by the "makeinfo" +program. +""" + +import docutils +from docutils import nodes, writers + +TEMPLATE = """\ +\\input texinfo @c -*-texinfo-*- +@c %%**start of header +@setfilename %(filename)s +@documentencoding UTF-8 +@copying +Generated by Docutils +@end copying +@settitle %(title)s +@defindex ge +@paragraphindent %(paragraphindent)s +@exampleindent %(exampleindent)s +%(direntry)s +@c %%**end of header + +@titlepage +@title %(title)s +@end titlepage +@contents + +@c %%** start of user preamble +%(preamble)s +@c %%** end of user preamble + +@ifnottex +@node Top +@top %(title)s +@end ifnottex + +@c %%**start of body +%(body)s +@c %%**end of body +@bye +""" + + +def find_subsections(section): + """Return a list of subsections for the given ``section``.""" + result = [] + for child in section.children: + if isinstance(child, nodes.section): + result.append(child) + continue + result.extend(find_subsections(child)) + return result + + +## Escaping +# Which characters to escape depends on the context. In some cases, +# namely menus and node names, it's not possible to escape certain +# characters. + +def escape(s): + """Return a string with Texinfo command characters escaped.""" + s = s.replace('@', '@@') + s = s.replace('{', '@{') + s = s.replace('}', '@}') + # Prevent "--" from being converted to an "em dash" + # s = s.replace('-', '@w{-}') + return s + +def escape_arg(s): + """Return an escaped string suitable for use as an argument + to a Texinfo command.""" + s = escape(s) + # Commas are the argument delimeters + s = s.replace(',', '@comma{}') + # Normalize white space + s = ' '.join(s.split()).strip() + return s + +def escape_id(s): + """Return an escaped string suitable for node names, menu entries, + and xrefs anchors.""" + bad_chars = ',:.()@{}' + for bc in bad_chars: + s = s.replace(bc, ' ') + s = ' '.join(s.split()).strip() + return s + + +class Writer(writers.Writer): + """Texinfo writer for generating Texinfo documents.""" + supported = ('texinfo', 'texi') + + settings_spec = ( + 'Texinfo Specific Options', + None, + ( + ("Name of the resulting Info file to be created by 'makeinfo'. " + "Should probably end with '.info'.", + ['--texinfo-filename'], + {'default': '', 'metavar': '<file>'}), + + ('Specify the Info dir entry category.', + ['--texinfo-dir-category'], + {'default': 'Miscellaneous', 'metavar': '<name>'}), + + ('The name to use for the Info dir entry. ' + 'If not provided, no entry will be created.', + ['--texinfo-dir-entry'], + {'default': '', 'metavar': '<name>'}), + + ('A brief description (one or two lines) to use for the ' + 'Info dir entry.', + ['--texinfo-dir-description'], + {'default': '', 'metavar': '<desc>'}), + ) + ) + + settings_defaults = {} + settings_default_overrides = {'docinfo_xform': 0} + + output = None + + visitor_attributes = ('output', 'fragment') + + def translate(self): + self.visitor = visitor = Translator(self.document) + self.document.walkabout(visitor) + visitor.finish() + for attr in self.visitor_attributes: + setattr(self, attr, getattr(visitor, attr)) + + +class Translator(nodes.NodeVisitor): + + default_elements = { + 'filename': '', + 'title': '', + 'paragraphindent': 2, + 'exampleindent': 4, + 'direntry': '', + 'preamble': '', + 'body': '', + } + + def __init__(self, document): + nodes.NodeVisitor.__init__(self, document) + self.init_settings() + + self.written_ids = set() # node names and anchors in output + self.referenced_ids = set() # node names and anchors that should + # be in output + self.node_names = {} # node name --> node's name to display + self.node_menus = {} # node name --> node's menu entries + self.rellinks = {} # node name --> (next, previous, up) + + self.collect_node_names() + self.collect_node_menus() + self.collect_rellinks() + + self.short_ids = {} + self.body = [] + self.previous_section = None + self.section_level = 0 + self.seen_title = False + self.next_section_targets = [] + self.escape_newlines = 0 + self.curfilestack = [] + + def finish(self): + while self.referenced_ids: + # Handle xrefs with missing anchors + r = self.referenced_ids.pop() + if r not in self.written_ids: + self.document.reporter.info( + "Unknown cross-reference target: `%s'" % r) + self.add_text('@anchor{%s}@w{%s}\n' % (r, ' ' * 30)) + self.fragment = ''.join(self.body).strip() + '\n' + self.elements['body'] = self.fragment + self.output = TEMPLATE % self.elements + + + ## Helper routines + + def init_settings(self): + settings = self.settings = self.document.settings + elements = self.elements = self.default_elements.copy() + elements.update({ + # if empty, the title is set to the first section title + 'title': settings.title, + # if empty, use basename of input file + 'filename': settings.texinfo_filename, + }) + # Title + title = elements['title'] + if not title: + title = self.document.next_node(nodes.title) + title = (title and title.astext()) or '<untitled>' + elements['title'] = escape_id(title) or '<untitled>' + # Filename + if not elements['filename']: + elements['filename'] = self.document.get('source') or 'untitled' + if elements['filename'][-4:] in ('.txt', '.rst'): + elements['filename'] = elements['filename'][:-4] + elements['filename'] += '.info' + # Direntry + if settings.texinfo_dir_entry: + elements['direntry'] = ('@dircategory %s\n' + '@direntry\n' + '* %s: (%s). %s\n' + '@end direntry\n') % ( + escape_id(settings.texinfo_dir_category), + escape_id(settings.texinfo_dir_entry), + elements['filename'], + escape_arg(settings.texinfo_dir_description)) + + def collect_node_names(self): + """Generates a unique id for each section. + + Assigns the attribute ``node_name`` to each section.""" + self.document['node_name'] = 'Top' + self.node_names['Top'] = 'Top' + self.written_ids.update(('Top', 'top')) + + for section in self.document.traverse(nodes.section): + title = section.next_node(nodes.Titular) + name = (title and title.astext()) or '<untitled>' + node_id = name = escape_id(name) or '<untitled>' + assert node_id and name + nth, suffix = 1, '' + while (node_id + suffix).lower() in self.written_ids: + nth += 1 + suffix = '<%s>' % nth + node_id += suffix + assert node_id not in self.node_names + assert node_id not in self.written_ids + assert node_id.lower() not in self.written_ids + section['node_name'] = node_id + self.node_names[node_id] = name + self.written_ids.update((node_id, node_id.lower())) + + def collect_node_menus(self): + """Collect the menu entries for each "node" section.""" + node_menus = self.node_menus + for node in ([self.document] + + self.document.traverse(nodes.section)): + assert 'node_name' in node and node['node_name'] + entries = tuple(s['node_name'] + for s in find_subsections(node)) + node_menus[node['node_name']] = entries + # Try to find a suitable "Top" node + title = self.document.next_node(nodes.title) + top = (title and title.parent) or self.document + if not isinstance(top, (nodes.document, nodes.section)): + top = self.document + if top is not self.document: + entries = node_menus[top['node_name']] + entries += node_menus['Top'][1:] + node_menus['Top'] = entries + del node_menus[top['node_name']] + top['node_name'] = 'Top' + + def collect_rellinks(self): + """Collect the relative links (next, previous, up) for each "node".""" + rellinks = self.rellinks + node_menus = self.node_menus + for id, entries in node_menus.items(): + rellinks[id] = ['', '', ''] + # Up's + for id, entries in node_menus.items(): + for e in entries: + rellinks[e][2] = id + # Next's and prev's + for id, entries in node_menus.items(): + for i, id in enumerate(entries): + # First child's prev is empty + if i != 0: + rellinks[id][1] = entries[i-1] + # Last child's next is empty + if i != len(entries) - 1: + rellinks[id][0] = entries[i+1] + # Top's next is its first child + try: + first = node_menus['Top'][0] + except IndexError: + pass + else: + rellinks['Top'][0] = first + rellinks[first][1] = 'Top' + + def add_text(self, text, fresh=False): + """Add some text to the output. + + Optional argument ``fresh`` means to insert a newline before + the text if the last character out was not a newline.""" + if fresh: + if self.body and not self.body[-1].endswith('\n'): + self.body.append('\n') + self.body.append(text) + + def rstrip(self): + """Strip trailing whitespace from the current output.""" + while self.body and not self.body[-1].strip(): + del self.body[-1] + if not self.body: + return + self.body[-1] = self.body[-1].rstrip() + + def add_menu_entries(self, entries): + for entry in entries: + name = self.node_names[entry] + if name == entry: + self.add_text('* %s::\n' % name, fresh=1) + else: + self.add_text('* %s: %s.\n' % (name, entry), fresh=1) + + def add_menu(self, section, master=False): + entries = self.node_menus[section['node_name']] + if not entries: + return + self.add_text('\n@menu\n') + self.add_menu_entries(entries) + if master: + # Write the "detailed menu" + started_detail = False + for entry in entries: + subentries = self.node_menus[entry] + if not subentries: + continue + if not started_detail: + started_detail = True + self.add_text('\n@detailmenu\n' + ' --- The Detailed Node Listing ---\n') + self.add_text('\n%s\n\n' % self.node_names[entry]) + self.add_menu_entries(subentries) + if started_detail: + self.rstrip() + self.add_text('\n@end detailmenu\n') + self.rstrip() + self.add_text('\n@end menu\n\n') + + + ## xref handling + + def get_short_id(self, id): + """Return a shorter 'id' associated with ``id``.""" + # Shorter ids improve paragraph filling in places + # that the id is hidden by Emacs. + try: + sid = self.short_ids[id] + except KeyError: + sid = hex(len(self.short_ids))[2:] + self.short_ids[id] = sid + return sid + + def add_anchor(self, id, msg_node=None): + # Anchors can be referenced by their original id + # or by the generated shortened id + id = escape_id(id).lower() + ids = (self.get_short_id(id), id) + for id in ids: + if id not in self.written_ids: + self.add_text('@anchor{%s}' % id) + self.written_ids.add(id) + + def add_xref(self, ref, name, node): + ref = self.get_short_id(escape_id(ref).lower()) + name = ' '.join(name.split()).strip() + if not name or ref == name: + self.add_text('@pxref{%s}' % ref) + else: + self.add_text('@pxref{%s,%s}' % (ref, name)) + self.referenced_ids.add(ref) + + ## Visiting + + def visit_document(self, node): + pass + def depart_document(self, node): + pass + + def visit_Text(self, node): + s = escape(node.astext()) + if self.escape_newlines: + s = s.replace('\n', ' ') + self.add_text(s) + def depart_Text(self, node): + pass + + def visit_section(self, node): + self.next_section_targets.extend(node.get('ids', [])) + if not self.seen_title: + return + if self.previous_section: + self.add_menu(self.previous_section) + else: + self.add_menu(self.document, master=True) + + node_name = node['node_name'] + pointers = tuple([node_name] + self.rellinks[node_name]) + self.add_text('\n@node %s,%s,%s,%s\n' % pointers) + if node_name != node_name.lower(): + self.add_text('@anchor{%s}' % node_name.lower()) + for id in self.next_section_targets: + self.add_anchor(id, node) + + self.next_section_targets = [] + self.previous_section = node + self.section_level += 1 + + def depart_section(self, node): + self.section_level -= 1 + + headings = ( + '@unnumbered', + '@chapter', + '@section', + '@subsection', + '@subsubsection', + ) + + rubrics = ( + '@heading', + '@subheading', + '@subsubheading', + ) + + def visit_title(self, node): + if not self.seen_title: + self.seen_title = 1 + raise nodes.SkipNode + parent = node.parent + if isinstance(parent, nodes.table): + return + if isinstance(parent, nodes.Admonition): + raise nodes.SkipNode + elif isinstance(parent, nodes.sidebar): + self.visit_rubric(node) + elif isinstance(parent, nodes.topic): + raise nodes.SkipNode + elif not isinstance(parent, nodes.section): + self.document.reporter.warning( + 'encountered title node not in section, topic, table, ' + 'admonition or sidebar', base_node=node) + self.visit_rubric(node) + else: + try: + heading = self.headings[self.section_level] + except IndexError: + heading = self.headings[-1] + self.add_text('%s ' % heading, fresh=1) + + def depart_title(self, node): + self.add_text('', fresh=1) + + def visit_rubric(self, node): + try: + rubric = self.rubrics[self.section_level] + except IndexError: + rubric = self.rubrics[-1] + self.add_text('%s ' % rubric, fresh=1) + def depart_rubric(self, node): + self.add_text('', fresh=1) + + def visit_subtitle(self, node): + self.add_text('\n\n@noindent\n') + def depart_subtitle(self, node): + self.add_text('\n\n') + + ## References + + def visit_target(self, node): + if node.get('ids'): + self.add_anchor(node['ids'][0], node) + elif node.get('refid'): + # Section targets need to go after the start of the section. + next = node.next_node(ascend=1, siblings=1) + while isinstance(next, nodes.target): + next = next.next_node(ascend=1, siblings=1) + if isinstance(next, nodes.section): + self.next_section_targets.append(node['refid']) + return + self.add_anchor(node['refid'], node) + elif node.get('refuri'): + pass + else: + self.document.reporter.error("Unknown target type: %r" % node) + + def visit_reference(self, node): + if isinstance(node.parent, nodes.title): + return + if isinstance(node[0], nodes.image): + return + name = node.get('name', node.astext()).strip() + if node.get('refid'): + self.add_xref(escape_id(node['refid']), + escape_id(name), node) + raise nodes.SkipNode + if not node.get('refuri'): + self.document.reporter.error("Unknown reference type: %s" % node) + return + uri = node['refuri'] + if uri.startswith('#'): + self.add_xref(escape_id(uri[1:]), escape_id(name), node) + elif uri.startswith('%'): + id = uri[1:] + if '#' in id: + src, id = uri[1:].split('#', 1) + assert '#' not in id + self.add_xref(escape_id(id), escape_id(name), node) + elif uri.startswith('mailto:'): + uri = escape_arg(uri[7:]) + name = escape_arg(name) + if not name or name == uri: + self.add_text('@email{%s}' % uri) + else: + self.add_text('@email{%s,%s}' % (uri, name)) + elif uri.startswith('info:'): + uri = uri[5:].replace('_', ' ') + uri = escape_arg(uri) + id = 'Top' + if '#' in uri: + uri, id = uri.split('#', 1) + id = escape_id(id) + name = escape_id(name) + if name == id: + self.add_text('@pxref{%s,,,%s}' % (id, uri)) + else: + self.add_text('@pxref{%s,,%s,%s}' % (id, name, uri)) + else: + uri = escape_arg(uri) + name = escape_arg(name) + if not name or uri == name: + self.add_text('@indicateurl{%s}' % uri) + else: + self.add_text('@uref{%s,%s}' % (uri, name)) + raise nodes.SkipNode + + def depart_reference(self, node): + pass + + def visit_title_reference(self, node): + text = node.astext() + self.add_text('@cite{%s}' % escape_arg(text)) + raise nodes.SkipNode + def visit_title_reference(self, node): + pass + + ## Blocks + + def visit_paragraph(self, node): + if 'continued' in node or isinstance(node.parent, nodes.compound): + self.add_text('@noindent\n', fresh=1) + def depart_paragraph(self, node): + self.add_text('\n\n') + + def visit_block_quote(self, node): + self.rstrip() + self.add_text('\n\n@quotation\n') + def depart_block_quote(self, node): + self.rstrip() + self.add_text('\n@end quotation\n\n') + + def visit_literal_block(self, node): + self.rstrip() + self.add_text('\n\n@example\n') + def depart_literal_block(self, node): + self.rstrip() + self.add_text('\n@end example\n\n' + '@noindent\n') + + visit_doctest_block = visit_literal_block + depart_doctest_block = depart_literal_block + + def visit_line_block(self, node): + self.add_text('@display\n', fresh=1) + def depart_line_block(self, node): + self.add_text('@end display\n', fresh=1) + + def visit_line(self, node): + self.rstrip() + self.add_text('\n') + self.escape_newlines += 1 + def depart_line(self, node): + self.add_text('@w{ }\n') + self.escape_newlines -= 1 + + ## Inline + + def visit_strong(self, node): + self.add_text('@strong{') + def depart_strong(self, node): + self.add_text('}') + + def visit_emphasis(self, node): + self.add_text('@emph{') + def depart_emphasis(self, node): + self.add_text('}') + + def visit_literal(self, node): + self.add_text('@code{') + def depart_literal(self, node): + self.add_text('}') + + def visit_superscript(self, node): + self.add_text('@w{^') + def depart_superscript(self, node): + self.add_text('}') + + def visit_subscript(self, node): + self.add_text('@w{[') + def depart_subscript(self, node): + self.add_text(']}') + + ## Footnotes + + def visit_footnote(self, node): + self.visit_block_quote(node) + def depart_footnote(self, node): + self.depart_block_quote(node) + + def visit_footnote_reference(self, node): + self.add_text('@w{(') + def depart_footnote_reference(self, node): + self.add_text(')}') + + visit_citation = visit_footnote + depart_citation = depart_footnote + + def visit_citation_reference(self, node): + self.add_text('@w{[') + def depart_citation_reference(self, node): + self.add_text(']}') + + ## Lists + + def visit_bullet_list(self, node): + bullet = node.get('bullet', '*') + self.rstrip() + self.add_text('\n\n@itemize %s\n' % bullet) + def depart_bullet_list(self, node): + self.rstrip() + self.add_text('\n@end itemize\n\n') + + def visit_enumerated_list(self, node): + # Doesn't support Roman numerals + enum = node.get('enumtype', 'arabic') + starters = {'arabic': '', + 'loweralpha': 'a', + 'upperalpha': 'A',} + start = node.get('start', starters.get(enum, '')) + self.rstrip() + self.add_text('\n\n@enumerate %s\n' % start) + def depart_enumerated_list(self, node): + self.rstrip() + self.add_text('\n@end enumerate\n\n') + + def visit_list_item(self, node): + self.rstrip() + self.add_text('\n@item\n') + def depart_list_item(self, node): + pass + + ## Option List + + def visit_option_list(self, node): + self.add_text('\n@table @option\n') + def depart_option_list(self, node): + self.rstrip() + self.add_text('\n@end table\n\n') + + def visit_option_list_item(self, node): + pass + def depart_option_list_item(self, node): + pass + + def visit_option_group(self, node): + self.at_item_x = '@item' + def depart_option_group(self, node): + pass + + def visit_option(self, node): + self.add_text(self.at_item_x + ' ', fresh=1) + self.at_item_x = '@itemx' + def depart_option(self, node): + pass + + def visit_option_string(self, node): + pass + def depart_option_string(self, node): + pass + + def visit_option_argument(self, node): + self.add_text(node.get('delimiter', ' ')) + def depart_option_argument(self, node): + pass + + def visit_description(self, node): + self.add_text('', fresh=1) + def depart_description(self, node): + pass + + ## Definitions + + def visit_definition_list(self, node): + self.add_text('\n@table @asis\n') + def depart_definition_list(self, node): + self.rstrip() + self.add_text('\n@end table\n\n') + + def visit_definition_list_item(self, node): + self.at_item_x = '@item' + def depart_definition_list_item(self, node): + pass + + def visit_term(self, node): + if node.get('ids') and node['ids'][0]: + self.add_anchor(node['ids'][0], node) + self.add_text(self.at_item_x + ' ', fresh=1) + self.at_item_x = '@itemx' + def depart_term(self, node): + pass + + def visit_classifier(self, node): + self.add_text(' : ') + def depart_classifier(self, node): + pass + + def visit_definition(self, node): + self.add_text('', fresh=1) + def depart_definition(self, node): + pass + + ## Tables + + def visit_table(self, node): + self.entry_sep = '@item' + def depart_table(self, node): + self.rstrip() + self.add_text('\n@end multitable\n\n') + + def visit_tabular_col_spec(self, node): + pass + def depart_tabular_col_spec(self, node): + pass + + def visit_colspec(self, node): + self.colwidths.append(node['colwidth']) + if len(self.colwidths) != self.n_cols: + return + self.add_text('@multitable ', fresh=1) + for i, n in enumerate(self.colwidths): + self.add_text('{%s} ' %('x' * (n+2))) + def depart_colspec(self, node): + pass + + def visit_tgroup(self, node): + self.colwidths = [] + self.n_cols = node['cols'] + def depart_tgroup(self, node): + pass + + def visit_thead(self, node): + self.entry_sep = '@headitem' + def depart_thead(self, node): + pass + + def visit_tbody(self, node): + pass + def depart_tbody(self, node): + pass + + def visit_row(self, node): + pass + def depart_row(self, node): + self.entry_sep = '@item' + + def visit_entry(self, node): + self.rstrip() + self.add_text('\n%s ' % self.entry_sep) + self.entry_sep = '@tab' + def depart_entry(self, node): + for i in xrange(node.get('morecols', 0)): + self.add_text('@tab\n', fresh=1) + self.add_text('', fresh=1) + + ## Field Lists + + def visit_field_list(self, node): + self.add_text('\n@itemize @w\n') + def depart_field_list(self, node): + self.rstrip() + self.add_text('\n@end itemize\n\n') + + def visit_field(self, node): + if not isinstance(node.parent, nodes.field_list): + self.visit_field_list(None) + def depart_field(self, node): + if not isinstance(node.parent, nodes.field_list): + self.depart_field_list(None) + + def visit_field_name(self, node): + self.add_text('@item ', fresh=1) + def depart_field_name(self, node): + self.add_text(':') + + def visit_field_body(self, node): + self.add_text('', fresh=1) + def depart_field_body(self, node): + pass + + ## Admonitions + + def visit_admonition(self, node): + title = escape(node[0].astext()) + self.add_text('\n@cartouche\n' + '@quotation %s\n' % title) + def depart_admonition(self, node): + self.rstrip() + self.add_text('\n@end quotation\n' + '@end cartouche\n\n') + + def _make_visit_admonition(typ): + def visit(self, node): + title = escape(typ) + self.add_text('\n@cartouche\n' + '@quotation %s\n' % title) + return visit + + visit_attention = _make_visit_admonition('Attention') + visit_caution = _make_visit_admonition('Caution') + visit_danger = _make_visit_admonition('Danger') + visit_error = _make_visit_admonition('Error') + visit_important = _make_visit_admonition('Important') + visit_note = _make_visit_admonition('Note') + visit_tip = _make_visit_admonition('Tip') + visit_hint = _make_visit_admonition('Hint') + visit_warning = _make_visit_admonition('Warning') + + depart_attention = depart_admonition + depart_caution = depart_admonition + depart_danger = depart_admonition + depart_error = depart_admonition + depart_important = depart_admonition + depart_note = depart_admonition + depart_tip = depart_admonition + depart_hint = depart_admonition + depart_warning = depart_admonition + + ## Misc + + def visit_docinfo(self, node): + # No 'docinfo_xform' + raise nodes.SkipNode + + def visit_topic(self, node): + # Ignore TOC's since we have to have a "menu" anyway + if 'contents' in node.get('classes', []): + raise nodes.SkipNode + title = node[0] + self.visit_rubric(title) + self.add_text('%s\n' % escape(title.astext())) + self.visit_block_quote(node) + def depart_topic(self, node): + self.depart_block_quote(node) + + def visit_generated(self, node): + raise nodes.SkipNode + def depart_generated(self, node): + pass + + def visit_transition(self, node): + self.add_text('\n\n@noindent\n' + '@exdent @w{%s}\n\n' + '@noindent\n' % ('_' * 70)) + def depart_transition(self, node): + pass + + def visit_attribution(self, node): + self.add_text('@flushright\n', fresh=1) + def depart_attribution(self, node): + self.add_text('@end flushright\n', fresh=1) + + def visit_raw(self, node): + format = node.get('format', '').split() + if 'texinfo' in format or 'texi' in format: + self.add_text(node.astext()) + raise nodes.SkipNode + def depart_raw(self, node): + pass + + def visit_figure(self, node): + self.add_text('\n@float Figure\n') + def depart_figure(self, node): + self.rstrip() + self.add_text('\n@end float\n\n') + + def visit_caption(self, node): + if not isinstance(node.parent, nodes.figure): + self.document.reporter.warning('Caption not inside a figure.', + base_node=node) + return + self.add_text('@caption{', fresh=1) + def depart_caption(self, node): + if isinstance(node.parent, nodes.figure): + self.rstrip() + self.add_text('}\n') + + def visit_image(self, node): + self.add_text('@w{[image]}') + raise nodes.SkipNode + def depart_image(self, node): + pass + + def visit_compound(self, node): + pass + def depart_compound(self, node): + pass + + def visit_sidebar(self, node): + pass + def depart_sidebar(self, node): + pass + + def visit_label(self, node): + self.add_text('@w{(') + def depart_label(self, node): + self.add_text(')} ') + + def visit_legend(self, node): + pass + def depart_legend(self, node): + pass + + def visit_substitution_reference(self, node): + pass + def depart_substitution_reference(self, node): + pass + + def visit_substitution_definition(self, node): + raise nodes.SkipNode + def depart_substitution_definition(self, node): + pass + + def visit_system_message(self, node): + self.add_text('\n@format\n' + '---------- SYSTEM MESSAGE -----------\n') + def depart_system_message(self, node): + self.rstrip() + self.add_text('\n------------------------------------\n' + '@end format\n') + + def visit_comment(self, node): + for line in node.astext().splitlines(): + self.add_text('@c %s\n' % line, fresh=1) + raise nodes.SkipNode + def depart_comment(self, node): + pass + + def visit_problematic(self, node): + self.add_text('>') + def depart_problematic(self, node): + self.add_text('<') + + def unimplemented_visit(self, node): + self.document.reporter.error("Unimplemented node type: `%s'" + % node.__class__.__name__, base_node=node) + + def unknown_visit(self, node): + self.document.reporter.error("Unknown node type: `%s'" + % node.__class__.__name__, base_node=node) + def unknown_departure(self, node): + pass Property changes on: trunk/sandbox/texinfo-writer/texinfo.py ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Added: svn:eol-style + native Added: trunk/sandbox/texinfo-writer/tools/rst2texinfo.el =================================================================== --- trunk/sandbox/texinfo-writer/tools/rst2texinfo.el (rev 0) +++ trunk/sandbox/texinfo-writer/tools/rst2texinfo.el 2010-09-16 21:41:05 UTC (rev 6421) @@ -0,0 +1,128 @@ +;; Copyright (C) 2010 Jon Waltman + +;; Author: Jon Waltman <jon...@gm...> +;; Keywords: reStructuredText rst reST texinfo info + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see <http://www.gnu.org/licenses/>. + +;;; Commentary: + +;; This package provides the interactive function `rst2texinfo-compile-preview' +;; for quickly viewing the output of running "rst2texinfo" on a buffer +;; visiting a reStructuredText file. + +;;; Code: + +(require 'rst) +(require 'info) + +(defcustom rst2texinfo-run-command "rst2texinfo.py --traceback" + "*Shell command used to run `rst2texinfo'" + :type 'string + :group 'rst2texinfo) + + + +(defun rst2texinfo-current-section () + "Return the name of the section containing point, in a rst file." + (save-excursion + (unless (car (rst-get-decoration)) + (rst-backward-section) + (when (bobp) + (rst-forward-section))) + (buffer-substring-no-properties (line-beginning-position) + (line-end-position)))) + +(defun rst2texinfo-compilation-sentinel (proc msg) + (compilation-sentinel proc msg) + (when (memq (process-status proc) '(signal exit)) + (let (node) + (if (or (null rst2texinfo-section-name) + (string= "" rst2texinfo-section-name)) + (setq node "Top") + (with-temp-buffer + (insert-file-contents rst2texinfo-file.texi) + (goto-char (point-min)) + (re-search-forward + (concat "^@node \\(" (replace-regexp-in-string + "[^[:alpha:]]" "." rst2texinfo-section-name) + "\\)") nil t) + (setq node (or (match-string 1) "Top")))) + (Info-revert-find-node rst2texinfo-file.info node)))) + +(defun rst2texinfo-compile-preview (no-query) + "Compile the current reStructuredText file to Info and view the result. + +The Texinfo source is created by invoking the shell command specified by +`rst2texinfo-run-command' with two arguments, the current buffer's filename +and the name of the output file. The name of the output file is the same as +the buffer's filename but with the `.texi' extension. + +After the Texinfo file is created, the `makeinfo' program is used to generate +the appropriate Info file. + +Finally, the current buffer is switched to view the resulting Info file. + +With prefix argument `no-query', the current buffer is automatically saved +and no prompts are issued before overwriting existing output files. +" + (interactive "P") + (cond ((null buffer-file-name) + (error "Buffer not visiting any file")) + ((buffer-modified-p) + (when (or no-query + (y-or-n-p "Buffer modified; do you want to save it? ")) + (save-buffer)))) + (let ((src (file-name-sans-extension buffer-file-name))) + ;; Not local since needed by `rst2texinfo-compilation-sentinel'. + (setq rst2texinfo-file.texi (concat src ".texi")) + (setq rst2texinfo-file.info (concat src ".info")) + (when (and (not no-query) + (file-exists-p rst2texinfo-file.texi)) + (unless (y-or-n-p (format "File `%s' exists; over write? " + rst2texinfo-file.texi)) + (error "Canceled"))) + (setq rst2texinfo-section-name (rst2texinfo-current-section)) + (save-excursion + (let* ((command (concat rst2texinfo-run-command + " " (shell-quote-argument buffer-file-name) + " " (shell-quote-argument rst2texinfo-file.texi) + " && makeinfo -v --no-split -o " + (shell-quote-argument rst2texinfo-file.info) + " " (shell-quote-argument rst2texinfo-file.texi))) + (buffer (compilation-start command)) + (process (get-buffer-process buffer))) + (set-process-sentinel process 'rst2texinfo-compilation-sentinel))))) + + + +(defadvice info-insert-file-contents (after + docutils-info-insert-file-contents + activate) + "Hack to make `Info-hide-note-references' buffer-local and +automatically set to `hide' iff it can be determined that this file +was created from a Texinfo file generated by Docutils or Sphinx." + (set (make-local-variable 'Info-hide-note-references) + (default-value 'Info-hide-note-references)) + (save-excursion + (save-restriction + (widen) (goto-char (point-min)) + (when (re-search-forward + "^Generated by \\(Sphinx\\|Docutils\\)" + (save-excursion (search-forward "" nil t)) t) + (set (make-local-variable 'Info-hide-note-references) + 'hide))))) + +(provide 'rst2texinfo) +;;; rst2texinfo.el ends here |