mirror of
https://github.com/olivierkes/manuskript.git
synced 2024-07-03 05:31:55 +12:00
5988 lines
200 KiB
Plaintext
5988 lines
200 KiB
Plaintext
|
#!/usr/bin/env python
|
||
|
# txt2tags - generic text conversion tool ---> READY TO PYTHON 3!
|
||
|
# http://txt2tags.org
|
||
|
#
|
||
|
# Copyright 2001-2010 Aurelio Jargas
|
||
|
#
|
||
|
# License: http://www.gnu.org/licenses/gpl-2.0.txt
|
||
|
# Subversion: http://svn.txt2tags.org
|
||
|
# Bug tracker: http://bugs.txt2tags.org
|
||
|
#
|
||
|
########################################################################
|
||
|
#
|
||
|
# BORING CODE EXPLANATION AHEAD
|
||
|
#
|
||
|
# Just read it if you wish to understand how the txt2tags code works.
|
||
|
#
|
||
|
########################################################################
|
||
|
#
|
||
|
# The code that [1] parses the marked text is separated from the
|
||
|
# code that [2] insert the target tags.
|
||
|
#
|
||
|
# [1] made by: def convert()
|
||
|
# [2] made by: class BlockMaster
|
||
|
#
|
||
|
# The structures of the marked text are identified and its contents are
|
||
|
# extracted into a data holder (Python lists and dictionaries).
|
||
|
#
|
||
|
# When parsing the source file, the blocks (para, lists, quote, table)
|
||
|
# are opened with BlockMaster, right when found. Then its contents,
|
||
|
# which spans on several lines, are feeded into a special holder on the
|
||
|
# BlockMaster instance. Just when the block is closed, the target tags
|
||
|
# are inserted for the full block as a whole, in one pass. This way, we
|
||
|
# have a better control on blocks. Much better than the previous line by
|
||
|
# line approach.
|
||
|
#
|
||
|
# In other words, whenever inside a block, the parser *holds* the tag
|
||
|
# insertion process, waiting until the full block is read. That was
|
||
|
# needed primary to close paragraphs for the XHTML target, but
|
||
|
# proved to be a very good adding, improving many other processing.
|
||
|
#
|
||
|
# -------------------------------------------------------------------
|
||
|
#
|
||
|
# These important classes are all documented:
|
||
|
# CommandLine, SourceDocument, ConfigMaster, ConfigLines.
|
||
|
#
|
||
|
# There is a RAW Config format and all kind of configuration is first
|
||
|
# converted to this format. Then a generic method parses it.
|
||
|
#
|
||
|
# These functions get information about the input file(s) and take
|
||
|
# care of the init processing:
|
||
|
# get_infiles_config(), process_source_file() and convert_this_files()
|
||
|
#
|
||
|
########################################################################
|
||
|
|
||
|
#XXX Python coding warning
|
||
|
# Avoid common mistakes:
|
||
|
# - do NOT use newlist=list instead newlist=list[:]
|
||
|
# - do NOT use newdic=dic instead newdic=dic.copy()
|
||
|
# - do NOT use dic[key] instead dic.get(key)
|
||
|
# - do NOT use del dic[key] without has_key() before
|
||
|
|
||
|
#XXX Smart Image Align don't work if the image is a link
|
||
|
# Can't fix that because the image is expanded together with the
|
||
|
# link, at the linkbank filling moment. Only the image is passed
|
||
|
# to parse_images(), not the full line, so it is always 'middle'.
|
||
|
|
||
|
#XXX Paragraph separation not valid inside Quote
|
||
|
# Quote will not have <p></p> inside, instead will close and open
|
||
|
# again the <blockquote>. This really sux in CSS, when defining a
|
||
|
# different background color. Still don't know how to fix it.
|
||
|
|
||
|
#XXX TODO (maybe)
|
||
|
# New mark or macro which expands to an anchor full title.
|
||
|
# It is necessary to parse the full document in this order:
|
||
|
# DONE 1st scan: HEAD: get all settings, including %!includeconf
|
||
|
# DONE 2nd scan: BODY: expand includes & apply %!preproc
|
||
|
# 3rd scan: BODY: read titles and compose TOC info
|
||
|
# 4th scan: BODY: full parsing, expanding [#anchor] 1st
|
||
|
# Steps 2 and 3 can be made together, with no tag adding.
|
||
|
# Two complete body scans will be *slow*, don't know if it worths.
|
||
|
# One solution may be add the titles as postproc rules
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
# User config (1=ON, 0=OFF)
|
||
|
|
||
|
USE_I18N = 1 # use gettext for i18ned messages? (default is 1)
|
||
|
COLOR_DEBUG = 1 # show debug messages in colors? (default is 1)
|
||
|
BG_LIGHT = 0 # your terminal background color is light (default is 0)
|
||
|
HTML_LOWER = 0 # use lowercased HTML tags instead upper? (default is 0)
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
# These are all the core Python modules used by txt2tags (KISS!)
|
||
|
import re, os, sys, time, getopt
|
||
|
|
||
|
# The CSV module is new in Python version 2.3
|
||
|
try:
|
||
|
import csv
|
||
|
except ImportError:
|
||
|
csv = None
|
||
|
|
||
|
# Program information
|
||
|
my_url = 'http://txt2tags.org'
|
||
|
my_name = 'txt2tags'
|
||
|
my_email = 'verde@aurelio.net'
|
||
|
my_version = '2.6'
|
||
|
|
||
|
# i18n - just use if available
|
||
|
if USE_I18N:
|
||
|
try:
|
||
|
import gettext
|
||
|
# If your locale dir is different, change it here
|
||
|
cat = gettext.Catalog('txt2tags',localedir='/usr/share/locale/')
|
||
|
_ = cat.gettext
|
||
|
except:
|
||
|
_ = lambda x:x
|
||
|
else:
|
||
|
_ = lambda x:x
|
||
|
|
||
|
# FLAGS : the conversion related flags , may be used in %!options
|
||
|
# OPTIONS : the conversion related options, may be used in %!options
|
||
|
# ACTIONS : the other behavior modifiers, valid on command line only
|
||
|
# MACROS : the valid macros with their default values for formatting
|
||
|
# SETTINGS: global miscellaneous settings, valid on RC file only
|
||
|
# NO_TARGET: actions that don't require a target specification
|
||
|
# NO_MULTI_INPUT: actions that don't accept more than one input file
|
||
|
# CONFIG_KEYWORDS: the valid %!key:val keywords
|
||
|
#
|
||
|
# FLAGS and OPTIONS are configs that affect the converted document.
|
||
|
# They usually have also a --no-<option> to turn them OFF.
|
||
|
#
|
||
|
# ACTIONS are needed because when doing multiple input files, strange
|
||
|
# behavior would be found, as use command line interface for the
|
||
|
# first file and gui for the second. There is no --no-<action>.
|
||
|
# --version and --help inside %!options are also odd
|
||
|
#
|
||
|
TARGETS = sorted( 'html xhtml sgml dbk tex lout man mgp wiki gwiki doku pmw moin pm6 txt art adoc creole'.split() )
|
||
|
|
||
|
FLAGS = {'headers' :1 , 'enum-title' :0 , 'mask-email' :0 ,
|
||
|
'toc-only' :0 , 'toc' :0 , 'rc' :1 ,
|
||
|
'css-sugar' :0 , 'css-suggar' :0 , 'css-inside' :0 ,
|
||
|
'quiet' :0 , 'slides' :0 }
|
||
|
OPTIONS = {'target' :'', 'toc-level' :3 , 'style' :'',
|
||
|
'infile' :'', 'outfile' :'', 'encoding' :'',
|
||
|
'config-file':'', 'split' :0 , 'lang' :'',
|
||
|
'width' :0 , 'height' :0 , 'art-chars' :'',
|
||
|
'show-config-value':''}
|
||
|
ACTIONS = {'help' :0 , 'version' :0 , 'gui' :0 ,
|
||
|
'verbose' :0 , 'debug' :0 , 'dump-config':0 ,
|
||
|
'dump-source':0 , 'targets' :0}
|
||
|
MACROS = {'date' : '%Y%m%d', 'infile': '%f',
|
||
|
'mtime': '%Y%m%d', 'outfile': '%f'}
|
||
|
SETTINGS = {} # for future use
|
||
|
NO_TARGET = ['help', 'version', 'gui', 'toc-only', 'dump-config', 'dump-source', 'targets']
|
||
|
NO_MULTI_INPUT = ['gui','dump-config','dump-source']
|
||
|
CONFIG_KEYWORDS = [
|
||
|
'target', 'encoding', 'style', 'options', 'preproc','postproc',
|
||
|
'guicolors']
|
||
|
|
||
|
TARGET_NAMES = {
|
||
|
'html' : _('HTML page'),
|
||
|
'xhtml' : _('XHTML page'),
|
||
|
'sgml' : _('SGML document'),
|
||
|
'dbk' : _('DocBook document'),
|
||
|
'tex' : _('LaTeX document'),
|
||
|
'lout' : _('Lout document'),
|
||
|
'man' : _('UNIX Manual page'),
|
||
|
'mgp' : _('MagicPoint presentation'),
|
||
|
'wiki' : _('Wikipedia page'),
|
||
|
'gwiki' : _('Google Wiki page'),
|
||
|
'doku' : _('DokuWiki page'),
|
||
|
'pmw' : _('PmWiki page'),
|
||
|
'moin' : _('MoinMoin page'),
|
||
|
'pm6' : _('PageMaker document'),
|
||
|
'txt' : _('Plain Text'),
|
||
|
'art' : _('ASCII Art text'),
|
||
|
'adoc' : _('AsciiDoc document'),
|
||
|
'creole' : _('Creole 1.0 document')
|
||
|
}
|
||
|
|
||
|
DEBUG = 0 # do not edit here, please use --debug
|
||
|
VERBOSE = 0 # do not edit here, please use -v, -vv or -vvv
|
||
|
QUIET = 0 # do not edit here, please use --quiet
|
||
|
GUI = 0 # do not edit here, please use --gui
|
||
|
AUTOTOC = 1 # do not edit here, please use --no-toc or %%toc
|
||
|
|
||
|
DFT_TEXT_WIDTH = 72 # do not edit here, please use --width
|
||
|
DFT_SLIDE_WIDTH = 80 # do not edit here, please use --width
|
||
|
DFT_SLIDE_HEIGHT = 25 # do not edit here, please use --height
|
||
|
|
||
|
# ASCII Art config
|
||
|
AA_KEYS = 'corner border side bar1 bar2 level2 level3 level4 level5'.split()
|
||
|
AA_VALUES = '+-|-==-^"' # do not edit here, please use --art-chars
|
||
|
AA = dict(zip(AA_KEYS, AA_VALUES))
|
||
|
AA_COUNT = 0
|
||
|
AA_TITLE = ''
|
||
|
|
||
|
RC_RAW = []
|
||
|
CMDLINE_RAW = []
|
||
|
CONF = {}
|
||
|
BLOCK = None
|
||
|
TITLE = None
|
||
|
regex = {}
|
||
|
TAGS = {}
|
||
|
rules = {}
|
||
|
|
||
|
# Gui globals
|
||
|
askopenfilename = None
|
||
|
showinfo = None
|
||
|
showwarning = None
|
||
|
showerror = None
|
||
|
|
||
|
lang = 'english'
|
||
|
TARGET = ''
|
||
|
|
||
|
STDIN = STDOUT = '-'
|
||
|
MODULEIN = MODULEOUT = '-module-'
|
||
|
ESCCHAR = '\x00'
|
||
|
SEPARATOR = '\x01'
|
||
|
LISTNAMES = {'-':'list', '+':'numlist', ':':'deflist'}
|
||
|
LINEBREAK = {'default':'\n', 'win':'\r\n', 'mac':'\r'}
|
||
|
|
||
|
# Platform specific settings
|
||
|
LB = LINEBREAK.get(sys.platform[:3]) or LINEBREAK['default']
|
||
|
|
||
|
VERSIONSTR = _("%s version %s <%s>")%(my_name,my_version,my_url)
|
||
|
|
||
|
USAGE = '\n'.join([
|
||
|
'',
|
||
|
_("Usage: %s [OPTIONS] [infile.t2t ...]") % my_name,
|
||
|
'',
|
||
|
_(" --targets print a list of all the available targets and exit"),
|
||
|
_(" -t, --target=TYPE set target document type. currently supported:"),
|
||
|
' %s,' % ', '.join(TARGETS[:9]),
|
||
|
' %s' % ', '.join(TARGETS[9:]),
|
||
|
_(" -i, --infile=FILE set FILE as the input file name ('-' for STDIN)"),
|
||
|
_(" -o, --outfile=FILE set FILE as the output file name ('-' for STDOUT)"),
|
||
|
_(" --encoding=ENC set target file encoding (utf-8, iso-8859-1, etc)"),
|
||
|
_(" --toc add an automatic Table of Contents to the output"),
|
||
|
_(" --toc-level=N set maximum TOC level (depth) to N"),
|
||
|
_(" --toc-only print the Table of Contents and exit"),
|
||
|
_(" -n, --enum-title enumerate all titles as 1, 1.1, 1.1.1, etc"),
|
||
|
_(" --style=FILE use FILE as the document style (like HTML CSS)"),
|
||
|
_(" --css-sugar insert CSS-friendly tags for HTML/XHTML"),
|
||
|
_(" --css-inside insert CSS file contents inside HTML/XHTML headers"),
|
||
|
_(" -H, --no-headers suppress header and footer from the output"),
|
||
|
_(" --mask-email hide email from spam robots. x@y.z turns <x (a) y z>"),
|
||
|
_(" --slides format output as presentation slides (used by -t art)"),
|
||
|
_(" --width=N set the output's width to N columns (used by -t art)"),
|
||
|
_(" --height=N set the output's height to N rows (used by -t art)"),
|
||
|
_(" -C, --config-file=F read configuration from file F"),
|
||
|
_(" --gui invoke Graphical Tk Interface"),
|
||
|
_(" -q, --quiet quiet mode, suppress all output (except errors)"),
|
||
|
_(" -v, --verbose print informative messages during conversion"),
|
||
|
_(" -h, --help print this help information and exit"),
|
||
|
_(" -V, --version print program version and exit"),
|
||
|
_(" --dump-config print all the configuration found and exit"),
|
||
|
_(" --dump-source print the document source, with includes expanded"),
|
||
|
'',
|
||
|
_("Turn OFF options:"),
|
||
|
" --no-css-inside, --no-css-sugar, --no-dump-config, --no-dump-source,",
|
||
|
" --no-encoding, --no-enum-title, --no-headers, --no-infile,",
|
||
|
" --no-mask-email, --no-outfile, --no-quiet, --no-rc, --no-slides,",
|
||
|
" --no-style, --no-targets, --no-toc, --no-toc-only",
|
||
|
'',
|
||
|
_("Example:"),
|
||
|
" %s -t html --toc %s" % (my_name, _("file.t2t")),
|
||
|
'',
|
||
|
_("By default, converted output is saved to 'infile.<target>'."),
|
||
|
_("Use --outfile to force an output file name."),
|
||
|
_("If input file is '-', reads from STDIN."),
|
||
|
_("If output file is '-', dumps output to STDOUT."),
|
||
|
'',
|
||
|
my_url,
|
||
|
''
|
||
|
])
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
# Here is all the target's templates
|
||
|
# You may edit them to fit your needs
|
||
|
# - the %(HEADERn)s strings represent the Header lines
|
||
|
# - the %(STYLE)s string is changed by --style contents
|
||
|
# - the %(ENCODING)s string is changed by --encoding contents
|
||
|
# - if any of the above is empty, the full line is removed
|
||
|
# - use %% to represent a literal %
|
||
|
#
|
||
|
HEADER_TEMPLATE = {
|
||
|
'art':"""
|
||
|
Fake template to respect the general process.
|
||
|
""",
|
||
|
'txt': """\
|
||
|
%(HEADER1)s
|
||
|
%(HEADER2)s
|
||
|
%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'sgml': """\
|
||
|
<!doctype linuxdoc system>
|
||
|
<article>
|
||
|
<title>%(HEADER1)s
|
||
|
<author>%(HEADER2)s
|
||
|
<date>%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'html': """\
|
||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||
|
<HTML>
|
||
|
<HEAD>
|
||
|
<META NAME="generator" CONTENT="http://txt2tags.org">
|
||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=%(ENCODING)s">
|
||
|
<LINK REL="stylesheet" TYPE="text/css" HREF="%(STYLE)s">
|
||
|
<TITLE>%(HEADER1)s</TITLE>
|
||
|
</HEAD><BODY BGCOLOR="white" TEXT="black">
|
||
|
<CENTER>
|
||
|
<H1>%(HEADER1)s</H1>
|
||
|
<FONT SIZE="4"><I>%(HEADER2)s</I></FONT><BR>
|
||
|
<FONT SIZE="4">%(HEADER3)s</FONT>
|
||
|
</CENTER>
|
||
|
""",
|
||
|
|
||
|
'htmlcss': """\
|
||
|
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
|
||
|
<HTML>
|
||
|
<HEAD>
|
||
|
<META NAME="generator" CONTENT="http://txt2tags.org">
|
||
|
<META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=%(ENCODING)s">
|
||
|
<LINK REL="stylesheet" TYPE="text/css" HREF="%(STYLE)s">
|
||
|
<TITLE>%(HEADER1)s</TITLE>
|
||
|
</HEAD>
|
||
|
<BODY>
|
||
|
|
||
|
<DIV CLASS="header" ID="header">
|
||
|
<H1>%(HEADER1)s</H1>
|
||
|
<H2>%(HEADER2)s</H2>
|
||
|
<H3>%(HEADER3)s</H3>
|
||
|
</DIV>
|
||
|
""",
|
||
|
|
||
|
'xhtml': """\
|
||
|
<?xml version="1.0"
|
||
|
encoding="%(ENCODING)s"
|
||
|
?>
|
||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\
|
||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
<head>
|
||
|
<title>%(HEADER1)s</title>
|
||
|
<meta name="generator" content="http://txt2tags.org" />
|
||
|
<link rel="stylesheet" type="text/css" href="%(STYLE)s" />
|
||
|
</head>
|
||
|
<body bgcolor="white" text="black">
|
||
|
<div align="center">
|
||
|
<h1>%(HEADER1)s</h1>
|
||
|
<h2>%(HEADER2)s</h2>
|
||
|
<h3>%(HEADER3)s</h3>
|
||
|
</div>
|
||
|
""",
|
||
|
|
||
|
'xhtmlcss': """\
|
||
|
<?xml version="1.0"
|
||
|
encoding="%(ENCODING)s"
|
||
|
?>
|
||
|
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\
|
||
|
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
||
|
<html xmlns="http://www.w3.org/1999/xhtml">
|
||
|
<head>
|
||
|
<title>%(HEADER1)s</title>
|
||
|
<meta name="generator" content="http://txt2tags.org" />
|
||
|
<link rel="stylesheet" type="text/css" href="%(STYLE)s" />
|
||
|
</head>
|
||
|
<body>
|
||
|
|
||
|
<div class="header" id="header">
|
||
|
<h1>%(HEADER1)s</h1>
|
||
|
<h2>%(HEADER2)s</h2>
|
||
|
<h3>%(HEADER3)s</h3>
|
||
|
</div>
|
||
|
""",
|
||
|
|
||
|
'dbk': """\
|
||
|
<?xml version="1.0"
|
||
|
encoding="%(ENCODING)s"
|
||
|
?>
|
||
|
<!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"\
|
||
|
"docbook/dtd/xml/4.5/docbookx.dtd">
|
||
|
<article lang="en">
|
||
|
<articleinfo>
|
||
|
<title>%(HEADER1)s</title>
|
||
|
<authorgroup>
|
||
|
<author><othername>%(HEADER2)s</othername></author>
|
||
|
</authorgroup>
|
||
|
<date>%(HEADER3)s</date>
|
||
|
</articleinfo>
|
||
|
""",
|
||
|
|
||
|
'man': """\
|
||
|
.TH "%(HEADER1)s" 1 "%(HEADER3)s" "%(HEADER2)s"
|
||
|
""",
|
||
|
|
||
|
# TODO style to <HR>
|
||
|
'pm6': """\
|
||
|
<PMTags1.0 win><C-COLORTABLE ("Preto" 1 0 0 0)
|
||
|
><@Normal=
|
||
|
<FONT "Times New Roman"><CCOLOR "Preto"><SIZE 11>
|
||
|
<HORIZONTAL 100><LETTERSPACE 0><CTRACK 127><CSSIZE 70><C+SIZE 58.3>
|
||
|
<C-POSITION 33.3><C+POSITION 33.3><P><CBASELINE 0><CNOBREAK 0><CLEADING -0.05>
|
||
|
<GGRID 0><GLEFT 7.2><GRIGHT 0><GFIRST 0><G+BEFORE 7.2><G+AFTER 0>
|
||
|
<GALIGNMENT "justify"><GMETHOD "proportional"><G& "ENGLISH">
|
||
|
<GPAIRS 12><G%% 120><GKNEXT 0><GKWIDOW 0><GKORPHAN 0><GTABS $>
|
||
|
<GHYPHENATION 2 34 0><GWORDSPACE 75 100 150><GSPACE -5 0 25>
|
||
|
><@Bullet=<@-PARENT "Normal"><FONT "Abadi MT Condensed Light">
|
||
|
<GLEFT 14.4><G+BEFORE 2.15><G%% 110><GTABS(25.2 l "")>
|
||
|
><@PreFormat=<@-PARENT "Normal"><FONT "Lucida Console"><SIZE 8><CTRACK 0>
|
||
|
<GLEFT 0><G+BEFORE 0><GALIGNMENT "left"><GWORDSPACE 100 100 100><GSPACE 0 0 0>
|
||
|
><@Title1=<@-PARENT "Normal"><FONT "Arial"><SIZE 14><B>
|
||
|
<GCONTENTS><GLEFT 0><G+BEFORE 0><GALIGNMENT "left">
|
||
|
><@Title2=<@-PARENT "Title1"><SIZE 12><G+BEFORE 3.6>
|
||
|
><@Title3=<@-PARENT "Title1"><SIZE 10><GLEFT 7.2><G+BEFORE 7.2>
|
||
|
><@Title4=<@-PARENT "Title3">
|
||
|
><@Title5=<@-PARENT "Title3">
|
||
|
><@Quote=<@-PARENT "Normal"><SIZE 10><I>>
|
||
|
|
||
|
%(HEADER1)s
|
||
|
%(HEADER2)s
|
||
|
%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'mgp': """\
|
||
|
#!/usr/X11R6/bin/mgp -t 90
|
||
|
%%deffont "normal" xfont "utopia-medium-r", charset "iso8859-1"
|
||
|
%%deffont "normal-i" xfont "utopia-medium-i", charset "iso8859-1"
|
||
|
%%deffont "normal-b" xfont "utopia-bold-r" , charset "iso8859-1"
|
||
|
%%deffont "normal-bi" xfont "utopia-bold-i" , charset "iso8859-1"
|
||
|
%%deffont "mono" xfont "courier-medium-r", charset "iso8859-1"
|
||
|
%%default 1 size 5
|
||
|
%%default 2 size 8, fore "yellow", font "normal-b", center
|
||
|
%%default 3 size 5, fore "white", font "normal", left, prefix " "
|
||
|
%%tab 1 size 4, vgap 30, prefix " ", icon arc "red" 40, leftfill
|
||
|
%%tab 2 prefix " ", icon arc "orange" 40, leftfill
|
||
|
%%tab 3 prefix " ", icon arc "brown" 40, leftfill
|
||
|
%%tab 4 prefix " ", icon arc "darkmagenta" 40, leftfill
|
||
|
%%tab 5 prefix " ", icon arc "magenta" 40, leftfill
|
||
|
%%%%------------------------- end of headers -----------------------------
|
||
|
%%page
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
%%size 10, center, fore "yellow"
|
||
|
%(HEADER1)s
|
||
|
|
||
|
%%font "normal-i", size 6, fore "white", center
|
||
|
%(HEADER2)s
|
||
|
|
||
|
%%font "mono", size 7, center
|
||
|
%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'moin': """\
|
||
|
'''%(HEADER1)s'''
|
||
|
|
||
|
''%(HEADER2)s''
|
||
|
|
||
|
%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'gwiki': """\
|
||
|
*%(HEADER1)s*
|
||
|
|
||
|
%(HEADER2)s
|
||
|
|
||
|
_%(HEADER3)s_
|
||
|
""",
|
||
|
|
||
|
'adoc': """\
|
||
|
= %(HEADER1)s
|
||
|
%(HEADER2)s
|
||
|
%(HEADER3)s
|
||
|
""",
|
||
|
|
||
|
'doku': """\
|
||
|
===== %(HEADER1)s =====
|
||
|
|
||
|
**//%(HEADER2)s//**
|
||
|
|
||
|
//%(HEADER3)s//
|
||
|
""",
|
||
|
|
||
|
'pmw': """\
|
||
|
(:Title %(HEADER1)s:)
|
||
|
|
||
|
(:Description %(HEADER2)s:)
|
||
|
|
||
|
(:Summary %(HEADER3)s:)
|
||
|
""",
|
||
|
|
||
|
'wiki': """\
|
||
|
'''%(HEADER1)s'''
|
||
|
|
||
|
%(HEADER2)s
|
||
|
|
||
|
''%(HEADER3)s''
|
||
|
""",
|
||
|
|
||
|
'tex': \
|
||
|
r"""\documentclass{article}
|
||
|
\usepackage{graphicx}
|
||
|
\usepackage{paralist} %% needed for compact lists
|
||
|
\usepackage[normalem]{ulem} %% needed by strike
|
||
|
\usepackage[urlcolor=blue,colorlinks=true]{hyperref}
|
||
|
\usepackage[%(ENCODING)s]{inputenc} %% char encoding
|
||
|
\usepackage{%(STYLE)s} %% user defined
|
||
|
|
||
|
\title{%(HEADER1)s}
|
||
|
\author{%(HEADER2)s}
|
||
|
\begin{document}
|
||
|
\date{%(HEADER3)s}
|
||
|
\maketitle
|
||
|
\clearpage
|
||
|
""",
|
||
|
|
||
|
'lout': """\
|
||
|
@SysInclude { doc }
|
||
|
@Document
|
||
|
@InitialFont { Times Base 12p } # Times, Courier, Helvetica, ...
|
||
|
@PageOrientation { Portrait } # Portrait, Landscape
|
||
|
@ColumnNumber { 1 } # Number of columns (2, 3, ...)
|
||
|
@PageHeaders { Simple } # None, Simple, Titles, NoTitles
|
||
|
@InitialLanguage { English } # German, French, Portuguese, ...
|
||
|
@OptimizePages { Yes } # Yes/No smart page break feature
|
||
|
//
|
||
|
@Text @Begin
|
||
|
@Display @Heading { %(HEADER1)s }
|
||
|
@Display @I { %(HEADER2)s }
|
||
|
@Display { %(HEADER3)s }
|
||
|
#@NP # Break page after Headers
|
||
|
""",
|
||
|
'creole': """\
|
||
|
%(HEADER1)s
|
||
|
%(HEADER2)s
|
||
|
%(HEADER3)s
|
||
|
"""
|
||
|
# @SysInclude { tbl } # Tables support
|
||
|
# setup: @MakeContents { Yes } # show TOC
|
||
|
# setup: @SectionGap # break page at each section
|
||
|
}
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
def getTags(config):
|
||
|
"Returns all the known tags for the specified target"
|
||
|
|
||
|
keys = """
|
||
|
title1 numtitle1
|
||
|
title2 numtitle2
|
||
|
title3 numtitle3
|
||
|
title4 numtitle4
|
||
|
title5 numtitle5
|
||
|
title1Open title1Close
|
||
|
title2Open title2Close
|
||
|
title3Open title3Close
|
||
|
title4Open title4Close
|
||
|
title5Open title5Close
|
||
|
blocktitle1Open blocktitle1Close
|
||
|
blocktitle2Open blocktitle2Close
|
||
|
blocktitle3Open blocktitle3Close
|
||
|
|
||
|
paragraphOpen paragraphClose
|
||
|
blockVerbOpen blockVerbClose
|
||
|
blockQuoteOpen blockQuoteClose blockQuoteLine
|
||
|
blockCommentOpen blockCommentClose
|
||
|
|
||
|
fontMonoOpen fontMonoClose
|
||
|
fontBoldOpen fontBoldClose
|
||
|
fontItalicOpen fontItalicClose
|
||
|
fontUnderlineOpen fontUnderlineClose
|
||
|
fontStrikeOpen fontStrikeClose
|
||
|
|
||
|
listOpen listClose
|
||
|
listOpenCompact listCloseCompact
|
||
|
listItemOpen listItemClose listItemLine
|
||
|
numlistOpen numlistClose
|
||
|
numlistOpenCompact numlistCloseCompact
|
||
|
numlistItemOpen numlistItemClose numlistItemLine
|
||
|
deflistOpen deflistClose
|
||
|
deflistOpenCompact deflistCloseCompact
|
||
|
deflistItem1Open deflistItem1Close
|
||
|
deflistItem2Open deflistItem2Close deflistItem2LinePrefix
|
||
|
|
||
|
bar1 bar2
|
||
|
url urlMark
|
||
|
email emailMark
|
||
|
img imgAlignLeft imgAlignRight imgAlignCenter
|
||
|
_imgAlignLeft _imgAlignRight _imgAlignCenter
|
||
|
|
||
|
tableOpen tableClose
|
||
|
_tableBorder _tableAlignLeft _tableAlignCenter
|
||
|
tableRowOpen tableRowClose tableRowSep
|
||
|
tableTitleRowOpen tableTitleRowClose
|
||
|
tableCellOpen tableCellClose tableCellSep
|
||
|
tableTitleCellOpen tableTitleCellClose tableTitleCellSep
|
||
|
_tableColAlignLeft _tableColAlignRight _tableColAlignCenter
|
||
|
_tableCellAlignLeft _tableCellAlignRight _tableCellAlignCenter
|
||
|
_tableCellColSpan tableColAlignSep
|
||
|
_tableCellMulticolOpen
|
||
|
_tableCellMulticolClose
|
||
|
|
||
|
bodyOpen bodyClose
|
||
|
cssOpen cssClose
|
||
|
tocOpen tocClose TOC
|
||
|
anchor
|
||
|
comment
|
||
|
pageBreak
|
||
|
EOD
|
||
|
""".split()
|
||
|
|
||
|
# TIP: \a represents the current text inside the mark
|
||
|
# TIP: ~A~, ~B~ and ~C~ are expanded to other tags parts
|
||
|
|
||
|
alltags = {
|
||
|
|
||
|
'art': {
|
||
|
'title1' : '\a' ,
|
||
|
'title2' : '\a' ,
|
||
|
'title3' : '\a' ,
|
||
|
'title4' : '\a' ,
|
||
|
'title5' : '\a' ,
|
||
|
'blockQuoteLine' : '\t' ,
|
||
|
'listItemOpen' : '- ' ,
|
||
|
'numlistItemOpen' : '\a. ' ,
|
||
|
'bar1' : aa_line(AA['bar1'], config['width']),
|
||
|
'bar2' : aa_line(AA['bar2'], config['width']),
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '\a (\a)' ,
|
||
|
'email' : '\a' ,
|
||
|
'emailMark' : '\a (\a)' ,
|
||
|
'img' : '[\a]' ,
|
||
|
},
|
||
|
|
||
|
'txt': {
|
||
|
'title1' : ' \a' ,
|
||
|
'title2' : '\t\a' ,
|
||
|
'title3' : '\t\t\a' ,
|
||
|
'title4' : '\t\t\t\a' ,
|
||
|
'title5' : '\t\t\t\t\a',
|
||
|
'blockQuoteLine' : '\t' ,
|
||
|
'listItemOpen' : '- ' ,
|
||
|
'numlistItemOpen' : '\a. ' ,
|
||
|
'bar1' : '\a' ,
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '\a (\a)' ,
|
||
|
'email' : '\a' ,
|
||
|
'emailMark' : '\a (\a)' ,
|
||
|
'img' : '[\a]' ,
|
||
|
},
|
||
|
|
||
|
'html': {
|
||
|
'paragraphOpen' : '<P>' ,
|
||
|
'paragraphClose' : '</P>' ,
|
||
|
'title1' : '~A~<H1>\a</H1>' ,
|
||
|
'title2' : '~A~<H2>\a</H2>' ,
|
||
|
'title3' : '~A~<H3>\a</H3>' ,
|
||
|
'title4' : '~A~<H4>\a</H4>' ,
|
||
|
'title5' : '~A~<H5>\a</H5>' ,
|
||
|
'anchor' : '<A NAME="\a"></A>\n',
|
||
|
'blockVerbOpen' : '<PRE>' ,
|
||
|
'blockVerbClose' : '</PRE>' ,
|
||
|
'blockQuoteOpen' : '<BLOCKQUOTE>' ,
|
||
|
'blockQuoteClose' : '</BLOCKQUOTE>' ,
|
||
|
'fontMonoOpen' : '<CODE>' ,
|
||
|
'fontMonoClose' : '</CODE>' ,
|
||
|
'fontBoldOpen' : '<B>' ,
|
||
|
'fontBoldClose' : '</B>' ,
|
||
|
'fontItalicOpen' : '<I>' ,
|
||
|
'fontItalicClose' : '</I>' ,
|
||
|
'fontUnderlineOpen' : '<U>' ,
|
||
|
'fontUnderlineClose' : '</U>' ,
|
||
|
'fontStrikeOpen' : '<S>' ,
|
||
|
'fontStrikeClose' : '</S>' ,
|
||
|
'listOpen' : '<UL>' ,
|
||
|
'listClose' : '</UL>' ,
|
||
|
'listItemOpen' : '<LI>' ,
|
||
|
'numlistOpen' : '<OL>' ,
|
||
|
'numlistClose' : '</OL>' ,
|
||
|
'numlistItemOpen' : '<LI>' ,
|
||
|
'deflistOpen' : '<DL>' ,
|
||
|
'deflistClose' : '</DL>' ,
|
||
|
'deflistItem1Open' : '<DT>' ,
|
||
|
'deflistItem1Close' : '</DT>' ,
|
||
|
'deflistItem2Open' : '<DD>' ,
|
||
|
'bar1' : '<HR NOSHADE SIZE=1>' ,
|
||
|
'bar2' : '<HR NOSHADE SIZE=5>' ,
|
||
|
'url' : '<A HREF="\a">\a</A>' ,
|
||
|
'urlMark' : '<A HREF="\a">\a</A>' ,
|
||
|
'email' : '<A HREF="mailto:\a">\a</A>' ,
|
||
|
'emailMark' : '<A HREF="mailto:\a">\a</A>' ,
|
||
|
'img' : '<IMG~A~ SRC="\a" BORDER="0" ALT="">',
|
||
|
'_imgAlignLeft' : ' ALIGN="left"' ,
|
||
|
'_imgAlignCenter' : ' ALIGN="middle"',
|
||
|
'_imgAlignRight' : ' ALIGN="right"' ,
|
||
|
'tableOpen' : '<TABLE~A~~B~ CELLPADDING="4">',
|
||
|
'tableClose' : '</TABLE>' ,
|
||
|
'tableRowOpen' : '<TR>' ,
|
||
|
'tableRowClose' : '</TR>' ,
|
||
|
'tableCellOpen' : '<TD~A~~S~>' ,
|
||
|
'tableCellClose' : '</TD>' ,
|
||
|
'tableTitleCellOpen' : '<TH~S~>' ,
|
||
|
'tableTitleCellClose' : '</TH>' ,
|
||
|
'_tableBorder' : ' BORDER="1"' ,
|
||
|
'_tableAlignCenter' : ' ALIGN="center"',
|
||
|
'_tableCellAlignRight' : ' ALIGN="right"' ,
|
||
|
'_tableCellAlignCenter': ' ALIGN="center"',
|
||
|
'_tableCellColSpan' : ' COLSPAN="\a"' ,
|
||
|
'cssOpen' : '<STYLE TYPE="text/css">',
|
||
|
'cssClose' : '</STYLE>' ,
|
||
|
'comment' : '<!-- \a -->' ,
|
||
|
'EOD' : '</BODY></HTML>'
|
||
|
},
|
||
|
|
||
|
#TIP xhtml inherits all HTML definitions (lowercased)
|
||
|
#TIP http://www.w3.org/TR/xhtml1/#guidelines
|
||
|
#TIP http://www.htmlref.com/samples/Chapt17/17_08.htm
|
||
|
'xhtml': {
|
||
|
'listItemClose' : '</li>' ,
|
||
|
'numlistItemClose' : '</li>' ,
|
||
|
'deflistItem2Close' : '</dd>' ,
|
||
|
'bar1' : '<hr class="light" />',
|
||
|
'bar2' : '<hr class="heavy" />',
|
||
|
'anchor' : '<a id="\a" name="\a"></a>\n',
|
||
|
'img' : '<img~A~ src="\a" border="0" alt=""/>',
|
||
|
},
|
||
|
|
||
|
'sgml': {
|
||
|
'paragraphOpen' : '<p>' ,
|
||
|
'title1' : '<sect>\a~A~<p>' ,
|
||
|
'title2' : '<sect1>\a~A~<p>' ,
|
||
|
'title3' : '<sect2>\a~A~<p>' ,
|
||
|
'title4' : '<sect3>\a~A~<p>' ,
|
||
|
'title5' : '<sect4>\a~A~<p>' ,
|
||
|
'anchor' : '<label id="\a">' ,
|
||
|
'blockVerbOpen' : '<tscreen><verb>' ,
|
||
|
'blockVerbClose' : '</verb></tscreen>' ,
|
||
|
'blockQuoteOpen' : '<quote>' ,
|
||
|
'blockQuoteClose' : '</quote>' ,
|
||
|
'fontMonoOpen' : '<tt>' ,
|
||
|
'fontMonoClose' : '</tt>' ,
|
||
|
'fontBoldOpen' : '<bf>' ,
|
||
|
'fontBoldClose' : '</bf>' ,
|
||
|
'fontItalicOpen' : '<em>' ,
|
||
|
'fontItalicClose' : '</em>' ,
|
||
|
'fontUnderlineOpen' : '<bf><em>' ,
|
||
|
'fontUnderlineClose' : '</em></bf>' ,
|
||
|
'listOpen' : '<itemize>' ,
|
||
|
'listClose' : '</itemize>' ,
|
||
|
'listItemOpen' : '<item>' ,
|
||
|
'numlistOpen' : '<enum>' ,
|
||
|
'numlistClose' : '</enum>' ,
|
||
|
'numlistItemOpen' : '<item>' ,
|
||
|
'deflistOpen' : '<descrip>' ,
|
||
|
'deflistClose' : '</descrip>' ,
|
||
|
'deflistItem1Open' : '<tag>' ,
|
||
|
'deflistItem1Close' : '</tag>' ,
|
||
|
'bar1' : '<!-- \a -->' ,
|
||
|
'url' : '<htmlurl url="\a" name="\a">' ,
|
||
|
'urlMark' : '<htmlurl url="\a" name="\a">' ,
|
||
|
'email' : '<htmlurl url="mailto:\a" name="\a">' ,
|
||
|
'emailMark' : '<htmlurl url="mailto:\a" name="\a">' ,
|
||
|
'img' : '<figure><ph vspace=""><img src="\a"></figure>',
|
||
|
'tableOpen' : '<table><tabular ca="~C~">' ,
|
||
|
'tableClose' : '</tabular></table>' ,
|
||
|
'tableRowSep' : '<rowsep>' ,
|
||
|
'tableCellSep' : '<colsep>' ,
|
||
|
'_tableColAlignLeft' : 'l' ,
|
||
|
'_tableColAlignRight' : 'r' ,
|
||
|
'_tableColAlignCenter' : 'c' ,
|
||
|
'comment' : '<!-- \a -->' ,
|
||
|
'TOC' : '<toc>' ,
|
||
|
'EOD' : '</article>'
|
||
|
},
|
||
|
|
||
|
'dbk': {
|
||
|
'paragraphOpen' : '<para>' ,
|
||
|
'paragraphClose' : '</para>' ,
|
||
|
'title1Open' : '~A~<sect1><title>\a</title>' ,
|
||
|
'title1Close' : '</sect1>' ,
|
||
|
'title2Open' : '~A~ <sect2><title>\a</title>' ,
|
||
|
'title2Close' : ' </sect2>' ,
|
||
|
'title3Open' : '~A~ <sect3><title>\a</title>' ,
|
||
|
'title3Close' : ' </sect3>' ,
|
||
|
'title4Open' : '~A~ <sect4><title>\a</title>' ,
|
||
|
'title4Close' : ' </sect4>' ,
|
||
|
'title5Open' : '~A~ <sect5><title>\a</title>',
|
||
|
'title5Close' : ' </sect5>' ,
|
||
|
'anchor' : '<anchor id="\a"/>\n' ,
|
||
|
'blockVerbOpen' : '<programlisting>' ,
|
||
|
'blockVerbClose' : '</programlisting>' ,
|
||
|
'blockQuoteOpen' : '<blockquote><para>' ,
|
||
|
'blockQuoteClose' : '</para></blockquote>' ,
|
||
|
'fontMonoOpen' : '<code>' ,
|
||
|
'fontMonoClose' : '</code>' ,
|
||
|
'fontBoldOpen' : '<emphasis role="bold">' ,
|
||
|
'fontBoldClose' : '</emphasis>' ,
|
||
|
'fontItalicOpen' : '<emphasis>' ,
|
||
|
'fontItalicClose' : '</emphasis>' ,
|
||
|
'fontUnderlineOpen' : '<emphasis role="underline">' ,
|
||
|
'fontUnderlineClose' : '</emphasis>' ,
|
||
|
# 'fontStrikeOpen' : '<emphasis role="strikethrough">' , # Don't know
|
||
|
# 'fontStrikeClose' : '</emphasis>' ,
|
||
|
'listOpen' : '<itemizedlist>' ,
|
||
|
'listClose' : '</itemizedlist>' ,
|
||
|
'listItemOpen' : '<listitem><para>' ,
|
||
|
'listItemClose' : '</para></listitem>' ,
|
||
|
'numlistOpen' : '<orderedlist numeration="arabic">' ,
|
||
|
'numlistClose' : '</orderedlist>' ,
|
||
|
'numlistItemOpen' : '<listitem><para>' ,
|
||
|
'numlistItemClose' : '</para></listitem>' ,
|
||
|
'deflistOpen' : '<variablelist>' ,
|
||
|
'deflistClose' : '</variablelist>' ,
|
||
|
'deflistItem1Open' : '<varlistentry><term>' ,
|
||
|
'deflistItem1Close' : '</term>' ,
|
||
|
'deflistItem2Open' : '<listitem><para>' ,
|
||
|
'deflistItem2Close' : '</para></listitem></varlistentry>' ,
|
||
|
# 'bar1' : '<>' , # Don't know
|
||
|
# 'bar2' : '<>' , # Don't know
|
||
|
'url' : '<ulink url="\a">\a</ulink>' ,
|
||
|
'urlMark' : '<ulink url="\a">\a</ulink>' ,
|
||
|
'email' : '<email>\a</email>' ,
|
||
|
'emailMark' : '<email>\a</email>' ,
|
||
|
'img' : '<mediaobject><imageobject><imagedata fileref="\a"/></imageobject></mediaobject>',
|
||
|
# '_imgAlignLeft' : '' , # Don't know
|
||
|
# '_imgAlignCenter' : '' , # Don't know
|
||
|
# '_imgAlignRight' : '' , # Don't know
|
||
|
# 'tableOpen' : '<informaltable><tgroup cols=""><tbody>', # Don't work, need to know number of cols
|
||
|
# 'tableClose' : '</tbody></tgroup></informaltable>' ,
|
||
|
# 'tableRowOpen' : '<row>' ,
|
||
|
# 'tableRowClose' : '</row>' ,
|
||
|
# 'tableCellOpen' : '<entry>' ,
|
||
|
# 'tableCellClose' : '</entry>' ,
|
||
|
# 'tableTitleRowOpen' : '<thead>' ,
|
||
|
# 'tableTitleRowClose' : '</thead>' ,
|
||
|
# '_tableBorder' : ' frame="all"' ,
|
||
|
# '_tableAlignCenter' : ' align="center"' ,
|
||
|
# '_tableCellAlignRight' : ' align="right"' ,
|
||
|
# '_tableCellAlignCenter': ' align="center"' ,
|
||
|
# '_tableCellColSpan' : ' COLSPAN="\a"' ,
|
||
|
'TOC' : '<index/>' ,
|
||
|
'comment' : '<!-- \a -->' ,
|
||
|
'EOD' : '</article>'
|
||
|
},
|
||
|
|
||
|
'tex': {
|
||
|
'title1' : '~A~\section*{\a}' ,
|
||
|
'title2' : '~A~\\subsection*{\a}' ,
|
||
|
'title3' : '~A~\\subsubsection*{\a}',
|
||
|
# title 4/5: DIRTY: para+BF+\\+\n
|
||
|
'title4' : '~A~\\paragraph{}\\textbf{\a}\\\\\n',
|
||
|
'title5' : '~A~\\paragraph{}\\textbf{\a}\\\\\n',
|
||
|
'numtitle1' : '\n~A~\section{\a}' ,
|
||
|
'numtitle2' : '~A~\\subsection{\a}' ,
|
||
|
'numtitle3' : '~A~\\subsubsection{\a}' ,
|
||
|
'anchor' : '\\hypertarget{\a}{}\n' ,
|
||
|
'blockVerbOpen' : '\\begin{verbatim}' ,
|
||
|
'blockVerbClose' : '\\end{verbatim}' ,
|
||
|
'blockQuoteOpen' : '\\begin{quotation}' ,
|
||
|
'blockQuoteClose' : '\\end{quotation}' ,
|
||
|
'fontMonoOpen' : '\\texttt{' ,
|
||
|
'fontMonoClose' : '}' ,
|
||
|
'fontBoldOpen' : '\\textbf{' ,
|
||
|
'fontBoldClose' : '}' ,
|
||
|
'fontItalicOpen' : '\\textit{' ,
|
||
|
'fontItalicClose' : '}' ,
|
||
|
'fontUnderlineOpen' : '\\underline{' ,
|
||
|
'fontUnderlineClose' : '}' ,
|
||
|
'fontStrikeOpen' : '\\sout{' ,
|
||
|
'fontStrikeClose' : '}' ,
|
||
|
'listOpen' : '\\begin{itemize}' ,
|
||
|
'listClose' : '\\end{itemize}' ,
|
||
|
'listOpenCompact' : '\\begin{compactitem}',
|
||
|
'listCloseCompact' : '\\end{compactitem}' ,
|
||
|
'listItemOpen' : '\\item ' ,
|
||
|
'numlistOpen' : '\\begin{enumerate}' ,
|
||
|
'numlistClose' : '\\end{enumerate}' ,
|
||
|
'numlistOpenCompact' : '\\begin{compactenum}',
|
||
|
'numlistCloseCompact' : '\\end{compactenum}' ,
|
||
|
'numlistItemOpen' : '\\item ' ,
|
||
|
'deflistOpen' : '\\begin{description}',
|
||
|
'deflistClose' : '\\end{description}' ,
|
||
|
'deflistOpenCompact' : '\\begin{compactdesc}',
|
||
|
'deflistCloseCompact' : '\\end{compactdesc}' ,
|
||
|
'deflistItem1Open' : '\\item[' ,
|
||
|
'deflistItem1Close' : ']' ,
|
||
|
'bar1' : '\\hrulefill{}' ,
|
||
|
'bar2' : '\\rule{\linewidth}{1mm}',
|
||
|
'url' : '\\htmladdnormallink{\a}{\a}',
|
||
|
'urlMark' : '\\htmladdnormallink{\a}{\a}',
|
||
|
'email' : '\\htmladdnormallink{\a}{mailto:\a}',
|
||
|
'emailMark' : '\\htmladdnormallink{\a}{mailto:\a}',
|
||
|
'img' : '\\includegraphics{\a}',
|
||
|
'tableOpen' : '\\begin{center}\\begin{tabular}{|~C~|}',
|
||
|
'tableClose' : '\\end{tabular}\\end{center}',
|
||
|
'tableRowOpen' : '\\hline ' ,
|
||
|
'tableRowClose' : ' \\\\' ,
|
||
|
'tableCellSep' : ' & ' ,
|
||
|
'_tableColAlignLeft' : 'l' ,
|
||
|
'_tableColAlignRight' : 'r' ,
|
||
|
'_tableColAlignCenter' : 'c' ,
|
||
|
'_tableCellAlignLeft' : 'l' ,
|
||
|
'_tableCellAlignRight' : 'r' ,
|
||
|
'_tableCellAlignCenter': 'c' ,
|
||
|
'_tableCellColSpan' : '\a' ,
|
||
|
'_tableCellMulticolOpen' : '\\multicolumn{\a}{|~C~|}{',
|
||
|
'_tableCellMulticolClose' : '}',
|
||
|
'tableColAlignSep' : '|' ,
|
||
|
'comment' : '% \a' ,
|
||
|
'TOC' : '\\tableofcontents',
|
||
|
'pageBreak' : '\\clearpage',
|
||
|
'EOD' : '\\end{document}'
|
||
|
},
|
||
|
|
||
|
'lout': {
|
||
|
'paragraphOpen' : '@LP' ,
|
||
|
'blockTitle1Open' : '@BeginSections' ,
|
||
|
'blockTitle1Close' : '@EndSections' ,
|
||
|
'blockTitle2Open' : ' @BeginSubSections' ,
|
||
|
'blockTitle2Close' : ' @EndSubSections' ,
|
||
|
'blockTitle3Open' : ' @BeginSubSubSections' ,
|
||
|
'blockTitle3Close' : ' @EndSubSubSections' ,
|
||
|
'title1Open' : '~A~@Section @Title { \a } @Begin',
|
||
|
'title1Close' : '@End @Section' ,
|
||
|
'title2Open' : '~A~ @SubSection @Title { \a } @Begin',
|
||
|
'title2Close' : ' @End @SubSection' ,
|
||
|
'title3Open' : '~A~ @SubSubSection @Title { \a } @Begin',
|
||
|
'title3Close' : ' @End @SubSubSection' ,
|
||
|
'title4Open' : '~A~@LP @LeftDisplay @B { \a }',
|
||
|
'title5Open' : '~A~@LP @LeftDisplay @B { \a }',
|
||
|
'anchor' : '@Tag { \a }\n' ,
|
||
|
'blockVerbOpen' : '@LP @ID @F @RawVerbatim @Begin',
|
||
|
'blockVerbClose' : '@End @RawVerbatim' ,
|
||
|
'blockQuoteOpen' : '@QD {' ,
|
||
|
'blockQuoteClose' : '}' ,
|
||
|
# enclosed inside {} to deal with joined**words**
|
||
|
'fontMonoOpen' : '{@F {' ,
|
||
|
'fontMonoClose' : '}}' ,
|
||
|
'fontBoldOpen' : '{@B {' ,
|
||
|
'fontBoldClose' : '}}' ,
|
||
|
'fontItalicOpen' : '{@II {' ,
|
||
|
'fontItalicClose' : '}}' ,
|
||
|
'fontUnderlineOpen' : '{@Underline{' ,
|
||
|
'fontUnderlineClose' : '}}' ,
|
||
|
# the full form is more readable, but could be BL EL LI NL TL DTI
|
||
|
'listOpen' : '@BulletList' ,
|
||
|
'listClose' : '@EndList' ,
|
||
|
'listItemOpen' : '@ListItem{' ,
|
||
|
'listItemClose' : '}' ,
|
||
|
'numlistOpen' : '@NumberedList' ,
|
||
|
'numlistClose' : '@EndList' ,
|
||
|
'numlistItemOpen' : '@ListItem{' ,
|
||
|
'numlistItemClose' : '}' ,
|
||
|
'deflistOpen' : '@TaggedList' ,
|
||
|
'deflistClose' : '@EndList' ,
|
||
|
'deflistItem1Open' : '@DropTagItem {' ,
|
||
|
'deflistItem1Close' : '}' ,
|
||
|
'deflistItem2Open' : '{' ,
|
||
|
'deflistItem2Close' : '}' ,
|
||
|
'bar1' : '@DP @FullWidthRule' ,
|
||
|
'url' : '{blue @Colour { \a }}' ,
|
||
|
'urlMark' : '\a ({blue @Colour { \a }})' ,
|
||
|
'email' : '{blue @Colour { \a }}' ,
|
||
|
'emailMark' : '\a ({blue Colour{ \a }})' ,
|
||
|
'img' : '~A~@IncludeGraphic { \a }' , # eps only!
|
||
|
'_imgAlignLeft' : '@LeftDisplay ' ,
|
||
|
'_imgAlignRight' : '@RightDisplay ' ,
|
||
|
'_imgAlignCenter' : '@CentredDisplay ' ,
|
||
|
# lout tables are *way* complicated, no support for now
|
||
|
#'tableOpen' : '~A~@Tbl~B~\naformat{ @Cell A | @Cell B } {',
|
||
|
#'tableClose' : '}' ,
|
||
|
#'tableRowOpen' : '@Rowa\n' ,
|
||
|
#'tableTitleRowOpen' : '@HeaderRowa' ,
|
||
|
#'tableCenterAlign' : '@CentredDisplay ' ,
|
||
|
#'tableCellOpen' : '\a {' , # A, B, ...
|
||
|
#'tableCellClose' : '}' ,
|
||
|
#'_tableBorder' : '\nrule {yes}' ,
|
||
|
'comment' : '# \a' ,
|
||
|
# @MakeContents must be on the config file
|
||
|
'TOC' : '@DP @ContentsGoesHere @DP',
|
||
|
'pageBreak' : '@NP' ,
|
||
|
'EOD' : '@End @Text'
|
||
|
},
|
||
|
|
||
|
# http://moinmo.in/SyntaxReference
|
||
|
'moin': {
|
||
|
'title1' : '= \a =' ,
|
||
|
'title2' : '== \a ==' ,
|
||
|
'title3' : '=== \a ===' ,
|
||
|
'title4' : '==== \a ====' ,
|
||
|
'title5' : '===== \a =====',
|
||
|
'blockVerbOpen' : '{{{' ,
|
||
|
'blockVerbClose' : '}}}' ,
|
||
|
'blockQuoteLine' : ' ' ,
|
||
|
'fontMonoOpen' : '{{{' ,
|
||
|
'fontMonoClose' : '}}}' ,
|
||
|
'fontBoldOpen' : "'''" ,
|
||
|
'fontBoldClose' : "'''" ,
|
||
|
'fontItalicOpen' : "''" ,
|
||
|
'fontItalicClose' : "''" ,
|
||
|
'fontUnderlineOpen' : '__' ,
|
||
|
'fontUnderlineClose' : '__' ,
|
||
|
'fontStrikeOpen' : '--(' ,
|
||
|
'fontStrikeClose' : ')--' ,
|
||
|
'listItemOpen' : ' * ' ,
|
||
|
'numlistItemOpen' : ' \a. ' ,
|
||
|
'deflistItem1Open' : ' ' ,
|
||
|
'deflistItem1Close' : '::' ,
|
||
|
'deflistItem2LinePrefix': ' :: ' ,
|
||
|
'bar1' : '----' ,
|
||
|
'bar2' : '--------' ,
|
||
|
'url' : '[\a]' ,
|
||
|
'urlMark' : '[\a \a]' ,
|
||
|
'email' : '[\a]' ,
|
||
|
'emailMark' : '[\a \a]' ,
|
||
|
'img' : '[\a]' ,
|
||
|
'tableRowOpen' : '||' ,
|
||
|
'tableCellOpen' : '~A~' ,
|
||
|
'tableCellClose' : '||' ,
|
||
|
'tableTitleCellClose' : '||' ,
|
||
|
'_tableCellAlignRight' : '<)>' ,
|
||
|
'_tableCellAlignCenter' : '<:>' ,
|
||
|
'comment' : '/* \a */' ,
|
||
|
'TOC' : '[[TableOfContents]]'
|
||
|
},
|
||
|
|
||
|
# http://code.google.com/p/support/wiki/WikiSyntax
|
||
|
'gwiki': {
|
||
|
'title1' : '= \a =' ,
|
||
|
'title2' : '== \a ==' ,
|
||
|
'title3' : '=== \a ===' ,
|
||
|
'title4' : '==== \a ====' ,
|
||
|
'title5' : '===== \a =====',
|
||
|
'blockVerbOpen' : '{{{' ,
|
||
|
'blockVerbClose' : '}}}' ,
|
||
|
'blockQuoteLine' : ' ' ,
|
||
|
'fontMonoOpen' : '{{{' ,
|
||
|
'fontMonoClose' : '}}}' ,
|
||
|
'fontBoldOpen' : '*' ,
|
||
|
'fontBoldClose' : '*' ,
|
||
|
'fontItalicOpen' : '_' , # underline == italic
|
||
|
'fontItalicClose' : '_' ,
|
||
|
'fontStrikeOpen' : '~~' ,
|
||
|
'fontStrikeClose' : '~~' ,
|
||
|
'listItemOpen' : ' * ' ,
|
||
|
'numlistItemOpen' : ' # ' ,
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '[\a \a]' ,
|
||
|
'email' : 'mailto:\a' ,
|
||
|
'emailMark' : '[mailto:\a \a]',
|
||
|
'img' : '[\a]' ,
|
||
|
'tableRowOpen' : '|| ' ,
|
||
|
'tableRowClose' : ' ||' ,
|
||
|
'tableCellSep' : ' || ' ,
|
||
|
},
|
||
|
|
||
|
# http://powerman.name/doc/asciidoc
|
||
|
'adoc': {
|
||
|
'title1' : '== \a' ,
|
||
|
'title2' : '=== \a' ,
|
||
|
'title3' : '==== \a' ,
|
||
|
'title4' : '===== \a' ,
|
||
|
'title5' : '===== \a' ,
|
||
|
'blockVerbOpen' : '----' ,
|
||
|
'blockVerbClose' : '----' ,
|
||
|
'fontMonoOpen' : '+' ,
|
||
|
'fontMonoClose' : '+' ,
|
||
|
'fontBoldOpen' : '*' ,
|
||
|
'fontBoldClose' : '*' ,
|
||
|
'fontItalicOpen' : '_' ,
|
||
|
'fontItalicClose' : '_' ,
|
||
|
'listItemOpen' : '- ' ,
|
||
|
'listItemLine' : '\t' ,
|
||
|
'numlistItemOpen' : '. ' ,
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '\a[\a]' ,
|
||
|
'email' : 'mailto:\a' ,
|
||
|
'emailMark' : 'mailto:\a[\a]' ,
|
||
|
'img' : 'image::\a[]' ,
|
||
|
},
|
||
|
|
||
|
# http://wiki.splitbrain.org/wiki:syntax
|
||
|
# Hint: <br> is \\ $
|
||
|
# Hint: You can add footnotes ((This is a footnote))
|
||
|
'doku': {
|
||
|
'title1' : '===== \a =====',
|
||
|
'title2' : '==== \a ====' ,
|
||
|
'title3' : '=== \a ===' ,
|
||
|
'title4' : '== \a ==' ,
|
||
|
'title5' : '= \a =' ,
|
||
|
# DokuWiki uses ' ' identation to mark verb blocks (see indentverbblock)
|
||
|
'blockQuoteLine' : '>' ,
|
||
|
'fontMonoOpen' : "''" ,
|
||
|
'fontMonoClose' : "''" ,
|
||
|
'fontBoldOpen' : "**" ,
|
||
|
'fontBoldClose' : "**" ,
|
||
|
'fontItalicOpen' : "//" ,
|
||
|
'fontItalicClose' : "//" ,
|
||
|
'fontUnderlineOpen' : "__" ,
|
||
|
'fontUnderlineClose' : "__" ,
|
||
|
'fontStrikeOpen' : '<del>' ,
|
||
|
'fontStrikeClose' : '</del>' ,
|
||
|
'listItemOpen' : ' * ' ,
|
||
|
'numlistItemOpen' : ' - ' ,
|
||
|
'bar1' : '----' ,
|
||
|
'url' : '[[\a]]' ,
|
||
|
'urlMark' : '[[\a|\a]]' ,
|
||
|
'email' : '[[\a]]' ,
|
||
|
'emailMark' : '[[\a|\a]]' ,
|
||
|
'img' : '{{\a}}' ,
|
||
|
'imgAlignLeft' : '{{\a }}' ,
|
||
|
'imgAlignRight' : '{{ \a}}' ,
|
||
|
'imgAlignCenter' : '{{ \a }}' ,
|
||
|
'tableTitleRowOpen' : '^ ' ,
|
||
|
'tableTitleRowClose' : ' ^' ,
|
||
|
'tableTitleCellSep' : ' ^ ' ,
|
||
|
'tableRowOpen' : '| ' ,
|
||
|
'tableRowClose' : ' |' ,
|
||
|
'tableCellSep' : ' | ' ,
|
||
|
# DokuWiki has no attributes. The content must be aligned!
|
||
|
# '_tableCellAlignRight' : '<)>' , # ??
|
||
|
# '_tableCellAlignCenter': '<:>' , # ??
|
||
|
# DokuWiki colspan is the same as txt2tags' with multiple |||
|
||
|
# 'comment' : '## \a' , # ??
|
||
|
# TOC is automatic
|
||
|
},
|
||
|
|
||
|
# http://www.pmwiki.org/wiki/PmWiki/TextFormattingRules
|
||
|
'pmw': {
|
||
|
'title1' : '~A~! \a ' ,
|
||
|
'title2' : '~A~!! \a ' ,
|
||
|
'title3' : '~A~!!! \a ' ,
|
||
|
'title4' : '~A~!!!! \a ' ,
|
||
|
'title5' : '~A~!!!!! \a ' ,
|
||
|
'blockQuoteOpen' : '->' ,
|
||
|
'blockQuoteClose' : '\n' ,
|
||
|
# In-text font
|
||
|
'fontLargeOpen' : "[+" ,
|
||
|
'fontLargeClose' : "+]" ,
|
||
|
'fontLargerOpen' : "[++" ,
|
||
|
'fontLargerClose' : "++]" ,
|
||
|
'fontSmallOpen' : "[-" ,
|
||
|
'fontSmallClose' : "-]" ,
|
||
|
'fontLargerOpen' : "[--" ,
|
||
|
'fontLargerClose' : "--]" ,
|
||
|
'fontMonoOpen' : "@@" ,
|
||
|
'fontMonoClose' : "@@" ,
|
||
|
'fontBoldOpen' : "'''" ,
|
||
|
'fontBoldClose' : "'''" ,
|
||
|
'fontItalicOpen' : "''" ,
|
||
|
'fontItalicClose' : "''" ,
|
||
|
'fontUnderlineOpen' : "{+" ,
|
||
|
'fontUnderlineClose' : "+}" ,
|
||
|
'fontStrikeOpen' : '{-' ,
|
||
|
'fontStrikeClose' : '-}' ,
|
||
|
# Lists
|
||
|
'listItemLine' : '*' ,
|
||
|
'numlistItemLine' : '#' ,
|
||
|
'deflistItem1Open' : ': ' ,
|
||
|
'deflistItem1Close' : ':' ,
|
||
|
'deflistItem2LineOpen' : '::' ,
|
||
|
'deflistItem2LineClose' : ':' ,
|
||
|
# Verbatim block
|
||
|
'blockVerbOpen' : '[@' ,
|
||
|
'blockVerbClose' : '@]' ,
|
||
|
'bar1' : '----' ,
|
||
|
# URL, email and anchor
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '[[\a -> \a]]' ,
|
||
|
'email' : '\a' ,
|
||
|
'emailMark' : '[[\a -> mailto:\a]]',
|
||
|
'anchor' : '[[#\a]]\n' ,
|
||
|
# Image markup
|
||
|
'img' : '\a' ,
|
||
|
#'imgAlignLeft' : '{{\a }}' ,
|
||
|
#'imgAlignRight' : '{{ \a}}' ,
|
||
|
#'imgAlignCenter' : '{{ \a }}' ,
|
||
|
# Table attributes
|
||
|
'tableTitleRowOpen' : '||! ' ,
|
||
|
'tableTitleRowClose' : '||' ,
|
||
|
'tableTitleCellSep' : ' ||!' ,
|
||
|
'tableRowOpen' : '||' ,
|
||
|
'tableRowClose' : '||' ,
|
||
|
'tableCellSep' : ' ||' ,
|
||
|
},
|
||
|
|
||
|
# http://en.wikipedia.org/wiki/Help:Editing
|
||
|
'wiki': {
|
||
|
'title1' : '== \a ==' ,
|
||
|
'title2' : '=== \a ===' ,
|
||
|
'title3' : '==== \a ====' ,
|
||
|
'title4' : '===== \a =====' ,
|
||
|
'title5' : '====== \a ======',
|
||
|
'blockVerbOpen' : '<pre>' ,
|
||
|
'blockVerbClose' : '</pre>' ,
|
||
|
'blockQuoteOpen' : '<blockquote>' ,
|
||
|
'blockQuoteClose' : '</blockquote>' ,
|
||
|
'fontMonoOpen' : '<tt>' ,
|
||
|
'fontMonoClose' : '</tt>' ,
|
||
|
'fontBoldOpen' : "'''" ,
|
||
|
'fontBoldClose' : "'''" ,
|
||
|
'fontItalicOpen' : "''" ,
|
||
|
'fontItalicClose' : "''" ,
|
||
|
'fontUnderlineOpen' : '<u>' ,
|
||
|
'fontUnderlineClose' : '</u>' ,
|
||
|
'fontStrikeOpen' : '<s>' ,
|
||
|
'fontStrikeClose' : '</s>' ,
|
||
|
#XXX Mixed lists not working: *#* list inside numlist inside list
|
||
|
'listItemLine' : '*' ,
|
||
|
'numlistItemLine' : '#' ,
|
||
|
'deflistItem1Open' : '; ' ,
|
||
|
'deflistItem2LinePrefix': ': ' ,
|
||
|
'bar1' : '----' ,
|
||
|
'url' : '[\a]' ,
|
||
|
'urlMark' : '[\a \a]' ,
|
||
|
'email' : 'mailto:\a' ,
|
||
|
'emailMark' : '[mailto:\a \a]' ,
|
||
|
# [[Image:foo.png|right|Optional alt/caption text]] (right, left, center, none)
|
||
|
'img' : '[[Image:\a~A~]]' ,
|
||
|
'_imgAlignLeft' : '|left' ,
|
||
|
'_imgAlignCenter' : '|center' ,
|
||
|
'_imgAlignRight' : '|right' ,
|
||
|
# {| border="1" cellspacing="0" cellpadding="4" align="center"
|
||
|
'tableOpen' : '{|~A~~B~ cellpadding="4"',
|
||
|
'tableClose' : '|}' ,
|
||
|
'tableRowOpen' : '|-\n| ' ,
|
||
|
'tableTitleRowOpen' : '|-\n! ' ,
|
||
|
'tableCellSep' : ' || ' ,
|
||
|
'tableTitleCellSep' : ' !! ' ,
|
||
|
'_tableBorder' : ' border="1"' ,
|
||
|
'_tableAlignCenter' : ' align="center"' ,
|
||
|
'comment' : '<!-- \a -->' ,
|
||
|
'TOC' : '__TOC__' ,
|
||
|
},
|
||
|
|
||
|
# http://www.inference.phy.cam.ac.uk/mackay/mgp/SYNTAX
|
||
|
# http://en.wikipedia.org/wiki/MagicPoint
|
||
|
'mgp': {
|
||
|
'paragraphOpen' : '%font "normal", size 5' ,
|
||
|
'title1' : '%page\n\n\a\n' ,
|
||
|
'title2' : '%page\n\n\a\n' ,
|
||
|
'title3' : '%page\n\n\a\n' ,
|
||
|
'title4' : '%page\n\n\a\n' ,
|
||
|
'title5' : '%page\n\n\a\n' ,
|
||
|
'blockVerbOpen' : '%font "mono"' ,
|
||
|
'blockVerbClose' : '%font "normal"' ,
|
||
|
'blockQuoteOpen' : '%prefix " "' ,
|
||
|
'blockQuoteClose' : '%prefix " "' ,
|
||
|
'fontMonoOpen' : '\n%cont, font "mono"\n' ,
|
||
|
'fontMonoClose' : '\n%cont, font "normal"\n' ,
|
||
|
'fontBoldOpen' : '\n%cont, font "normal-b"\n' ,
|
||
|
'fontBoldClose' : '\n%cont, font "normal"\n' ,
|
||
|
'fontItalicOpen' : '\n%cont, font "normal-i"\n' ,
|
||
|
'fontItalicClose' : '\n%cont, font "normal"\n' ,
|
||
|
'fontUnderlineOpen' : '\n%cont, fore "cyan"\n' ,
|
||
|
'fontUnderlineClose' : '\n%cont, fore "white"\n' ,
|
||
|
'listItemLine' : '\t' ,
|
||
|
'numlistItemLine' : '\t' ,
|
||
|
'numlistItemOpen' : '\a. ' ,
|
||
|
'deflistItem1Open' : '\t\n%cont, font "normal-b"\n',
|
||
|
'deflistItem1Close' : '\n%cont, font "normal"\n' ,
|
||
|
'bar1' : '%bar "white" 5' ,
|
||
|
'bar2' : '%pause' ,
|
||
|
'url' : '\n%cont, fore "cyan"\n\a' +\
|
||
|
'\n%cont, fore "white"\n' ,
|
||
|
'urlMark' : '\a \n%cont, fore "cyan"\n\a'+\
|
||
|
'\n%cont, fore "white"\n' ,
|
||
|
'email' : '\n%cont, fore "cyan"\n\a' +\
|
||
|
'\n%cont, fore "white"\n' ,
|
||
|
'emailMark' : '\a \n%cont, fore "cyan"\n\a'+\
|
||
|
'\n%cont, fore "white"\n' ,
|
||
|
'img' : '~A~\n%newimage "\a"\n%left\n',
|
||
|
'_imgAlignLeft' : '\n%left' ,
|
||
|
'_imgAlignRight' : '\n%right' ,
|
||
|
'_imgAlignCenter' : '\n%center' ,
|
||
|
'comment' : '%% \a' ,
|
||
|
'pageBreak' : '%page\n\n\n' ,
|
||
|
'EOD' : '%%EOD'
|
||
|
},
|
||
|
|
||
|
# man groff_man ; man 7 groff
|
||
|
'man': {
|
||
|
'paragraphOpen' : '.P' ,
|
||
|
'title1' : '.SH \a' ,
|
||
|
'title2' : '.SS \a' ,
|
||
|
'title3' : '.SS \a' ,
|
||
|
'title4' : '.SS \a' ,
|
||
|
'title5' : '.SS \a' ,
|
||
|
'blockVerbOpen' : '.nf' ,
|
||
|
'blockVerbClose' : '.fi\n' ,
|
||
|
'blockQuoteOpen' : '.RS' ,
|
||
|
'blockQuoteClose' : '.RE' ,
|
||
|
'fontBoldOpen' : '\\fB' ,
|
||
|
'fontBoldClose' : '\\fR' ,
|
||
|
'fontItalicOpen' : '\\fI' ,
|
||
|
'fontItalicClose' : '\\fR' ,
|
||
|
'listOpen' : '.RS' ,
|
||
|
'listItemOpen' : '.IP \(bu 3\n',
|
||
|
'listClose' : '.RE' ,
|
||
|
'numlistOpen' : '.RS' ,
|
||
|
'numlistItemOpen' : '.IP \a. 3\n',
|
||
|
'numlistClose' : '.RE' ,
|
||
|
'deflistItem1Open' : '.TP\n' ,
|
||
|
'bar1' : '\n\n' ,
|
||
|
'url' : '\a' ,
|
||
|
'urlMark' : '\a (\a)',
|
||
|
'email' : '\a' ,
|
||
|
'emailMark' : '\a (\a)',
|
||
|
'img' : '\a' ,
|
||
|
'tableOpen' : '.TS\n~A~~B~tab(^); ~C~.',
|
||
|
'tableClose' : '.TE' ,
|
||
|
'tableRowOpen' : ' ' ,
|
||
|
'tableCellSep' : '^' ,
|
||
|
'_tableAlignCenter' : 'center, ',
|
||
|
'_tableBorder' : 'allbox, ',
|
||
|
'_tableColAlignLeft' : 'l' ,
|
||
|
'_tableColAlignRight' : 'r' ,
|
||
|
'_tableColAlignCenter' : 'c' ,
|
||
|
'comment' : '.\\" \a'
|
||
|
},
|
||
|
|
||
|
'pm6': {
|
||
|
'paragraphOpen' : '<@Normal:>' ,
|
||
|
'title1' : '<@Title1:>\a',
|
||
|
'title2' : '<@Title2:>\a',
|
||
|
'title3' : '<@Title3:>\a',
|
||
|
'title4' : '<@Title4:>\a',
|
||
|
'title5' : '<@Title5:>\a',
|
||
|
'blockVerbOpen' : '<@PreFormat:>' ,
|
||
|
'blockQuoteLine' : '<@Quote:>' ,
|
||
|
'fontMonoOpen' : '<FONT "Lucida Console"><SIZE 9>' ,
|
||
|
'fontMonoClose' : '<SIZE$><FONT$>',
|
||
|
'fontBoldOpen' : '<B>' ,
|
||
|
'fontBoldClose' : '<P>' ,
|
||
|
'fontItalicOpen' : '<I>' ,
|
||
|
'fontItalicClose' : '<P>' ,
|
||
|
'fontUnderlineOpen' : '<U>' ,
|
||
|
'fontUnderlineClose' : '<P>' ,
|
||
|
'listOpen' : '<@Bullet:>' ,
|
||
|
'listItemOpen' : '*\t' , # \x95 == ~U -> changed to "*"
|
||
|
'numlistOpen' : '<@Bullet:>' ,
|
||
|
'numlistItemOpen' : '*\t' ,
|
||
|
'bar1' : '\a' ,
|
||
|
'url' : '<U>\a<P>' , # underline
|
||
|
'urlMark' : '\a <U>\a<P>' ,
|
||
|
'email' : '\a' ,
|
||
|
'emailMark' : '\a \a' ,
|
||
|
'img' : '\a'
|
||
|
},
|
||
|
# http://www.wikicreole.org/wiki/AllMarkup
|
||
|
'creole': {
|
||
|
'title1' : '= \a =' ,
|
||
|
'title2' : '== \a ==' ,
|
||
|
'title3' : '=== \a ===' ,
|
||
|
'title4' : '==== \a ====' ,
|
||
|
'title5' : '===== \a =====',
|
||
|
'blockVerbOpen' : '{{{' ,
|
||
|
'blockVerbClose' : '}}}' ,
|
||
|
'blockQuoteLine' : ' ' ,
|
||
|
# 'fontMonoOpen' : '##' , # planned for 2.0,
|
||
|
# 'fontMonoClose' : '##' , # meanwhile we disable it
|
||
|
'fontBoldOpen' : '**' ,
|
||
|
'fontBoldClose' : '**' ,
|
||
|
'fontItalicOpen' : '//' ,
|
||
|
'fontItalicClose' : '//' ,
|
||
|
'fontUnderlineOpen' : '//' , # no underline in 1.0, planned for 2.0,
|
||
|
'fontUnderlineClose' : '//' , # meanwhile we can use italic (emphasized)
|
||
|
# 'fontStrikeOpen' : '--' , # planned for 2.0,
|
||
|
# 'fontStrikeClose' : '--' , # meanwhile we disable it
|
||
|
'listItemLine' : '*' ,
|
||
|
'numlistItemLine' : '#' ,
|
||
|
'deflistItem2LinePrefix': ':' ,
|
||
|
'bar1' : '----' ,
|
||
|
'url' : '[[\a]]' ,
|
||
|
'urlMark' : '[[\a|\a]]' ,
|
||
|
'img' : '{{\a}}' ,
|
||
|
'tableTitleRowOpen' : '|= ' ,
|
||
|
'tableTitleRowClose' : '|' ,
|
||
|
'tableTitleCellSep' : ' |= ' ,
|
||
|
'tableRowOpen' : '| ' ,
|
||
|
'tableRowClose' : ' |' ,
|
||
|
'tableCellSep' : ' | ' ,
|
||
|
# TODO: placeholder (mark for unknown syntax)
|
||
|
# if possible: http://www.wikicreole.org/wiki/Placeholder
|
||
|
}
|
||
|
}
|
||
|
|
||
|
# Exceptions for --css-sugar
|
||
|
if config['css-sugar'] and config['target'] in ('html','xhtml'):
|
||
|
# Change just HTML because XHTML inherits it
|
||
|
htmltags = alltags['html']
|
||
|
# Table with no cellpadding
|
||
|
htmltags['tableOpen'] = htmltags['tableOpen'].replace(' CELLPADDING="4"', '')
|
||
|
# DIVs
|
||
|
htmltags['tocOpen' ] = '<DIV CLASS="toc">'
|
||
|
htmltags['tocClose'] = '</DIV>'
|
||
|
htmltags['bodyOpen'] = '<DIV CLASS="body" ID="body">'
|
||
|
htmltags['bodyClose']= '</DIV>'
|
||
|
|
||
|
# Make the HTML -> XHTML inheritance
|
||
|
xhtml = alltags['html'].copy()
|
||
|
for key in xhtml.keys(): xhtml[key] = xhtml[key].lower()
|
||
|
# Some like HTML tags as lowercase, some don't... (headers out)
|
||
|
if HTML_LOWER: alltags['html'] = xhtml.copy()
|
||
|
xhtml.update(alltags['xhtml'])
|
||
|
alltags['xhtml'] = xhtml.copy()
|
||
|
|
||
|
# Compose the target tags dictionary
|
||
|
tags = {}
|
||
|
target_tags = alltags[config['target']].copy()
|
||
|
|
||
|
for key in keys: tags[key] = '' # create empty keys
|
||
|
for key in target_tags.keys():
|
||
|
tags[key] = maskEscapeChar(target_tags[key]) # populate
|
||
|
|
||
|
# Map strong line to pagebreak
|
||
|
if rules['mapbar2pagebreak'] and tags['pageBreak']:
|
||
|
tags['bar2'] = tags['pageBreak']
|
||
|
|
||
|
# Map strong line to separator if not defined
|
||
|
if not tags['bar2'] and tags['bar1']:
|
||
|
tags['bar2'] = tags['bar1']
|
||
|
|
||
|
return tags
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
def getRules(config):
|
||
|
"Returns all the target-specific syntax rules"
|
||
|
|
||
|
ret = {}
|
||
|
allrules = [
|
||
|
|
||
|
# target rules (ON/OFF)
|
||
|
'linkable', # target supports external links
|
||
|
'tableable', # target supports tables
|
||
|
'imglinkable', # target supports images as links
|
||
|
'imgalignable', # target supports image alignment
|
||
|
'imgasdefterm', # target supports image as definition term
|
||
|
'autonumberlist', # target supports numbered lists natively
|
||
|
'autonumbertitle', # target supports numbered titles natively
|
||
|
'stylable', # target supports external style files
|
||
|
'parainsidelist', # lists items supports paragraph
|
||
|
'compactlist', # separate enclosing tags for compact lists
|
||
|
'spacedlistitem', # lists support blank lines between items
|
||
|
'listnotnested', # lists cannot be nested
|
||
|
'quotenotnested', # quotes cannot be nested
|
||
|
'verbblocknotescaped', # don't escape specials in verb block
|
||
|
'verbblockfinalescape', # do final escapes in verb block
|
||
|
'escapeurl', # escape special in link URL
|
||
|
'labelbeforelink', # label comes before the link on the tag
|
||
|
'onelinepara', # dump paragraph as a single long line
|
||
|
'tabletitlerowinbold', # manually bold any cell on table titles
|
||
|
'tablecellstrip', # strip extra spaces from each table cell
|
||
|
'tablecellspannable', # the table cells can have span attribute
|
||
|
'tablecellmulticol', # separate open+close tags for multicol cells
|
||
|
'barinsidequote', # bars are allowed inside quote blocks
|
||
|
'finalescapetitle', # perform final escapes on title lines
|
||
|
'autotocnewpagebefore', # break page before automatic TOC
|
||
|
'autotocnewpageafter', # break page after automatic TOC
|
||
|
'autotocwithbars', # automatic TOC surrounded by bars
|
||
|
'mapbar2pagebreak', # map the strong bar to a page break
|
||
|
'titleblocks', # titles must be on open/close section blocks
|
||
|
|
||
|
# Target code beautify (ON/OFF)
|
||
|
'indentverbblock', # add leading spaces to verb block lines
|
||
|
'breaktablecell', # break lines after any table cell
|
||
|
'breaktablelineopen', # break line after opening table line
|
||
|
'notbreaklistopen', # don't break line after opening a new list
|
||
|
'keepquoteindent', # don't remove the leading TABs on quotes
|
||
|
'keeplistindent', # don't remove the leading spaces on lists
|
||
|
'blankendautotoc', # append a blank line at the auto TOC end
|
||
|
'tagnotindentable', # tags must be placed at the line beginning
|
||
|
'spacedlistitemopen', # append a space after the list item open tag
|
||
|
'spacednumlistitemopen',# append a space after the numlist item open tag
|
||
|
'deflisttextstrip', # strip the contents of the deflist text
|
||
|
'blanksaroundpara', # put a blank line before and after paragraphs
|
||
|
'blanksaroundverb', # put a blank line before and after verb blocks
|
||
|
'blanksaroundquote', # put a blank line before and after quotes
|
||
|
'blanksaroundlist', # put a blank line before and after lists
|
||
|
'blanksaroundnumlist', # put a blank line before and after numlists
|
||
|
'blanksarounddeflist', # put a blank line before and after deflists
|
||
|
'blanksaroundtable', # put a blank line before and after tables
|
||
|
'blanksaroundbar', # put a blank line before and after bars
|
||
|
'blanksaroundtitle', # put a blank line before and after titles
|
||
|
'blanksaroundnumtitle', # put a blank line before and after numtitles
|
||
|
|
||
|
# Value settings
|
||
|
'listmaxdepth', # maximum depth for lists
|
||
|
'quotemaxdepth', # maximum depth for quotes
|
||
|
'tablecellaligntype', # type of table cell align: cell, column
|
||
|
]
|
||
|
|
||
|
rules_bank = {
|
||
|
'txt': {
|
||
|
'indentverbblock':1,
|
||
|
'spacedlistitem':1,
|
||
|
'parainsidelist':1,
|
||
|
'keeplistindent':1,
|
||
|
'barinsidequote':1,
|
||
|
'autotocwithbars':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'art': {
|
||
|
#TIP art inherits all TXT rules
|
||
|
},
|
||
|
'html': {
|
||
|
'indentverbblock':1,
|
||
|
'linkable':1,
|
||
|
'stylable':1,
|
||
|
'escapeurl':1,
|
||
|
'imglinkable':1,
|
||
|
'imgalignable':1,
|
||
|
'imgasdefterm':1,
|
||
|
'autonumberlist':1,
|
||
|
'spacedlistitem':1,
|
||
|
'parainsidelist':1,
|
||
|
'tableable':1,
|
||
|
'tablecellstrip':1,
|
||
|
'breaktablecell':1,
|
||
|
'breaktablelineopen':1,
|
||
|
'keeplistindent':1,
|
||
|
'keepquoteindent':1,
|
||
|
'barinsidequote':1,
|
||
|
'autotocwithbars':1,
|
||
|
'tablecellspannable':1,
|
||
|
'tablecellaligntype':'cell',
|
||
|
|
||
|
# 'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'xhtml': {
|
||
|
#TIP xhtml inherits all HTML rules
|
||
|
},
|
||
|
'sgml': {
|
||
|
'linkable':1,
|
||
|
'escapeurl':1,
|
||
|
'autonumberlist':1,
|
||
|
'spacedlistitem':1,
|
||
|
'tableable':1,
|
||
|
'tablecellstrip':1,
|
||
|
'blankendautotoc':1,
|
||
|
'quotenotnested':1,
|
||
|
'keeplistindent':1,
|
||
|
'keepquoteindent':1,
|
||
|
'barinsidequote':1,
|
||
|
'finalescapetitle':1,
|
||
|
'tablecellaligntype':'column',
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'dbk': {
|
||
|
'linkable':1,
|
||
|
'tableable':0, # activate when table tags are ready
|
||
|
'imglinkable':1,
|
||
|
'imgalignable':1,
|
||
|
'imgasdefterm':1,
|
||
|
'autonumberlist':1,
|
||
|
'autonumbertitle':1,
|
||
|
'parainsidelist':1,
|
||
|
'spacedlistitem':1,
|
||
|
'titleblocks':1,
|
||
|
},
|
||
|
'mgp': {
|
||
|
'tagnotindentable':1,
|
||
|
'spacedlistitem':1,
|
||
|
'imgalignable':1,
|
||
|
'autotocnewpagebefore':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
# 'blanksaroundtitle':1,
|
||
|
# 'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'tex': {
|
||
|
'stylable':1,
|
||
|
'escapeurl':1,
|
||
|
'autonumberlist':1,
|
||
|
'autonumbertitle':1,
|
||
|
'spacedlistitem':1,
|
||
|
'compactlist':1,
|
||
|
'parainsidelist':1,
|
||
|
'tableable':1,
|
||
|
'tablecellstrip':1,
|
||
|
'tabletitlerowinbold':1,
|
||
|
'verbblocknotescaped':1,
|
||
|
'keeplistindent':1,
|
||
|
'listmaxdepth':4, # deflist is 6
|
||
|
'quotemaxdepth':6,
|
||
|
'barinsidequote':1,
|
||
|
'finalescapetitle':1,
|
||
|
'autotocnewpageafter':1,
|
||
|
'mapbar2pagebreak':1,
|
||
|
'tablecellaligntype':'column',
|
||
|
'tablecellmulticol':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'lout': {
|
||
|
'keepquoteindent':1,
|
||
|
'deflisttextstrip':1,
|
||
|
'escapeurl':1,
|
||
|
'verbblocknotescaped':1,
|
||
|
'imgalignable':1,
|
||
|
'mapbar2pagebreak':1,
|
||
|
'titleblocks':1,
|
||
|
'autonumberlist':1,
|
||
|
'parainsidelist':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'moin': {
|
||
|
'spacedlistitem':1,
|
||
|
'linkable':1,
|
||
|
'keeplistindent':1,
|
||
|
'tableable':1,
|
||
|
'barinsidequote':1,
|
||
|
'tabletitlerowinbold':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autotocwithbars':1,
|
||
|
'tablecellaligntype':'cell',
|
||
|
'deflisttextstrip':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
# 'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'gwiki': {
|
||
|
'spacedlistitem':1,
|
||
|
'linkable':1,
|
||
|
'keeplistindent':1,
|
||
|
'tableable':1,
|
||
|
'tabletitlerowinbold':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autonumberlist':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
# 'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'adoc': {
|
||
|
'spacedlistitem':1,
|
||
|
'linkable':1,
|
||
|
'keeplistindent':1,
|
||
|
'autonumberlist':1,
|
||
|
'autonumbertitle':1,
|
||
|
'listnotnested':1,
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'doku': {
|
||
|
'indentverbblock':1, # DokuWiki uses ' ' to mark verb blocks
|
||
|
'spacedlistitem':1,
|
||
|
'linkable':1,
|
||
|
'keeplistindent':1,
|
||
|
'tableable':1,
|
||
|
'barinsidequote':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autotocwithbars':1,
|
||
|
'autonumberlist':1,
|
||
|
'imgalignable':1,
|
||
|
'tablecellaligntype':'cell',
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'pmw': {
|
||
|
'indentverbblock':1,
|
||
|
'spacedlistitem':1,
|
||
|
'linkable':1,
|
||
|
'labelbeforelink':1,
|
||
|
# 'keeplistindent':1,
|
||
|
'tableable':1,
|
||
|
'barinsidequote':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autotocwithbars':1,
|
||
|
'autonumberlist':1,
|
||
|
'spacedlistitemopen':1,
|
||
|
'spacednumlistitemopen':1,
|
||
|
'imgalignable':1,
|
||
|
'tabletitlerowinbold':1,
|
||
|
'tablecellaligntype':'cell',
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'wiki': {
|
||
|
'linkable':1,
|
||
|
'tableable':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autotocwithbars':1,
|
||
|
'spacedlistitemopen':1,
|
||
|
'spacednumlistitemopen':1,
|
||
|
'deflisttextstrip':1,
|
||
|
'autonumberlist':1,
|
||
|
'imgalignable':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'man': {
|
||
|
'spacedlistitem':1,
|
||
|
'tagnotindentable':1,
|
||
|
'tableable':1,
|
||
|
'tablecellaligntype':'column',
|
||
|
'tabletitlerowinbold':1,
|
||
|
'tablecellstrip':1,
|
||
|
'barinsidequote':1,
|
||
|
'parainsidelist':0,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
# 'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'pm6': {
|
||
|
'keeplistindent':1,
|
||
|
'verbblockfinalescape':1,
|
||
|
#TODO add support for these
|
||
|
# maybe set a JOINNEXT char and do it on addLineBreaks()
|
||
|
'notbreaklistopen':1,
|
||
|
'barinsidequote':1,
|
||
|
'autotocwithbars':1,
|
||
|
'onelinepara':1,
|
||
|
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
# 'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
# 'blanksaroundtable':1,
|
||
|
# 'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
'blanksaroundnumtitle':1,
|
||
|
},
|
||
|
'creole': {
|
||
|
'linkable':1,
|
||
|
'tableable':1,
|
||
|
'imglinkable':1,
|
||
|
'tablecellstrip':1,
|
||
|
'autotocwithbars':1,
|
||
|
'spacedlistitemopen':1,
|
||
|
'spacednumlistitemopen':1,
|
||
|
'deflisttextstrip':1,
|
||
|
'verbblocknotescaped':1,
|
||
|
'blanksaroundpara':1,
|
||
|
'blanksaroundverb':1,
|
||
|
'blanksaroundquote':1,
|
||
|
'blanksaroundlist':1,
|
||
|
'blanksaroundnumlist':1,
|
||
|
'blanksarounddeflist':1,
|
||
|
'blanksaroundtable':1,
|
||
|
'blanksaroundbar':1,
|
||
|
'blanksaroundtitle':1,
|
||
|
},
|
||
|
}
|
||
|
|
||
|
# Exceptions for --css-sugar
|
||
|
if config['css-sugar'] and config['target'] in ('html','xhtml'):
|
||
|
rules_bank['html']['indentverbblock'] = 0
|
||
|
rules_bank['html']['autotocwithbars'] = 0
|
||
|
|
||
|
# Get the target specific rules
|
||
|
if config['target'] == 'xhtml':
|
||
|
myrules = rules_bank['html'].copy() # inheritance
|
||
|
myrules.update(rules_bank['xhtml']) # get XHTML specific
|
||
|
elif config['target'] == 'art':
|
||
|
myrules = rules_bank['txt'].copy() # inheritance
|
||
|
if config['slides']:
|
||
|
myrules['blanksaroundtitle'] = 0
|
||
|
myrules['blanksaroundnumtitle'] = 0
|
||
|
else:
|
||
|
myrules = rules_bank[config['target']].copy()
|
||
|
|
||
|
# Populate return dictionary
|
||
|
for key in allrules: ret[key] = 0 # reset all
|
||
|
ret.update(myrules) # get rules
|
||
|
|
||
|
return ret
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
def getRegexes():
|
||
|
"Returns all the regexes used to find the t2t marks"
|
||
|
|
||
|
bank = {
|
||
|
'blockVerbOpen':
|
||
|
re.compile(r'^```\s*$'),
|
||
|
'blockVerbClose':
|
||
|
re.compile(r'^```\s*$'),
|
||
|
'blockRawOpen':
|
||
|
re.compile(r'^"""\s*$'),
|
||
|
'blockRawClose':
|
||
|
re.compile(r'^"""\s*$'),
|
||
|
'blockTaggedOpen':
|
||
|
re.compile(r"^'''\s*$"),
|
||
|
'blockTaggedClose':
|
||
|
re.compile(r"^'''\s*$"),
|
||
|
'blockCommentOpen':
|
||
|
re.compile(r'^%%%\s*$'),
|
||
|
'blockCommentClose':
|
||
|
re.compile(r'^%%%\s*$'),
|
||
|
'quote':
|
||
|
re.compile(r'^\t+'),
|
||
|
'1lineVerb':
|
||
|
re.compile(r'^``` (?=.)'),
|
||
|
'1lineRaw':
|
||
|
re.compile(r'^""" (?=.)'),
|
||
|
'1lineTagged':
|
||
|
re.compile(r"^''' (?=.)"),
|
||
|
# mono, raw, bold, italic, underline:
|
||
|
# - marks must be glued with the contents, no boundary spaces
|
||
|
# - they are greedy, so in ****bold****, turns to <b>**bold**</b>
|
||
|
'fontMono':
|
||
|
re.compile( r'``([^\s](|.*?[^\s])`*)``'),
|
||
|
'raw':
|
||
|
re.compile( r'""([^\s](|.*?[^\s])"*)""'),
|
||
|
'tagged':
|
||
|
re.compile( r"''([^\s](|.*?[^\s])'*)''"),
|
||
|
'fontBold':
|
||
|
re.compile(r'\*\*([^\s](|.*?[^\s])\**)\*\*'),
|
||
|
'fontItalic':
|
||
|
re.compile( r'//([^\s](|.*?[^\s])/*)//'),
|
||
|
'fontUnderline':
|
||
|
re.compile( r'__([^\s](|.*?[^\s])_*)__'),
|
||
|
'fontStrike':
|
||
|
re.compile( r'--([^\s](|.*?[^\s])-*)--'),
|
||
|
'list':
|
||
|
re.compile(r'^( *)(-) (?=[^ ])'),
|
||
|
'numlist':
|
||
|
re.compile(r'^( *)(\+) (?=[^ ])'),
|
||
|
'deflist':
|
||
|
re.compile(r'^( *)(:) (.*)$'),
|
||
|
'listclose':
|
||
|
re.compile(r'^( *)([-+:])\s*$'),
|
||
|
'bar':
|
||
|
re.compile(r'^(\s*)([_=-]{20,})\s*$'),
|
||
|
'table':
|
||
|
re.compile(r'^ *\|\|? '),
|
||
|
'blankline':
|
||
|
re.compile(r'^\s*$'),
|
||
|
'comment':
|
||
|
re.compile(r'^%'),
|
||
|
|
||
|
# Auxiliary tag regexes
|
||
|
'_imgAlign' : re.compile(r'~A~', re.I),
|
||
|
'_tableAlign' : re.compile(r'~A~', re.I),
|
||
|
'_anchor' : re.compile(r'~A~', re.I),
|
||
|
'_tableBorder' : re.compile(r'~B~', re.I),
|
||
|
'_tableColAlign' : re.compile(r'~C~', re.I),
|
||
|
'_tableCellColSpan': re.compile(r'~S~', re.I),
|
||
|
'_tableCellAlign' : re.compile(r'~A~', re.I),
|
||
|
}
|
||
|
|
||
|
# Special char to place data on TAGs contents (\a == bell)
|
||
|
bank['x'] = re.compile('\a')
|
||
|
|
||
|
# %%macroname [ (formatting) ]
|
||
|
bank['macros'] = re.compile(r'%%%%(?P<name>%s)\b(\((?P<fmt>.*?)\))?' % (
|
||
|
'|'.join(MACROS.keys())), re.I)
|
||
|
|
||
|
# %%TOC special macro for TOC positioning
|
||
|
bank['toc'] = re.compile(r'^ *%%toc\s*$', re.I)
|
||
|
|
||
|
# Almost complicated title regexes ;)
|
||
|
titskel = r'^ *(?P<id>%s)(?P<txt>%s)\1(\[(?P<label>[\w-]*)\])?\s*$'
|
||
|
bank[ 'title'] = re.compile(titskel%('[=]{1,5}','[^=](|.*[^=])'))
|
||
|
bank['numtitle'] = re.compile(titskel%('[+]{1,5}','[^+](|.*[^+])'))
|
||
|
|
||
|
### Complicated regexes begin here ;)
|
||
|
#
|
||
|
# Textual descriptions on --help's style: [...] is optional, | is OR
|
||
|
|
||
|
|
||
|
### First, some auxiliary variables
|
||
|
#
|
||
|
|
||
|
# [image.EXT]
|
||
|
patt_img = r'\[([\w_,.+%$#@!?+~/-]+\.(png|jpe?g|gif|eps|bmp))\]'
|
||
|
|
||
|
# Link things
|
||
|
# http://www.gbiv.com/protocols/uri/rfc/rfc3986.html
|
||
|
# pchar: A-Za-z._~- / %FF / !$&'()*+,;= / :@
|
||
|
# Recomended order: scheme://user:pass@domain/path?query=foo#anchor
|
||
|
# Also works : scheme://user:pass@domain/path#anchor?query=foo
|
||
|
# TODO form: !'():
|
||
|
urlskel = {
|
||
|
'proto' : r'(https?|ftp|news|telnet|gopher|wais)://',
|
||
|
'guess' : r'(www[23]?|ftp)\.', # w/out proto, try to guess
|
||
|
'login' : r'A-Za-z0-9_.-', # for ftp://login@domain.com
|
||
|
'pass' : r'[^ @]*', # for ftp://login:pass@dom.com
|
||
|
'chars' : r'A-Za-z0-9%._/~:,=$@&+-', # %20(space), :80(port), D&D
|
||
|
'anchor': r'A-Za-z0-9%._-', # %nn(encoded)
|
||
|
'form' : r'A-Za-z0-9/%&=+:;.,$@*_-', # .,@*_-(as is)
|
||
|
'punct' : r'.,;:!?'
|
||
|
}
|
||
|
|
||
|
# username [ :password ] @
|
||
|
patt_url_login = r'([%s]+(:%s)?@)?'%(urlskel['login'],urlskel['pass'])
|
||
|
|
||
|
# [ http:// ] [ username:password@ ] domain.com [ / ]
|
||
|
# [ #anchor | ?form=data ]
|
||
|
retxt_url = r'\b(%s%s|%s)[%s]+\b/*(\?[%s]+)?(#[%s]*)?'%(
|
||
|
urlskel['proto'],patt_url_login, urlskel['guess'],
|
||
|
urlskel['chars'],urlskel['form'],urlskel['anchor'])
|
||
|
|
||
|
# filename | [ filename ] #anchor
|
||
|
retxt_url_local = r'[%s]+|[%s]*(#[%s]*)'%(
|
||
|
urlskel['chars'],urlskel['chars'],urlskel['anchor'])
|
||
|
|
||
|
# user@domain [ ?form=data ]
|
||
|
patt_email = r'\b[%s]+@([A-Za-z0-9_-]+\.)+[A-Za-z]{2,4}\b(\?[%s]+)?'%(
|
||
|
urlskel['login'],urlskel['form'])
|
||
|
|
||
|
# Saving for future use
|
||
|
bank['_urlskel'] = urlskel
|
||
|
|
||
|
### And now the real regexes
|
||
|
#
|
||
|
|
||
|
bank['email'] = re.compile(patt_email,re.I)
|
||
|
|
||
|
# email | url
|
||
|
bank['link'] = re.compile(r'%s|%s'%(retxt_url,patt_email), re.I)
|
||
|
|
||
|
# \[ label | imagetag url | email | filename \]
|
||
|
bank['linkmark'] = re.compile(
|
||
|
r'\[(?P<label>%s|[^]]+) (?P<link>%s|%s|%s)\]'%(
|
||
|
patt_img, retxt_url, patt_email, retxt_url_local),
|
||
|
re.L+re.I)
|
||
|
|
||
|
# Image
|
||
|
bank['img'] = re.compile(patt_img, re.L+re.I)
|
||
|
|
||
|
# Special things
|
||
|
bank['special'] = re.compile(r'^%!\s*')
|
||
|
return bank
|
||
|
### END OF regex nightmares
|
||
|
|
||
|
################# functions for the ASCII Art backend ########################
|
||
|
|
||
|
def aa_line(char, length):
|
||
|
return char * length
|
||
|
|
||
|
def aa_box(txt, length):
|
||
|
len_txt = len(txt)
|
||
|
nspace = int((length - len_txt - 4) / 2)
|
||
|
line_box = " " * nspace + AA['corner'] + AA['border'] * (len_txt + 2) + AA['corner']
|
||
|
# <----- nspace " " -----> "+" <----- len_txt+2 "-" -----> "+"
|
||
|
# +-------------------------------+
|
||
|
# | all theeeeeeeeeeeeeeeeee text |
|
||
|
# <----- nspace " " -----> "| " <--------- txt ---------> " |"
|
||
|
line_txt = " " * nspace + AA['side'] + ' ' + txt + ' ' + AA['side']
|
||
|
return [line_box, line_txt, line_box]
|
||
|
|
||
|
def aa_header(header_data, length, n, end):
|
||
|
header = [aa_line(AA['bar2'], length)]
|
||
|
header.extend(['']*int(n)) #n must be int!
|
||
|
for h in 'HEADER1', 'HEADER2', 'HEADER3':
|
||
|
if header_data[h]:
|
||
|
header.extend(aa_box(header_data[h], length))
|
||
|
header.extend(['']*int(n)) #Again, n must be int!!
|
||
|
header.extend(['']*end)
|
||
|
header.append(aa_line(AA['bar2'], length))
|
||
|
return header
|
||
|
|
||
|
def aa_slide(title, length):
|
||
|
res = [aa_line(AA['bar2'], length)]
|
||
|
res.append('')
|
||
|
res.append(title.center(length))
|
||
|
res.append('')
|
||
|
res.append(aa_line(AA['bar2'], length))
|
||
|
return res
|
||
|
|
||
|
def aa_table(table):
|
||
|
data = [row[2:-2].split(' | ') for row in table]
|
||
|
n = max([len(line) for line in data])
|
||
|
data = [line + (n - len(line)) * [''] for line in data]
|
||
|
tab = []
|
||
|
for i in range(n):
|
||
|
tab.append([line[i] for line in data])
|
||
|
length = [max([len(el) for el in line]) for line in tab]
|
||
|
res = "+"
|
||
|
for i in range(n):
|
||
|
res = res + (length[i] + 2) * "-" + '+'
|
||
|
ret = []
|
||
|
for line in data:
|
||
|
aff = "|"
|
||
|
ret.append(res)
|
||
|
for j,el in enumerate(line):
|
||
|
aff = aff + " " + el + (length[j] - len(el) + 1) * " " + "|"
|
||
|
ret.append(aff)
|
||
|
ret.append(res)
|
||
|
return ret
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
class error(Exception):
|
||
|
pass
|
||
|
def echo(msg): # for quick debug
|
||
|
print('\033[32;1m%s\033[m'%msg)
|
||
|
def Quit(msg=''):
|
||
|
if msg: print(msg)
|
||
|
sys.exit(0)
|
||
|
def Error(msg):
|
||
|
msg = _("%s: Error: ")%my_name + msg
|
||
|
raise error(msg)
|
||
|
def getTraceback():
|
||
|
try:
|
||
|
from traceback import format_exception
|
||
|
etype, value, tb = sys.exc_info()
|
||
|
return ''.join(format_exception(etype, value, tb))
|
||
|
except: pass
|
||
|
def getUnknownErrorMessage():
|
||
|
msg = '%s\n%s (%s):\n\n%s'%(
|
||
|
_('Sorry! Txt2tags aborted by an unknown error.'),
|
||
|
_('Please send the following Error Traceback to the author'),
|
||
|
my_email, getTraceback())
|
||
|
return msg
|
||
|
def Message(msg,level):
|
||
|
if level <= VERBOSE and not QUIET:
|
||
|
prefix = '-'*5
|
||
|
print("%s %s"%(prefix*level, msg))
|
||
|
def Debug(msg,id_=0,linenr=None):
|
||
|
"Show debug messages, categorized (colored or not)"
|
||
|
if QUIET or not DEBUG: return
|
||
|
if int(id_) not in range(8): id_ = 0
|
||
|
# 0:black 1:red 2:green 3:yellow 4:blue 5:pink 6:cyan 7:white ;1:light
|
||
|
ids = ['INI','CFG','SRC','BLK','HLD','GUI','OUT','DET']
|
||
|
colors_bgdark = ['7;1','1;1','3;1','6;1','4;1','5;1','2;1','7;1']
|
||
|
colors_bglight = ['0' ,'1' ,'3' ,'6' ,'4' ,'5' ,'2' ,'0' ]
|
||
|
if linenr is not None: msg = "LINE %04d: %s"%(linenr,msg)
|
||
|
if COLOR_DEBUG:
|
||
|
if BG_LIGHT: color = colors_bglight[id_]
|
||
|
else : color = colors_bgdark[id_]
|
||
|
msg = '\033[3%sm%s\033[m'%(color,msg)
|
||
|
print("++ %s: %s"%(ids[id_],msg))
|
||
|
|
||
|
def Readfile(file_path, remove_linebreaks=0, ignore_error=0):
|
||
|
data = []
|
||
|
if file_path == '-':
|
||
|
try:
|
||
|
data = sys.stdin.readlines()
|
||
|
except:
|
||
|
if not ignore_error:
|
||
|
Error(_('You must feed me with data on STDIN!'))
|
||
|
else:
|
||
|
try:
|
||
|
f = open(file_path)
|
||
|
data = f.readlines()
|
||
|
f.close()
|
||
|
except:
|
||
|
if not ignore_error:
|
||
|
Error(_("Cannot read file:") + ' ' + file_path)
|
||
|
if remove_linebreaks:
|
||
|
data = [re.sub('[\n\r]+$', '', x) for x in data]
|
||
|
Message(_("File read (%d lines): %s") % (len(data), file_path), 2)
|
||
|
return data
|
||
|
|
||
|
def Savefile(file_path, contents):
|
||
|
try:
|
||
|
f = open(file_path, 'w')
|
||
|
except:
|
||
|
Error(_("Cannot open file for writing:") + ' ' + file_path)
|
||
|
if isinstance(contents, type([])):
|
||
|
doit = f.writelines
|
||
|
else:
|
||
|
doit = f.write
|
||
|
doit(contents)
|
||
|
f.close()
|
||
|
|
||
|
def showdic(dic):
|
||
|
for k in dic.keys(): print("%15s : %s" % (k,dic[k]))
|
||
|
def dotted_spaces(txt=''):
|
||
|
return txt.replace(' ', '.')
|
||
|
|
||
|
# TIP: win env vars http://www.winnetmag.com/Article/ArticleID/23873/23873.html
|
||
|
def get_rc_path():
|
||
|
"Return the full path for the users' RC file"
|
||
|
# Try to get the path from an env var. if yes, we're done
|
||
|
user_defined = os.environ.get('T2TCONFIG')
|
||
|
if user_defined: return user_defined
|
||
|
# Env var not found, so perform automatic path composing
|
||
|
# Set default filename according system platform
|
||
|
rc_names = {'default':'.txt2tagsrc', 'win':'_t2trc'}
|
||
|
rc_file = rc_names.get(sys.platform[:3]) or rc_names['default']
|
||
|
# The file must be on the user directory, but where is this dir?
|
||
|
rc_dir_search = ['HOME', 'HOMEPATH']
|
||
|
for var in rc_dir_search:
|
||
|
rc_dir = os.environ.get(var)
|
||
|
if rc_dir: break
|
||
|
# rc dir found, now we must join dir+file to compose the full path
|
||
|
if rc_dir:
|
||
|
# Compose path and return it if the file exists
|
||
|
rc_path = os.path.join(rc_dir, rc_file)
|
||
|
# On windows, prefix with the drive (%homedrive%: 2k/XP/NT)
|
||
|
if sys.platform.startswith('win'):
|
||
|
rc_drive = os.environ.get('HOMEDRIVE')
|
||
|
rc_path = os.path.join(rc_drive,rc_path)
|
||
|
return rc_path
|
||
|
# Sorry, not found
|
||
|
return ''
|
||
|
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
class CommandLine:
|
||
|
"""
|
||
|
Command Line class - Masters command line
|
||
|
|
||
|
This class checks and extract data from the provided command line.
|
||
|
The --long options and flags are taken from the global OPTIONS,
|
||
|
FLAGS and ACTIONS dictionaries. The short options are registered
|
||
|
here, and also their equivalence to the long ones.
|
||
|
|
||
|
_compose_short_opts() -> str
|
||
|
_compose_long_opts() -> list
|
||
|
Compose the valid short and long options list, on the
|
||
|
'getopt' format.
|
||
|
|
||
|
parse() -> (opts, args)
|
||
|
Call getopt to check and parse the command line.
|
||
|
It expects to receive the command line as a list, and
|
||
|
without the program name (sys.argv[1:]).
|
||
|
|
||
|
get_raw_config() -> [RAW config]
|
||
|
Scans command line and convert the data to the RAW config
|
||
|
format. See ConfigMaster class to the RAW format description.
|
||
|
Optional 'ignore' and 'filter_' arguments are used to filter
|
||
|
in or out specified keys.
|
||
|
|
||
|
compose_cmdline(dict) -> [Command line]
|
||
|
Compose a command line list from an already parsed config
|
||
|
dictionary, generated from RAW by ConfigMaster(). Use
|
||
|
this to compose an optimal command line for a group of
|
||
|
options.
|
||
|
|
||
|
The get_raw_config() calls parse(), so the typical use of this
|
||
|
class is:
|
||
|
|
||
|
raw = CommandLine().get_raw_config(sys.argv[1:])
|
||
|
"""
|
||
|
def __init__(self):
|
||
|
self.all_options = list(OPTIONS.keys())
|
||
|
self.all_flags = list(FLAGS.keys())
|
||
|
self.all_actions = list(ACTIONS.keys())
|
||
|
|
||
|
# short:long options equivalence
|
||
|
self.short_long = {
|
||
|
'C':'config-file',
|
||
|
'h':'help',
|
||
|
'H':'no-headers',
|
||
|
'i':'infile',
|
||
|
'n':'enum-title',
|
||
|
'o':'outfile',
|
||
|
'q':'quiet',
|
||
|
't':'target',
|
||
|
'v':'verbose',
|
||
|
'V':'version',
|
||
|
}
|
||
|
|
||
|
# Compose valid short and long options data for getopt
|
||
|
self.short_opts = self._compose_short_opts()
|
||
|
self.long_opts = self._compose_long_opts()
|
||
|
|
||
|
def _compose_short_opts(self):
|
||
|
"Returns a string like 'hVt:o' with all short options/flags"
|
||
|
ret = []
|
||
|
for opt in self.short_long.keys():
|
||
|
long_ = self.short_long[opt]
|
||
|
if long_ in self.all_options: # is flag or option?
|
||
|
opt = opt+':' # option: have param
|
||
|
ret.append(opt)
|
||
|
#Debug('Valid SHORT options: %s'%ret)
|
||
|
return ''.join(ret)
|
||
|
|
||
|
def _compose_long_opts(self):
|
||
|
"Returns a list with all the valid long options/flags"
|
||
|
ret = [x+'=' for x in self.all_options] # add =
|
||
|
ret.extend(self.all_flags) # flag ON
|
||
|
ret.extend(self.all_actions) # actions
|
||
|
ret.extend(['no-'+x for x in self.all_flags]) # add no-*
|
||
|
ret.extend(['no-style','no-encoding']) # turn OFF
|
||
|
ret.extend(['no-outfile','no-infile']) # turn OFF
|
||
|
ret.extend(['no-dump-config', 'no-dump-source']) # turn OFF
|
||
|
ret.extend(['no-targets']) # turn OFF
|
||
|
#Debug('Valid LONG options: %s'%ret)
|
||
|
return ret
|
||
|
|
||
|
def _tokenize(self, cmd_string=''):
|
||
|
"Convert a command line string to a list"
|
||
|
#TODO protect quotes contents -- Don't use it, pass cmdline as list
|
||
|
return cmd_string.split()
|
||
|
|
||
|
def parse(self, cmdline=[]):
|
||
|
"Check/Parse a command line list TIP: no program name!"
|
||
|
# Get the valid options
|
||
|
short, long_ = self.short_opts, self.long_opts
|
||
|
# Parse it!
|
||
|
try:
|
||
|
opts, args = getopt.getopt(cmdline, short, long_)
|
||
|
except getopt.error as errmsg:
|
||
|
Error(_("%s (try --help)")%errmsg)
|
||
|
return (opts, args)
|
||
|
|
||
|
def get_raw_config(self, cmdline=[], ignore=[], filter_=[], relative=0):
|
||
|
"Returns the options/arguments found as RAW config"
|
||
|
|
||
|
if not cmdline: return []
|
||
|
ret = []
|
||
|
|
||
|
# We need lists, not strings (such as from %!options)
|
||
|
if type(cmdline) in (type(''), type('')):
|
||
|
cmdline = self._tokenize(cmdline)
|
||
|
|
||
|
# Extract name/value pair of all configs, check for invalid names
|
||
|
options, arguments = self.parse(cmdline[:])
|
||
|
|
||
|
# Some cleanup on the raw config
|
||
|
for name, value in options:
|
||
|
|
||
|
# Remove leading - and --
|
||
|
name = re.sub('^--?', '', name)
|
||
|
|
||
|
# Fix old misspelled --suGGar, --no-suGGar
|
||
|
name = name.replace('suggar', 'sugar')
|
||
|
|
||
|
# Translate short option to long
|
||
|
if len(name) == 1:
|
||
|
name = self.short_long[name]
|
||
|
|
||
|
# Outfile exception: path relative to PWD
|
||
|
if name == 'outfile' and relative and value not in [STDOUT, MODULEOUT]:
|
||
|
value = os.path.abspath(value)
|
||
|
|
||
|
# -C, --config-file inclusion, path relative to PWD
|
||
|
if name == 'config-file':
|
||
|
ret.extend(ConfigLines().include_config_file(value))
|
||
|
continue
|
||
|
|
||
|
# Save this config
|
||
|
ret.append(['all', name, value])
|
||
|
|
||
|
# All configuration was read and saved
|
||
|
|
||
|
# Get infile, if any
|
||
|
while arguments:
|
||
|
infile = arguments.pop(0)
|
||
|
ret.append(['all', 'infile', infile])
|
||
|
|
||
|
# Apply 'ignore' and 'filter_' rules (filter_ is stronger)
|
||
|
if (ignore or filter_):
|
||
|
filtered = []
|
||
|
for target, name, value in ret:
|
||
|
if (filter_ and name in filter_) or \
|
||
|
(ignore and name not in ignore):
|
||
|
filtered.append([target, name, value])
|
||
|
ret = filtered[:]
|
||
|
|
||
|
# Add the original command line string as 'realcmdline'
|
||
|
ret.append( ['all', 'realcmdline', cmdline] )
|
||
|
|
||
|
return ret
|
||
|
|
||
|
def compose_cmdline(self, conf={}, no_check=0):
|
||
|
"compose a full (and diet) command line from CONF dict"
|
||
|
if not conf: return []
|
||
|
args = []
|
||
|
dft_options = OPTIONS.copy()
|
||
|
cfg = conf.copy()
|
||
|
valid_opts = self.all_options + self.all_flags
|
||
|
use_short = {'no-headers':'H', 'enum-title':'n'}
|
||
|
# Remove useless options
|
||
|
if not no_check and cfg.get('toc-only'):
|
||
|
if 'no-headers' in cfg:
|
||
|
del cfg['no-headers']
|
||
|
if 'outfile' in cfg:
|
||
|
del cfg['outfile'] # defaults to STDOUT
|
||
|
if cfg.get('target') == 'txt':
|
||
|
del cfg['target'] # already default
|
||
|
args.append('--toc-only') # must be the first
|
||
|
del cfg['toc-only']
|
||
|
# Add target type
|
||
|
if 'target' in cfg:
|
||
|
args.append('-t '+cfg['target'])
|
||
|
del cfg['target']
|
||
|
# Add other options
|
||
|
for key in cfg.keys():
|
||
|
if key not in valid_opts: continue # may be a %!setting
|
||
|
if key == 'outfile' or key == 'infile': continue # later
|
||
|
val = cfg[key]
|
||
|
if not val: continue
|
||
|
# Default values are useless on cmdline
|
||
|
if val == dft_options.get(key): continue
|
||
|
# -short format
|
||
|
if key in use_short.keys():
|
||
|
args.append('-'+use_short[key])
|
||
|
continue
|
||
|
# --long format
|
||
|
if key in self.all_flags: # add --option
|
||
|
args.append('--'+key)
|
||
|
else: # add --option=value
|
||
|
args.append('--%s=%s'%(key,val))
|
||
|
# The outfile using -o
|
||
|
if 'outfile' in cfg and \
|
||
|
cfg['outfile'] != dft_options.get('outfile'):
|
||
|
args.append('-o '+cfg['outfile'])
|
||
|
# Place input file(s) always at the end
|
||
|
if 'infile' in cfg:
|
||
|
args.append(' '.join(cfg['infile']))
|
||
|
# Return as a nice list
|
||
|
Debug("Diet command line: %s"%' '.join(args), 1)
|
||
|
return args
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
class SourceDocument:
|
||
|
"""
|
||
|
SourceDocument class - scan document structure, extract data
|
||
|
|
||
|
It knows about full files. It reads a file and identify all
|
||
|
the areas beginning (Head,Conf,Body). With this info it can
|
||
|
extract each area contents.
|
||
|
Note: the original line break is removed.
|
||
|
|
||
|
DATA:
|
||
|
self.arearef - Save Head, Conf, Body init line number
|
||
|
self.areas - Store the area names which are not empty
|
||
|
self.buffer - The full file contents (with NO \\r, \\n)
|
||
|
|
||
|
METHODS:
|
||
|
get() - Access the contents of an Area. Example:
|
||
|
config = SourceDocument(file).get('conf')
|
||
|
|
||
|
split() - Get all the document Areas at once. Example:
|
||
|
head, conf, body = SourceDocument(file).split()
|
||
|
|
||
|
RULES:
|
||
|
* The document parts are sequential: Head, Conf and Body.
|
||
|
* One ends when the next begins.
|
||
|
* The Conf Area is optional, so a document can have just
|
||
|
Head and Body Areas.
|
||
|
|
||
|
These are the Areas limits:
|
||
|
- Head Area: the first three lines
|
||
|
- Body Area: from the first valid text line to the end
|
||
|
- Conf Area: the comments between Head and Body Areas
|
||
|
|
||
|
Exception: If the first line is blank, this means no
|
||
|
header info, so the Head Area is just the first line.
|
||
|
"""
|
||
|
def __init__(self, filename='', contents=[]):
|
||
|
self.areas = ['head','conf','body']
|
||
|
self.arearef = []
|
||
|
self.areas_fancy = ''
|
||
|
self.filename = filename
|
||
|
self.buffer = []
|
||
|
if filename:
|
||
|
self.scan_file(filename)
|
||
|
elif contents:
|
||
|
self.scan(contents)
|
||
|
|
||
|
def split(self):
|
||
|
"Returns all document parts, splitted into lists."
|
||
|
return self.get('head'), self.get('conf'), self.get('body')
|
||
|
|
||
|
def get(self, areaname):
|
||
|
"Returns head|conf|body contents from self.buffer"
|
||
|
# Sanity
|
||
|
if areaname not in self.areas: return []
|
||
|
if not self.buffer : return []
|
||
|
# Go get it
|
||
|
bufini = 1
|
||
|
bufend = len(self.buffer)
|
||
|
if areaname == 'head':
|
||
|
ini = bufini
|
||
|
end = self.arearef[1] or self.arearef[2] or bufend
|
||
|
elif areaname == 'conf':
|
||
|
ini = self.arearef[1]
|
||
|
end = self.arearef[2] or bufend
|
||
|
elif areaname == 'body':
|
||
|
ini = self.arearef[2]
|
||
|
end = bufend
|
||
|
else:
|
||
|
Error("Unknown Area name '%s'"%areaname)
|
||
|
lines = self.buffer[ini:end]
|
||
|
# Make sure head will always have 3 lines
|
||
|
while areaname == 'head' and len(lines) < 3:
|
||
|
lines.append('')
|
||
|
return lines
|
||
|
|
||
|
def scan_file(self, filename):
|
||
|
Debug("source file: %s"%filename)
|
||
|
Message(_("Loading source document"),1)
|
||
|
buf = Readfile(filename, remove_linebreaks=1)
|
||
|
self.scan(buf)
|
||
|
|
||
|
def scan(self, lines):
|
||
|
"Run through source file and identify head/conf/body areas"
|
||
|
buf = lines
|
||
|
if len(buf) == 0:
|
||
|
Error(_('The input file is empty: %s')%self.filename)
|
||
|
cfg_parser = ConfigLines().parse_line
|
||
|
buf.insert(0, '') # text start at pos 1
|
||
|
ref = [1,4,0]
|
||
|
if not buf[1].strip(): # no header
|
||
|
ref[0] = 0 ; ref[1] = 2
|
||
|
rgx = getRegexes()
|
||
|
on_comment_block = 0
|
||
|
for i in range(ref[1],len(buf)): # find body init:
|
||
|
# Handle comment blocks inside config area
|
||
|
if not on_comment_block \
|
||
|
and rgx['blockCommentOpen'].search(buf[i]):
|
||
|
on_comment_block = 1
|
||
|
continue
|
||
|
if on_comment_block \
|
||
|
and rgx['blockCommentOpen'].search(buf[i]):
|
||
|
on_comment_block = 0
|
||
|
continue
|
||
|
if on_comment_block: continue
|
||
|
|
||
|
if buf[i].strip() and ( # ... not blank and
|
||
|
buf[i][0] != '%' or # ... not comment or
|
||
|
rgx['macros'].match(buf[i]) or # ... %%macro
|
||
|
rgx['toc'].match(buf[i]) or # ... %%toc
|
||
|
cfg_parser(buf[i],'include')[1] or # ... %!include
|
||
|
cfg_parser(buf[i],'csv')[1] # ... %!csv
|
||
|
):
|
||
|
ref[2] = i ; break
|
||
|
if ref[1] == ref[2]: ref[1] = 0 # no conf area
|
||
|
for i in 0,1,2: # del !existent
|
||
|
if ref[i] >= len(buf): ref[i] = 0 # title-only
|
||
|
if not ref[i]: self.areas[i] = ''
|
||
|
Debug('Head,Conf,Body start line: %s'%ref)
|
||
|
self.arearef = ref # save results
|
||
|
self.buffer = buf
|
||
|
# Fancyness sample: head conf body (1 4 8)
|
||
|
self.areas_fancy = "%s (%s)"%(
|
||
|
' '.join(self.areas),
|
||
|
' '.join(map(str, [x or '' for x in ref])))
|
||
|
Message(_("Areas found: %s")%self.areas_fancy, 2)
|
||
|
|
||
|
def get_raw_config(self):
|
||
|
"Handy method to get the CONF area RAW config (if any)"
|
||
|
if not self.areas.count('conf'): return []
|
||
|
Message(_("Scanning source document CONF area"),1)
|
||
|
raw = ConfigLines(
|
||
|
file_=self.filename, lines=self.get('conf'),
|
||
|
first_line=self.arearef[1]).get_raw_config()
|
||
|
Debug("document raw config: %s"%raw, 1)
|
||
|
return raw
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
class ConfigMaster:
|
||
|
"""
|
||
|
ConfigMaster class - the configuration wizard
|
||
|
|
||
|
This class is the configuration master. It knows how to handle
|
||
|
the RAW and PARSED config format. It also performs the sanity
|
||
|
checking for a given configuration.
|
||
|
|
||
|
DATA:
|
||
|
self.raw - Stores the config on the RAW format
|
||
|
self.parsed - Stores the config on the PARSED format
|
||
|
self.defaults - Stores the default values for all keys
|
||
|
self.off - Stores the OFF values for all keys
|
||
|
self.multi - List of keys which can have multiple values
|
||
|
self.numeric - List of keys which value must be a number
|
||
|
self.incremental - List of keys which are incremental
|
||
|
|
||
|
RAW FORMAT:
|
||
|
The RAW format is a list of lists, being each mother list item
|
||
|
a full configuration entry. Any entry is a 3 item list, on
|
||
|
the following format: [ TARGET, KEY, VALUE ]
|
||
|
Being a list, the order is preserved, so it's easy to use
|
||
|
different kinds of configs, as CONF area and command line,
|
||
|
respecting the precedence.
|
||
|
The special target 'all' is used when no specific target was
|
||
|
defined on the original config.
|
||
|
|
||
|
PARSED FORMAT:
|
||
|
The PARSED format is a dictionary, with all the 'key : value'
|
||
|
found by reading the RAW config. The self.target contents
|
||
|
matters, so this dictionary only contains the target's
|
||
|
config. The configs of other targets are ignored.
|
||
|
|
||
|
The CommandLine and ConfigLines classes have the get_raw_config()
|
||
|
method which convert the configuration found to the RAW format.
|
||
|
Just feed it to parse() and get a brand-new ready-to-use config
|
||
|
dictionary. Example:
|
||
|
|
||
|
>>> raw = CommandLine().get_raw_config(['-n', '-H'])
|
||
|
>>> print raw
|
||
|
[['all', 'enum-title', ''], ['all', 'no-headers', '']]
|
||
|
>>> parsed = ConfigMaster(raw).parse()
|
||
|
>>> print parsed
|
||
|
{'enum-title': 1, 'headers': 0}
|
||
|
"""
|
||
|
def __init__(self, raw=[], target=''):
|
||
|
self.raw = raw
|
||
|
self.target = target
|
||
|
self.parsed = {}
|
||
|
self.dft_options = OPTIONS.copy()
|
||
|
self.dft_flags = FLAGS.copy()
|
||
|
self.dft_actions = ACTIONS.copy()
|
||
|
self.dft_settings = SETTINGS.copy()
|
||
|
self.defaults = self._get_defaults()
|
||
|
self.off = self._get_off()
|
||
|
self.incremental = ['verbose']
|
||
|
self.numeric = ['toc-level', 'split', 'width', 'height']
|
||
|
self.multi = ['infile', 'preproc', 'postproc', 'options', 'style']
|
||
|
|
||
|
def _get_defaults(self):
|
||
|
"Get the default values for all config/options/flags"
|
||
|
empty = {}
|
||
|
for kw in CONFIG_KEYWORDS: empty[kw] = ''
|
||
|
empty.update(self.dft_options)
|
||
|
empty.update(self.dft_flags)
|
||
|
empty.update(self.dft_actions)
|
||
|
empty.update(self.dft_settings)
|
||
|
empty['realcmdline'] = '' # internal use only
|
||
|
empty['sourcefile'] = '' # internal use only
|
||
|
return empty
|
||
|
|
||
|
def _get_off(self):
|
||
|
"Turns OFF all the config/options/flags"
|
||
|
off = {}
|
||
|
for key in self.defaults.keys():
|
||
|
kind = type(self.defaults[key])
|
||
|
if isinstance(0, kind):
|
||
|
off[key] = 0
|
||
|
elif isinstance('', kind):
|
||
|
off[key] = ''
|
||
|
elif isinstance([], kind):
|
||
|
off[key] = []
|
||
|
else:
|
||
|
Error('ConfigMaster: %s: Unknown type' % key)
|
||
|
return off
|
||
|
|
||
|
def _check_target(self):
|
||
|
"Checks if the target is already defined. If not, do it"
|
||
|
if not self.target:
|
||
|
self.target = self.find_value('target')
|
||
|
|
||
|
def get_target_raw(self):
|
||
|
"Returns the raw config for self.target or 'all'"
|
||
|
ret = []
|
||
|
self._check_target()
|
||
|
for entry in self.raw:
|
||
|
if entry[0] == self.target or entry[0] == 'all':
|
||
|
ret.append(entry)
|
||
|
return ret
|
||
|
|
||
|
def add(self, key, val):
|
||
|
"Adds the key:value pair to the config dictionary (if needed)"
|
||
|
# %!options
|
||
|
if key == 'options':
|
||
|
ignoreme = list(self.dft_actions.keys()) + ['target']
|
||
|
ignoreme.remove('dump-config')
|
||
|
ignoreme.remove('dump-source')
|
||
|
ignoreme.remove('targets')
|
||
|
raw_opts = CommandLine().get_raw_config(
|
||
|
val, ignore=ignoreme)
|
||
|
for target, key, val in raw_opts:
|
||
|
self.add(key, val)
|
||
|
return
|
||
|
# The no- prefix turns OFF this key
|
||
|
if key.startswith('no-'):
|
||
|
key = key[3:] # remove prefix
|
||
|
val = self.off.get(key) # turn key OFF
|
||
|
# Is this key valid?
|
||
|
if key not in self.defaults.keys():
|
||
|
Debug('Bogus Config %s:%s'%(key,val),1)
|
||
|
return
|
||
|
# Is this value the default one?
|
||
|
if val == self.defaults.get(key):
|
||
|
# If default value, remove previous key:val
|
||
|
if key in self.parsed:
|
||
|
del self.parsed[key]
|
||
|
# Nothing more to do
|
||
|
return
|
||
|
# Flags ON comes empty. we'll add the 1 value now
|
||
|
if val == '' and (
|
||
|
key in self.dft_flags.keys() or
|
||
|
key in self.dft_actions.keys()):
|
||
|
val = 1
|
||
|
# Multi value or single?
|
||
|
if key in self.multi:
|
||
|
# First one? start new list
|
||
|
if key not in self.parsed:
|
||
|
self.parsed[key] = []
|
||
|
self.parsed[key].append(val)
|
||
|
# Incremental value? so let's add it
|
||
|
elif key in self.incremental:
|
||
|
self.parsed[key] = (self.parsed.get(key) or 0) + val
|
||
|
else:
|
||
|
self.parsed[key] = val
|
||
|
fancykey = dotted_spaces("%12s"%key)
|
||
|
Message(_("Added config %s : %s")%(fancykey,val),3)
|
||
|
|
||
|
def get_outfile_name(self, config={}):
|
||
|
"Dirname is the same for {in,out}file"
|
||
|
infile, outfile = config['sourcefile'], config['outfile']
|
||
|
if outfile and outfile not in (STDOUT, MODULEOUT) \
|
||
|
and not os.path.isabs(outfile):
|
||
|
outfile = os.path.join(os.path.dirname(infile), outfile)
|
||
|
if infile == STDIN and not outfile: outfile = STDOUT
|
||
|
if infile == MODULEIN and not outfile: outfile = MODULEOUT
|
||
|
if not outfile and (infile and config.get('target')):
|
||
|
basename = re.sub('\.(txt|t2t)$','',infile)
|
||
|
outfile = "%s.%s"%(basename, config['target'])
|
||
|
Debug(" infile: '%s'"%infile , 1)
|
||
|
Debug("outfile: '%s'"%outfile, 1)
|
||
|
return outfile
|
||
|
|
||
|
def sanity(self, config, gui=0):
|
||
|
"Basic config sanity checking"
|
||
|
global AA
|
||
|
if not config: return {}
|
||
|
target = config.get('target')
|
||
|
# Some actions don't require target specification
|
||
|
if not target:
|
||
|
for action in NO_TARGET:
|
||
|
if config.get(action):
|
||
|
target = 'txt'
|
||
|
break
|
||
|
# On GUI, some checking are skipped
|
||
|
if not gui:
|
||
|
# We *need* a target
|
||
|
if not target:
|
||
|
Error(_('No target specified (try --help)') + '\n\n' +
|
||
|
_('Please inform a target using the -t option or the %!target command.') + '\n' +
|
||
|
_('Example:') + ' %s -t html %s' % (my_name, _('file.t2t')) + '\n\n' +
|
||
|
_("Run 'txt2tags --targets' to see all the available targets."))
|
||
|
# And of course, an infile also
|
||
|
# TODO#1: It seems that this checking is never reached
|
||
|
if not config.get('infile'):
|
||
|
Error(_('Missing input file (try --help)'))
|
||
|
# Is the target valid?
|
||
|
if not TARGETS.count(target):
|
||
|
Error(_("Invalid target '%s'") % target + '\n\n' +
|
||
|
_("Run 'txt2tags --targets' to see all the available targets."))
|
||
|
# Ensure all keys are present
|
||
|
empty = self.defaults.copy() ; empty.update(config)
|
||
|
config = empty.copy()
|
||
|
# Check integers options
|
||
|
for key in config.keys():
|
||
|
if key in self.numeric:
|
||
|
try:
|
||
|
config[key] = int(config[key])
|
||
|
except ValueError:
|
||
|
Error(_('--%s value must be a number') % key)
|
||
|
# Check split level value
|
||
|
if config['split'] not in (0,1,2):
|
||
|
Error(_('Option --split must be 0, 1 or 2'))
|
||
|
# Slides needs width and height
|
||
|
if config['slides'] and target == 'art':
|
||
|
if not config['width']:
|
||
|
config['width'] = DFT_SLIDE_WIDTH
|
||
|
if not config['height']:
|
||
|
config['height'] = DFT_SLIDE_HEIGHT
|
||
|
# ASCII Art needs a width
|
||
|
if target == 'art' and not config['width']:
|
||
|
config['width'] = DFT_TEXT_WIDTH
|
||
|
# Check/set user ASCII Art formatting characters
|
||
|
if config['art-chars']:
|
||
|
if len(config['art-chars']) != len(AA_VALUES):
|
||
|
Error(_("--art-chars: Expected %i chars, got %i") % (
|
||
|
len(AA_VALUES), len(config['art-chars'])))
|
||
|
else:
|
||
|
AA = dict(zip(AA_KEYS, config['art-chars']))
|
||
|
# --toc-only is stronger than others
|
||
|
if config['toc-only']:
|
||
|
config['headers'] = 0
|
||
|
config['toc'] = 0
|
||
|
config['split'] = 0
|
||
|
config['gui'] = 0
|
||
|
config['outfile'] = config['outfile'] or STDOUT
|
||
|
# Splitting is disable for now (future: HTML only, no STDOUT)
|
||
|
config['split'] = 0
|
||
|
# Restore target
|
||
|
config['target'] = target
|
||
|
# Set output file name
|
||
|
config['outfile'] = self.get_outfile_name(config)
|
||
|
# Checking suicide
|
||
|
if config['sourcefile'] == config['outfile'] and \
|
||
|
config['outfile'] not in [STDOUT,MODULEOUT] and not gui:
|
||
|
Error(_("Input and Output files are the same: %s") % config['outfile'])
|
||
|
return config
|
||
|
|
||
|
def parse(self):
|
||
|
"Returns the parsed config for the current target"
|
||
|
raw = self.get_target_raw()
|
||
|
for target, key, value in raw:
|
||
|
self.add(key, value)
|
||
|
Message(_("Added the following keys: %s") % ', '.join(self.parsed.keys()), 2)
|
||
|
return self.parsed.copy()
|
||
|
|
||
|
def find_value(self, key='', target=''):
|
||
|
"Scans ALL raw config to find the desired key"
|
||
|
ret = []
|
||
|
# Scan and save all values found
|
||
|
for targ, k, val in self.raw:
|
||
|
if k == key and (targ == target or targ == 'all'):
|
||
|
ret.append(val)
|
||
|
if not ret: return ''
|
||
|
# If not multi value, return only the last found
|
||
|
if key in self.multi: return ret
|
||
|
else : return ret[-1]
|
||
|
|
||
|
########################################################################
|
||
|
|
||
|
class ConfigLines:
|
||
|
"""
|
||
|
ConfigLines class - the config file data extractor
|
||
|
|
||
|
This class reads and parse the config lines on the %!key:val
|
||
|
format, converting it to RAW config. It deals with user
|
||
|
config file (RC file), source document CONF area and
|
||
|
%!includeconf directives.
|
||
|
|
||
|
Call it passing a file name or feed the desired config lines.
|
||
|
Then just call the get_raw_config() method and wait to
|
||
|
receive the full config data on the RAW format. This method
|
||
|
also follows the possible %!includeconf directives found on
|
||
|
the config lines. Example:
|
||
|
|
||
|
raw = ConfigLines(file=".txt2tagsrc").get_raw_config()
|
||
|
|
||
|
The parse_line() method is also useful to be used alone,
|
||
|
to identify and tokenize a single config line. For example,
|
||
|
to get the %!include command components, on the source
|
||
|
document BODY:
|
||
|
|
||
|
target, key, value = ConfigLines().parse_line(body_line)
|
||
|
"""
|
||
|
def __init__(self, file_='', lines=[], first_line=1):
|
||
|
self.file = file_ or 'NOFILE'
|
||
|
self.lines = lines
|
||
|
self.first_line = first_line
|
||
|
|
||
|
def load_lines(self):
|
||
|
"Make sure we've loaded the file contents into buffer"
|
||
|
if not self.lines and not self.file:
|
||
|
Error("ConfigLines: No file or lines provided")
|
||
|
if not self.lines:
|
||
|
self.lines = self.read_config_file(self.file)
|
||
|
|
||
|
def read_config_file(self, filename=''):
|
||
|
"Read a Config File contents, aborting on invalid line"
|
||
|
if not filename: return []
|
||
|
errormsg = _("Invalid CONFIG line on %s")+"\n%03d:%s"
|
||
|
lines = Readfile(filename, remove_linebreaks=1)
|
||
|
# Sanity: try to find invalid config lines
|
||
|
for i in range(len(lines)):
|
||
|
line = lines[i].rstrip()
|
||
|
if not line: continue # empty
|
||
|
if line[0] != '%': Error(errormsg%(filename,i+1,line))
|
||
|
return lines
|
||
|
|
||
|
def include_config_file(self, file_=''):
|
||
|
"Perform the %!includeconf action, returning RAW config"
|
||
|
if not file_: return []
|
||
|
# Current dir relative to the current file (self.file)
|
||
|
current_dir = os.path.dirname(self.file)
|
||
|
file_ = os.path.join(current_dir, file_)
|
||
|
# Read and parse included config file contents
|
||
|
lines = self.read_config_file(file_)
|
||
|
return ConfigLines(file_=file_, lines=lines).get_raw_config()
|
||
|
|
||
|
def get_raw_config(self):
|
||
|
"Scan buffer and extract all config as RAW (including includes)"
|
||
|
ret = []
|
||
|
self.load_lines()
|
||
|
first = self.first_line
|
||
|
for i in range(len(self.lines)):
|
||
|
line = self.lines[i]
|
||
|
Message(_("Processing line %03d: %s")%(first+i,line),2)
|
||
|
target, key, val = self.parse_line(line)
|
||
|
if not key: continue # no config on this line
|
||
|
if key == 'includeconf':
|
||
|
err = _('A file cannot include itself (loop!)')
|
||
|
if val == self.file:
|
||
|
Error("%s: %%!includeconf: %s" % (err, self.file))
|
||
|
more_raw = self.include_config_file(val)
|
||
|
ret.extend(more_raw)
|
||
|
Message(_("Finished Config file inclusion: %s") % val, 2)
|
||
|
else:
|
||
|
ret.append([target, key, val])
|
||
|
Message(_("Added %s")%key,3)
|
||
|
return ret
|
||
|
|
||
|
def parse_line(self, line='', keyname='', target=''):
|
||
|
"Detects %!key:val config lines and extract data from it"
|
||
|
empty = ['', '', '']
|
||
|
if not line: return empty
|
||
|
no_target = ['target', 'includeconf']
|
||
|
re_name = keyname or '[a-z]+'
|
||
|
re_target = target or '[a-z]*'
|
||
|
# XXX TODO <value>\S.+? requires TWO chars, breaks %!include:a
|
||
|
cfgregex = re.compile("""
|
||
|
^%%!\s* # leading id with opt spaces
|
||
|
(?P<name>%s)\s* # config name
|
||
|
(\((?P<target>%s)\))? # optional target spec inside ()
|
||
|
\s*:\s* # key:value delimiter with opt spaces
|
||
|
(?P<value>\S.+?) # config value
|
||
|
\s*$ # rstrip() spaces and hit EOL
|
||
|
"""%(re_name, re_target), re.I+re.VERBOSE)
|
||
|
prepostregex = re.compile("""
|
||
|
# ---[ PATTERN ]---
|
||
|
^( "([^"]*)" # "double quoted" or
|
||
|
| '([^']*)' # 'single quoted' or
|
||
|
| ([^\s]+) # single_word
|
||
|
)
|
||
|
\s+ # separated by spaces
|
||
|
|
||
|
# ---[ REPLACE ]---
|
||
|
( "([^"]*)" # "double quoted" or
|
||
|
| '([^']*)' # 'single quoted' or
|
||
|
| (.*) # anything
|
||
|
)
|
||
|
\s*$
|
||
|
""", re.VERBOSE)
|
||
|
guicolors = re.compile("^([^\s]+\s+){3}[^\s]+") # 4 tokens
|
||
|
|
||
|
# Give me a match or get out
|
||
|
match = cfgregex.match(line)
|
||
|
if not match: return empty
|
||
|
|
||
|
# Save information about this config
|
||
|
name = (match.group('name') or '').lower()
|
||
|
target = (match.group('target') or 'all').lower()
|
||
|
value = match.group('value')
|
||
|
|
||
|
# %!keyword(target) not allowed for these
|
||
|
if name in no_target and match.group('target'):
|
||
|
Error(
|
||
|
_("You can't use (target) with %s") % ('%!' + name)
|
||
|
+ "\n%s" % line)
|
||
|
|
||
|
# Force no_target keywords to be valid for all targets
|
||
|
if name in no_target:
|
||
|
target = 'all'
|
||
|
|
||
|
# Special config for GUI colors
|
||
|
if name == 'guicolors':
|
||
|
valmatch = guicolors.search(value)
|
||
|
if not valmatch: return empty
|
||
|
value = re.split('\s+', value)
|
||
|
|
||
|
# Special config with two quoted values (%!preproc: "foo" 'bar')
|
||
|
if name == 'preproc' or name == 'postproc':
|
||
|
valmatch = prepostregex.search(value)
|
||
|
if not valmatch: return empty
|
||
|
getval = valmatch.group
|
||
|
patt = getval(2) or getval(3) or getval(4) or ''
|
||
|
repl = getval(6) or getval(7) or getval(8) or ''
|
||
|
value = (patt, repl)
|
||
|
return [target, name, value]
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
class MaskMaster:
|
||
|
"(Un)Protect important structures from escaping and formatting"
|
||
|
def __init__(self):
|
||
|
self.linkmask = 'vvvLINKvvv'
|
||
|
self.monomask = 'vvvMONOvvv'
|
||
|
self.macromask = 'vvvMACROvvv'
|
||
|
self.rawmask = 'vvvRAWvvv'
|
||
|
self.taggedmask= 'vvvTAGGEDvvv'
|
||
|
self.tocmask = 'vvvTOCvvv'
|
||
|
self.macroman = MacroMaster()
|
||
|
self.reset()
|
||
|
|
||
|
def reset(self):
|
||
|
self.linkbank = []
|
||
|
self.monobank = []
|
||
|
self.macrobank = []
|
||
|
self.rawbank = []
|
||
|
self.taggedbank = []
|
||
|
|
||
|
def mask(self, line=''):
|
||
|
global AUTOTOC
|
||
|
|
||
|
# The verbatim, raw and tagged inline marks are mutually exclusive.
|
||
|
# This means that one can't appear inside the other.
|
||
|
# If found, the inner marks must be ignored.
|
||
|
# Example: ``foo ""bar"" ''baz''``
|
||
|
# In HTML: <code>foo ""bar"" ''baz''</code>
|
||
|
#
|
||
|
# The trick here is to protect the mark who appears first on the line.
|
||
|
# The three regexes are tried and the one with the lowest index wins.
|
||
|
# If none is found (else), we get out of the loop.
|
||
|
#
|
||
|
while True:
|
||
|
|
||
|
# Try to match the line for the three marks
|
||
|
# Note: 'z' > 999999
|
||
|
#
|
||
|
t = r = v = 1000000 #Python 3 doesn't allow comparison between str and int!
|
||
|
try: t = regex['tagged'].search(line).start()
|
||
|
except: pass
|
||
|
try: r = regex['raw'].search(line).start()
|
||
|
except: pass
|
||
|
try: v = regex['fontMono'].search(line).start()
|
||
|
except: pass
|
||
|
|
||
|
# Protect tagged text
|
||
|
if t >= 0 and t < r and t < v:
|
||
|
txt = regex['tagged'].search(line).group(1)
|
||
|
self.taggedbank.append(txt)
|
||
|
line = regex['tagged'].sub(self.taggedmask,line,1)
|
||
|
|
||
|
# Protect raw text
|
||
|
elif r >= 0 and r < t and r < v:
|
||
|
txt = regex['raw'].search(line).group(1)
|
||
|
txt = doEscape(TARGET,txt)
|
||
|
self.rawbank.append(txt)
|
||
|
line = regex['raw'].sub(self.rawmask,line,1)
|
||
|
|
||
|
# Protect verbatim text
|
||
|
elif v >= 0 and v < t and v < r:
|
||
|
txt = regex['fontMono'].search(line).group(1)
|
||
|
txt = doEscape(TARGET,txt)
|
||
|
self.monobank.append(txt)
|
||
|
line = regex['fontMono'].sub(self.monomask,line,1)
|
||
|
else:
|
||
|
break
|
||
|
|
||
|
# Protect macros
|
||
|
while regex['macros'].search(line):
|
||
|
txt = regex['macros'].search(line).group()
|
||
|
self.macrobank.append(txt)
|
||
|
line = regex['macros'].sub(self.macromask,line,1)
|
||
|
|
||
|
# Protect TOC location
|
||
|
while regex['toc'].search(line):
|
||
|
line = regex['toc'].sub(self.tocmask,line)
|
||
|
AUTOTOC = 0
|
||
|
|
||
|
# Protect URLs and emails
|
||
|
while regex['linkmark'].search(line) or \
|
||
|
regex['link' ].search(line):
|
||
|
|
||
|
# Try to match plain or named links
|
||
|
match_link = regex['link'].search(line)
|
||
|
match_named = regex['linkmark'].search(line)
|
||
|
|
||
|
# Define the current match
|
||
|
if match_link and match_named:
|
||
|
# Both types found, which is the first?
|
||
|
m = match_link
|
||
|
if match_named.start() < match_link.start():
|
||
|
m = match_named
|
||
|
else:
|
||
|
# Just one type found, we're fine
|
||
|
m = match_link or match_named
|
||
|
|
||
|
# Extract link data and apply mask
|
||
|
if m == match_link: # plain link
|
||
|
link = m.group()
|
||
|
label = ''
|
||
|
link_re = regex['link']
|
||
|
else: # named link
|
||
|
link = m.group('link')
|
||
|
label = m.group('label').rstrip()
|
||
|
link_re = regex['linkmark']
|
||
|
line = link_re.sub(self.linkmask,line,1)
|
||
|
|
||
|
# Save link data to the link bank
|
||
|
self.linkbank.append((label, link))
|
||
|
return line
|
||
|
|
||
|
def undo(self, line):
|
||
|
|
||
|
# url & email
|
||
|
for label,url in self.linkbank:
|
||
|
link = get_tagged_link(label, url)
|
||
|
line = line.replace(self.linkmask, link, 1)
|
||
|
|
||
|
# Expand macros
|
||
|
for macro in self.macrobank:
|
||
|
macro = self.macroman.expand(macro)
|
||
|
line = line.replace(self.macromask, macro, 1)
|
||
|
|
||
|
# Expand verb
|
||
|
for mono in self.monobank:
|
||
|
open_,close = TAGS['fontMonoOpen'],TAGS['fontMonoClose']
|
||
|
line = line.replace(self.monomask, open_+mono+close, 1)
|
||
|
|
||
|
# Expand raw
|
||
|
for raw in self.rawbank:
|
||
|
line = line.replace(self.rawmask, raw, 1)
|
||
|
|
||
|
# Expand tagged
|
||
|
for tagged in self.taggedbank:
|
||
|
line = line.replace(self.taggedmask, tagged, 1)
|
||
|
|
||
|
return line
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
class TitleMaster:
|
||
|
"Title things"
|
||
|
def __init__(self):
|
||
|
self.count = ['',0,0,0,0,0]
|
||
|
self.toc = []
|
||
|
self.level = 0
|
||
|
self.kind = ''
|
||
|
self.txt = ''
|
||
|
self.label = ''
|
||
|
self.tag = ''
|
||
|
self.tag_hold = []
|
||
|
self.last_level = 0
|
||
|
self.count_id = ''
|
||
|
self.user_labels = {}
|
||
|
self.anchor_count = 0
|
||
|
self.anchor_prefix = 'toc'
|
||
|
|
||
|
def _open_close_blocks(self):
|
||
|
"Open new title blocks, closing the previous (if any)"
|
||
|
if not rules['titleblocks']: return
|
||
|
tag = ''
|
||
|
last = self.last_level
|
||
|
curr = self.level
|
||
|
|
||
|
# Same level, just close the previous
|
||
|
if curr == last:
|
||
|
tag = TAGS.get('title%dClose'%last)
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
# Section -> subsection, more depth
|
||
|
while curr > last:
|
||
|
last += 1
|
||
|
|
||
|
# Open the new block of subsections
|
||
|
tag = TAGS.get('blockTitle%dOpen'%last)
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
# Jump from title1 to title3 or more
|
||
|
# Fill the gap with an empty section
|
||
|
if curr - last > 0:
|
||
|
tag = TAGS.get('title%dOpen'%last)
|
||
|
tag = regex['x'].sub('', tag) # del \a
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
# Section <- subsection, less depth
|
||
|
while curr < last:
|
||
|
# Close the current opened subsection
|
||
|
tag = TAGS.get('title%dClose'%last)
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
# Close the current opened block of subsections
|
||
|
tag = TAGS.get('blockTitle%dClose'%last)
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
last -= 1
|
||
|
|
||
|
# Close the previous section of the same level
|
||
|
# The subsections were under it
|
||
|
if curr == last:
|
||
|
tag = TAGS.get('title%dClose'%last)
|
||
|
if tag: self.tag_hold.append(tag)
|
||
|
|
||
|
def add(self, line):
|
||
|
"Parses a new title line."
|
||
|
if not line: return
|
||
|
self._set_prop(line)
|
||
|
self._open_close_blocks()
|
||
|
self._set_count_id()
|
||
|
self._set_label()
|
||
|
self._save_toc_info()
|
||
|
|
||
|
def close_all(self):
|
||
|
"Closes all opened title blocks"
|
||
|
ret = []
|
||
|
ret.extend(self.tag_hold)
|
||
|
while self.level:
|
||
|
tag = TAGS.get('title%dClose'%self.level)
|
||
|
if tag: ret.append(tag)
|
||
|
tag = TAGS.get('blockTitle%dClose'%self.level)
|
||
|
if tag: ret.append(tag)
|
||
|
self.level -= 1
|
||
|
return ret
|
||
|
|
||
|
def _save_toc_info(self):
|
||
|
"Save TOC info, used by self.dump_marked_toc()"
|
||
|
self.toc.append((self.level, self.count_id, self.txt, self.label))
|
||
|
|
||
|
def _set_prop(self, line=''):
|
||
|
"Extract info from original line and set data holders."
|
||
|
# Detect title type (numbered or not)
|
||
|
id_ = line.lstrip()[0]
|
||
|
if id_ == '=': kind = 'title'
|
||
|
elif id_ == '+': kind = 'numtitle'
|
||
|
else: Error("Unknown Title ID '%s'"%id_)
|
||
|
# Extract line info
|
||
|
match = regex[kind].search(line)
|
||
|
level = len(match.group('id'))
|
||
|
txt = match.group('txt').strip()
|
||
|
label = match.group('label')
|
||
|
# Parse info & save
|
||
|
if CONF['enum-title']: kind = 'numtitle' # force
|
||
|
if rules['titleblocks']:
|
||
|
self.tag = TAGS.get('%s%dOpen'%(kind,level)) or \
|
||
|
TAGS.get('title%dOpen'%level)
|
||
|
else:
|
||
|
self.tag = TAGS.get(kind+repr(level)) or \
|
||
|
TAGS.get('title'+repr(level))
|
||
|
self.last_level = self.level
|
||
|
self.kind = kind
|
||
|
self.level = level
|
||
|
self.txt = txt
|
||
|
self.label = label
|
||
|
|
||
|
def _set_count_id(self):
|
||
|
"Compose and save the title count identifier (if needed)."
|
||
|
count_id = ''
|
||
|
if self.kind == 'numtitle' and not rules['autonumbertitle']:
|
||
|
# Manually increase title count
|
||
|
self.count[self.level] += 1
|
||
|
# Reset sublevels count (if any)
|
||
|
max_levels = len(self.count)
|
||
|
if self.level < max_levels-1:
|
||
|
for i in range(self.level+1, max_levels):
|
||
|
self.count[i] = 0
|
||
|
# Compose count id from hierarchy
|
||
|
for i in range(self.level):
|
||
|
count_id= "%s%d."%(count_id, self.count[i+1])
|
||
|
self.count_id = count_id
|
||
|
|
||
|
def _set_label(self):
|
||
|
"Compose and save title label, used by anchors."
|
||
|
# Remove invalid chars from label set by user
|
||
|
self.label = re.sub('[^A-Za-z0-9_-]', '', self.label or '')
|
||
|
# Generate name as 15 first :alnum: chars
|
||
|
#TODO how to translate safely accented chars to plain?
|
||
|
#self.label = re.sub('[^A-Za-z0-9]', '', self.txt)[:15]
|
||
|
# 'tocN' label - sequential count, ignoring 'toc-level'
|
||
|
#self.label = self.anchor_prefix + str(len(self.toc)+1)
|
||
|
|
||
|
def _get_tagged_anchor(self):
|
||
|
"Return anchor if user defined a label, or TOC is on."
|
||
|
ret = ''
|
||
|
label = self.label
|
||
|
if CONF['toc'] and self.level <= CONF['toc-level']:
|
||
|
# This count is needed bcos self.toc stores all
|
||
|
# titles, regardless of the 'toc-level' setting,
|
||
|
# so we can't use self.toc length to number anchors
|
||
|
self.anchor_count += 1
|
||
|
# Autonumber label (if needed)
|
||
|
label = label or '%s%s' % (self.anchor_prefix, self.anchor_count)
|
||
|
if label and TAGS['anchor']:
|
||
|
ret = regex['x'].sub(label,TAGS['anchor'])
|
||
|
return ret
|
||
|
|
||
|
def _get_full_title_text(self):
|
||
|
"Returns the full title contents, already escaped."
|
||
|
ret = self.txt
|
||
|
# Insert count_id (if any) before text
|
||
|
if self.count_id:
|
||
|
ret = '%s %s'%(self.count_id, ret)
|
||
|
# Escape specials
|
||
|
ret = doEscape(TARGET, ret)
|
||
|
# Same targets needs final escapes on title lines
|
||
|
# It's here because there is a 'continue' after title
|
||
|
if rules['finalescapetitle']:
|
||
|
ret = doFinalEscape(TARGET, ret)
|
||
|
return ret
|
||
|
|
||
|
def get(self):
|
||
|
"Returns the tagged title as a list."
|
||
|
global AA_TITLE
|
||
|
ret = []
|
||
|
|
||
|
# Maybe some anchoring before?
|
||
|
anchor = self._get_tagged_anchor()
|
||
|
self.tag = regex['_anchor'].sub(anchor, self.tag)
|
||
|
|
||
|
### Compose & escape title text (TOC uses unescaped)
|
||
|
full_title = self._get_full_title_text()
|
||
|
|
||
|
# Close previous section area
|
||
|
ret.extend(self.tag_hold)
|
||
|
self.tag_hold = []
|
||
|
|
||
|
tagged = regex['x'].sub(full_title, self.tag)
|
||
|
|
||
|
# Adds "underline" on TXT target
|
||
|
if TARGET == 'txt':
|
||
|
if BLOCK.count > 1: ret.append('') # blank line before
|
||
|
ret.append(tagged)
|
||
|
#Can't be used on Python 3 because every string is unicode unless declared as b'non-unicode string. Array of bytes'
|
||
|
#It doesn't support 'decode'
|
||
|
#
|
||
|
# Get the right letter count for UTF
|
||
|
#if CONF['encoding'].lower() == 'utf-8':
|
||
|
# i = len(full_title.decode('utf-8'))
|
||
|
#else:
|
||
|
i = len(full_title)
|
||
|
ret.append(regex['x'].sub('='*i, self.tag))
|
||
|
elif TARGET == 'art' and self.level == 1:
|
||
|
if CONF['slides'] :
|
||
|
AA_TITLE = tagged
|
||
|
else :
|
||
|
if BLOCK.count > 1: ret.append('') # blank line before
|
||
|
ret.extend(aa_box(tagged, CONF['width']))
|
||
|
elif TARGET == 'art':
|
||
|
level = 'level'+str(self.level)
|
||
|
if BLOCK.count > 1: ret.append('') # blank line before
|
||
|
ret.append(tagged)
|
||
|
ret.append(AA[level] * len(full_title))
|
||
|
else:
|
||
|
ret.append(tagged)
|
||
|
return ret
|
||
|
|
||
|
def dump_marked_toc(self, max_level=99):
|
||
|
"Dumps all toc itens as a valid t2t-marked list"
|
||
|
ret = []
|
||
|
toc_count = 1
|
||
|
for level, count_id, txt, label in self.toc:
|
||
|
if level > max_level: continue # ignore
|
||
|
indent = ' '*level
|
||
|
id_txt = ('%s %s'%(count_id, txt)).lstrip()
|
||
|
label = label or self.anchor_prefix+repr(toc_count)
|
||
|
toc_count += 1
|
||
|
|
||
|
# TOC will have crosslinks to anchors
|
||
|
if TAGS['anchor']:
|
||
|
if CONF['enum-title'] and level == 1:
|
||
|
# 1. [Foo #anchor] is more readable than [1. Foo #anchor] in level 1.
|
||
|
# This is a stoled idea from Windows .CHM help files.
|
||
|
tocitem = '%s+ [""%s"" #%s]' % (indent, txt, label)
|
||
|
else:
|
||
|
tocitem = '%s- [""%s"" #%s]' % (indent, id_txt, label)
|
||
|
|
||
|
# TOC will be plain text (no links)
|
||
|
else:
|
||
|
if TARGET in ['txt', 'man', 'art']:
|
||
|
# For these, the list is not necessary, just dump the text
|
||
|
tocitem = '%s""%s""' % (indent, id_txt)
|
||
|
else:
|
||
|
tocitem = '%s- ""%s""' % (indent, id_txt)
|
||
|
ret.append(tocitem)
|
||
|
return ret
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
#TODO check all this table mess
|
||
|
# It uses parse_row properties for table lines
|
||
|
# BLOCK.table() replaces the cells by the parsed content
|
||
|
class TableMaster:
|
||
|
def __init__(self, line=''):
|
||
|
self.rows = []
|
||
|
self.border = 0
|
||
|
self.align = 'Left'
|
||
|
self.cellalign = []
|
||
|
self.colalign = []
|
||
|
self.cellspan = []
|
||
|
if line:
|
||
|
prop = self.parse_row(line)
|
||
|
self.border = prop['border']
|
||
|
self.align = prop['align']
|
||
|
self.cellalign = prop['cellalign']
|
||
|
self.cellspan = prop['cellspan']
|
||
|
self.colalign = self._get_col_align()
|
||
|
|
||
|
def _get_col_align(self):
|
||
|
colalign = []
|
||
|
for cell in range(0,len(self.cellalign)):
|
||
|
align = self.cellalign[cell]
|
||
|
span = self.cellspan[cell]
|
||
|
colalign.extend([align] * span)
|
||
|
return colalign
|
||
|
|
||
|
def _get_open_tag(self):
|
||
|
topen = TAGS['tableOpen']
|
||
|
tborder = TAGS['_tableBorder']
|
||
|
talign = TAGS['_tableAlign'+self.align]
|
||
|
calignsep = TAGS['tableColAlignSep']
|
||
|
calign = ''
|
||
|
|
||
|
# The first line defines if table has border or not
|
||
|
if not self.border: tborder = ''
|
||
|
# Set the columns alignment
|
||
|
if rules['tablecellaligntype'] == 'column':
|
||
|
calign = [TAGS['_tableColAlign%s'%x] for x in self.colalign]
|
||
|
calign = calignsep.join(calign)
|
||
|
# Align full table, set border and Column align (if any)
|
||
|
topen = regex['_tableAlign' ].sub(talign , topen)
|
||
|
topen = regex['_tableBorder' ].sub(tborder, topen)
|
||
|
topen = regex['_tableColAlign'].sub(calign , topen)
|
||
|
# Tex table spec, border or not: {|l|c|r|} , {lcr}
|
||
|
if calignsep and not self.border:
|
||
|
# Remove cell align separator
|
||
|
topen = topen.replace(calignsep, '')
|
||
|
return topen
|
||
|
|
||
|
def _get_cell_align(self, cells):
|
||
|
ret = []
|
||
|
for cell in cells:
|
||
|
align = 'Left'
|
||
|
if cell.strip():
|
||
|
if cell[0] == ' ' and cell[-1] == ' ':
|
||
|
align = 'Center'
|
||
|
elif cell[0] == ' ':
|
||
|
align = 'Right'
|
||
|
ret.append(align)
|
||
|
return ret
|
||
|
|
||
|
def _get_cell_span(self, cells):
|
||
|
ret = []
|
||
|
for cell in cells:
|
||
|
span = 1
|
||
|
m = re.search('\a(\|+)$', cell)
|
||
|
if m: span = len(m.group(1))+1
|
||
|
ret.append(span)
|
||
|
return ret
|
||
|
|
||
|
def _tag_cells(self, rowdata):
|
||
|
row = []
|
||
|
cells = rowdata['cells']
|
||
|
open_ = TAGS['tableCellOpen']
|
||
|
close = TAGS['tableCellClose']
|
||
|
sep = TAGS['tableCellSep']
|
||
|
calign = [TAGS['_tableCellAlign'+x] for x in rowdata['cellalign']]
|
||
|
calignsep = TAGS['tableColAlignSep']
|
||
|
ncolumns = len(self.colalign)
|
||
|
|
||
|
# Populate the span and multicol open tags
|
||
|
cspan = []
|
||
|
multicol = []
|
||
|
colindex = 0
|
||
|
for cellindex in range(0,len(rowdata['cellspan'])):
|
||
|
|
||
|
span = rowdata['cellspan'][cellindex]
|
||
|
align = rowdata['cellalign'][cellindex]
|
||
|
|
||
|
if span > 1:
|
||
|
cspan.append(regex['x'].sub(
|
||
|
str(span), TAGS['_tableCellColSpan']))
|
||
|
|
||
|
mcopen = regex['x'].sub(str(span), TAGS['_tableCellMulticolOpen'])
|
||
|
multicol.append(mcopen)
|
||
|
else:
|
||
|
cspan.append('')
|
||
|
|
||
|
if colindex < ncolumns and align != self.colalign[colindex]:
|
||
|
mcopen = regex['x'].sub('1', TAGS['_tableCellMulticolOpen'])
|
||
|
multicol.append(mcopen)
|
||
|
else:
|
||
|
multicol.append('')
|
||
|
|
||
|
if not self.border:
|
||
|
multicol[-1] = multicol[-1].replace(calignsep, '')
|
||
|
|
||
|
colindex += span
|
||
|
|
||
|
# Maybe is it a title row?
|
||
|
if rowdata['title']:
|
||
|
open_ = TAGS['tableTitleCellOpen'] or open_
|
||
|
close = TAGS['tableTitleCellClose'] or close
|
||
|
sep = TAGS['tableTitleCellSep'] or sep
|
||
|
|
||
|
# Should we break the line on *each* table cell?
|
||
|
if rules['breaktablecell']: close = close+'\n'
|
||
|
|
||
|
# Cells pre processing
|
||
|
if rules['tablecellstrip']:
|
||
|
cells = [x.strip() for x in cells]
|
||
|
if rowdata['title'] and rules['tabletitlerowinbold']:
|
||
|
cells = [enclose_me('fontBold',x) for x in cells]
|
||
|
|
||
|
# Add cell BEGIN/END tags
|
||
|
for cell in cells:
|
||
|
copen = open_
|
||
|
cclose = close
|
||
|
# Make sure we will pop from some filled lists
|
||
|
# Fixes empty line bug '| |'
|
||
|
this_align = this_span = this_mcopen = ''
|
||
|
if calign: this_align = calign.pop(0)
|
||
|
if cspan : this_span = cspan.pop(0)
|
||
|
if multicol: this_mcopen = multicol.pop(0)
|
||
|
|
||
|
# Insert cell align into open tag (if cell is alignable)
|
||
|
if rules['tablecellaligntype'] == 'cell':
|
||
|
copen = regex['_tableCellAlign'].sub(
|
||
|
this_align, copen)
|
||
|
|
||
|
# Insert cell span into open tag (if cell is spannable)
|
||
|
if rules['tablecellspannable']:
|
||
|
copen = regex['_tableCellColSpan'].sub(
|
||
|
this_span, copen)
|
||
|
|
||
|
# Use multicol tags instead (if multicol supported, and if
|
||
|
# cell has a span or is aligned differently to column)
|
||
|
if rules['tablecellmulticol']:
|
||
|
if this_mcopen:
|
||
|
copen = regex['_tableColAlign'].sub(this_align, this_mcopen)
|
||
|
cclose = TAGS['_tableCellMulticolClose']
|
||
|
|
||
|
row.append(copen + cell + cclose)
|
||
|
|
||
|
# Maybe there are cell separators?
|
||
|
return sep.join(row)
|
||
|
|
||
|
def add_row(self, cells):
|
||
|
self.rows.append(cells)
|
||
|
|
||
|
def parse_row(self, line):
|
||
|
# Default table properties
|
||
|
ret = {
|
||
|
'border':0, 'title':0, 'align':'Left',
|
||
|
'cells':[], 'cellalign':[], 'cellspan':[]
|
||
|
}
|
||
|
# Detect table align (and remove spaces mark)
|
||
|
if line[0] == ' ': ret['align'] = 'Center'
|
||
|
line = line.lstrip()
|
||
|
# Detect title mark
|
||
|
if line[1] == '|': ret['title'] = 1
|
||
|
# Detect border mark and normalize the EOL
|
||
|
m = re.search(' (\|+) *$', line)
|
||
|
if m: line = line+' ' ; ret['border'] = 1
|
||
|
else: line = line+' | '
|
||
|
# Delete table mark
|
||
|
line = regex['table'].sub('', line)
|
||
|
# Detect colspan | foo | bar baz |||
|
||
|
line = re.sub(' (\|+)\| ', '\a\\1 | ', line)
|
||
|
# Split cells (the last is fake)
|
||
|
ret['cells'] = line.split(' | ')[:-1]
|
||
|
# Find cells span
|
||
|
ret['cellspan'] = self._get_cell_span(ret['cells'])
|
||
|
# Remove span ID
|
||
|
ret['cells'] = [re.sub('\a\|+$','',x) for x in ret['cells']]
|
||
|
# Find cells align
|
||
|
ret['cellalign'] = self._get_cell_align(ret['cells'])
|
||
|
# Hooray!
|
||
|
Debug('Table Prop: %s' % ret, 7)
|
||
|
return ret
|
||
|
|
||
|
def dump(self):
|
||
|
open_ = self._get_open_tag()
|
||
|
rows = self.rows
|
||
|
close = TAGS['tableClose']
|
||
|
|
||
|
rowopen = TAGS['tableRowOpen']
|
||
|
rowclose = TAGS['tableRowClose']
|
||
|
rowsep = TAGS['tableRowSep']
|
||
|
titrowopen = TAGS['tableTitleRowOpen'] or rowopen
|
||
|
titrowclose = TAGS['tableTitleRowClose'] or rowclose
|
||
|
|
||
|
if rules['breaktablelineopen']:
|
||
|
rowopen = rowopen + '\n'
|
||
|
titrowopen = titrowopen + '\n'
|
||
|
|
||
|
# Tex gotchas
|
||
|
if TARGET == 'tex':
|
||
|
if not self.border:
|
||
|
rowopen = titrowopen = ''
|
||
|
else:
|
||
|
close = rowopen + close
|
||
|
|
||
|
# Now we tag all the table cells on each row
|
||
|
#tagged_cells = map(lambda x: self._tag_cells(x), rows) #!py15
|
||
|
tagged_cells = []
|
||
|
for cell in rows: tagged_cells.append(self._tag_cells(cell))
|
||
|
|
||
|
# Add row separator tags between lines
|
||
|
tagged_rows = []
|
||
|
if rowsep:
|
||
|
#!py15
|
||
|
#tagged_rows = map(lambda x:x+rowsep, tagged_cells)
|
||
|
for cell in tagged_cells:
|
||
|
tagged_rows.append(cell+rowsep)
|
||
|
# Remove last rowsep, because the table is over
|
||
|
tagged_rows[-1] = tagged_rows[-1].replace(rowsep, '')
|
||
|
# Add row BEGIN/END tags for each line
|
||
|
else:
|
||
|
for rowdata in rows:
|
||
|
if rowdata['title']:
|
||
|
o,c = titrowopen, titrowclose
|
||
|
else:
|
||
|
o,c = rowopen, rowclose
|
||
|
row = tagged_cells.pop(0)
|
||
|
tagged_rows.append(o + row + c)
|
||
|
|
||
|
# Join the pieces together
|
||
|
fulltable = []
|
||
|
if open_: fulltable.append(open_)
|
||
|
fulltable.extend(tagged_rows)
|
||
|
if close: fulltable.append(close)
|
||
|
|
||
|
return fulltable
|
||
|
|
||
|
|
||
|
##############################################################################
|
||
|
|
||
|
|
||
|
class BlockMaster:
|
||
|
"TIP: use blockin/out to add/del holders"
|
||
|
def __init__(self):
|
||
|
self.BLK = []
|
||
|
self.HLD = []
|
||
|
self.PRP = []
|
||
|
self.depth = 0
|
||
|
self.count = 0
|
||
|
self.last = ''
|
||
|
self.tableparser = None
|
||
|
self.contains = {
|
||
|
'para' :['comment','raw','tagged'],
|
||
|
'verb' :[],
|
||
|
'table' :['comment'],
|
||
|
'raw' :[],
|
||
|
'tagged' :[],
|
||
|
'comment' :[],
|
||
|
'quote' :['quote','comment','raw','tagged'],
|
||
|
'list' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
|
||
|
'numlist' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
|
||
|
'deflist' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
|
||
|
'bar' :[],
|
||
|
'title' :[],
|
||
|
'numtitle':[],
|
||
|
}
|
||
|
self.allblocks = list(self.contains.keys())
|
||
|
|
||
|
# If one is found inside another, ignore the marks
|
||
|
self.exclusive = ['comment','verb','raw','tagged']
|
||
|
|
||
|
# May we include bars inside quotes?
|
||
|
if rules['barinsidequote']:
|
||
|
self.contains['quote'].append('bar')
|
||
|
|
||
|
def block(self):
|
||
|
if not self.BLK: return ''
|
||
|
return self.BLK[-1]
|
||
|
|
||
|
def isblock(self, name=''):
|
||
|
return self.block() == name
|
||
|
|
||
|
def prop(self, key):
|
||
|
if not self.PRP: return ''
|
||
|
return self.PRP[-1].get(key) or ''
|
||
|
|
||
|
def propset(self, key, val):
|
||
|
self.PRP[-1][key] = val
|
||
|
#Debug('BLOCK prop ++: %s->%s'%(key,repr(val)), 1)
|
||
|
#Debug('BLOCK props: %s'%(repr(self.PRP)), 1)
|
||
|
|
||
|
def hold(self):
|
||
|
if not self.HLD: return []
|
||
|
return self.HLD[-1]
|
||
|
|
||
|
def holdadd(self, line):
|
||
|
if self.block().endswith('list'): line = [line]
|
||
|
self.HLD[-1].append(line)
|
||
|
Debug('HOLD add: %s'%repr(line), 4)
|
||
|
Debug('FULL HOLD: %s'%self.HLD, 4)
|
||
|
|
||
|
def holdaddsub(self, line):
|
||
|
self.HLD[-1][-1].append(line)
|
||
|
Debug('HOLD addsub: %s'%repr(line), 4)
|
||
|
Debug('FULL HOLD: %s'%self.HLD, 4)
|
||
|
|
||
|
def holdextend(self, lines):
|
||
|
if self.block().endswith('list'): lines = [lines]
|
||
|
self.HLD[-1].extend(lines)
|
||
|
Debug('HOLD extend: %s'%repr(lines), 4)
|
||
|
Debug('FULL HOLD: %s'%self.HLD, 4)
|
||
|
|
||
|
def blockin(self, block):
|
||
|
ret = []
|
||
|
if block not in self.allblocks:
|
||
|
Error("Invalid block '%s'"%block)
|
||
|
|
||
|
# First, let's close other possible open blocks
|
||
|
while self.block() and block not in self.contains[self.block()]:
|
||
|
ret.extend(self.blockout())
|
||
|
|
||
|
# Now we can gladly add this new one
|
||
|
self.BLK.append(block)
|
||
|
self.HLD.append([])
|
||
|
self.PRP.append({})
|
||
|
self.count += 1
|
||
|
if block == 'table': self.tableparser = TableMaster()
|
||
|
# Deeper and deeper
|
||
|
self.depth = len(self.BLK)
|
||
|
Debug('block ++ (%s): %s' % (block,self.BLK), 3)
|
||
|
return ret
|
||
|
|
||
|
def blockout(self):
|
||
|
global AA_COUNT
|
||
|
|
||
|
if not self.BLK: Error('No block to pop')
|
||
|
blockname = self.BLK.pop()
|
||
|
result = getattr(self, blockname)()
|
||
|
parsed = self.HLD.pop()
|
||
|
self.PRP.pop()
|
||
|
self.depth = len(self.BLK)
|
||
|
if blockname == 'table': del self.tableparser
|
||
|
|
||
|
# Inserting a nested block into mother
|
||
|
if self.block():
|
||
|
if blockname != 'comment': # ignore comment blocks
|
||
|
if self.block().endswith('list'):
|
||
|
self.HLD[-1][-1].append(result)
|
||
|
else:
|
||
|
self.HLD[-1].append(result)
|
||
|
# Reset now. Mother block will have it all
|
||
|
result = []
|
||
|
|
||
|
Debug('block -- (%s): %s' % (blockname,self.BLK), 3)
|
||
|
Debug('RELEASED (%s): %s' % (blockname,parsed), 3)
|
||
|
|
||
|
# Save this top level block name (produced output)
|
||
|
# The next block will use it
|
||
|
if result:
|
||
|
self.last = blockname
|
||
|
Debug('BLOCK: %s'%result, 6)
|
||
|
|
||
|
# ASCII Art processing
|
||
|
if TARGET == 'art' and CONF['slides'] and not CONF['toc-only'] and not CONF.get('art-no-title'):
|
||
|
n = (CONF['height'] - 1) - (AA_COUNT % (CONF['height'] - 1) + 1)
|
||
|
if n < len(result) and not (TITLE.level == 1 and blockname in ["title", "numtitle"]):
|
||
|
result = ([''] * n) + [aa_line(AA['bar1'], CONF['width'])] + aa_slide(AA_TITLE, CONF['width']) + [''] + result
|
||
|
if blockname in ["title", "numtitle"] and TITLE.level == 1:
|
||
|
aa_title = aa_slide(AA_TITLE, CONF['width']) + ['']
|
||
|
if AA_COUNT:
|
||
|
aa_title = ([''] * n) + [aa_line(AA['bar2'], CONF['width'])] + aa_title
|
||
|
result = aa_title + result
|
||
|
AA_COUNT += len(result)
|
||
|
|
||
|
return result
|
||
|
|
||
|
def _last_escapes(self, line):
|
||
|
return doFinalEscape(TARGET, line)
|
||
|
|
||
|
def _get_escaped_hold(self):
|
||
|
ret = []
|
||
|
for line in self.hold():
|
||
|
linetype = type(line)
|
||
|
if isinstance('', linetype):
|
||
|
ret.append(self._last_escapes(line))
|
||
|
elif isinstance([], linetype):
|
||
|
ret.extend(line)
|
||
|
else:
|
||
|
Error("BlockMaster: Unknown HOLD item type: %s" % linetype)
|
||
|
return ret
|
||
|
|
||
|
def _remove_twoblanks(self, lastitem):
|
||
|
if len(lastitem) > 1 and lastitem[-2:] == ['','']:
|
||
|
return lastitem[:-2]
|
||
|
return lastitem
|
||
|
|
||
|
def _should_add_blank_line(self, where, blockname):
|
||
|
"Validates the blanksaround* rules"
|
||
|
|
||
|
# Nestable blocks: only mother blocks (level 1) are spaced
|
||
|
if blockname.endswith('list') and self.depth > 1:
|
||
|
return False
|
||
|
|
||
|
# The blank line after the block is always added
|
||
|
if where == 'after' \
|
||
|
and rules['blanksaround'+blockname]:
|
||
|
return True
|
||
|
|
||
|
# # No blank before if it's the first block of the body
|
||
|
# elif where == 'before' \
|
||
|
# and BLOCK.count == 1:
|
||
|
# return False
|
||
|
|
||
|
# # No blank before if it's the first block of this level (nested)
|
||
|
# elif where == 'before' \
|
||
|
# and self.count == 1:
|
||
|
# return False
|
||
|
|
||
|
# The blank line before the block is only added if
|
||
|
# the previous block haven't added a blank line
|
||
|
# (to avoid consecutive blanks)
|
||
|
elif where == 'before' \
|
||
|
and rules['blanksaround'+blockname] \
|
||
|
and not rules.get('blanksaround'+self.last):
|
||
|
return True
|
||
|
|
||
|
# Nested quotes are handled here,
|
||
|
# because the mother quote isn't closed yet
|
||
|
elif where == 'before' \
|
||
|
and blockname == 'quote' \
|
||
|
and rules['blanksaround'+blockname] \
|
||
|
and self.depth > 1:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
def comment(self):
|
||
|
return ''
|
||
|
|
||
|
def raw(self):
|
||
|
lines = self.hold()
|
||
|
return [doEscape(TARGET, x) for x in lines]
|
||
|
|
||
|
def tagged(self):
|
||
|
return self.hold()
|
||
|
|
||
|
def para(self):
|
||
|
result = []
|
||
|
open_ = TAGS['paragraphOpen']
|
||
|
close = TAGS['paragraphClose']
|
||
|
lines = self._get_escaped_hold()
|
||
|
|
||
|
# Blank line before?
|
||
|
if self._should_add_blank_line('before', 'para'): result.append('')
|
||
|
|
||
|
# Open tag
|
||
|
if open_: result.append(open_)
|
||
|
|
||
|
# Pagemaker likes a paragraph as a single long line
|
||
|
if rules['onelinepara']:
|
||
|
result.append(' '.join(lines))
|
||
|
# Others are normal :)
|
||
|
else:
|
||
|
result.extend(lines)
|
||
|
|
||
|
# Close tag
|
||
|
if close: result.append(close)
|
||
|
|
||
|
# Blank line after?
|
||
|
if self._should_add_blank_line('after', 'para'): result.append('')
|
||
|
|
||
|
# Very very very very very very very very very UGLY fix
|
||
|
# Needed because <center> can't appear inside <p>
|
||
|
try:
|
||
|
if len(lines) == 1 and \
|
||
|
TARGET in ('html', 'xhtml') and \
|
||
|
re.match('^\s*<center>.*</center>\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 range(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 isinstance(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 <p>
|
||
|
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 <p>
|
||
|
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 isinstance(line, type([])): # sublist inside
|
||
|
listbody.extend(line)
|
||
|
else:
|
||
|
line = self._last_escapes(line)
|
||
|
|
||
|
# Blank lines turns to <p>
|
||
|
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 = sorted(TARGETS)
|
||
|
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'))
|
||
|
for key in sorted( parsed_config.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 isinstance(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 target not in HEADER_TEMPLATE:
|
||
|
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 = [re.sub('(?i)\.sty$','',x) for x in 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 range(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 range(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)(</HEAD>)', 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)(</HEAD>)', '\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 isinstance(data, type([])):
|
||
|
return [EscapeCharHandler('mask', x) for x in data]
|
||
|
return EscapeCharHandler('mask',data)
|
||
|
|
||
|
def unmaskEscapeChar(data):
|
||
|
"Undo the Escape char \ masking (Input: str or list)"
|
||
|
if isinstance(data, type([])):
|
||
|
return [EscapeCharHandler('unmask', x) for x in 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 range(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 isinstance(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 list(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 = "<center>%s</center>" %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 as 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 tkinter.filedialog import askopenfilename
|
||
|
from tkinter.messagebox 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 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 = [x for x in TARGETS if TARGET_NAMES[x] == name]
|
||
|
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 'infile' in real_cmdline_conf:
|
||
|
del real_cmdline_conf['infile']
|
||
|
if 'target' in real_cmdline_conf:
|
||
|
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
|
||
|
opt in real_cmdline_conf):
|
||
|
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 = [TARGET_NAMES[x] for x in 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 = _('<screen>')
|
||
|
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 as 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.
|