Source code for leo.plugins.mod_leo2ascd

#@+leo-ver=5-thin
#@+node:ekr.20101110093449.5822: * @file mod_leo2ascd.py
__version__ = ".7" # Set version for the plugin handler.

import leo.core.leoGlobals as g
import leo.core.leoPlugins as leoPlugins
import re
import os
#@+<< define classes >>
#@+node:ekr.20141110071911.17: ** << define classes >>
#@+others
#@+node:ekr.20101110094152.5824: *3* class _AssignUniqueConstantValue
class   _AssignUniqueConstantValue(object):
    """ Provide unique value to be used as a constant """

    #@+others
    #@+node:ekr.20101110094152.5825: *4* __init__
    def __init__(self):
        self.UniqueInternalValue = 0
        self.Assign_at_start()

    #@+node:ekr.20101110094152.5826: *4* class ConstError
    class ConstError(TypeError):
        pass
    #@+node:ekr.20101110094152.5827: *4* __setattr__
    def __setattr__(self,name,value):

        if name in self.__dict__:
            if name != "UniqueInternalValue":
                raise self.ConstError("Can't rebind const(%s)"%name)
        self.__dict__[name]=value
    #@+node:ekr.20101110094152.5828: *4* Assign_at_start
    def Assign_at_start(self):
        self.END_PROGRAM = self.Next()   # signal abort
        self.LINE_WAS_NONE = self.Next() # describe last line printed
        self.LINE_WAS_CODE = self.Next()
        self.LINE_WAS_DOC  = self.Next()
        self.LINE_WAS_HEAD = self.Next()
        self.LINE_PENDING_NONE  = self.Next() # describe next line to be printed
        self.LINE_PENDING_CODE  = self.Next()
        self.LINE_PENDING_DOC   = self.Next()
    #@+node:ekr.20101110094152.5829: *4* Next
    def Next(self):
        self.UniqueInternalValue += 1
        return(self.UniqueInternalValue)
    #@-others
#@+node:ekr.20101110094152.5830: *3* class _ConfigOptions
class _ConfigOptions(object):
    """Hold current configuration options."""
    #@+others
    #@+node:ekr.20101110094152.5831: *4* __init__
    def __init__(self):
        self.current = {}
        self.default = {}
        self.default["maxCodeLineLength"] = '76'
        self.default["delimiterForCodeStart"] = '~-~--- code starts --------'
        self.default["delimiterForCodeEnd"]   = '~-~--- code ends ----------'
        self.default["delimiterForCodeSectionDefinition"] = '*example*'
        self.default["headingUnderlines"] = '=-~^+'
        self.default["asciiDocSectionLevels"] = '5'
        self.default["PrintHeadings"] = "on"

    #@+node:ekr.20101110094152.5832: *4* __GetNodeOptions
    def __GetNodeOptions(self, vnode):
        bodyString = vnode.bodyString()
        lines = bodyString.splitlines()
        for line in lines:
            containsAscConfigDirective = patternAscDirectiveConfig.match(line)
            if containsAscConfigDirective:
                # Leo uses unicode, convert to plain ascii
                name = str(containsAscConfigDirective.group(1))
                value = str(containsAscConfigDirective.group(2))
                if name in self.current:
                    self.current[name] = value
                else:
                    g.es(vnode.headString())
                    g.es("  No such config option: %s" % name)

    #@+node:ekr.20101110094152.5833: *4* GetCurrentOptions
    def GetCurrentOptions(self,c,p):
        self.current.clear()
        self.current = self.default.copy()
        self.__GetNodeOptions(c.rootPosition())
        self.__GetNodeOptions(p)

    #@-others
#@-others
#@-<< define classes >>
#@+<< constants >>
#@+node:ekr.20101110094152.5834: ** << constants >>
CV = _AssignUniqueConstantValue()
CV.NODE_IGNORE = CV.Next()      # demo of adding in code
Conf = _ConfigOptions()

# compile the patterns we'll be searching for frequently
# pylint: disable=anomalous-backslash-in-string
patternSectionName = re.compile("\<\< *(.+?) *\>\>")
patternSectionDefinition = re.compile("(\<\< *)(.+?)( *\>\>)(=)")
patternDirective = re.compile(r"^@")
patternCodeDirective = re.compile(r"^(@c *$)|(@code)")
patternDocDirective = re.compile(r"^(@ |@doc)(.*)")
patternRootDirective = re.compile(r"^@root\s+(.+)")
patternAscDirective = re.compile(r"^@asc")

# New Leo2AsciiDoc directives
patternAscDirectiveConfig = re.compile(r'^@ascconfig\W+(\w+)\s+(\S+)')
patternAscDirectiveFile = re.compile(r'^@ascfile *"*([\w\\/\.]*)"*')
patternAscDirectiveExit = re.compile(r"^@ascexit")
patternAscDirectiveIgnore = re.compile(r"^@ascignore")
patternAscDirectiveSkip = re.compile(r"^@ascskip")
patternAscDirectiveSkipToggle = re.compile(r"^@ascskip\s*(\w+)+.*")
#@-<< constants >>
#@+others
#@+node:ekr.20140920145803.17999: ** init
[docs]def init(): '''Return True if the plugin has loaded successfully.''' leoPlugins.registerHandler(('new','menu2'), CreateAscMenu) g.plugin_signon(__name__) return True
#@+node:ekr.20140920145803.18000: ** Functions #@+node:ekr.20101110094152.5837: *3* CodeChunk
[docs]def CodeChunk(text, width=72): """Split a line of text into a list of chunks not longer than width.""" chunkList = [] chunkStart = 0 chunkEnd = 0 lastSpacePosition = 0 shortWidth = width - 4 prefix = '' # suffix = ' \\' textLen = len(text) if width > textLen: chunkList.append(text) else: while chunkEnd < textLen: if chunkList: prefix = ' ' chunkEnd = chunkStart + shortWidth if chunkEnd > textLen: chunkList.append(prefix + text[chunkStart:]) chunkEnd = textLen # get out of jail else: lastSpacePosition = text.rfind(' ',chunkStart, chunkEnd +1) if lastSpacePosition != -1: # success chunkList.append(prefix + text[chunkStart:lastSpacePosition] + ' \\') chunkStart = lastSpacePosition + 1 else: chunkEnd = chunkStart + shortWidth chunkList.append(prefix + text[chunkStart:chunkEnd] + ' \\') chunkStart = chunkEnd return chunkList
#@+node:ekr.20101110094152.5847: *3* CreateAscMenu
[docs]def CreateAscMenu(tag,keywords): """Create the Outline to AsciiDoc menu item in the Export menu.""" c = keywords.get('c') if not c: return exportMenu = c.frame.menu.getMenu('export') newEntries = ( ("-", None, None), ("Export all to &AsciiDoc","Alt+Shift+A",WriteAll), ("Export current tree to AsciiDoc","Alt+Shift+T",WriteTreeOfCurrentNode), ("Log all root and ascfile to log pane","Alt+Shift+L",WriteAllRoots), ) c.frame.menu.createMenuEntries(exportMenu,newEntries,dynamicMenu=True)
#@+node:ekr.20101110094152.5836: *3* GetAscFilename
[docs]def GetAscFilename(c,p): 'Checks a node for a filename directive.' # f is the Leo outline ascFileName = None lines = p.b.splitlines() for line in lines: containsAscFileDirective = patternAscDirectiveFile.match(line) if containsAscFileDirective: ascFileName = containsAscFileDirective.group(1) if (ascFileName is not None): base = os.path.split(c.mFileName)[0] # linux or windows if (((base[0]=="/") and (ascFileName[0] != "/")) or ((base[1]==":") and (ascFileName[1] != ":"))): # no full pathname specified ascFileName = os.path.join(base, ascFileName) Conf.GetCurrentOptions(c,p) return ascFileName
#@+node:ekr.20101110094152.5835: *3* SectionUnderline
[docs]def SectionUnderline(h,level,v): 'Return a section underline string.' asciiDocSectionLevels = int(Conf.current["asciiDocSectionLevels"]) if level < 0: g.es("Section level is less than 1:\n %s" % v.headString()) level = 1 elif level > asciiDocSectionLevels - 1: g.es("Section level is more than maximum Section Levels: %d\n %s" % ( asciiDocSectionLevels, v.headString())) level = asciiDocSectionLevels - 1 str = Conf.current["headingUnderlines"][level] #' return str*max(len(h),1)
#@+node:ekr.20101110094152.5843: *3* WriteAll
[docs]def WriteAll(c): p = c.rootPosition() while p: ascFileN = GetAscFilename(c,p) if ascFileN: WriteTreeAsAsc(p,ascFileN) p.moveToNodeAfterTree() else: p.moveToThreadNext()
#@+node:ekr.20101110094152.5845: *3* WriteAllRoots
[docs]def WriteAllRoots(c): "Writes @root directive and/or @ascfile directive to log pane." patternAscDirectiveFile = re.compile(r'^@ascfile') patternRoot = re.compile(r'^@root') g.es('Looking for @root or @ascfile.') for p in c.all_positions(): printedHeading = False for line in p.b.splitlines(): printLine = False containsAscFileDirective = patternAscDirectiveFile.match(line) if containsAscFileDirective: printLine = True containsRootDirective = patternRoot.match(line) if containsRootDirective: printLine = True if printLine: if not printedHeading: g.es(p.h) printedHeading = True g.es(' ' + line)
#@+node:ekr.20101110094152.5839: *3* WriteNode
[docs]def WriteNode(v,startinglevel, ascFile): 'Writes the contents of the node v to the ascFile.' containsAscIignore = None skippingDocLines = False startingCodeExtract = False inCodeExtract = False statusOfWriteOutputLine = None def WriteOutputLine(lineString): 'Writes a line of text to the output file.' try: ascFile.write("%s\n" % lineString) except IOError: g.es("Could not write to output file: %s" % ascFile.name) # statusOfWriteOutputLine = CV.END_PROGRAM # Get the headline text. h = v.headString() markedupAsSection = patternSectionName.match(h) if markedupAsSection: h = markedupAsSection.group(1) # dump the angle brackets # Put the body text into a list of lines. bodyString = v.bodyString() lines = bodyString.splitlines() lastLinePrintedType = CV.LINE_WAS_NONE # By default, nodes start with a code section. pendinglineType = CV.LINE_PENDING_CODE for line in lines: containsRootDirective = None containsSectionDefinition = patternSectionDefinition.match(line) if containsSectionDefinition: # dump the angle brackets, etc. # line = containsSectionDefinition.group(2) + '\n' + \ # (SectionUnderline(containsSectionDefinition.group(2),2,v)) line = '.' + containsSectionDefinition.group(2) pendinglineType = CV.LINE_PENDING_CODE startingCodeExtract = True containsCodeDirective = patternCodeDirective.match(line) if containsCodeDirective: pendinglineType = CV.LINE_PENDING_CODE skippingDocLines = False continue # don't print this line containsDocDirective = patternDocDirective.match(line) if containsDocDirective: pendinglineType = CV.LINE_PENDING_DOC if containsDocDirective.group(2): # it is legal to have text on the same line # as a doc directive. line = containsDocDirective.group(2) else: continue containsAscDirective = patternAscDirective.match(line) if containsAscDirective: containsAscIignore = patternAscDirectiveIgnore.match(line) if containsAscIignore: break containsAscExit = patternAscDirectiveExit.match(line) if containsAscExit: break containsAscSkip = patternAscDirectiveSkip.match(line) if containsAscSkip: containsAscSkipDirectiveToggle = patternAscDirectiveSkipToggle.match(line) if containsAscSkipDirectiveToggle: if containsAscSkipDirectiveToggle.group(1).lower() == "on": skippingDocLines = True elif containsAscSkipDirectiveToggle.group(1).lower() == "off": skippingDocLines = False continue containsOtherDirective = patternDirective.match(line) if containsOtherDirective: containsRootDirective = patternRootDirective.match(line) if containsRootDirective: line = "*note*\nThe code sections that follow, when extracted from a " + \ "Leo outline, will be located in: %s\n*note*" % \ containsRootDirective.group(1) else: continue # We have something to print, so print heading. if lastLinePrintedType == CV.LINE_WAS_NONE: if h and (Conf.current["PrintHeadings"] == "on"): WriteOutputLine("\n\n%s" % h) WriteOutputLine(SectionUnderline(h,v.level()-startinglevel,v)) lastLinePrintedType = CV.LINE_WAS_HEAD if pendinglineType == CV.LINE_PENDING_DOC: if lastLinePrintedType != CV.LINE_WAS_DOC and \ lastLinePrintedType != CV.LINE_WAS_HEAD: WriteOutputLine("%s" % Conf.current["delimiterForCodeEnd"]) if inCodeExtract: WriteOutputLine("\n%s" % Conf.current["delimiterForCodeSectionDefinition"]) inCodeExtract = False lastLinePrintedType = CV.LINE_WAS_DOC if skippingDocLines: if not containsRootDirective: # always document a root directive continue if pendinglineType == CV.LINE_PENDING_CODE: if lastLinePrintedType != CV.LINE_WAS_CODE: if startingCodeExtract: WriteOutputLine("\n%s" % line) WriteOutputLine("%s" % Conf.current["delimiterForCodeSectionDefinition"]) inCodeExtract = True line = '' WriteOutputLine("%s" % Conf.current["delimiterForCodeStart"]) lastLinePrintedType = CV.LINE_WAS_CODE if startingCodeExtract: startingCodeExtract = False continue maxCodeLineLength = int(Conf.current["maxCodeLineLength"]) if len(line) <= maxCodeLineLength: WriteOutputLine("%s" % line) elif len(line.rstrip()) <= maxCodeLineLength: WriteOutputLine("%s" % line.rstrip()) else: lineList = CodeChunk(line, maxCodeLineLength) for ln in lineList: WriteOutputLine("%s" % ln) lastLinePrintedType = CV.LINE_WAS_CODE else: WriteOutputLine("%s" % line) if statusOfWriteOutputLine is not None: return statusOfWriteOutputLine if lastLinePrintedType == CV.LINE_WAS_CODE: WriteOutputLine("%s" % Conf.current["delimiterForCodeEnd"]) if inCodeExtract: WriteOutputLine("\n%s" % Conf.current["delimiterForCodeSectionDefinition"]) inCodeExtract = False if containsAscIignore is not None: return CV.NODE_IGNORE # flag ignore tree to caller
#@+node:ekr.20101110094152.5838: *3* WriteTreeAsAsc
[docs]def WriteTreeAsAsc(p,fn): 'Writes the tree under p to the file ascFile' try: ascFile = open(fn,'w') except IOError: g.es("Could not open output file: %s" % fn) return stopHere = p.nodeAfterTree() startinglevel = p.level() while p and p != stopHere: val = WriteNode(p,startinglevel,ascFile) if val == CV.END_PROGRAM: ascFile.close() return elif val == CV.NODE_IGNORE: p.moveToNodeAfterTree() # ran into an @ascignore else: p.moveToThreadNext() ascFile.close() g.es('wrote: %s' % fn)
#@+node:ekr.20101110094152.5841: *3* WriteTreeOfCurrentNode (not used)
[docs]def WriteTreeOfCurrentNode(c): p = c.p while p: ascFileN = GetAscFilename(c,p) if ascFileN: break else: p.moveToParent() if ascFileN is None: g.es("Sorry, there was no @ascfile directive in this outline tree.") else: WriteTreeAsAsc(p,ascFileN)
#@-others #@@language python #@@tabwidth -4 #@-leo