#@+leo-ver=4-thin #@+node:ekr.20040331071319:@thin rst2.py """A plugin to generate HTML or LaTeX files from reStructured Text embedded in Leo outlines. If a headline starts with @rst , double-clicking on it will write a file in outline order, with the headlines converted to reStructuredText section headings. If the name of the has the extension .html, .htm or .tex, and if you have docutils installed, it will generate HTML or LaTeX, respectively.""" # By Josef Dalcolmo: contributed under the same licensed as Leo.py itself. import leoGlobals as g import leoPlugins import os #@<< change log >> #@+node:ekr.20040331071319.2:<< change log >> #@+at #@nonl # Change log: # # - JD 2003-03-10 (rev 1.3): some more corrections to the unicode-> encoding # translation. # No only check for missing docutils (doesn't mask other errors any more). # # - JD 2003-03-11 (rev 1.4): separated out the file launching code to a # different pluging. # # - 2003-11-02 Added generation of LaTeX files, just make the extension of the # filename '.tex'. --Timo Honkasalo # # - 2003-12-24 S Zatz modifications to introduce concept of plain @rst nodes # to improve program documentation # # - 2004-04-08 EKR: # - Eliminated "comment" text at start of nodes. # - Rewrote code that strips @nocolor, @ignore and @wrap directives from # start of text. # - Changed code to show explicitly that it uses positions. # - Added comments to << define code-block>> # - Added div.code-block style to silver_city.css (see documentation) # - Rewrote documentation. #@-at #@nonl #@-node:ekr.20040331071319.2:<< change log >> #@nl #@+others #@+node:ekr.20040331071319.3:onIconDoubleClick # by Josef Dalcolmo 2003-01-13 # # this does not check for proper filename syntax. # path is the current dir, or the place @folder points to # this should probably be changed to @path or so. def onIconDoubleClick(tag,keywords): c = keywords.get("c") p = keywords.get("p") if not c or not p: return h = p.headString().strip() if g.match_word(h,0,"@rst"): if len(h) > 5: fname = h[5:] ext = os.path.splitext(fname)[1].lower() if ext in ('.htm','.html','.tex'): #@ << write rST as HTML/LaTeX >> #@+node:ekr.20040331071319.4:<< write rST as HTML/LaTeX >> try: import docutils except: g.es('HTML/LaTeX generation requires docutils') return else: import docutils.parsers.rst from docutils.core import Publisher from docutils.io import StringOutput, StringInput import StringIO # Set the writer and encoding for the converted file if ext in ('.html','.htm'): writer='html' ; enc="utf-8" else: writer='latex' ; enc="iso-8859-1" if writer == 'html': try: import SilverCity except: g.es('SilverCity not present so no syntax highlighting') syntax = False else: #@ << define code-block >> #@+node:ekr.20040331071319.5:<< define code-block >> def code_block(name,arguments,options,content,lineno,content_offset,block_text,state,state_machine): """Create a code-block directive for docutils.""" # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/252170 language = arguments[0] module = getattr(SilverCity,language) generator = getattr(module,language+"HTMLGenerator") io = StringIO.StringIO() generator().generate_html(io, '\n'.join(content)) html = '
\n%s\n
\n'%io.getvalue() raw = docutils.nodes.raw('',html, format='html') #(self, rawsource='', text='', *children, **attributes): return [raw] # These are documented at http://docutils.sourceforge.net/spec/howto/rst-directives.html. code_block.arguments = ( 1, # Number of required arguments. 0, # Number of optional arguments. 0) # True if final argument may contain whitespace. # A mapping from option name to conversion function. code_block.options = { 'language' : docutils.parsers.rst.directives.unchanged # Return the text argument, unchanged } code_block.content = 1 # True if content is allowed. # Register the directive with docutils. docutils.parsers.rst.directives.register_directive('code-block',code_block) #@nonl #@-node:ekr.20040331071319.5:<< define code-block >> #@nl syntax = True else: syntax = False rstFile = StringIO.StringIO() writeTreeAsRst(rstFile, fname, p, c, syntax) rstText = rstFile.getvalue() # This code snipped has been taken from code contributed by Paul Paterson 2002-12-05. pub = Publisher() pub.source = StringInput(source=rstText) pub.destination = StringOutput(pub.settings, encoding=enc) pub.set_reader('standalone', None, 'restructuredtext') pub.set_writer(writer) output = pub.publish() convertedFile = file(fname,'w') convertedFile.write(output) convertedFile.close() rstFile.close() writeFullFileName(fname) #@-node:ekr.20040331071319.4:<< write rST as HTML/LaTeX >> #@nl else: #@ << write rST file >> #@+node:ekr.20040331071319.6:<< write rST file >> rstFile = file(fname,'w') writeTreeAsRst(rstFile,fname,p,c) rstFile.close() writeFullFileName(fname) #@nonl #@-node:ekr.20040331071319.6:<< write rST file >> #@nl else: # if the headline only contains @rst then open the node and its parent in text editor # this works for me but needs to be generalized and should probably be a component # of the open_with plugin. if 0: c.openWith(("os.startfile", None, ".txt")) c.selectVnode(p.parent()) c.openWith(("os.startfile", None, ".tp")) #@nonl #@-node:ekr.20040331071319.3:onIconDoubleClick #@+node:ekr.20040811064922:writeFullFileName def writeFullFileName (fname): path = g.os_path_join(os.getcwd(),fname) path = g.os_path_abspath(path) g.es('rst written: ' + path,color="blue") #@nonl #@-node:ekr.20040811064922:writeFullFileName #@+node:ekr.20040331071319.7:writeTreeAsRst def writeTreeAsRst(rstFile,fname,p,c,syntax): 'Write the tree under position p to the file rstFile (fname is the filename)' # we don't write a title, so the titlepage can be customized # use '#' for title under/overline directives = g.scanDirectives(c,p=p) # changed name because don't want to use keyword dict #@ << set encoding >> #@+node:ekr.20040408160625.1:<< set encoding >> encoding = directives.get("encoding",None) if encoding == None: encoding = g.app.config.default_derived_file_encoding #@nonl #@-node:ekr.20040408160625.1:<< set encoding >> #@nl #@ << set code_dir >> #@+node:ekr.20040403202850:<< set code_dir >> if syntax: lang_dict = {'python':'Python', 'ruby':'Ruby', 'perl':'Perl', 'c':'CPP'} language = directives['language'] # SilverCity modules have first letter in caps if language in lang_dict: code_dir = '**code**:\n\n.. code-block:: %s\n\n' % lang_dict[language] else: code_dir = '**code**:\n\n.. class:: code\n..\n\n::\n\n' else: code_dir = '**code**:\n\n.. class:: code\n..\n\n::\n\n' #@nonl #@-node:ekr.20040403202850:<< set code_dir >> #@nl s = g.toEncodedString(fname,encoding,reportErrors=True) rstFile.write('.. filename: '+s+'\n') rstFile.write('\n') s = p.bodyString() s = g.toEncodedString(s,encoding,reportErrors=True) rstFile.write(s+'\n') # write body of titlepage. rstFile.write('\n') toplevel = p.level()+1 # Dec 20 h = p.headString() for p in p.subtree_iter(): h = p.headString().strip() if g.match_word(h,0,"@rst"): #@ << handle an rst node >> #@+node:ekr.20040403202850.1:<< handle an rst node >> s = p.bodyString() s = g.toEncodedString(s,encoding,reportErrors=True) # Skip any leading @ignore, @nocolor, @wrap directives. while ( g.match_word(s,0,"@ignore") or g.match_word(s,0,"@nocolor") or g.match_word(s,0,"@wrap") ): i = g.skip_line(s,0) s = s[i:] rstFile.write('%s\n\n'%s.strip()) #@-node:ekr.20040403202850.1:<< handle an rst node >> #@nl else: #@ << handle a plain node >> #@+node:ekr.20040403202850.2:<< handle a plain node >> if g.match_word(h,0,"@file-nosent"): h = h[13:] h = g.toEncodedString(h,encoding,reportErrors=True) s = p.bodyString() s = g.toEncodedString(s,encoding,reportErrors=True) rstFile.write(h+'\n') rstFile.write(underline(h,p.level()-toplevel)) rstFile.write('\n') if s.strip(): rstFile.write(code_dir) s = s.split('\n') for linenum,linetext in enumerate(s[:-1]): if "@others" in linetext: #deleting lines with @other directive from output continue rstFile.write('\t%2d %s\n'%(linenum+1,linetext)) rstFile.write('\n') #@nonl #@-node:ekr.20040403202850.2:<< handle a plain node >> #@nl #@nonl #@-node:ekr.20040331071319.7:writeTreeAsRst #@+node:ekr.20040331071319.8:underline # note the first character is intentionally unused, to serve as the underline # character in a title (in the body of the @rst node) def underline(h,level): """Return the underlining string to be used at the given level for headline h.""" str = """#=+*^~"'`-:><_"""[level] return str*max(len(h),4)+'\n' #@nonl #@-node:ekr.20040331071319.8:underline #@-others # Register the handlers... leoPlugins.registerHandler("icondclick1",onIconDoubleClick) __version__ = "2.1" g.plugin_signon(__name__) #@nonl #@-node:ekr.20040331071319:@thin rst2.py #@-leo