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 = []
open_ = TAGS['blockVerbOpen']
close = TAGS['blockVerbClose']
# Blank line before?
if self._should_add_blank_line('before', 'verb'): result.append('')
# Open tag
if open_: result.append(open_)
# Get contents
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)
# Close tag
if close: result.append(close)
# Blank line after?
if self._should_add_blank_line('after', 'verb'): result.append('')
return result
def numtitle(self): return self.title('numtitle')
def title(self, name='title'):
result = []
# Blank line before?
if self._should_add_blank_line('before', name): result.append('')
# Get contents
result.extend(TITLE.get())
# Blank line after?
if self._should_add_blank_line('after', name): result.append('')
return result
def table(self):
result = []
# Blank line before?
if self._should_add_blank_line('before', 'table'): result.append('')
# Rewrite all table cells by the unmasked and escaped data
lines = self._get_escaped_hold()
for i in xrange(len(lines)):
cells = lines[i].split(SEPARATOR)
self.tableparser.rows[i]['cells'] = cells
result.extend(self.tableparser.dump())
# Blank line after?
if self._should_add_blank_line('after', 'table'): result.append('')
return result
def quote(self):
result = []
open_ = TAGS['blockQuoteOpen'] # block based
close = TAGS['blockQuoteClose']
qline = TAGS['blockQuoteLine'] # line based
indent = tagindent = '\t'*self.depth
# Apply rules
if rules['tagnotindentable']: tagindent = ''
if not rules['keepquoteindent']: indent = ''
# Blank line before?
if self._should_add_blank_line('before', 'quote'): result.append('')
# Open tag
if open_: result.append(tagindent+open_)
# Get contents
for item in self.hold():
if type(item) == type([]):
result.extend(item) # subquotes
else:
item = regex['quote'].sub('', item) # del TABs
item = self._last_escapes(item)
item = qline*self.depth + item
result.append(indent+item) # quote line
# Close tag
if close: result.append(tagindent+close)
# Blank line after?
if self._should_add_blank_line('after', 'quote'): result.append('')
return result
def bar(self):
result = []
bar_tag = ''
# Blank line before?
if self._should_add_blank_line('before', 'bar'): result.append('')
# Get the original bar chars
bar_chars = self.hold()[0].strip()
# Set bar type
if bar_chars.startswith('='): bar_tag = TAGS['bar2']
else : bar_tag = TAGS['bar1']
# To avoid comment tag confusion like (sgml)
if TAGS['comment'].count('--'):
bar_chars = bar_chars.replace('--', '__')
# Get the bar tag (may contain \a)
result.append(regex['x'].sub(bar_chars, bar_tag))
# Blank line after?
if self._should_add_blank_line('after', 'bar'): result.append('')
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
listline = TAGS.get(name+'ItemLine')
itemcount = 0
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 = ''
# Apply rules
if rules['tagnotindentable']: tagindent = ''
if not rules['keeplistindent']: indent = tagindent = ''
# ItemLine: number of leading chars identifies list depth
if listline:
itemopen = listline*self.depth + itemopen
# Adds trailing space on opening tags
if (name == 'list' and rules['spacedlistitemopen']) or \
(name == 'numlist' and rules['spacednumlistitemopen']):
itemopen = itemopen + ' '
# Remove two-blanks from list ending mark, to avoid
items[-1] = self._remove_twoblanks(items[-1])
# Blank line before?
if self._should_add_blank_line('before', name): result.append('')
# Tag each list item (multiline items), store in listbody
itemopenorig = itemopen
listbody = []
widelist = 0
for item in items:
# Add "manual" item count for noautonum targets
itemcount += 1
if name == 'numlist' and not rules['autonumberlist']:
n = str(itemcount)
itemopen = regex['x'].sub(n, itemopenorig)
del n
# Tag it
item[0] = self._last_escapes(item[0])
if name == 'deflist':
z,term,rest = item[0].split(SEPARATOR, 2)
item[0] = rest
if not item[0]: del item[0] # to avoid
listbody.append(tagindent+itemopen+term+itemsep)
else:
fullitem = tagindent+itemopen
listbody.append(item[0].replace(SEPARATOR, fullitem))
del item[0]
# Process next lines for this item (if any)
for line in item:
if type(line) == type([]): # sublist inside
listbody.extend(line)
else:
line = self._last_escapes(line)
# Blank lines turns to
if not line and rules['parainsidelist']:
line = indent + TAGS['paragraphOpen'] + TAGS['paragraphClose']
line = line.rstrip()
widelist = 1
# Some targets don't like identation here (wiki)
if not rules['keeplistindent'] or (name == 'deflist' and rules['deflisttextstrip']):
line = line.lstrip()
# Maybe we have a line prefix to add? (wiki)
if name == 'deflist' and TAGS['deflistItem2LinePrefix']:
line = TAGS['deflistItem2LinePrefix'] + line
listbody.append(line)
# Close item (if needed)
if itemclose: listbody.append(tagindent+itemclose)
if not widelist and rules['compactlist']:
listopen = TAGS.get(name+'OpenCompact')
listclose = TAGS.get(name+'CloseCompact')
else:
listopen = TAGS.get(name+'Open')
listclose = TAGS.get(name+'Close')
# Open list (not nestable lists are only opened at mother)
if listopen and not \
(rules['listnotnested'] and BLOCK.depth != 1):
result.append(tagindent+listopen)
result.extend(listbody)
# Close list (not nestable lists are only closed at mother)
if listclose and not \
(rules['listnotnested'] and self.depth != 1):
result.append(tagindent+listclose)
# Blank line after?
if self._should_add_blank_line('after', name): 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 += 1
if c == '%': # hot char!
if i == len(fmt): # % at the end
ret = ret + c
break
c = fmt[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 = m.group('name').lower()
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 listTargets():
"""list all available targets"""
targets = TARGETS
targets.sort()
for target in targets:
print "%s\t%s" % (target, TARGET_NAMES.get(target))
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 = sep.join(val)
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 line.count(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):
"Returns the tagged TOC, as a single tag or a tagged list"
ret = []
# Convert the TOC list (t2t-marked) to the target's list format
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
fakeconf['art-no-title'] = 1 # needed for --toc and --slides together, avoids slide title before TOC
ret,foo = convert(toc, fakeconf)
set_global_config(config) # restore config
# Our TOC list is not needed, the target already knows how to do a TOC
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
# Art: An automatic "Table of Contents" header is added to the TOC slide
if config['target'] == 'art' and config['slides']:
n = (config['height'] - 1) - (len(toc) + 6) % (config['height'] - 1)
toc = aa_slide(_("Table of Contents"), config['width']) + toc + ([''] * n)
toc.append(aa_line(AA['bar2'], config['width']))
return 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('-' * DFT_TEXT_WIDTH, TAGS['bar1'])
tocbar = [para, bar, para]
if config['target'] == 'art' and config['headers']:
# exception: header already printed a bar
ret = [para] + ret + tocbar
else:
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: Unknown target '%s'"%target)
if target in ('html','xhtml') and config.get('css-sugar'):
template = HEADER_TEMPLATE[target+'css'].split('\n')
else:
template = HEADER_TEMPLATE[target].split('\n')
head_data = {'STYLE':[], 'ENCODING':''}
for key in head_data.keys():
val = config.get(key.lower())
# 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)
# ASCII Art does not use a header template, aa_header() formats the header
if target == 'art':
n_h = len([v for v in head_data if v.startswith("HEADER") and head_data[v]])
if not n_h :
return []
if config['slides']:
x = config['height'] - 3 - (n_h * 3)
n = x / (n_h + 1)
end = x % (n_h + 1)
template = aa_header(head_data, config['width'], n, end)
else:
template = [''] + aa_header(head_data, config['width'], 2, 0)
# Header done, let's get out
return template
# 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 line.count('%%(%s)s'%key):
sline = line.replace('%%(%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 template[i].count(style_mark):
while styles:
template.insert(i+1, template[i].replace(style_mark, styles.pop()))
del template[i]
break
# Populate template with data (dict expansion)
template = '\n'.join(template) % 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'],
'\n'.join(contents),
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 template.split('\n')
def doCommentLine(txt):
# The -- string ends a (h|sg|xht)ml comment :(
txt = maskEscapeChar(txt)
if TAGS['comment'].count('--') and txt.count('--'):
txt = re.sub('-(?=-)', r'-\\', txt)
if TAGS['comment']:
return regex['x'].sub(txt, TAGS['comment'])
return ''
def doFooter(config):
ret = []
# No footer. The --no-headers option hides header AND footer
if not config['headers']:
return []
# Only add blank line before footer if last block doesn't added by itself
if not rules.get('blanksaround'+BLOCK.last):
ret.append('')
# Add txt2tags info at footer, if target supports comments
if TAGS['comment']:
# Not using TARGET_NAMES because it's i18n'ed.
# It's best to always present this info in english.
target = config['target']
if config['target'] == 'tex':
target = 'LaTeX2e'
t2t_version = '%s code generated by %s %s (%s)' % (target, my_name, my_version, my_url)
cmdline = 'cmdline: %s %s' % (my_name, ' '.join(config['realcmdline']))
ret.append(doCommentLine(t2t_version))
ret.append(doCommentLine(cmdline))
# Maybe we have a specific tag to close the document?
if TAGS['EOD']:
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','dbk'):
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 = txt.replace(ESCCHAR, ESCCHAR+'e') # \e
elif target == 'lout':
# TIP: / moved to FinalEscape to avoid //italic//
# TIP: these are also converted by lout: ... --- --
txt = txt.replace(ESCCHAR, tmpmask) # \
txt = txt.replace('"', '"%s""'%ESCCHAR) # "\""
txt = re.sub('([|&{}@#^~])', '"\\1"', txt) # "@"
txt = txt.replace(tmpmask, '"%s"'%(ESCCHAR*2)) # "\\"
elif target == 'tex':
# Mark literal \ to be changed to $\backslash$ later
txt = txt.replace(ESCCHAR, tmpmask)
txt = re.sub('([#$&%{}])', ESCCHAR+r'\1' , txt) # \%
txt = re.sub('([~^])' , ESCCHAR+r'\1{}', txt) # \~{}
txt = re.sub('([<|>])' , r'$\1$', txt) # $>$
txt = txt.replace(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 = txt.replace(ESCCHAR+'<', r'<\#92><')
elif target == 'man' : txt = txt.replace('-', r'\-')
elif target == 'sgml': txt = txt.replace('[', '[')
elif target == 'lout': txt = txt.replace('/', '"/"')
elif target == 'tex' :
txt = txt.replace('_', r'\_')
txt = txt.replace('vvvvTexUndervvvv', '_') # shame!
return txt
def EscapeCharHandler(action, data):
"Mask/Unmask the Escape Char on the given string"
if not data.strip(): return data
if action not in ('mask','unmask'):
Error("EscapeCharHandler: Invalid action '%s'"%action)
if action == 'mask': return data.replace('\\', ESCCHAR)
else: return data.replace(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 = line.replace('\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(line.split('\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, underline or strike"
# Exception: Doesn't parse an horizontal bar as strike
if name == 'strike' and regex['bar'].search(line): return line
name = 'font%s' % name.capitalize()
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 = url.replace('@', ' (a) ')
url = url.replace('.', ' ')
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['labelbeforelink'] or not rules['linkable']:
urlorder = [label, url] # label before link
else:
urlorder = [url, label] # link before label
# 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_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
'utf-8' : 'utf8',
'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][enc.lower()]
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()
# The user just want to know a single config value (hidden feature)
#TODO pick a better name than --show-config-value
elif full_parsed.get('show-config-value'):
config_value = full_parsed.get(full_parsed['show-config-value'])
if config_value:
if type(config_value) == type([]):
print '\n'.join(config_value)
else:
print config_value
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
# Close the last slide
if myconf['slides'] and not myconf['toc-only'] and myconf['target'] == 'art':
n = (myconf['height'] - 1) - (AA_COUNT % (myconf['height'] - 1) + 1)
target_body = target_body + ([''] * n) + [aa_line(AA['bar2'], myconf['width'])]
# Compose the target file Footer
Message(_("Composing target Footer"),1)
target_foot = doFooter(myconf)
# 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 = []
# 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']
# If target supports image alignment, here we go
if rules['imgalignable']:
align = get_image_align(line) # right
align_name = align.capitalize() # Right
# The align is a full tag, or part of the image tag (~A~)
if TAGS['imgAlign'+align_name]:
tag = TAGS['imgAlign'+align_name]
else:
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 = txt.replace('_', 'vvvvTexUndervvvv')
# Ugly hack to avoid infinite loop when target's image tag contains []
tag = tag.replace('[', 'vvvvEscapeSquareBracketvvvv')
line = regex['img'].sub(tag,line,1)
line = regex['x'].sub(txt,line,1)
return line.replace('vvvvEscapeSquareBracketvvvv','[')
def add_inline_tags(line):
# Beautifiers
for beauti in ('bold', 'italic', 'underline', 'strike'):
if regex['font%s'%beauti.capitalize()].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)
#TODO fix images relative path if file has a path, ie.: chapter1/index.t2t (wait until tree parsing)
#TODO for the images path fix, also respect outfile path, if different from infile (wait until tree parsing)
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
rules = getRules(CONF)
TAGS = getTags(CONF)
regex = getRegexes()
TARGET = config['target'] # save for buggy functions that need global
def convert(bodylines, config, firstlinenr=1):
global BLOCK, TITLE
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 += 1
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
#-------------------------[ Tagged Text ]----------------------
# We're already on a tagged block
if BLOCK.block() == 'tagged':
# Closing tagged
if regex['blockTaggedClose'].search(line):
ret.extend(BLOCK.blockout())
continue
# Normal tagged-inside line
BLOCK.holdadd(line)
continue
# Detecting tagged block init
if regex['blockTaggedOpen'].search(line) \
and BLOCK.block() not in BLOCK.exclusive:
ret.extend(BLOCK.blockin('tagged'))
continue
# One line tagged text
if regex['1lineTagged'].search(line) \
and BLOCK.block() not in BLOCK.exclusive:
ret.extend(BLOCK.blockin('tagged'))
line = regex['1lineTagged'].sub('',line)
BLOCK.holdadd(line)
ret.extend(BLOCK.blockout())
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().endswith('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().endswith('list'):
BLOCK.holdaddsub('')
f_lastwasblank = 1
continue
#---------------------[ special ]---------------------------
if regex['special'].search(line):
targ, key, val = ConfigLines().parse_line(line, None, target)
if key:
Debug("Found config '%s', value '%s'" % (key, val), 1, linenr)
else:
Debug('Bogus Special Line', 1, linenr)
# %!include command
if key == 'include':
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()
# This line is done, go to next
continue
# %!csv command
elif key == 'csv':
if not csv:
Error("Python module 'csv' not found, but needed for %!csv")
table = []
filename = val
reader = csv.reader(Readfile(filename))
# Convert each CSV line to a txt2tags' table line
# foo,bar,baz -> | foo | bar | baz |
try:
for row in reader:
table.append('| %s |' % ' | '.join(row))
except csv.Error, e:
Error('CSV: file %s: %s' % (filename, e))
# Parse and convert the new table
# Note: cell contents is raw, no t2t marks are parsed
if rules['tableable']:
ret.extend(BLOCK.blockin('table'))
if table:
BLOCK.tableparser.__init__(table[0])
for row in table:
tablerow = TableMaster().parse_row(row)
BLOCK.tableparser.add_row(tablerow)
# Very ugly, but necessary for escapes
line = SEPARATOR.join(tablerow['cells'])
BLOCK.holdadd(doEscape(target, line))
ret.extend(BLOCK.blockout())
# Tables are mapped to verb when target is not table-aware
else:
if target == 'art' and table:
table = aa_table(table)
ret.extend(BLOCK.blockin('verb'))
BLOCK.propset('mapped', 'table')
for row in table:
BLOCK.holdadd(row)
ret.extend(BLOCK.blockout())
# This line is done, go to next
continue
#---------------------[ 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):
# Bars inside quotes are handled on the Quote processing
# Otherwise we parse the bars right here
#
if not (BLOCK.isblock('quote') or regex['quote'].search(line)) \
or (BLOCK.isblock('quote') and not rules['barinsidequote']):
# Close all the opened blocks
ret.extend(BLOCK.blockin('bar'))
# Extract the bar chars (- or =)
m = regex['bar'].search(line)
bar_chars = m.group(2)
# Process and dump the tagged bar
BLOCK.holdadd(bar_chars)
ret.extend(BLOCK.blockout())
Debug("BAR: %s"%line, 6)
# We're done, nothing more to process
continue
#---------------------[ Title ]-----------------------------
if (regex['title'].search(line) or regex['numtitle'].search(line)) \
and not BLOCK.block().endswith('list'):
if regex['title'].search(line):
name = 'title'
else:
name = 'numtitle'
# Close all the opened blocks
ret.extend(BLOCK.blockin(name))
# Process title
TITLE.add(line)
ret.extend(BLOCK.blockout())
# We're done, nothing more to process
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())
# Bar inside quote
if regex['bar'].search(line) and rules['barinsidequote']:
tempBlock = BlockMaster()
tagged_bar = []
tagged_bar.extend(tempBlock.blockin('bar'))
tempBlock.holdadd(line)
tagged_bar.extend(tempBlock.blockout())
BLOCK.holdextend(tagged_bar)
continue
#---------------------[ Lists ]-----------------------------
# An empty item also closes the current list
if BLOCK.block().endswith('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 = ''.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().endswith('list') \
and listname != BLOCK.block() \
and len(listitemindent) == len(listindent):
ret.extend(BLOCK.blockout())
listindent = BLOCK.prop('indent')
# Open mother list or sublist
if not BLOCK.block().endswith('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 not BLOCK.block().endswith('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
# XXX Bug: | **bo | ld** | turns **bo\x01ld** and gets converted :(
# TODO isolate unmask+inlines parsing to use here
line = SEPARATOR.join(tablerow['cells'])
#---------------------[ Paragraph ]-------------------------
if not BLOCK.block() and \
not line.count(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().endswith('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,
'activebackground':bg,
'highlightbackground':bg,
'fg':fg,
'bg':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 += 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, '\n'.join(txt))
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),
config['target'].upper())
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"%(my_name.upper(), 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 = setting.capitalize()
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 += 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.startswith('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)
if cmdline_parsed.get('targets'):
listTargets()
Quit()
# 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"%(sys.version.split('(')[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 GUI:
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
# TODO#1: this checking should be only in ConfigMaster.sanity()
if not infiles:
Error(_('Missing input file (try --help)') + '\n\n' +
_('Please inform an input file (.t2t) at the end of the command.') + '\n' +
_('Example:') + ' %s -t html %s' % (my_name, _('file.t2t')))
convert_this_files(infiles_config)
Message(_("Txt2tags finished successfully"),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()
# The End.