can't appear inside
+ try:
+ if len(lines) == 1 and \
+ TARGET in ('html', 'xhtml') and \
+ re.match('^\s*
.*\s*$', lines[0]):
+ result = [lines[0]]
+ except: pass
+
+ return result
+
+ def verb(self):
+ "Verbatim lines are not masked, so there's no need to unmask"
+ result = []
+ result.append(TAGS['blockVerbOpen'])
+ for line in self.hold():
+ if self.prop('mapped') == 'table':
+ line = MacroMaster().expand(line)
+ if not rules['verbblocknotescaped']:
+ line = doEscape(TARGET,line)
+ if rules['indentverbblock']:
+ line = ' '+line
+ if rules['verbblockfinalescape']:
+ line = doFinalEscape(TARGET, line)
+ result.append(line)
+ #TODO maybe use if not TAGS['blockVerbClose']
+ if TARGET != 'pm6':
+ result.append(TAGS['blockVerbClose'])
+ return result
+
+ def table(self):
+ # Rewrite all table cells by the unmasked and escaped data
+ lines = self._get_escaped_hold()
+ for i in xrange(len(lines)):
+ cells = string.split(lines[i], SEPARATOR)
+ self.tableparser.rows[i]['cells'] = cells
+
+ return self.tableparser.dump()
+
+ def quote(self):
+ result = []
+ myre = regex['quote']
+ open = TAGS['blockQuoteOpen'] # block based
+ close = TAGS['blockQuoteClose']
+ qline = TAGS['blockQuoteLine'] # line based
+ indent = tagindent = '\t'*self.depth
+ if rules['tagnotindentable']: tagindent = ''
+ if not rules['keepquoteindent']: indent = ''
+
+ if open: result.append(tagindent+open) # open block
+ for item in self.hold():
+ if type(item) == type([]):
+ result.extend(item) # subquotes
+ else:
+ item = myre.sub('', item) # del TABs
+ if rules['barinsidequote']:
+ item = get_tagged_bar(item)
+ item = self._last_escapes(item)
+ item = qline*self.depth + item
+ result.append(indent+item) # quote line
+ if close: result.append(tagindent+close) # close block
+ return result
+
+ def deflist(self): return self.list('deflist')
+ def numlist(self): return self.list('numlist')
+ def list(self, name='list'):
+ result = []
+ items = self.hold()
+ indent = self.prop('indent')
+ tagindent = indent
+ listopen = TAGS.get(name+'Open')
+ listclose = TAGS.get(name+'Close')
+ listline = TAGS.get(name+'ItemLine')
+ itemcount = 0
+ if rules['tagnotindentable']: tagindent = ''
+ if not rules['keeplistindent']: indent = ''
+
+ if name == 'deflist':
+ itemopen = TAGS[name+'Item1Open']
+ itemclose = TAGS[name+'Item2Close']
+ itemsep = TAGS[name+'Item1Close']+\
+ TAGS[name+'Item2Open']
+ else:
+ itemopen = TAGS[name+'ItemOpen']
+ itemclose = TAGS[name+'ItemClose']
+ itemsep = ''
+
+ # ItemLine: number of leading chars identifies list depth
+ if listline:
+ itemopen = listline*self.depth
+ # Dirty fix for mgp
+ if name == 'numlist': itemopen = itemopen + '\a. '
+
+ # Remove two-blanks from list ending mark, to avoid
+ items[-1] = self._remove_twoblanks(items[-1])
+
+ # Open list (not nestable lists are only opened at mother)
+ if listopen and not \
+ (rules['listnotnested'] and BLOCK.depth != 1):
+ result.append(tagindent+listopen)
+
+ # Tag each list item (multiline items)
+ itemopenorig = itemopen
+ for item in items:
+
+ # Add "manual" item count for noautonum targets
+ itemcount = itemcount + 1
+ if name == 'numlist' and not rules['autonumberlist']:
+ n = str(itemcount)
+ itemopen = regex['x'].sub(n, itemopenorig)
+ del n
+
+ item[0] = self._last_escapes(item[0])
+ if name == 'deflist':
+ z,term,rest = string.split(item[0],SEPARATOR,2)
+ item[0] = rest
+ if not item[0]: del item[0] # to avoid
+ result.append(tagindent+itemopen+term+itemsep)
+ else:
+ fullitem = tagindent+itemopen
+ result.append(string.replace(
+ item[0], SEPARATOR, fullitem))
+ del item[0]
+
+ # Process next lines for this item (if any)
+ for line in item:
+ if type(line) == type([]): # sublist inside
+ result.extend(line)
+ else:
+ line = self._last_escapes(line)
+ # Blank lines turns to
+ if not line and rules['parainsidelist']:
+ line = string.rstrip(indent +\
+ TAGS['paragraphOpen']+\
+ TAGS['paragraphClose'])
+ if not rules['keeplistindent']:
+ line = string.lstrip(line)
+ result.append(line)
+
+ # Close item (if needed)
+ if itemclose: result.append(tagindent+itemclose)
+
+ # Close list (not nestable lists are only closed at mother)
+ if listclose and not \
+ (rules['listnotnested'] and BLOCK.depth != 1):
+ result.append(tagindent+listclose)
+
+ if rules['blankendmotherlist'] and BLOCK.depth == 1:
+ result.append('')
+
+ return result
+
+
+##############################################################################
+
+
+class MacroMaster:
+ def __init__(self, config={}):
+ self.name = ''
+ self.config = config or CONF
+ self.infile = self.config['sourcefile']
+ self.outfile = self.config['outfile']
+ self.currdate = time.localtime(time.time())
+ self.rgx = regex.get('macros') or getRegexes()['macros']
+ self.fileinfo = { 'infile': None, 'outfile': None }
+ self.dft_fmt = MACROS
+
+ def walk_file_format(self, fmt):
+ "Walks the %%{in/out}file format string, expanding the % flags"
+ i = 0; ret = '' # counter/hold
+ while i < len(fmt): # char by char
+ c = fmt[i]; i = i + 1
+ if c == '%': # hot char!
+ if i == len(fmt): # % at the end
+ ret = ret + c
+ break
+ c = fmt[i]; i = i + 1 # read next
+ ret = ret + self.expand_file_flag(c)
+ else:
+ ret = ret +c # common char
+ return ret
+
+ def expand_file_flag(self, flag):
+ "%f: filename %F: filename (w/o extension)"
+ "%d: dirname %D: dirname (only parent dir)"
+ "%p: file path %e: extension"
+ info = self.fileinfo[self.name] # get dict
+ if flag == '%': x = '%' # %% -> %
+ elif flag == 'f': x = info['name']
+ elif flag == 'F': x = re.sub('\.[^.]*$','',info['name'])
+ elif flag == 'd': x = info['dir']
+ elif flag == 'D': x = os.path.split(info['dir'])[-1]
+ elif flag == 'p': x = info['path']
+ elif flag == 'e': x = re.search('.(\.([^.]+))?$',info['name']
+ ).group(2) or ''
+ #TODO simpler way for %e ?
+ else : x = '%'+flag # false alarm
+ return x
+
+ def set_file_info(self, macroname):
+ if self.fileinfo.get(macroname): return # already done
+ file = getattr(self, self.name) # self.infile
+ if file == STDOUT or file == MODULEOUT:
+ dir = ''; path = name = file
+ else:
+ path = os.path.abspath(file)
+ dir = os.path.dirname(path)
+ name = os.path.basename(path)
+ self.fileinfo[macroname] = {'path':path,'dir':dir,'name':name}
+
+ def expand(self, line=''):
+ "Expand all macros found on the line"
+ while self.rgx.search(line):
+ m = self.rgx.search(line)
+ name = self.name = string.lower(m.group('name'))
+ fmt = m.group('fmt') or self.dft_fmt.get(name)
+ if name == 'date':
+ txt = time.strftime(fmt,self.currdate)
+ elif name == 'mtime':
+ if self.infile in (STDIN, MODULEIN):
+ fdate = self.currdate
+ else:
+ mtime = os.path.getmtime(self.infile)
+ fdate = time.localtime(mtime)
+ txt = time.strftime(fmt,fdate)
+ elif name == 'infile' or name == 'outfile':
+ self.set_file_info(name)
+ txt = self.walk_file_format(fmt)
+ else:
+ Error("Unknown macro name '%s'"%name)
+ line = self.rgx.sub(txt,line,1)
+ return line
+
+
+##############################################################################
+
+
+def dumpConfig(source_raw, parsed_config):
+ onoff = {1:_('ON'), 0:_('OFF')}
+ data = [
+ (_('RC file') , RC_RAW ),
+ (_('source document'), source_raw ),
+ (_('command line') , CMDLINE_RAW)
+ ]
+ # First show all RAW data found
+ for label, cfg in data:
+ print _('RAW config for %s')%label
+ for target,key,val in cfg:
+ target = '(%s)'%target
+ key = dotted_spaces("%-14s"%key)
+ val = val or _('ON')
+ print ' %-8s %s: %s'%(target,key,val)
+ print
+ # Then the parsed results of all of them
+ print _('Full PARSED config')
+ keys = parsed_config.keys() ; keys.sort() # sorted
+ for key in keys:
+ val = parsed_config[key]
+ # Filters are the last
+ if key == 'preproc' or key == 'postproc':
+ continue
+ # Flag beautifier
+ if key in FLAGS.keys() or key in ACTIONS.keys():
+ val = onoff.get(val) or val
+ # List beautifier
+ if type(val) == type([]):
+ if key == 'options': sep = ' '
+ else : sep = ', '
+ val = string.join(val, sep)
+ print "%25s: %s"%(dotted_spaces("%-14s"%key),val)
+ print
+ print _('Active filters')
+ for filter in ['preproc','postproc']:
+ for rule in parsed_config.get(filter) or []:
+ print "%25s: %s -> %s"%(
+ dotted_spaces("%-14s"%filter),rule[0],rule[1])
+
+
+def get_file_body(file):
+ "Returns all the document BODY lines"
+ return process_source_file(file, noconf=1)[1][2]
+
+
+def finish_him(outlist, config):
+ "Writing output to screen or file"
+ outfile = config['outfile']
+ outlist = unmaskEscapeChar(outlist)
+ outlist = expandLineBreaks(outlist)
+
+ # Apply PostProc filters
+ if config['postproc']:
+ filters = compile_filters(config['postproc'],
+ _('Invalid PostProc filter regex'))
+ postoutlist = []
+ errmsg = _('Invalid PostProc filter replacement')
+ for line in outlist:
+ for rgx,repl in filters:
+ try: line = rgx.sub(repl, line)
+ except: Error("%s: '%s'"%(errmsg, repl))
+ postoutlist.append(line)
+ outlist = postoutlist[:]
+
+ if outfile == MODULEOUT:
+ return outlist
+ elif outfile == STDOUT:
+ if GUI:
+ return outlist, config
+ else:
+ for line in outlist: print line
+ else:
+ Savefile(outfile, addLineBreaks(outlist))
+ if not GUI and not QUIET:
+ print _('%s wrote %s')%(my_name,outfile)
+
+ if config['split']:
+ if not QUIET: print "--- html..."
+ sgml2html = 'sgml2html -s %s -l %s %s'%(
+ config['split'],config['lang'] or lang,outfile)
+ if not QUIET: print "Running system command:", sgml2html
+ os.system(sgml2html)
+
+
+def toc_inside_body(body, toc, config):
+ ret = []
+ if AUTOTOC: return body # nothing to expand
+ toc_mark = MaskMaster().tocmask
+ # Expand toc mark with TOC contents
+ for line in body:
+ if string.count(line, toc_mark): # toc mark found
+ if config['toc']:
+ ret.extend(toc) # include if --toc
+ else:
+ pass # or remove %%toc line
+ else:
+ ret.append(line) # common line
+ return ret
+
+def toc_tagger(toc, config):
+ "Convert t2t-marked TOC (it is a list) to target-tagged TOC"
+ ret = []
+ # Tag if TOC-only TOC "by hand" (target don't have a TOC tag)
+ if config['toc-only'] or (config['toc'] and not TAGS['TOC']):
+ fakeconf = config.copy()
+ fakeconf['headers'] = 0
+ fakeconf['toc-only'] = 0
+ fakeconf['mask-email'] = 0
+ fakeconf['preproc'] = []
+ fakeconf['postproc'] = []
+ fakeconf['css-sugar'] = 0
+ ret,foo = convert(toc, fakeconf)
+ set_global_config(config) # restore config
+ # Target TOC is a tag
+ elif config['toc'] and TAGS['TOC']:
+ ret = [TAGS['TOC']]
+ return ret
+
+def toc_formatter(toc, config):
+ "Formats TOC for automatic placement between headers and body"
+ if config['toc-only']: return toc # no formatting needed
+ if not config['toc'] : return [] # TOC disabled
+ ret = toc
+ # TOC open/close tags (if any)
+ if TAGS['tocOpen' ]: ret.insert(0, TAGS['tocOpen'])
+ if TAGS['tocClose']: ret.append(TAGS['tocClose'])
+ # Autotoc specific formatting
+ if AUTOTOC:
+ if rules['autotocwithbars']: # TOC between bars
+ para = TAGS['paragraphOpen']+TAGS['paragraphClose']
+ bar = regex['x'].sub('-'*72,TAGS['bar1'])
+ tocbar = [para, bar, para]
+ ret = tocbar + ret + tocbar
+ if rules['blankendautotoc']: # blank line after TOC
+ ret.append('')
+ if rules['autotocnewpagebefore']: # page break before TOC
+ ret.insert(0,TAGS['pageBreak'])
+ if rules['autotocnewpageafter']: # page break after TOC
+ ret.append(TAGS['pageBreak'])
+ return ret
+
+
+def doHeader(headers, config):
+ if not config['headers']: return []
+ if not headers: headers = ['','','']
+ target = config['target']
+ if not HEADER_TEMPLATE.has_key(target):
+ Error("doheader: Unknow target '%s'"%target)
+
+ if target in ('html','xhtml') and config.get('css-sugar'):
+ template = string.split(HEADER_TEMPLATE[target+'css'], '\n')
+ else:
+ template = string.split(HEADER_TEMPLATE[target], '\n')
+
+ head_data = {'STYLE':[], 'ENCODING':''}
+ for key in head_data.keys():
+ val = config.get(string.lower(key))
+ # Remove .sty extension from each style filename (freaking tex)
+ # XXX Can't handle --style foo.sty,bar.sty
+ if target == 'tex' and key == 'STYLE':
+ val = map(lambda x:re.sub('(?i)\.sty$','',x), val)
+ if key == 'ENCODING':
+ val = get_encoding_string(val, target)
+ head_data[key] = val
+ # Parse header contents
+ for i in 0,1,2:
+ # Expand macros
+ contents = MacroMaster(config=config).expand(headers[i])
+ # Escapes - on tex, just do it if any \tag{} present
+ if target != 'tex' or \
+ (target == 'tex' and re.search(r'\\\w+{', contents)):
+ contents = doEscape(target, contents)
+ if target == 'lout':
+ contents = doFinalEscape(target, contents)
+
+ head_data['HEADER%d'%(i+1)] = contents
+ # css-inside removes STYLE line
+ #XXX In tex, this also removes the modules call (%!style:amsfonts)
+ if target in ('html','xhtml') and config.get('css-inside') and \
+ config.get('style'):
+ head_data['STYLE'] = []
+ Debug("Header Data: %s"%head_data, 1)
+ # Scan for empty dictionary keys
+ # If found, scan template lines for that key reference
+ # If found, remove the reference
+ # If there isn't any other key reference on the same line, remove it
+ #TODO loop by template line > key
+ for key in head_data.keys():
+ if head_data.get(key): continue
+ for line in template:
+ if string.count(line, '%%(%s)s'%key):
+ sline = string.replace(line, '%%(%s)s'%key, '')
+ if not re.search(r'%\([A-Z0-9]+\)s', sline):
+ template.remove(line)
+ # Style is a multiple tag.
+ # - If none or just one, use default template
+ # - If two or more, insert extra lines in a loop (and remove original)
+ styles = head_data['STYLE']
+ if len(styles) == 1:
+ head_data['STYLE'] = styles[0]
+ elif len(styles) > 1:
+ style_mark = '%(STYLE)s'
+ for i in xrange(len(template)):
+ if string.count(template[i], style_mark):
+ while styles:
+ template.insert(i+1,
+ string.replace(
+ template[i],
+ style_mark,
+ styles.pop()))
+ del template[i]
+ break
+ # Populate template with data (dict expansion)
+ template = string.join(template, '\n') % head_data
+
+ # Adding CSS contents into template (for --css-inside)
+ # This code sux. Dirty++
+ if target in ('html','xhtml') and config.get('css-inside') and \
+ config.get('style'):
+ set_global_config(config) # usually on convert(), needed here
+ for i in xrange(len(config['style'])):
+ cssfile = config['style'][i]
+ if not os.path.isabs(cssfile):
+ infile = config.get('sourcefile')
+ cssfile = os.path.join(
+ os.path.dirname(infile), cssfile)
+ try:
+ contents = Readfile(cssfile, 1)
+ css = "\n%s\n%s\n%s\n%s\n" % (
+ doCommentLine("Included %s" % cssfile),
+ TAGS['cssOpen'],
+ string.join(contents, '\n'),
+ TAGS['cssClose'])
+ # Style now is content, needs escaping (tex)
+ #css = maskEscapeChar(css)
+ except:
+ errmsg = "CSS include failed for %s" % cssfile
+ css = "\n%s\n" % (doCommentLine(errmsg))
+ # Insert this CSS file contents on the template
+ template = re.sub('(?i)()', css+r'\1', template)
+ # template = re.sub(r'(?i)(\\begin{document})',
+ # css+'\n'+r'\1', template) # tex
+
+ # The last blank line to keep everything separated
+ template = re.sub('(?i)()', '\n'+r'\1', template)
+
+ return string.split(template, '\n')
+
+def doCommentLine(txt):
+ # The -- string ends a (h|sg|xht)ml comment :(
+ txt = maskEscapeChar(txt)
+ if string.count(TAGS['comment'], '--') and \
+ string.count(txt, '--'):
+ txt = re.sub('-(?=-)', r'-\\', txt)
+
+ if TAGS['comment']:
+ return regex['x'].sub(txt, TAGS['comment'])
+ return ''
+
+def doFooter(config):
+ if not config['headers']: return []
+ ret = []
+ target = config['target']
+ cmdline = config['realcmdline']
+ typename = target
+ if target == 'tex': typename = 'LaTeX2e'
+ ppgd = '%s code generated by %s %s (%s)'%(
+ typename,my_name,my_version,my_url)
+ cmdline = 'cmdline: %s %s'%(my_name, string.join(cmdline, ' '))
+ ret.append('')
+ ret.append(doCommentLine(ppgd))
+ ret.append(doCommentLine(cmdline))
+ ret.append(TAGS['EOD'])
+ return ret
+
+def doEscape(target,txt):
+ "Target-specific special escapes. Apply *before* insert any tag."
+ tmpmask = 'vvvvThisEscapingSuxvvvv'
+ if target in ('html','sgml','xhtml'):
+ txt = re.sub('&','&',txt)
+ txt = re.sub('<','<',txt)
+ txt = re.sub('>','>',txt)
+ if target == 'sgml':
+ txt = re.sub('\xff','ÿ',txt) # "+y
+ elif target == 'pm6':
+ txt = re.sub('<','<\#60>',txt)
+ elif target == 'mgp':
+ txt = re.sub('^%',' %',txt) # add leading blank to avoid parse
+ elif target == 'man':
+ txt = re.sub("^([.'])", '\\&\\1',txt) # command ID
+ txt = string.replace(txt,ESCCHAR, ESCCHAR+'e') # \e
+ elif target == 'lout':
+ # TIP: / moved to FinalEscape to avoid //italic//
+ # TIP: these are also converted by lout: ... --- --
+ txt = string.replace(txt, ESCCHAR, tmpmask) # \
+ txt = string.replace(txt, '"', '"%s""'%ESCCHAR) # "\""
+ txt = re.sub('([|&{}@#^~])', '"\\1"',txt) # "@"
+ txt = string.replace(txt, tmpmask, '"%s"'%(ESCCHAR*2)) # "\\"
+ elif target == 'tex':
+ # Mark literal \ to be changed to $\backslash$ later
+ txt = string.replace( txt, ESCCHAR, tmpmask)
+ txt = re.sub('([#$&%{}])', ESCCHAR+r'\1' , txt) # \%
+ txt = re.sub('([~^])' , ESCCHAR+r'\1{}', txt) # \~{}
+ txt = re.sub('([<|>])' , r'$\1$', txt) # $>$
+ txt = string.replace(txt, tmpmask,
+ maskEscapeChar(r'$\backslash$'))
+ # TIP the _ is escaped at the end
+ return txt
+
+# TODO man: where - really needs to be escaped?
+def doFinalEscape(target, txt):
+ "Last escapes of each line"
+ if target == 'pm6' : txt = string.replace(txt,ESCCHAR+'<',r'<\#92><')
+ elif target == 'man' : txt = string.replace(txt, '-', r'\-')
+ elif target == 'sgml': txt = string.replace(txt, '[', '[')
+ elif target == 'lout': txt = string.replace(txt, '/', '"/"')
+ elif target == 'tex' :
+ txt = string.replace(txt, '_', r'\_')
+ txt = string.replace(txt, 'vvvvTexUndervvvv', '_') # shame!
+ return txt
+
+def EscapeCharHandler(action, data):
+ "Mask/Unmask the Escape Char on the given string"
+ if not string.strip(data): return data
+ if action not in ('mask','unmask'):
+ Error("EscapeCharHandler: Invalid action '%s'"%action)
+ if action == 'mask': return string.replace(data,'\\',ESCCHAR)
+ else: return string.replace(data,ESCCHAR,'\\')
+
+def maskEscapeChar(data):
+ "Replace any Escape Char \ with a text mask (Input: str or list)"
+ if type(data) == type([]):
+ return map(lambda x: EscapeCharHandler('mask', x), data)
+ return EscapeCharHandler('mask',data)
+
+def unmaskEscapeChar(data):
+ "Undo the Escape char \ masking (Input: str or list)"
+ if type(data) == type([]):
+ return map(lambda x: EscapeCharHandler('unmask', x), data)
+ return EscapeCharHandler('unmask',data)
+
+def addLineBreaks(mylist):
+ "use LB to respect sys.platform"
+ ret = []
+ for line in mylist:
+ line = string.replace(line,'\n',LB) # embedded \n's
+ ret.append(line+LB) # add final line break
+ return ret
+
+# Convert ['foo\nbar'] to ['foo', 'bar']
+def expandLineBreaks(mylist):
+ ret = []
+ for line in mylist:
+ ret.extend(string.split(line, '\n'))
+ return ret
+
+def compile_filters(filters, errmsg='Filter'):
+ if filters:
+ for i in xrange(len(filters)):
+ patt,repl = filters[i]
+ try: rgx = re.compile(patt)
+ except: Error("%s: '%s'"%(errmsg, patt))
+ filters[i] = (rgx,repl)
+ return filters
+
+def enclose_me(tagname, txt):
+ return TAGS.get(tagname+'Open') + txt + TAGS.get(tagname+'Close')
+
+def beautify_me(name, line):
+ "where name is: bold, italic or underline"
+ name = 'font%s' % string.capitalize(name)
+ open = TAGS['%sOpen'%name]
+ close = TAGS['%sClose'%name]
+ txt = r'%s\1%s'%(open, close)
+ line = regex[name].sub(txt,line)
+ return line
+
+def get_tagged_link(label, url):
+ ret = ''
+ target = CONF['target']
+ image_re = regex['img']
+
+ # Set link type
+ if regex['email'].match(url):
+ linktype = 'email'
+ else:
+ linktype = 'url';
+
+ # Escape specials from TEXT parts
+ label = doEscape(target,label)
+
+ # Escape specials from link URL
+ if not rules['linkable'] or rules['escapeurl']:
+ url = doEscape(target, url)
+
+ # Adding protocol to guessed link
+ guessurl = ''
+ if linktype == 'url' and \
+ re.match('(?i)'+regex['_urlskel']['guess'], url):
+ if url[0] in 'Ww': guessurl = 'http://' +url
+ else : guessurl = 'ftp://' +url
+
+ # Not link aware targets -> protocol is useless
+ if not rules['linkable']: guessurl = ''
+
+ # Simple link (not guessed)
+ if not label and not guessurl:
+ if CONF['mask-email'] and linktype == 'email':
+ # Do the email mask feature (no TAGs, just text)
+ url = string.replace(url,'@',' (a) ')
+ url = string.replace(url,'.',' ')
+ url = "<%s>" % url
+ if rules['linkable']: url = doEscape(target, url)
+ ret = url
+ else:
+ # Just add link data to tag
+ tag = TAGS[linktype]
+ ret = regex['x'].sub(url,tag)
+
+ # Named link or guessed simple link
+ else:
+ # Adjusts for guessed link
+ if not label: label = url # no protocol
+ if guessurl : url = guessurl # with protocol
+
+ # Image inside link!
+ if image_re.match(label):
+ if rules['imglinkable']: # get image tag
+ label = parse_images(label)
+ else: # img@link !supported
+ label = "(%s)"%image_re.match(label).group(1)
+
+ # Putting data on the right appearance order
+ if rules['linkable']:
+ urlorder = [url, label] # link before label
+ else:
+ urlorder = [label, url] # label before link
+
+ # Add link data to tag (replace \a's)
+ ret = TAGS["%sMark"%linktype]
+ for data in urlorder:
+ ret = regex['x'].sub(data,ret,1)
+
+ return ret
+
+
+def parse_deflist_term(line):
+ "Extract and parse definition list term contents"
+ img_re = regex['img']
+ term = regex['deflist'].search(line).group(3)
+
+ # Mask image inside term as (image.jpg), where not supported
+ if not rules['imgasdefterm'] and img_re.search(term):
+ while img_re.search(term):
+ imgfile = img_re.search(term).group(1)
+ term = img_re.sub('(%s)'%imgfile, term, 1)
+
+ #TODO tex: escape ] on term. \], \rbrack{} and \verb!]! don't work :(
+ return term
+
+
+def get_tagged_bar(line):
+ m = regex['bar'].search(line)
+ if not m: return line
+ txt = m.group(2)
+
+ # Map strong bar to pagebreak
+ if rules['mapbar2pagebreak'] and TAGS['pageBreak']:
+ TAGS['bar2'] = TAGS['pageBreak']
+
+ # Set bar type
+ if txt[0] == '=': bar = TAGS['bar2']
+ else : bar = TAGS['bar1']
+
+ # To avoid comment tag confusion like
+ if string.count(TAGS['comment'], '--'):
+ txt = string.replace(txt,'--','__')
+
+ # Tag line
+ return regex['x'].sub(txt, bar)
+
+
+def get_image_align(line):
+ "Return the image (first found) align for the given line"
+
+ # First clear marks that can mess align detection
+ line = re.sub(SEPARATOR+'$', '', line) # remove deflist sep
+ line = re.sub('^'+SEPARATOR, '', line) # remove list sep
+ line = re.sub('^[\t]+' , '', line) # remove quote mark
+
+ # Get image position on the line
+ m = regex['img'].search(line)
+ ini = m.start() ; head = 0
+ end = m.end() ; tail = len(line)
+
+ # The align detection algorithm
+ if ini == head and end != tail: align = 'left' # ^img + text$
+ elif ini != head and end == tail: align = 'right' # ^text + img$
+ else : align = 'center' # default align
+
+ # Some special cases
+ if BLOCK.isblock('table'): align = 'center' # ignore when table
+# if TARGET == 'mgp' and align == 'center': align = 'center'
+
+ return align
+
+
+# Reference: http://www.iana.org/assignments/character-sets
+# http://www.drclue.net/F1.cgi/HTML/META/META.html
+def get_encoding_string(enc, target):
+ if not enc: return ''
+ # Target specific translation table
+ translate = {
+ 'tex': {
+ # missing: ansinew , applemac , cp437 , cp437de , cp865
+ 'us-ascii' : 'ascii',
+ 'windows-1250': 'cp1250',
+ 'windows-1252': 'cp1252',
+ 'ibm850' : 'cp850',
+ 'ibm852' : 'cp852',
+ 'iso-8859-1' : 'latin1',
+ 'iso-8859-2' : 'latin2',
+ 'iso-8859-3' : 'latin3',
+ 'iso-8859-4' : 'latin4',
+ 'iso-8859-5' : 'latin5',
+ 'iso-8859-9' : 'latin9',
+ 'koi8-r' : 'koi8-r'
+ }
+ }
+ # Normalization
+ enc = re.sub('(?i)(us[-_]?)?ascii|us|ibm367','us-ascii' , enc)
+ enc = re.sub('(?i)(ibm|cp)?85([02])' ,'ibm85\\2' , enc)
+ enc = re.sub('(?i)(iso[_-]?)?8859[_-]?' ,'iso-8859-' , enc)
+ enc = re.sub('iso-8859-($|[^1-9]).*' ,'iso-8859-1', enc)
+ # Apply translation table
+ try: enc = translate[target][string.lower(enc)]
+ except: pass
+ return enc
+
+
+##############################################################################
+##MerryChristmas,IdontwanttofighttonightwithyouImissyourbodyandIneedyourlove##
+##############################################################################
+
+
+def process_source_file(file='', noconf=0, contents=[]):
+ """
+ Find and Join all the configuration available for a source file.
+ No sanity checking is done on this step.
+ It also extracts the source document parts into separate holders.
+
+ The config scan order is:
+ 1. The user configuration file (i.e. $HOME/.txt2tagsrc)
+ 2. The source document's CONF area
+ 3. The command line options
+
+ The return data is a tuple of two items:
+ 1. The parsed config dictionary
+ 2. The document's parts, as a (head, conf, body) tuple
+
+ All the conversion process will be based on the data and
+ configuration returned by this function.
+ The source files is read on this step only.
+ """
+ if contents:
+ source = SourceDocument(contents=contents)
+ else:
+ source = SourceDocument(file)
+ head, conf, body = source.split()
+ Message(_("Source document contents stored"),2)
+ if not noconf:
+ # Read document config
+ source_raw = source.get_raw_config()
+ # Join all the config directives found, then parse it
+ full_raw = RC_RAW + source_raw + CMDLINE_RAW
+ Message(_("Parsing and saving all config found (%03d items)")%(
+ len(full_raw)),1)
+ full_parsed = ConfigMaster(full_raw).parse()
+ # Add manually the filename to the conf dic
+ if contents:
+ full_parsed['sourcefile'] = MODULEIN
+ full_parsed['infile'] = MODULEIN
+ full_parsed['outfile'] = MODULEOUT
+ else:
+ full_parsed['sourcefile'] = file
+ # Maybe should we dump the config found?
+ if full_parsed.get('dump-config'):
+ dumpConfig(source_raw, full_parsed)
+ Quit()
+ # Okay, all done
+ Debug("FULL config for this file: %s"%full_parsed, 1)
+ else:
+ full_parsed = {}
+ return full_parsed, (head,conf,body)
+
+def get_infiles_config(infiles):
+ """
+ Find and Join into a single list, all configuration available
+ for each input file. This function is supposed to be the very
+ first one to be called, before any processing.
+ """
+ return map(process_source_file, infiles)
+
+def convert_this_files(configs):
+ global CONF
+ for myconf,doc in configs: # multifile support
+ target_head = []
+ target_toc = []
+ target_body = []
+ target_foot = []
+ source_head, source_conf, source_body = doc
+ myconf = ConfigMaster().sanity(myconf)
+ # Compose the target file Headers
+ #TODO escape line before?
+ #TODO see exceptions by tex and mgp
+ Message(_("Composing target Headers"),1)
+ target_head = doHeader(source_head, myconf)
+ # Parse the full marked body into tagged target
+ first_body_line = (len(source_head) or 1)+ len(source_conf) + 1
+ Message(_("Composing target Body"),1)
+ target_body, marked_toc = convert(source_body, myconf,
+ firstlinenr=first_body_line)
+ # If dump-source, we're done
+ if myconf['dump-source']:
+ for line in source_head+source_conf+target_body:
+ print line
+ return
+ # Make TOC (if needed)
+ Message(_("Composing target TOC"),1)
+ tagged_toc = toc_tagger(marked_toc, myconf)
+ target_toc = toc_formatter(tagged_toc, myconf)
+ target_body = toc_inside_body(target_body, target_toc, myconf)
+ if not AUTOTOC and not myconf['toc-only']: target_toc = []
+ # Compose the target file Footer
+ Message(_("Composing target Footer"),1)
+ target_foot = doFooter(myconf)
+ # Finally, we have our document
+ outlist = target_head + target_toc + target_body + target_foot
+ # If on GUI, abort before finish_him
+ # If module, return finish_him as list
+ # Else, write results to file or STDOUT
+ if GUI:
+ return outlist, myconf
+ elif myconf.get('outfile') == MODULEOUT:
+ return finish_him(outlist, myconf), myconf
+ else:
+ Message(_("Saving results to the output file"),1)
+ finish_him(outlist, myconf)
+
+
+def parse_images(line):
+ "Tag all images found"
+ while regex['img'].search(line) and TAGS['img'] != '[\a]':
+ txt = regex['img'].search(line).group(1)
+ tag = TAGS['img']
+
+ # HTML, XHTML and mgp!
+ if rules['imgalignable']:
+ align = get_image_align(line)
+ # Add align on tag
+ align_name = string.capitalize(align)
+ align_tag = TAGS['imgAlign'+align_name]
+ tag = regex['_imgAlign'].sub(align_tag, tag, 1)
+ # Dirty fix to allow centered solo images
+ if align == 'center' and TARGET in ('html','xhtml'):
+ rest = regex['img'].sub('',line,1)
+ if re.match('^\s+$', rest):
+ tag = "
%s" %tag
+
+ if TARGET == 'tex':
+ tag = re.sub(r'\\b',r'\\\\b',tag)
+ txt = string.replace(txt, '_', 'vvvvTexUndervvvv')
+
+ line = regex['img'].sub(tag,line,1)
+ line = regex['x'].sub(txt,line,1)
+ return line
+
+
+def add_inline_tags(line):
+ # Beautifiers
+ for beauti in ('Bold', 'Italic', 'Underline'):
+ if regex['font%s'%beauti].search(line):
+ line = beautify_me(beauti, line)
+
+ line = parse_images(line)
+ return line
+
+
+def get_include_contents(file, path=''):
+ "Parses %!include: value and extract file contents"
+ ids = {'`':'verb', '"':'raw', "'":'tagged' }
+ id = 't2t'
+ # Set include type and remove identifier marks
+ mark = file[0]
+ if mark in ids.keys():
+ if file[:2] == file[-2:] == mark*2:
+ id = ids[mark] # set type
+ file = file[2:-2] # remove marks
+ # Handle remote dir execution
+ filepath = os.path.join(path, file)
+ # Read included file contents
+ lines = Readfile(filepath, remove_linebreaks=1)
+ # Default txt2tags marked text, just BODY matters
+ if id == 't2t':
+ lines = get_file_body(filepath)
+ lines.insert(0, '%%INCLUDED(%s) starts here: %s'%(id,file))
+ # This appears when included hit EOF with verbatim area open
+ #lines.append('%%INCLUDED(%s) ends here: %s'%(id,file))
+ return id, lines
+
+
+def set_global_config(config):
+ global CONF, TAGS, regex, rules, TARGET
+ CONF = config
+ TAGS = getTags(CONF)
+ rules = getRules(CONF)
+ regex = getRegexes()
+ TARGET = config['target'] # save for buggy functions that need global
+
+
+def convert(bodylines, config, firstlinenr=1):
+ global BLOCK
+
+ set_global_config(config)
+
+ target = config['target']
+ BLOCK = BlockMaster()
+ MASK = MaskMaster()
+ TITLE = TitleMaster()
+
+ ret = []
+ dump_source = []
+ f_lastwasblank = 0
+
+ # Compiling all PreProc regexes
+ pre_filter = compile_filters(
+ CONF['preproc'], _('Invalid PreProc filter regex'))
+
+ # Let's mark it up!
+ linenr = firstlinenr-1
+ lineref = 0
+ while lineref < len(bodylines):
+ # Defaults
+ MASK.reset()
+ results_box = ''
+
+ untouchedline = bodylines[lineref]
+ dump_source.append(untouchedline)
+
+ line = re.sub('[\n\r]+$','',untouchedline) # del line break
+
+ # Apply PreProc filters
+ if pre_filter:
+ errmsg = _('Invalid PreProc filter replacement')
+ for rgx,repl in pre_filter:
+ try: line = rgx.sub(repl, line)
+ except: Error("%s: '%s'"%(errmsg, repl))
+
+ line = maskEscapeChar(line) # protect \ char
+ linenr = linenr +1
+ lineref = lineref +1
+
+ Debug(repr(line), 2, linenr) # heavy debug: show each line
+
+ #------------------[ Comment Block ]------------------------
+
+ # We're already on a comment block
+ if BLOCK.block() == 'comment':
+
+ # Closing comment
+ if regex['blockCommentClose'].search(line):
+ ret.extend(BLOCK.blockout() or [])
+ continue
+
+ # Normal comment-inside line. Ignore it.
+ continue
+
+ # Detecting comment block init
+ if regex['blockCommentOpen'].search(line) \
+ and BLOCK.block() not in BLOCK.exclusive:
+ ret.extend(BLOCK.blockin('comment'))
+ continue
+
+ #-------------------------[ Raw Text ]----------------------
+
+ # We're already on a raw block
+ if BLOCK.block() == 'raw':
+
+ # Closing raw
+ if regex['blockRawClose'].search(line):
+ ret.extend(BLOCK.blockout())
+ continue
+
+ # Normal raw-inside line
+ BLOCK.holdadd(line)
+ continue
+
+ # Detecting raw block init
+ if regex['blockRawOpen'].search(line) \
+ and BLOCK.block() not in BLOCK.exclusive:
+ ret.extend(BLOCK.blockin('raw'))
+ continue
+
+ # One line raw text
+ if regex['1lineRaw'].search(line) \
+ and BLOCK.block() not in BLOCK.exclusive:
+ ret.extend(BLOCK.blockin('raw'))
+ line = regex['1lineRaw'].sub('',line)
+ BLOCK.holdadd(line)
+ ret.extend(BLOCK.blockout())
+ continue
+
+ #------------------------[ Verbatim ]----------------------
+
+ #TIP We'll never support beautifiers inside verbatim
+
+ # Closing table mapped to verb
+ if BLOCK.block() == 'verb' \
+ and BLOCK.prop('mapped') == 'table' \
+ and not regex['table'].search(line):
+ ret.extend(BLOCK.blockout())
+
+ # We're already on a verb block
+ if BLOCK.block() == 'verb':
+
+ # Closing verb
+ if regex['blockVerbClose'].search(line):
+ ret.extend(BLOCK.blockout())
+ continue
+
+ # Normal verb-inside line
+ BLOCK.holdadd(line)
+ continue
+
+ # Detecting verb block init
+ if regex['blockVerbOpen'].search(line) \
+ and BLOCK.block() not in BLOCK.exclusive:
+ ret.extend(BLOCK.blockin('verb'))
+ f_lastwasblank = 0
+ continue
+
+ # One line verb-formatted text
+ if regex['1lineVerb'].search(line) \
+ and BLOCK.block() not in BLOCK.exclusive:
+ ret.extend(BLOCK.blockin('verb'))
+ line = regex['1lineVerb'].sub('',line)
+ BLOCK.holdadd(line)
+ ret.extend(BLOCK.blockout())
+ f_lastwasblank = 0
+ continue
+
+ # Tables are mapped to verb when target is not table-aware
+ if not rules['tableable'] and regex['table'].search(line):
+ if not BLOCK.isblock('verb'):
+ ret.extend(BLOCK.blockin('verb'))
+ BLOCK.propset('mapped', 'table')
+ BLOCK.holdadd(line)
+ continue
+
+ #---------------------[ blank lines ]-----------------------
+
+ if regex['blankline'].search(line):
+
+ # Close open paragraph
+ if BLOCK.isblock('para'):
+ ret.extend(BLOCK.blockout())
+ f_lastwasblank = 1
+ continue
+
+ # Close all open tables
+ if BLOCK.isblock('table'):
+ ret.extend(BLOCK.blockout())
+ f_lastwasblank = 1
+ continue
+
+ # Close all open quotes
+ while BLOCK.isblock('quote'):
+ ret.extend(BLOCK.blockout())
+
+ # Closing all open lists
+ if f_lastwasblank: # 2nd consecutive blank
+ if BLOCK.block()[-4:] == 'list':
+ BLOCK.holdaddsub('') # helps parser
+ while BLOCK.depth: # closes list (if any)
+ ret.extend(BLOCK.blockout())
+ continue # ignore consecutive blanks
+
+ # Paragraph (if any) is wanted inside lists also
+ if BLOCK.block()[-4:] == 'list':
+ BLOCK.holdaddsub('')
+ else:
+ # html: show blank line (needs tag)
+ if target in ('html','xhtml'):
+ ret.append(TAGS['paragraphOpen']+\
+ TAGS['paragraphClose'])
+ # Otherwise we just show a blank line
+ else:
+ ret.append('')
+
+ f_lastwasblank = 1
+ continue
+
+
+ #---------------------[ special ]---------------------------
+
+ if regex['special'].search(line):
+ # Include command
+ targ, key, val = ConfigLines().parse_line(
+ line, 'include', target)
+ if key:
+ Debug("Found config '%s', value '%s'"%(
+ key,val),1,linenr)
+
+ incpath = os.path.dirname(CONF['sourcefile'])
+ incfile = val
+ err = _('A file cannot include itself (loop!)')
+ if CONF['sourcefile'] == incfile:
+ Error("%s: %s"%(err,incfile))
+ inctype, inclines = get_include_contents(
+ incfile, incpath)
+ # Verb, raw and tagged are easy
+ if inctype != 't2t':
+ ret.extend(BLOCK.blockin(inctype))
+ BLOCK.holdextend(inclines)
+ ret.extend(BLOCK.blockout())
+ else:
+ # Insert include lines into body
+ #TODO include maxdepth limit
+ bodylines = bodylines[:lineref] \
+ +inclines \
+ +bodylines[lineref:]
+ #TODO fix path if include@include
+ # Remove %!include call
+ if CONF['dump-source']:
+ dump_source.pop()
+ continue
+ else:
+ Debug('Bogus Special Line',1,linenr)
+
+ #---------------------[ dump-source ]-----------------------
+
+ # We don't need to go any further
+ if CONF['dump-source']:
+ continue
+
+ #---------------------[ Comments ]--------------------------
+
+ # Just skip them (if not macro)
+ if regex['comment'].search(line) and not \
+ regex['macros'].match(line) and not \
+ regex['toc'].match(line):
+ continue
+
+ #---------------------[ Triggers ]--------------------------
+
+ # Valid line, reset blank status
+ f_lastwasblank = 0
+
+ # Any NOT quote line closes all open quotes
+ if BLOCK.isblock('quote') and not regex['quote'].search(line):
+ while BLOCK.isblock('quote'):
+ ret.extend(BLOCK.blockout())
+
+ # Any NOT table line closes an open table
+ if BLOCK.isblock('table') and not regex['table'].search(line):
+ ret.extend(BLOCK.blockout())
+
+
+ #---------------------[ Horizontal Bar ]--------------------
+
+ if regex['bar'].search(line):
+
+ # A bar closes a paragraph
+ if BLOCK.isblock('para'):
+ ret.extend(BLOCK.blockout())
+
+ # We need to close all opened quote blocks
+ # if bar isn't allowed inside or if not a quote line
+ if BLOCK.isblock('quote'):
+ if not rules['barinsidequote'] or \
+ not regex['quote'].search(line):
+ while BLOCK.isblock('quote'):
+ ret.extend(BLOCK.blockout())
+
+ # Quote + bar: continue processing for quoting
+ if rules['barinsidequote'] and \
+ regex['quote'].search(line):
+ pass
+
+ # Just bar: save tagged line and we're done
+ else:
+ line = get_tagged_bar(line)
+ if BLOCK.block()[-4:] == 'list':
+ BLOCK.holdaddsub(line)
+ elif BLOCK.block():
+ BLOCK.holdadd(line)
+ else:
+ ret.append(line)
+ Debug("BAR: %s"%line, 6)
+ continue
+
+ #---------------------[ Title ]-----------------------------
+
+ #TODO set next blank and set f_lastwasblank or f_lasttitle
+ if (regex['title'].search(line) or
+ regex['numtitle'].search(line)) and \
+ BLOCK.block()[-4:] != 'list':
+
+ # A title closes a paragraph
+ if BLOCK.isblock('para'):
+ ret.extend(BLOCK.blockout())
+
+ TITLE.add(line)
+ tagged_title = TITLE.get()
+ ret.extend(tagged_title)
+ Debug("TITLE: %s"%tagged_title, 6)
+
+ f_lastwasblank = 1
+ continue
+
+ #---------------------[ %%toc ]-----------------------
+
+ # %%toc line closes paragraph
+ if BLOCK.block() == 'para' and regex['toc'].search(line):
+ ret.extend(BLOCK.blockout())
+
+ #---------------------[ apply masks ]-----------------------
+
+ line = MASK.mask(line)
+
+ #XXX from here, only block-inside lines will pass
+
+ #---------------------[ Quote ]-----------------------------
+
+ if regex['quote'].search(line):
+
+ # Store number of leading TABS
+ quotedepth = len(regex['quote'].search(line).group(0))
+
+ # SGML doesn't support nested quotes
+ if rules['quotenotnested']: quotedepth = 1
+
+ # Don't cross depth limit
+ maxdepth = rules['quotemaxdepth']
+ if maxdepth and quotedepth > maxdepth:
+ quotedepth = maxdepth
+
+ # New quote
+ if not BLOCK.isblock('quote'):
+ ret.extend(BLOCK.blockin('quote'))
+
+ # New subquotes
+ while BLOCK.depth < quotedepth:
+ BLOCK.blockin('quote')
+
+ # Closing quotes
+ while quotedepth < BLOCK.depth:
+ ret.extend(BLOCK.blockout())
+
+ #---------------------[ Lists ]-----------------------------
+
+ # An empty item also closes the current list
+ if BLOCK.block()[-4:] == 'list':
+ m = regex['listclose'].match(line)
+ if m:
+ listindent = m.group(1)
+ listtype = m.group(2)
+ currlisttype = BLOCK.prop('type')
+ currlistindent = BLOCK.prop('indent')
+ if listindent == currlistindent and \
+ listtype == currlisttype:
+ ret.extend(BLOCK.blockout())
+ continue
+
+ if regex['list'].search(line) or \
+ regex['numlist'].search(line) or \
+ regex['deflist'].search(line):
+
+ listindent = BLOCK.prop('indent')
+ listids = string.join(LISTNAMES.keys(), '')
+ m = re.match('^( *)([%s]) '%listids, line)
+ listitemindent = m.group(1)
+ listtype = m.group(2)
+ listname = LISTNAMES[listtype]
+ results_box = BLOCK.holdadd
+
+ # Del list ID (and separate term from definition)
+ if listname == 'deflist':
+ term = parse_deflist_term(line)
+ line = regex['deflist'].sub(
+ SEPARATOR+term+SEPARATOR,line)
+ else:
+ line = regex[listname].sub(SEPARATOR,line)
+
+ # Don't cross depth limit
+ maxdepth = rules['listmaxdepth']
+ if maxdepth and BLOCK.depth == maxdepth:
+ if len(listitemindent) > len(listindent):
+ listitemindent = listindent
+
+ # List bumping (same indent, diff mark)
+ # Close the currently open list to clear the mess
+ if BLOCK.block()[-4:] == 'list' \
+ and listname != BLOCK.block() \
+ and len(listitemindent) == len(listindent):
+ ret.extend(BLOCK.blockout())
+ listindent = BLOCK.prop('indent')
+
+ # Open mother list or sublist
+ if BLOCK.block()[-4:] != 'list' or \
+ len(listitemindent) > len(listindent):
+ ret.extend(BLOCK.blockin(listname))
+ BLOCK.propset('indent',listitemindent)
+ BLOCK.propset('type',listtype)
+
+ # Closing sublists
+ while len(listitemindent) < len(BLOCK.prop('indent')):
+ ret.extend(BLOCK.blockout())
+
+ # O-oh, sublist before list ("\n\n - foo\n- foo")
+ # Fix: close sublist (as mother), open another list
+ if BLOCK.block()[-4:] != 'list':
+ ret.extend(BLOCK.blockin(listname))
+ BLOCK.propset('indent',listitemindent)
+ BLOCK.propset('type',listtype)
+
+ #---------------------[ Table ]-----------------------------
+
+ #TODO escape undesired format inside table
+ #TODO add pm6 target
+ if regex['table'].search(line):
+
+ if not BLOCK.isblock('table'): # first table line!
+ ret.extend(BLOCK.blockin('table'))
+ BLOCK.tableparser.__init__(line)
+
+ tablerow = TableMaster().parse_row(line)
+ BLOCK.tableparser.add_row(tablerow) # save config
+
+ # Maintain line to unmask and inlines
+ line = string.join(tablerow['cells'], SEPARATOR)
+
+ #---------------------[ Paragraph ]-------------------------
+
+ if not BLOCK.block() and \
+ not string.count(line, MASK.tocmask): # new para!
+ ret.extend(BLOCK.blockin('para'))
+
+
+ ############################################################
+ ############################################################
+ ############################################################
+
+
+ #---------------------[ Final Parses ]----------------------
+
+ # The target-specific special char escapes for body lines
+ line = doEscape(target,line)
+
+ line = add_inline_tags(line)
+ line = MASK.undo(line)
+
+
+ #---------------------[ Hold or Return? ]-------------------
+
+ ### Now we must choose where to put the parsed line
+ #
+ if not results_box:
+ # List item extra lines
+ if BLOCK.block()[-4:] == 'list':
+ results_box = BLOCK.holdaddsub
+ # Other blocks
+ elif BLOCK.block():
+ results_box = BLOCK.holdadd
+ # No blocks
+ else:
+ line = doFinalEscape(target, line)
+ results_box = ret.append
+
+ results_box(line)
+
+ # EOF: close any open para/verb/lists/table/quotes
+ Debug('EOF',7)
+ while BLOCK.block():
+ ret.extend(BLOCK.blockout())
+
+ # Maybe close some opened title area?
+ if rules['titleblocks']:
+ ret.extend(TITLE.close_all())
+
+ # Maybe a major tag to enclose body? (like DIV for CSS)
+ if TAGS['bodyOpen' ]: ret.insert(0, TAGS['bodyOpen'])
+ if TAGS['bodyClose']: ret.append(TAGS['bodyClose'])
+
+ if CONF['toc-only']: ret = []
+ marked_toc = TITLE.dump_marked_toc(CONF['toc-level'])
+
+ # If dump-source, all parsing is ignored
+ if CONF['dump-source']: ret = dump_source[:]
+
+ return ret, marked_toc
+
+
+
+##############################################################################
+################################### GUI ######################################
+##############################################################################
+#
+# Tk help: http://python.org/topics/tkinter/
+# Tuto: http://ibiblio.org/obp/py4fun/gui/tkPhone.html
+# /usr/lib/python*/lib-tk/Tkinter.py
+#
+# grid table : row=0, column=0, columnspan=2, rowspan=2
+# grid align : sticky='n,s,e,w' (North, South, East, West)
+# pack place : side='top,bottom,right,left'
+# pack fill : fill='x,y,both,none', expand=1
+# pack align : anchor='n,s,e,w' (North, South, East, West)
+# padding : padx=10, pady=10, ipadx=10, ipady=10 (internal)
+# checkbox : offvalue is return if the _user_ deselected the box
+# label align: justify=left,right,center
+
+def load_GUI_resources():
+ "Load all extra modules and methods used by GUI"
+ global askopenfilename, showinfo, showwarning, showerror, Tkinter
+ from tkFileDialog import askopenfilename
+ from tkMessageBox import showinfo,showwarning,showerror
+ import Tkinter
+
+class Gui:
+ "Graphical Tk Interface"
+ def __init__(self, conf={}):
+ self.root = Tkinter.Tk() # mother window, come to butthead
+ self.root.title(my_name) # window title bar text
+ self.window = self.root # variable "focus" for inclusion
+ self.row = 0 # row count for grid()
+
+ self.action_length = 150 # left column length (pixel)
+ self.frame_margin = 10 # frame margin size (pixel)
+ self.frame_border = 6 # frame border size (pixel)
+
+ # The default Gui colors, can be changed by %!guicolors
+ self.dft_gui_colors = ['#6c6','white','#cf9','#030']
+ self.gui_colors = []
+ self.bg1 = self.fg1 = self.bg2 = self.fg2 = ''
+
+ # On Tk, vars need to be set/get using setvar()/get()
+ self.infile = self.setvar('')
+ self.target = self.setvar('')
+ self.target_name = self.setvar('')
+
+ # The checks appearance order
+ self.checks = [
+ 'headers','enum-title','toc','mask-email',
+ 'toc-only','stdout']
+
+ # Creating variables for all checks
+ for check in self.checks:
+ setattr(self, 'f_'+check, self.setvar(''))
+
+ # Load RC config
+ self.conf = {}
+ if conf: self.load_config(conf)
+
+ def load_config(self, conf):
+ self.conf = conf
+ self.gui_colors = conf.get('guicolors') or self.dft_gui_colors
+ self.bg1, self.fg1, self.bg2, self.fg2 = self.gui_colors
+ self.root.config(bd=15,bg=self.bg1)
+
+ ### Config as dic for python 1.5 compat (**opts don't work :( )
+ def entry(self, **opts): return Tkinter.Entry(self.window, opts)
+ def label(self, txt='', bg=None, **opts):
+ opts.update({'text':txt,'bg':bg or self.bg1})
+ return Tkinter.Label(self.window, opts)
+ def button(self,name,cmd,**opts):
+ opts.update({'text':name,'command':cmd})
+ return Tkinter.Button(self.window, opts)
+ def check(self,name,checked=0,**opts):
+ bg, fg = self.bg2, self.fg2
+ opts.update({
+ 'text':name, 'onvalue':1, 'offvalue':0,
+ 'activeforeground':fg, 'fg':fg,
+ 'activebackground':bg, 'bg':bg,
+ 'highlightbackground':bg, 'anchor':'w'
+ })
+ chk = Tkinter.Checkbutton(self.window, opts)
+ if checked: chk.select()
+ chk.grid(columnspan=2, sticky='w', padx=0)
+ def menu(self,sel,items):
+ return apply(Tkinter.OptionMenu,(self.window,sel)+tuple(items))
+
+ # Handy auxiliary functions
+ def action(self, txt):
+ self.label(txt, fg=self.fg1, bg=self.bg1,
+ wraplength=self.action_length).grid(column=0,row=self.row)
+ def frame_open(self):
+ self.window = Tkinter.Frame(self.root,bg=self.bg2,
+ borderwidth=self.frame_border)
+ def frame_close(self):
+ self.window.grid(column=1, row=self.row, sticky='w',
+ padx=self.frame_margin)
+ self.window = self.root
+ self.label('').grid()
+ self.row = self.row + 2 # update row count
+ def target_name2key(self):
+ name = self.target_name.get()
+ target = filter(lambda x: TARGET_NAMES[x] == name, TARGETS)
+ try : key = target[0]
+ except: key = ''
+ self.target = self.setvar(key)
+ def target_key2name(self):
+ key = self.target.get()
+ name = TARGET_NAMES.get(key) or key
+ self.target_name = self.setvar(name)
+
+ def exit(self): self.root.destroy()
+ def setvar(self, val): z = Tkinter.StringVar() ; z.set(val) ; return z
+
+ def askfile(self):
+ ftypes= [(_('txt2tags files'),('*.t2t','*.txt')),
+ (_('All files'),'*')]
+ newfile = askopenfilename(filetypes=ftypes)
+ if newfile:
+ self.infile.set(newfile)
+ newconf = process_source_file(newfile)[0]
+ newconf = ConfigMaster().sanity(newconf, gui=1)
+ # Restate all checkboxes after file selection
+ #TODO how to make a refresh without killing it?
+ self.root.destroy()
+ self.__init__(newconf)
+ self.mainwindow()
+
+ def scrollwindow(self, txt='no text!', title=''):
+ # Create components
+ win = Tkinter.Toplevel() ; win.title(title)
+ frame = Tkinter.Frame(win)
+ scroll = Tkinter.Scrollbar(frame)
+ text = Tkinter.Text(frame,yscrollcommand=scroll.set)
+ button = Tkinter.Button(win)
+ # Config
+ text.insert(Tkinter.END, string.join(txt,'\n'))
+ scroll.config(command=text.yview)
+ button.config(text=_('Close'), command=win.destroy)
+ button.focus_set()
+ # Packing
+ text.pack(side='left', fill='both', expand=1)
+ scroll.pack(side='right', fill='y')
+ frame.pack(fill='both', expand=1)
+ button.pack(ipadx=30)
+
+ def runprogram(self):
+ global CMDLINE_RAW
+ # Prepare
+ self.target_name2key()
+ infile, target = self.infile.get(), self.target.get()
+ # Sanity
+ if not target:
+ showwarning(my_name,_("You must select a target type!"))
+ return
+ if not infile:
+ showwarning(my_name,
+ _("You must provide the source file location!"))
+ return
+ # Compose cmdline
+ guiflags = []
+ real_cmdline_conf = ConfigMaster(CMDLINE_RAW).parse()
+ if real_cmdline_conf.has_key('infile'):
+ del real_cmdline_conf['infile']
+ if real_cmdline_conf.has_key('target'):
+ del real_cmdline_conf['target']
+ real_cmdline = CommandLine().compose_cmdline(real_cmdline_conf)
+ default_outfile = ConfigMaster().get_outfile_name(
+ {'sourcefile':infile, 'outfile':'', 'target':target})
+ for opt in self.checks:
+ val = int(getattr(self, 'f_%s'%opt).get() or "0")
+ if opt == 'stdout': opt = 'outfile'
+ on_config = self.conf.get(opt) or 0
+ on_cmdline = real_cmdline_conf.get(opt) or 0
+ if opt == 'outfile':
+ if on_config == STDOUT: on_config = 1
+ else: on_config = 0
+ if on_cmdline == STDOUT: on_cmdline = 1
+ else: on_cmdline = 0
+ if val != on_config or (
+ val == on_config == on_cmdline and
+ real_cmdline_conf.has_key(opt)):
+ if val:
+ # Was not set, but user selected on GUI
+ Debug("user turned ON: %s"%opt)
+ if opt == 'outfile': opt = '-o-'
+ else: opt = '--%s'%opt
+ else:
+ # Was set, but user deselected on GUI
+ Debug("user turned OFF: %s"%opt)
+ if opt == 'outfile':
+ opt = "-o%s"%default_outfile
+ else: opt = '--no-%s'%opt
+ guiflags.append(opt)
+ cmdline = [my_name, '-t', target] +real_cmdline \
+ +guiflags +[infile]
+ Debug('Gui/Tk cmdline: %s'%cmdline,5)
+ # Run!
+ cmdline_raw_orig = CMDLINE_RAW
+ try:
+ # Fake the GUI cmdline as the real one, and parse file
+ CMDLINE_RAW = CommandLine().get_raw_config(cmdline[1:])
+ data = process_source_file(infile)
+ # On GUI, convert_* returns the data, not finish_him()
+ outlist, config = convert_this_files([data])
+ # On GUI and STDOUT, finish_him() returns the data
+ result = finish_him(outlist, config)
+ # Show outlist in s a nice new window
+ if result:
+ outlist, config = result
+ title = _('%s: %s converted to %s')%(
+ my_name, os.path.basename(infile),
+ string.upper(config['target']))
+ self.scrollwindow(outlist, title)
+ # Show the "file saved" message
+ else:
+ msg = "%s\n\n %s\n%s\n\n %s\n%s"%(
+ _('Conversion done!'),
+ _('FROM:'), infile,
+ _('TO:'), config['outfile'])
+ showinfo(my_name, msg)
+ except error: # common error (windowed), not quit
+ pass
+ except: # fatal error (windowed and printed)
+ errormsg = getUnknownErrorMessage()
+ print errormsg
+ showerror(_('%s FATAL ERROR!')%my_name,errormsg)
+ self.exit()
+ CMDLINE_RAW = cmdline_raw_orig
+
+ def mainwindow(self):
+ self.infile.set(self.conf.get('sourcefile') or '')
+ self.target.set(self.conf.get('target') or \
+ _('-- select one --'))
+ outfile = self.conf.get('outfile')
+ if outfile == STDOUT: # map -o-
+ self.conf['stdout'] = 1
+ if self.conf.get('headers') == None:
+ self.conf['headers'] = 1 # map default
+
+ action1 = _("Enter the source file location:")
+ action2 = _("Choose the target document type:")
+ action3 = _("Some options you may check:")
+ action4 = _("Some extra options:")
+ checks_txt = {
+ 'headers' : _("Include headers on output"),
+ 'enum-title': _("Number titles (1, 1.1, 1.1.1, etc)"),
+ 'toc' : _("Do TOC also (Table of Contents)"),
+ 'mask-email': _("Hide e-mails from SPAM robots"),
+
+ 'toc-only' : _("Just do TOC, nothing more"),
+ 'stdout' : _("Dump to screen (Don't save target file)")
+ }
+ targets_menu = map(lambda x: TARGET_NAMES[x], TARGETS)
+
+ # Header
+ self.label("%s %s"%(string.upper(my_name), my_version),
+ bg=self.bg2, fg=self.fg2).grid(columnspan=2, ipadx=10)
+ self.label(_("ONE source, MULTI targets")+'\n%s\n'%my_url,
+ bg=self.bg1, fg=self.fg1).grid(columnspan=2)
+ self.row = 2
+ # Choose input file
+ self.action(action1) ; self.frame_open()
+ e_infile = self.entry(textvariable=self.infile,width=25)
+ e_infile.grid(row=self.row, column=0, sticky='e')
+ if not self.infile.get(): e_infile.focus_set()
+ self.button(_("Browse"), self.askfile).grid(
+ row=self.row, column=1, sticky='w', padx=10)
+ # Show outfile name, style and encoding (if any)
+ txt = ''
+ if outfile:
+ txt = outfile
+ if outfile == STDOUT: txt = _('')
+ l_output = self.label(_('Output: ')+txt,
+ fg=self.fg2,bg=self.bg2)
+ l_output.grid(columnspan=2, sticky='w')
+ for setting in ['style','encoding']:
+ if self.conf.get(setting):
+ name = string.capitalize(setting)
+ val = self.conf[setting]
+ self.label('%s: %s'%(name, val),
+ fg=self.fg2, bg=self.bg2).grid(
+ columnspan=2, sticky='w')
+ # Choose target
+ self.frame_close() ; self.action(action2)
+ self.frame_open()
+ self.target_key2name()
+ self.menu(self.target_name, targets_menu).grid(
+ columnspan=2, sticky='w')
+ # Options checkboxes label
+ self.frame_close() ; self.action(action3)
+ self.frame_open()
+ # Compose options check boxes, example:
+ # self.check(checks_txt['toc'],1,variable=self.f_toc)
+ for check in self.checks:
+ # Extra options label
+ if check == 'toc-only':
+ self.frame_close() ; self.action(action4)
+ self.frame_open()
+ txt = checks_txt[check]
+ var = getattr(self, 'f_'+check)
+ checked = self.conf.get(check)
+ self.check(txt,checked,variable=var)
+ self.frame_close()
+ # Spacer and buttons
+ self.label('').grid() ; self.row = self.row + 1
+ b_quit = self.button(_("Quit"), self.exit)
+ b_quit.grid(row=self.row, column=0, sticky='w', padx=30)
+ b_conv = self.button(_("Convert!"), self.runprogram)
+ b_conv.grid(row=self.row, column=1, sticky='e', padx=30)
+ if self.target.get() and self.infile.get():
+ b_conv.focus_set()
+
+ # As documentation told me
+ if sys.platform[:3] == 'win':
+ self.root.iconify()
+ self.root.update()
+ self.root.deiconify()
+
+ self.root.mainloop()
+
+
+##############################################################################
+##############################################################################
+
+def exec_command_line(user_cmdline=[]):
+ global CMDLINE_RAW, RC_RAW, DEBUG, VERBOSE, QUIET, GUI, Error
+
+ # Extract command line data
+ cmdline_data = user_cmdline or sys.argv[1:]
+ CMDLINE_RAW = CommandLine().get_raw_config(cmdline_data, relative=1)
+ cmdline_parsed = ConfigMaster(CMDLINE_RAW).parse()
+ DEBUG = cmdline_parsed.get('debug' ) or 0
+ VERBOSE = cmdline_parsed.get('verbose') or 0
+ QUIET = cmdline_parsed.get('quiet' ) or 0
+ GUI = cmdline_parsed.get('gui' ) or 0
+ infiles = cmdline_parsed.get('infile' ) or []
+
+ Message(_("Txt2tags %s processing begins")%my_version,1)
+
+ # The easy ones
+ if cmdline_parsed.get('help' ): Quit(USAGE)
+ if cmdline_parsed.get('version'): Quit(VERSIONSTR)
+
+ # Multifile haters
+ if len(infiles) > 1:
+ errmsg=_("Option --%s can't be used with multiple input files")
+ for option in NO_MULTI_INPUT:
+ if cmdline_parsed.get(option):
+ Error(errmsg%option)
+
+ Debug("system platform: %s"%sys.platform)
+ Debug("python version: %s"%(string.split(sys.version,'(')[0]))
+ Debug("line break char: %s"%repr(LB))
+ Debug("command line: %s"%sys.argv)
+ Debug("command line raw config: %s"%CMDLINE_RAW,1)
+
+ # Extract RC file config
+ if cmdline_parsed.get('rc') == 0:
+ Message(_("Ignoring user configuration file"),1)
+ else:
+ rc_file = get_rc_path()
+ if os.path.isfile(rc_file):
+ Message(_("Loading user configuration file"),1)
+ RC_RAW = ConfigLines(file=rc_file).get_raw_config()
+
+ Debug("rc file: %s"%rc_file)
+ Debug("rc file raw config: %s"%RC_RAW,1)
+
+ # Get all infiles config (if any)
+ infiles_config = get_infiles_config(infiles)
+
+ # Is GUI available?
+ # Try to load and start GUI interface for --gui
+ # If program was called with no arguments, try GUI also
+ if GUI or not infiles:
+ try:
+ load_GUI_resources()
+ Debug("GUI resources OK (Tk module is installed)")
+ winbox = Gui()
+ Debug("GUI display OK")
+ GUI = 1
+ except:
+ Debug("GUI Error: no Tk module or no DISPLAY")
+ GUI = 0
+
+ # User forced --gui, but it's not available
+ if cmdline_parsed.get('gui') and not GUI:
+ print getTraceback(); print
+ Error("Sorry, I can't run my Graphical Interface - GUI\n"
+ "- Check if Python Tcl/Tk module is installed (Tkinter)\n"
+ "- Make sure you are in a graphical environment (like X)")
+
+ # Okay, we will use GUI
+ if GUI:
+ Message(_("We are on GUI interface"),1)
+
+ # Redefine Error function to raise exception instead sys.exit()
+ def Error(msg):
+ showerror(_('txt2tags ERROR!'), msg)
+ raise error
+
+ # If no input file, get RC+cmdline config, else full config
+ if not infiles:
+ gui_conf = ConfigMaster(RC_RAW+CMDLINE_RAW).parse()
+ else:
+ try : gui_conf = infiles_config[0][0]
+ except: gui_conf = {}
+
+ # Sanity is needed to set outfile and other things
+ gui_conf = ConfigMaster().sanity(gui_conf, gui=1)
+ Debug("GUI config: %s"%gui_conf,5)
+
+ # Insert config and populate the nice window!
+ winbox.load_config(gui_conf)
+ winbox.mainwindow()
+
+ # Console mode rocks forever!
+ else:
+ Message(_("We are on Command Line interface"),1)
+
+ # Called with no arguments, show error
+ if not infiles: Error(_('Missing input file (try --help)'))
+
+ convert_this_files(infiles_config)
+
+ Message(_("Txt2tags finished sucessfuly"),1)
+
+if __name__ == '__main__':
+ try:
+ exec_command_line()
+ except error, msg:
+ sys.stderr.write("%s\n"%msg)
+ sys.stderr.flush()
+ sys.exit(1)
+ except SystemExit:
+ pass
+ except:
+ sys.stderr.write(getUnknownErrorMessage())
+ sys.stderr.flush()
+ sys.exit(1)
+ Quit()
+
+
+# vim: ts=8
}
Context:
[bump up version
tero.hasu@hut.fi**20070907151520]
[added ctypes.py
tero.hasu@hut.fi**20070907151129]
[added doxyfile
tero.hasu@hut.fi**20070907151019]
[something working now
tero.hasu@hut.fi**20070906140254]
[compiling now
tero.hasu@hut.fi**20070905143749]
[added stgdict.c
tero.hasu@hut.fi**20070905121530]
[now compiles, but does not link
tero.hasu@hut.fi**20070905120136]
[starting work on ctypes port
tero.hasu@hut.fi**20070904152611]
Patch bundle hash:
dd2feb101a49860d9d9f153f80182ac36edd527f