PageRenderTime 92ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/old/txt2tags-2.6.py

http://txt2tags.googlecode.com/
Python | 5987 lines | 5359 code | 249 blank | 379 comment | 250 complexity | 79cde3d3abf5bbc7e2598b375572527c MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, WTFPL
  1. #!/usr/bin/env python
  2. # txt2tags - generic text conversion tool
  3. # http://txt2tags.org
  4. #
  5. # Copyright 2001-2010 Aurelio Jargas
  6. #
  7. # License: http://www.gnu.org/licenses/gpl-2.0.txt
  8. # Subversion: http://svn.txt2tags.org
  9. # Bug tracker: http://bugs.txt2tags.org
  10. #
  11. ########################################################################
  12. #
  13. # BORING CODE EXPLANATION AHEAD
  14. #
  15. # Just read it if you wish to understand how the txt2tags code works.
  16. #
  17. ########################################################################
  18. #
  19. # The code that [1] parses the marked text is separated from the
  20. # code that [2] insert the target tags.
  21. #
  22. # [1] made by: def convert()
  23. # [2] made by: class BlockMaster
  24. #
  25. # The structures of the marked text are identified and its contents are
  26. # extracted into a data holder (Python lists and dictionaries).
  27. #
  28. # When parsing the source file, the blocks (para, lists, quote, table)
  29. # are opened with BlockMaster, right when found. Then its contents,
  30. # which spans on several lines, are feeded into a special holder on the
  31. # BlockMaster instance. Just when the block is closed, the target tags
  32. # are inserted for the full block as a whole, in one pass. This way, we
  33. # have a better control on blocks. Much better than the previous line by
  34. # line approach.
  35. #
  36. # In other words, whenever inside a block, the parser *holds* the tag
  37. # insertion process, waiting until the full block is read. That was
  38. # needed primary to close paragraphs for the XHTML target, but
  39. # proved to be a very good adding, improving many other processing.
  40. #
  41. # -------------------------------------------------------------------
  42. #
  43. # These important classes are all documented:
  44. # CommandLine, SourceDocument, ConfigMaster, ConfigLines.
  45. #
  46. # There is a RAW Config format and all kind of configuration is first
  47. # converted to this format. Then a generic method parses it.
  48. #
  49. # These functions get information about the input file(s) and take
  50. # care of the init processing:
  51. # get_infiles_config(), process_source_file() and convert_this_files()
  52. #
  53. ########################################################################
  54. #XXX Python coding warning
  55. # Avoid common mistakes:
  56. # - do NOT use newlist=list instead newlist=list[:]
  57. # - do NOT use newdic=dic instead newdic=dic.copy()
  58. # - do NOT use dic[key] instead dic.get(key)
  59. # - do NOT use del dic[key] without has_key() before
  60. #XXX Smart Image Align don't work if the image is a link
  61. # Can't fix that because the image is expanded together with the
  62. # link, at the linkbank filling moment. Only the image is passed
  63. # to parse_images(), not the full line, so it is always 'middle'.
  64. #XXX Paragraph separation not valid inside Quote
  65. # Quote will not have <p></p> inside, instead will close and open
  66. # again the <blockquote>. This really sux in CSS, when defining a
  67. # different background color. Still don't know how to fix it.
  68. #XXX TODO (maybe)
  69. # New mark or macro which expands to an anchor full title.
  70. # It is necessary to parse the full document in this order:
  71. # DONE 1st scan: HEAD: get all settings, including %!includeconf
  72. # DONE 2nd scan: BODY: expand includes & apply %!preproc
  73. # 3rd scan: BODY: read titles and compose TOC info
  74. # 4th scan: BODY: full parsing, expanding [#anchor] 1st
  75. # Steps 2 and 3 can be made together, with no tag adding.
  76. # Two complete body scans will be *slow*, don't know if it worths.
  77. # One solution may be add the titles as postproc rules
  78. ##############################################################################
  79. # User config (1=ON, 0=OFF)
  80. USE_I18N = 1 # use gettext for i18ned messages? (default is 1)
  81. COLOR_DEBUG = 1 # show debug messages in colors? (default is 1)
  82. BG_LIGHT = 0 # your terminal background color is light (default is 0)
  83. HTML_LOWER = 0 # use lowercased HTML tags instead upper? (default is 0)
  84. ##############################################################################
  85. # These are all the core Python modules used by txt2tags (KISS!)
  86. import re, os, sys, time, getopt
  87. # The CSV module is new in Python version 2.3
  88. try:
  89. import csv
  90. except ImportError:
  91. csv = None
  92. # Program information
  93. my_url = 'http://txt2tags.org'
  94. my_name = 'txt2tags'
  95. my_email = 'verde@aurelio.net'
  96. my_version = '2.6'
  97. # i18n - just use if available
  98. if USE_I18N:
  99. try:
  100. import gettext
  101. # If your locale dir is different, change it here
  102. cat = gettext.Catalog('txt2tags',localedir='/usr/share/locale/')
  103. _ = cat.gettext
  104. except:
  105. _ = lambda x:x
  106. else:
  107. _ = lambda x:x
  108. # FLAGS : the conversion related flags , may be used in %!options
  109. # OPTIONS : the conversion related options, may be used in %!options
  110. # ACTIONS : the other behavior modifiers, valid on command line only
  111. # MACROS : the valid macros with their default values for formatting
  112. # SETTINGS: global miscellaneous settings, valid on RC file only
  113. # NO_TARGET: actions that don't require a target specification
  114. # NO_MULTI_INPUT: actions that don't accept more than one input file
  115. # CONFIG_KEYWORDS: the valid %!key:val keywords
  116. #
  117. # FLAGS and OPTIONS are configs that affect the converted document.
  118. # They usually have also a --no-<option> to turn them OFF.
  119. #
  120. # ACTIONS are needed because when doing multiple input files, strange
  121. # behavior would be found, as use command line interface for the
  122. # first file and gui for the second. There is no --no-<action>.
  123. # --version and --help inside %!options are also odd
  124. #
  125. TARGETS = 'html xhtml sgml dbk tex lout man mgp wiki gwiki doku pmw moin pm6 txt art adoc creole'.split()
  126. TARGETS.sort()
  127. FLAGS = {'headers' :1 , 'enum-title' :0 , 'mask-email' :0 ,
  128. 'toc-only' :0 , 'toc' :0 , 'rc' :1 ,
  129. 'css-sugar' :0 , 'css-suggar' :0 , 'css-inside' :0 ,
  130. 'quiet' :0 , 'slides' :0 }
  131. OPTIONS = {'target' :'', 'toc-level' :3 , 'style' :'',
  132. 'infile' :'', 'outfile' :'', 'encoding' :'',
  133. 'config-file':'', 'split' :0 , 'lang' :'',
  134. 'width' :0 , 'height' :0 , 'art-chars' :'',
  135. 'show-config-value':''}
  136. ACTIONS = {'help' :0 , 'version' :0 , 'gui' :0 ,
  137. 'verbose' :0 , 'debug' :0 , 'dump-config':0 ,
  138. 'dump-source':0 , 'targets' :0}
  139. MACROS = {'date' : '%Y%m%d', 'infile': '%f',
  140. 'mtime': '%Y%m%d', 'outfile': '%f'}
  141. SETTINGS = {} # for future use
  142. NO_TARGET = ['help', 'version', 'gui', 'toc-only', 'dump-config', 'dump-source', 'targets']
  143. NO_MULTI_INPUT = ['gui','dump-config','dump-source']
  144. CONFIG_KEYWORDS = [
  145. 'target', 'encoding', 'style', 'options', 'preproc','postproc',
  146. 'guicolors']
  147. TARGET_NAMES = {
  148. 'html' : _('HTML page'),
  149. 'xhtml' : _('XHTML page'),
  150. 'sgml' : _('SGML document'),
  151. 'dbk' : _('DocBook document'),
  152. 'tex' : _('LaTeX document'),
  153. 'lout' : _('Lout document'),
  154. 'man' : _('UNIX Manual page'),
  155. 'mgp' : _('MagicPoint presentation'),
  156. 'wiki' : _('Wikipedia page'),
  157. 'gwiki' : _('Google Wiki page'),
  158. 'doku' : _('DokuWiki page'),
  159. 'pmw' : _('PmWiki page'),
  160. 'moin' : _('MoinMoin page'),
  161. 'pm6' : _('PageMaker document'),
  162. 'txt' : _('Plain Text'),
  163. 'art' : _('ASCII Art text'),
  164. 'adoc' : _('AsciiDoc document'),
  165. 'creole' : _('Creole 1.0 document')
  166. }
  167. DEBUG = 0 # do not edit here, please use --debug
  168. VERBOSE = 0 # do not edit here, please use -v, -vv or -vvv
  169. QUIET = 0 # do not edit here, please use --quiet
  170. GUI = 0 # do not edit here, please use --gui
  171. AUTOTOC = 1 # do not edit here, please use --no-toc or %%toc
  172. DFT_TEXT_WIDTH = 72 # do not edit here, please use --width
  173. DFT_SLIDE_WIDTH = 80 # do not edit here, please use --width
  174. DFT_SLIDE_HEIGHT = 25 # do not edit here, please use --height
  175. # ASCII Art config
  176. AA_KEYS = 'corner border side bar1 bar2 level2 level3 level4 level5'.split()
  177. AA_VALUES = '+-|-==-^"' # do not edit here, please use --art-chars
  178. AA = dict(zip(AA_KEYS, AA_VALUES))
  179. AA_COUNT = 0
  180. AA_TITLE = ''
  181. RC_RAW = []
  182. CMDLINE_RAW = []
  183. CONF = {}
  184. BLOCK = None
  185. TITLE = None
  186. regex = {}
  187. TAGS = {}
  188. rules = {}
  189. # Gui globals
  190. askopenfilename = None
  191. showinfo = None
  192. showwarning = None
  193. showerror = None
  194. lang = 'english'
  195. TARGET = ''
  196. STDIN = STDOUT = '-'
  197. MODULEIN = MODULEOUT = '-module-'
  198. ESCCHAR = '\x00'
  199. SEPARATOR = '\x01'
  200. LISTNAMES = {'-':'list', '+':'numlist', ':':'deflist'}
  201. LINEBREAK = {'default':'\n', 'win':'\r\n', 'mac':'\r'}
  202. # Platform specific settings
  203. LB = LINEBREAK.get(sys.platform[:3]) or LINEBREAK['default']
  204. VERSIONSTR = _("%s version %s <%s>")%(my_name,my_version,my_url)
  205. USAGE = '\n'.join([
  206. '',
  207. _("Usage: %s [OPTIONS] [infile.t2t ...]") % my_name,
  208. '',
  209. _(" --targets print a list of all the available targets and exit"),
  210. _(" -t, --target=TYPE set target document type. currently supported:"),
  211. ' %s,' % ', '.join(TARGETS[:9]),
  212. ' %s' % ', '.join(TARGETS[9:]),
  213. _(" -i, --infile=FILE set FILE as the input file name ('-' for STDIN)"),
  214. _(" -o, --outfile=FILE set FILE as the output file name ('-' for STDOUT)"),
  215. _(" --encoding=ENC set target file encoding (utf-8, iso-8859-1, etc)"),
  216. _(" --toc add an automatic Table of Contents to the output"),
  217. _(" --toc-level=N set maximum TOC level (depth) to N"),
  218. _(" --toc-only print the Table of Contents and exit"),
  219. _(" -n, --enum-title enumerate all titles as 1, 1.1, 1.1.1, etc"),
  220. _(" --style=FILE use FILE as the document style (like HTML CSS)"),
  221. _(" --css-sugar insert CSS-friendly tags for HTML/XHTML"),
  222. _(" --css-inside insert CSS file contents inside HTML/XHTML headers"),
  223. _(" -H, --no-headers suppress header and footer from the output"),
  224. _(" --mask-email hide email from spam robots. x@y.z turns <x (a) y z>"),
  225. _(" --slides format output as presentation slides (used by -t art)"),
  226. _(" --width=N set the output's width to N columns (used by -t art)"),
  227. _(" --height=N set the output's height to N rows (used by -t art)"),
  228. _(" -C, --config-file=F read configuration from file F"),
  229. _(" --gui invoke Graphical Tk Interface"),
  230. _(" -q, --quiet quiet mode, suppress all output (except errors)"),
  231. _(" -v, --verbose print informative messages during conversion"),
  232. _(" -h, --help print this help information and exit"),
  233. _(" -V, --version print program version and exit"),
  234. _(" --dump-config print all the configuration found and exit"),
  235. _(" --dump-source print the document source, with includes expanded"),
  236. '',
  237. _("Turn OFF options:"),
  238. " --no-css-inside, --no-css-sugar, --no-dump-config, --no-dump-source,",
  239. " --no-encoding, --no-enum-title, --no-headers, --no-infile,",
  240. " --no-mask-email, --no-outfile, --no-quiet, --no-rc, --no-slides,",
  241. " --no-style, --no-targets, --no-toc, --no-toc-only",
  242. '',
  243. _("Example:"),
  244. " %s -t html --toc %s" % (my_name, _("file.t2t")),
  245. '',
  246. _("By default, converted output is saved to 'infile.<target>'."),
  247. _("Use --outfile to force an output file name."),
  248. _("If input file is '-', reads from STDIN."),
  249. _("If output file is '-', dumps output to STDOUT."),
  250. '',
  251. my_url,
  252. ''
  253. ])
  254. ##############################################################################
  255. # Here is all the target's templates
  256. # You may edit them to fit your needs
  257. # - the %(HEADERn)s strings represent the Header lines
  258. # - the %(STYLE)s string is changed by --style contents
  259. # - the %(ENCODING)s string is changed by --encoding contents
  260. # - if any of the above is empty, the full line is removed
  261. # - use %% to represent a literal %
  262. #
  263. HEADER_TEMPLATE = {
  264. 'art':"""
  265. Fake template to respect the general process.
  266. """,
  267. 'txt': """\
  268. %(HEADER1)s
  269. %(HEADER2)s
  270. %(HEADER3)s
  271. """,
  272. 'sgml': """\
  273. <!doctype linuxdoc system>
  274. <article>
  275. <title>%(HEADER1)s
  276. <author>%(HEADER2)s
  277. <date>%(HEADER3)s
  278. """,
  279. 'html': """\
  280. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  281. <HTML>
  282. <HEAD>
  283. <META NAME="generator" CONTENT="http://txt2tags.org">
  284. <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=%(ENCODING)s">
  285. <LINK REL="stylesheet" TYPE="text/css" HREF="%(STYLE)s">
  286. <TITLE>%(HEADER1)s</TITLE>
  287. </HEAD><BODY BGCOLOR="white" TEXT="black">
  288. <CENTER>
  289. <H1>%(HEADER1)s</H1>
  290. <FONT SIZE="4"><I>%(HEADER2)s</I></FONT><BR>
  291. <FONT SIZE="4">%(HEADER3)s</FONT>
  292. </CENTER>
  293. """,
  294. 'htmlcss': """\
  295. <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
  296. <HTML>
  297. <HEAD>
  298. <META NAME="generator" CONTENT="http://txt2tags.org">
  299. <META HTTP-EQUIV="Content-Type" CONTENT="text/html; charset=%(ENCODING)s">
  300. <LINK REL="stylesheet" TYPE="text/css" HREF="%(STYLE)s">
  301. <TITLE>%(HEADER1)s</TITLE>
  302. </HEAD>
  303. <BODY>
  304. <DIV CLASS="header" ID="header">
  305. <H1>%(HEADER1)s</H1>
  306. <H2>%(HEADER2)s</H2>
  307. <H3>%(HEADER3)s</H3>
  308. </DIV>
  309. """,
  310. 'xhtml': """\
  311. <?xml version="1.0"
  312. encoding="%(ENCODING)s"
  313. ?>
  314. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\
  315. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  316. <html xmlns="http://www.w3.org/1999/xhtml">
  317. <head>
  318. <title>%(HEADER1)s</title>
  319. <meta name="generator" content="http://txt2tags.org" />
  320. <link rel="stylesheet" type="text/css" href="%(STYLE)s" />
  321. </head>
  322. <body bgcolor="white" text="black">
  323. <div align="center">
  324. <h1>%(HEADER1)s</h1>
  325. <h2>%(HEADER2)s</h2>
  326. <h3>%(HEADER3)s</h3>
  327. </div>
  328. """,
  329. 'xhtmlcss': """\
  330. <?xml version="1.0"
  331. encoding="%(ENCODING)s"
  332. ?>
  333. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"\
  334. "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
  335. <html xmlns="http://www.w3.org/1999/xhtml">
  336. <head>
  337. <title>%(HEADER1)s</title>
  338. <meta name="generator" content="http://txt2tags.org" />
  339. <link rel="stylesheet" type="text/css" href="%(STYLE)s" />
  340. </head>
  341. <body>
  342. <div class="header" id="header">
  343. <h1>%(HEADER1)s</h1>
  344. <h2>%(HEADER2)s</h2>
  345. <h3>%(HEADER3)s</h3>
  346. </div>
  347. """,
  348. 'dbk': """\
  349. <?xml version="1.0"
  350. encoding="%(ENCODING)s"
  351. ?>
  352. <!DOCTYPE article PUBLIC "-//OASIS//DTD DocBook XML V4.5//EN"\
  353. "docbook/dtd/xml/4.5/docbookx.dtd">
  354. <article lang="en">
  355. <articleinfo>
  356. <title>%(HEADER1)s</title>
  357. <authorgroup>
  358. <author><othername>%(HEADER2)s</othername></author>
  359. </authorgroup>
  360. <date>%(HEADER3)s</date>
  361. </articleinfo>
  362. """,
  363. 'man': """\
  364. .TH "%(HEADER1)s" 1 "%(HEADER3)s" "%(HEADER2)s"
  365. """,
  366. # TODO style to <HR>
  367. 'pm6': """\
  368. <PMTags1.0 win><C-COLORTABLE ("Preto" 1 0 0 0)
  369. ><@Normal=
  370. <FONT "Times New Roman"><CCOLOR "Preto"><SIZE 11>
  371. <HORIZONTAL 100><LETTERSPACE 0><CTRACK 127><CSSIZE 70><C+SIZE 58.3>
  372. <C-POSITION 33.3><C+POSITION 33.3><P><CBASELINE 0><CNOBREAK 0><CLEADING -0.05>
  373. <GGRID 0><GLEFT 7.2><GRIGHT 0><GFIRST 0><G+BEFORE 7.2><G+AFTER 0>
  374. <GALIGNMENT "justify"><GMETHOD "proportional"><G& "ENGLISH">
  375. <GPAIRS 12><G%% 120><GKNEXT 0><GKWIDOW 0><GKORPHAN 0><GTABS $>
  376. <GHYPHENATION 2 34 0><GWORDSPACE 75 100 150><GSPACE -5 0 25>
  377. ><@Bullet=<@-PARENT "Normal"><FONT "Abadi MT Condensed Light">
  378. <GLEFT 14.4><G+BEFORE 2.15><G%% 110><GTABS(25.2 l "")>
  379. ><@PreFormat=<@-PARENT "Normal"><FONT "Lucida Console"><SIZE 8><CTRACK 0>
  380. <GLEFT 0><G+BEFORE 0><GALIGNMENT "left"><GWORDSPACE 100 100 100><GSPACE 0 0 0>
  381. ><@Title1=<@-PARENT "Normal"><FONT "Arial"><SIZE 14><B>
  382. <GCONTENTS><GLEFT 0><G+BEFORE 0><GALIGNMENT "left">
  383. ><@Title2=<@-PARENT "Title1"><SIZE 12><G+BEFORE 3.6>
  384. ><@Title3=<@-PARENT "Title1"><SIZE 10><GLEFT 7.2><G+BEFORE 7.2>
  385. ><@Title4=<@-PARENT "Title3">
  386. ><@Title5=<@-PARENT "Title3">
  387. ><@Quote=<@-PARENT "Normal"><SIZE 10><I>>
  388. %(HEADER1)s
  389. %(HEADER2)s
  390. %(HEADER3)s
  391. """,
  392. 'mgp': """\
  393. #!/usr/X11R6/bin/mgp -t 90
  394. %%deffont "normal" xfont "utopia-medium-r", charset "iso8859-1"
  395. %%deffont "normal-i" xfont "utopia-medium-i", charset "iso8859-1"
  396. %%deffont "normal-b" xfont "utopia-bold-r" , charset "iso8859-1"
  397. %%deffont "normal-bi" xfont "utopia-bold-i" , charset "iso8859-1"
  398. %%deffont "mono" xfont "courier-medium-r", charset "iso8859-1"
  399. %%default 1 size 5
  400. %%default 2 size 8, fore "yellow", font "normal-b", center
  401. %%default 3 size 5, fore "white", font "normal", left, prefix " "
  402. %%tab 1 size 4, vgap 30, prefix " ", icon arc "red" 40, leftfill
  403. %%tab 2 prefix " ", icon arc "orange" 40, leftfill
  404. %%tab 3 prefix " ", icon arc "brown" 40, leftfill
  405. %%tab 4 prefix " ", icon arc "darkmagenta" 40, leftfill
  406. %%tab 5 prefix " ", icon arc "magenta" 40, leftfill
  407. %%%%------------------------- end of headers -----------------------------
  408. %%page
  409. %%size 10, center, fore "yellow"
  410. %(HEADER1)s
  411. %%font "normal-i", size 6, fore "white", center
  412. %(HEADER2)s
  413. %%font "mono", size 7, center
  414. %(HEADER3)s
  415. """,
  416. 'moin': """\
  417. '''%(HEADER1)s'''
  418. ''%(HEADER2)s''
  419. %(HEADER3)s
  420. """,
  421. 'gwiki': """\
  422. *%(HEADER1)s*
  423. %(HEADER2)s
  424. _%(HEADER3)s_
  425. """,
  426. 'adoc': """\
  427. = %(HEADER1)s
  428. %(HEADER2)s
  429. %(HEADER3)s
  430. """,
  431. 'doku': """\
  432. ===== %(HEADER1)s =====
  433. **//%(HEADER2)s//**
  434. //%(HEADER3)s//
  435. """,
  436. 'pmw': """\
  437. (:Title %(HEADER1)s:)
  438. (:Description %(HEADER2)s:)
  439. (:Summary %(HEADER3)s:)
  440. """,
  441. 'wiki': """\
  442. '''%(HEADER1)s'''
  443. %(HEADER2)s
  444. ''%(HEADER3)s''
  445. """,
  446. 'tex': \
  447. r"""\documentclass{article}
  448. \usepackage{graphicx}
  449. \usepackage{paralist} %% needed for compact lists
  450. \usepackage[normalem]{ulem} %% needed by strike
  451. \usepackage[urlcolor=blue,colorlinks=true]{hyperref}
  452. \usepackage[%(ENCODING)s]{inputenc} %% char encoding
  453. \usepackage{%(STYLE)s} %% user defined
  454. \title{%(HEADER1)s}
  455. \author{%(HEADER2)s}
  456. \begin{document}
  457. \date{%(HEADER3)s}
  458. \maketitle
  459. \clearpage
  460. """,
  461. 'lout': """\
  462. @SysInclude { doc }
  463. @Document
  464. @InitialFont { Times Base 12p } # Times, Courier, Helvetica, ...
  465. @PageOrientation { Portrait } # Portrait, Landscape
  466. @ColumnNumber { 1 } # Number of columns (2, 3, ...)
  467. @PageHeaders { Simple } # None, Simple, Titles, NoTitles
  468. @InitialLanguage { English } # German, French, Portuguese, ...
  469. @OptimizePages { Yes } # Yes/No smart page break feature
  470. //
  471. @Text @Begin
  472. @Display @Heading { %(HEADER1)s }
  473. @Display @I { %(HEADER2)s }
  474. @Display { %(HEADER3)s }
  475. #@NP # Break page after Headers
  476. """,
  477. 'creole': """\
  478. %(HEADER1)s
  479. %(HEADER2)s
  480. %(HEADER3)s
  481. """
  482. # @SysInclude { tbl } # Tables support
  483. # setup: @MakeContents { Yes } # show TOC
  484. # setup: @SectionGap # break page at each section
  485. }
  486. ##############################################################################
  487. def getTags(config):
  488. "Returns all the known tags for the specified target"
  489. keys = """
  490. title1 numtitle1
  491. title2 numtitle2
  492. title3 numtitle3
  493. title4 numtitle4
  494. title5 numtitle5
  495. title1Open title1Close
  496. title2Open title2Close
  497. title3Open title3Close
  498. title4Open title4Close
  499. title5Open title5Close
  500. blocktitle1Open blocktitle1Close
  501. blocktitle2Open blocktitle2Close
  502. blocktitle3Open blocktitle3Close
  503. paragraphOpen paragraphClose
  504. blockVerbOpen blockVerbClose
  505. blockQuoteOpen blockQuoteClose blockQuoteLine
  506. blockCommentOpen blockCommentClose
  507. fontMonoOpen fontMonoClose
  508. fontBoldOpen fontBoldClose
  509. fontItalicOpen fontItalicClose
  510. fontUnderlineOpen fontUnderlineClose
  511. fontStrikeOpen fontStrikeClose
  512. listOpen listClose
  513. listOpenCompact listCloseCompact
  514. listItemOpen listItemClose listItemLine
  515. numlistOpen numlistClose
  516. numlistOpenCompact numlistCloseCompact
  517. numlistItemOpen numlistItemClose numlistItemLine
  518. deflistOpen deflistClose
  519. deflistOpenCompact deflistCloseCompact
  520. deflistItem1Open deflistItem1Close
  521. deflistItem2Open deflistItem2Close deflistItem2LinePrefix
  522. bar1 bar2
  523. url urlMark
  524. email emailMark
  525. img imgAlignLeft imgAlignRight imgAlignCenter
  526. _imgAlignLeft _imgAlignRight _imgAlignCenter
  527. tableOpen tableClose
  528. _tableBorder _tableAlignLeft _tableAlignCenter
  529. tableRowOpen tableRowClose tableRowSep
  530. tableTitleRowOpen tableTitleRowClose
  531. tableCellOpen tableCellClose tableCellSep
  532. tableTitleCellOpen tableTitleCellClose tableTitleCellSep
  533. _tableColAlignLeft _tableColAlignRight _tableColAlignCenter
  534. _tableCellAlignLeft _tableCellAlignRight _tableCellAlignCenter
  535. _tableCellColSpan tableColAlignSep
  536. _tableCellMulticolOpen
  537. _tableCellMulticolClose
  538. bodyOpen bodyClose
  539. cssOpen cssClose
  540. tocOpen tocClose TOC
  541. anchor
  542. comment
  543. pageBreak
  544. EOD
  545. """.split()
  546. # TIP: \a represents the current text inside the mark
  547. # TIP: ~A~, ~B~ and ~C~ are expanded to other tags parts
  548. alltags = {
  549. 'art': {
  550. 'title1' : '\a' ,
  551. 'title2' : '\a' ,
  552. 'title3' : '\a' ,
  553. 'title4' : '\a' ,
  554. 'title5' : '\a' ,
  555. 'blockQuoteLine' : '\t' ,
  556. 'listItemOpen' : '- ' ,
  557. 'numlistItemOpen' : '\a. ' ,
  558. 'bar1' : aa_line(AA['bar1'], config['width']),
  559. 'bar2' : aa_line(AA['bar2'], config['width']),
  560. 'url' : '\a' ,
  561. 'urlMark' : '\a (\a)' ,
  562. 'email' : '\a' ,
  563. 'emailMark' : '\a (\a)' ,
  564. 'img' : '[\a]' ,
  565. },
  566. 'txt': {
  567. 'title1' : ' \a' ,
  568. 'title2' : '\t\a' ,
  569. 'title3' : '\t\t\a' ,
  570. 'title4' : '\t\t\t\a' ,
  571. 'title5' : '\t\t\t\t\a',
  572. 'blockQuoteLine' : '\t' ,
  573. 'listItemOpen' : '- ' ,
  574. 'numlistItemOpen' : '\a. ' ,
  575. 'bar1' : '\a' ,
  576. 'url' : '\a' ,
  577. 'urlMark' : '\a (\a)' ,
  578. 'email' : '\a' ,
  579. 'emailMark' : '\a (\a)' ,
  580. 'img' : '[\a]' ,
  581. },
  582. 'html': {
  583. 'paragraphOpen' : '<P>' ,
  584. 'paragraphClose' : '</P>' ,
  585. 'title1' : '~A~<H1>\a</H1>' ,
  586. 'title2' : '~A~<H2>\a</H2>' ,
  587. 'title3' : '~A~<H3>\a</H3>' ,
  588. 'title4' : '~A~<H4>\a</H4>' ,
  589. 'title5' : '~A~<H5>\a</H5>' ,
  590. 'anchor' : '<A NAME="\a"></A>\n',
  591. 'blockVerbOpen' : '<PRE>' ,
  592. 'blockVerbClose' : '</PRE>' ,
  593. 'blockQuoteOpen' : '<BLOCKQUOTE>' ,
  594. 'blockQuoteClose' : '</BLOCKQUOTE>' ,
  595. 'fontMonoOpen' : '<CODE>' ,
  596. 'fontMonoClose' : '</CODE>' ,
  597. 'fontBoldOpen' : '<B>' ,
  598. 'fontBoldClose' : '</B>' ,
  599. 'fontItalicOpen' : '<I>' ,
  600. 'fontItalicClose' : '</I>' ,
  601. 'fontUnderlineOpen' : '<U>' ,
  602. 'fontUnderlineClose' : '</U>' ,
  603. 'fontStrikeOpen' : '<S>' ,
  604. 'fontStrikeClose' : '</S>' ,
  605. 'listOpen' : '<UL>' ,
  606. 'listClose' : '</UL>' ,
  607. 'listItemOpen' : '<LI>' ,
  608. 'numlistOpen' : '<OL>' ,
  609. 'numlistClose' : '</OL>' ,
  610. 'numlistItemOpen' : '<LI>' ,
  611. 'deflistOpen' : '<DL>' ,
  612. 'deflistClose' : '</DL>' ,
  613. 'deflistItem1Open' : '<DT>' ,
  614. 'deflistItem1Close' : '</DT>' ,
  615. 'deflistItem2Open' : '<DD>' ,
  616. 'bar1' : '<HR NOSHADE SIZE=1>' ,
  617. 'bar2' : '<HR NOSHADE SIZE=5>' ,
  618. 'url' : '<A HREF="\a">\a</A>' ,
  619. 'urlMark' : '<A HREF="\a">\a</A>' ,
  620. 'email' : '<A HREF="mailto:\a">\a</A>' ,
  621. 'emailMark' : '<A HREF="mailto:\a">\a</A>' ,
  622. 'img' : '<IMG~A~ SRC="\a" BORDER="0" ALT="">',
  623. '_imgAlignLeft' : ' ALIGN="left"' ,
  624. '_imgAlignCenter' : ' ALIGN="middle"',
  625. '_imgAlignRight' : ' ALIGN="right"' ,
  626. 'tableOpen' : '<TABLE~A~~B~ CELLPADDING="4">',
  627. 'tableClose' : '</TABLE>' ,
  628. 'tableRowOpen' : '<TR>' ,
  629. 'tableRowClose' : '</TR>' ,
  630. 'tableCellOpen' : '<TD~A~~S~>' ,
  631. 'tableCellClose' : '</TD>' ,
  632. 'tableTitleCellOpen' : '<TH~S~>' ,
  633. 'tableTitleCellClose' : '</TH>' ,
  634. '_tableBorder' : ' BORDER="1"' ,
  635. '_tableAlignCenter' : ' ALIGN="center"',
  636. '_tableCellAlignRight' : ' ALIGN="right"' ,
  637. '_tableCellAlignCenter': ' ALIGN="center"',
  638. '_tableCellColSpan' : ' COLSPAN="\a"' ,
  639. 'cssOpen' : '<STYLE TYPE="text/css">',
  640. 'cssClose' : '</STYLE>' ,
  641. 'comment' : '<!-- \a -->' ,
  642. 'EOD' : '</BODY></HTML>'
  643. },
  644. #TIP xhtml inherits all HTML definitions (lowercased)
  645. #TIP http://www.w3.org/TR/xhtml1/#guidelines
  646. #TIP http://www.htmlref.com/samples/Chapt17/17_08.htm
  647. 'xhtml': {
  648. 'listItemClose' : '</li>' ,
  649. 'numlistItemClose' : '</li>' ,
  650. 'deflistItem2Close' : '</dd>' ,
  651. 'bar1' : '<hr class="light" />',
  652. 'bar2' : '<hr class="heavy" />',
  653. 'anchor' : '<a id="\a" name="\a"></a>\n',
  654. 'img' : '<img~A~ src="\a" border="0" alt=""/>',
  655. },
  656. 'sgml': {
  657. 'paragraphOpen' : '<p>' ,
  658. 'title1' : '<sect>\a~A~<p>' ,
  659. 'title2' : '<sect1>\a~A~<p>' ,
  660. 'title3' : '<sect2>\a~A~<p>' ,
  661. 'title4' : '<sect3>\a~A~<p>' ,
  662. 'title5' : '<sect4>\a~A~<p>' ,
  663. 'anchor' : '<label id="\a">' ,
  664. 'blockVerbOpen' : '<tscreen><verb>' ,
  665. 'blockVerbClose' : '</verb></tscreen>' ,
  666. 'blockQuoteOpen' : '<quote>' ,
  667. 'blockQuoteClose' : '</quote>' ,
  668. 'fontMonoOpen' : '<tt>' ,
  669. 'fontMonoClose' : '</tt>' ,
  670. 'fontBoldOpen' : '<bf>' ,
  671. 'fontBoldClose' : '</bf>' ,
  672. 'fontItalicOpen' : '<em>' ,
  673. 'fontItalicClose' : '</em>' ,
  674. 'fontUnderlineOpen' : '<bf><em>' ,
  675. 'fontUnderlineClose' : '</em></bf>' ,
  676. 'listOpen' : '<itemize>' ,
  677. 'listClose' : '</itemize>' ,
  678. 'listItemOpen' : '<item>' ,
  679. 'numlistOpen' : '<enum>' ,
  680. 'numlistClose' : '</enum>' ,
  681. 'numlistItemOpen' : '<item>' ,
  682. 'deflistOpen' : '<descrip>' ,
  683. 'deflistClose' : '</descrip>' ,
  684. 'deflistItem1Open' : '<tag>' ,
  685. 'deflistItem1Close' : '</tag>' ,
  686. 'bar1' : '<!-- \a -->' ,
  687. 'url' : '<htmlurl url="\a" name="\a">' ,
  688. 'urlMark' : '<htmlurl url="\a" name="\a">' ,
  689. 'email' : '<htmlurl url="mailto:\a" name="\a">' ,
  690. 'emailMark' : '<htmlurl url="mailto:\a" name="\a">' ,
  691. 'img' : '<figure><ph vspace=""><img src="\a"></figure>',
  692. 'tableOpen' : '<table><tabular ca="~C~">' ,
  693. 'tableClose' : '</tabular></table>' ,
  694. 'tableRowSep' : '<rowsep>' ,
  695. 'tableCellSep' : '<colsep>' ,
  696. '_tableColAlignLeft' : 'l' ,
  697. '_tableColAlignRight' : 'r' ,
  698. '_tableColAlignCenter' : 'c' ,
  699. 'comment' : '<!-- \a -->' ,
  700. 'TOC' : '<toc>' ,
  701. 'EOD' : '</article>'
  702. },
  703. 'dbk': {
  704. 'paragraphOpen' : '<para>' ,
  705. 'paragraphClose' : '</para>' ,
  706. 'title1Open' : '~A~<sect1><title>\a</title>' ,
  707. 'title1Close' : '</sect1>' ,
  708. 'title2Open' : '~A~ <sect2><title>\a</title>' ,
  709. 'title2Close' : ' </sect2>' ,
  710. 'title3Open' : '~A~ <sect3><title>\a</title>' ,
  711. 'title3Close' : ' </sect3>' ,
  712. 'title4Open' : '~A~ <sect4><title>\a</title>' ,
  713. 'title4Close' : ' </sect4>' ,
  714. 'title5Open' : '~A~ <sect5><title>\a</title>',
  715. 'title5Close' : ' </sect5>' ,
  716. 'anchor' : '<anchor id="\a"/>\n' ,
  717. 'blockVerbOpen' : '<programlisting>' ,
  718. 'blockVerbClose' : '</programlisting>' ,
  719. 'blockQuoteOpen' : '<blockquote><para>' ,
  720. 'blockQuoteClose' : '</para></blockquote>' ,
  721. 'fontMonoOpen' : '<code>' ,
  722. 'fontMonoClose' : '</code>' ,
  723. 'fontBoldOpen' : '<emphasis role="bold">' ,
  724. 'fontBoldClose' : '</emphasis>' ,
  725. 'fontItalicOpen' : '<emphasis>' ,
  726. 'fontItalicClose' : '</emphasis>' ,
  727. 'fontUnderlineOpen' : '<emphasis role="underline">' ,
  728. 'fontUnderlineClose' : '</emphasis>' ,
  729. # 'fontStrikeOpen' : '<emphasis role="strikethrough">' , # Don't know
  730. # 'fontStrikeClose' : '</emphasis>' ,
  731. 'listOpen' : '<itemizedlist>' ,
  732. 'listClose' : '</itemizedlist>' ,
  733. 'listItemOpen' : '<listitem><para>' ,
  734. 'listItemClose' : '</para></listitem>' ,
  735. 'numlistOpen' : '<orderedlist numeration="arabic">' ,
  736. 'numlistClose' : '</orderedlist>' ,
  737. 'numlistItemOpen' : '<listitem><para>' ,
  738. 'numlistItemClose' : '</para></listitem>' ,
  739. 'deflistOpen' : '<variablelist>' ,
  740. 'deflistClose' : '</variablelist>' ,
  741. 'deflistItem1Open' : '<varlistentry><term>' ,
  742. 'deflistItem1Close' : '</term>' ,
  743. 'deflistItem2Open' : '<listitem><para>' ,
  744. 'deflistItem2Close' : '</para></listitem></varlistentry>' ,
  745. # 'bar1' : '<>' , # Don't know
  746. # 'bar2' : '<>' , # Don't know
  747. 'url' : '<ulink url="\a">\a</ulink>' ,
  748. 'urlMark' : '<ulink url="\a">\a</ulink>' ,
  749. 'email' : '<email>\a</email>' ,
  750. 'emailMark' : '<email>\a</email>' ,
  751. 'img' : '<mediaobject><imageobject><imagedata fileref="\a"/></imageobject></mediaobject>',
  752. # '_imgAlignLeft' : '' , # Don't know
  753. # '_imgAlignCenter' : '' , # Don't know
  754. # '_imgAlignRight' : '' , # Don't know
  755. # 'tableOpen' : '<informaltable><tgroup cols=""><tbody>', # Don't work, need to know number of cols
  756. # 'tableClose' : '</tbody></tgroup></informaltable>' ,
  757. # 'tableRowOpen' : '<row>' ,
  758. # 'tableRowClose' : '</row>' ,
  759. # 'tableCellOpen' : '<entry>' ,
  760. # 'tableCellClose' : '</entry>' ,
  761. # 'tableTitleRowOpen' : '<thead>' ,
  762. # 'tableTitleRowClose' : '</thead>' ,
  763. # '_tableBorder' : ' frame="all"' ,
  764. # '_tableAlignCenter' : ' align="center"' ,
  765. # '_tableCellAlignRight' : ' align="right"' ,
  766. # '_tableCellAlignCenter': ' align="center"' ,
  767. # '_tableCellColSpan' : ' COLSPAN="\a"' ,
  768. 'TOC' : '<index/>' ,
  769. 'comment' : '<!-- \a -->' ,
  770. 'EOD' : '</article>'
  771. },
  772. 'tex': {
  773. 'title1' : '~A~\section*{\a}' ,
  774. 'title2' : '~A~\\subsection*{\a}' ,
  775. 'title3' : '~A~\\subsubsection*{\a}',
  776. # title 4/5: DIRTY: para+BF+\\+\n
  777. 'title4' : '~A~\\paragraph{}\\textbf{\a}\\\\\n',
  778. 'title5' : '~A~\\paragraph{}\\textbf{\a}\\\\\n',
  779. 'numtitle1' : '\n~A~\section{\a}' ,
  780. 'numtitle2' : '~A~\\subsection{\a}' ,
  781. 'numtitle3' : '~A~\\subsubsection{\a}' ,
  782. 'anchor' : '\\hypertarget{\a}{}\n' ,
  783. 'blockVerbOpen' : '\\begin{verbatim}' ,
  784. 'blockVerbClose' : '\\end{verbatim}' ,
  785. 'blockQuoteOpen' : '\\begin{quotation}' ,
  786. 'blockQuoteClose' : '\\end{quotation}' ,
  787. 'fontMonoOpen' : '\\texttt{' ,
  788. 'fontMonoClose' : '}' ,
  789. 'fontBoldOpen' : '\\textbf{' ,
  790. 'fontBoldClose' : '}' ,
  791. 'fontItalicOpen' : '\\textit{' ,
  792. 'fontItalicClose' : '}' ,
  793. 'fontUnderlineOpen' : '\\underline{' ,
  794. 'fontUnderlineClose' : '}' ,
  795. 'fontStrikeOpen' : '\\sout{' ,
  796. 'fontStrikeClose' : '}' ,
  797. 'listOpen' : '\\begin{itemize}' ,
  798. 'listClose' : '\\end{itemize}' ,
  799. 'listOpenCompact' : '\\begin{compactitem}',
  800. 'listCloseCompact' : '\\end{compactitem}' ,
  801. 'listItemOpen' : '\\item ' ,
  802. 'numlistOpen' : '\\begin{enumerate}' ,
  803. 'numlistClose' : '\\end{enumerate}' ,
  804. 'numlistOpenCompact' : '\\begin{compactenum}',
  805. 'numlistCloseCompact' : '\\end{compactenum}' ,
  806. 'numlistItemOpen' : '\\item ' ,
  807. 'deflistOpen' : '\\begin{description}',
  808. 'deflistClose' : '\\end{description}' ,
  809. 'deflistOpenCompact' : '\\begin{compactdesc}',
  810. 'deflistCloseCompact' : '\\end{compactdesc}' ,
  811. 'deflistItem1Open' : '\\item[' ,
  812. 'deflistItem1Close' : ']' ,
  813. 'bar1' : '\\hrulefill{}' ,
  814. 'bar2' : '\\rule{\linewidth}{1mm}',
  815. 'url' : '\\htmladdnormallink{\a}{\a}',
  816. 'urlMark' : '\\htmladdnormallink{\a}{\a}',
  817. 'email' : '\\htmladdnormallink{\a}{mailto:\a}',
  818. 'emailMark' : '\\htmladdnormallink{\a}{mailto:\a}',
  819. 'img' : '\\includegraphics{\a}',
  820. 'tableOpen' : '\\begin{center}\\begin{tabular}{|~C~|}',
  821. 'tableClose' : '\\end{tabular}\\end{center}',
  822. 'tableRowOpen' : '\\hline ' ,
  823. 'tableRowClose' : ' \\\\' ,
  824. 'tableCellSep' : ' & ' ,
  825. '_tableColAlignLeft' : 'l' ,
  826. '_tableColAlignRight' : 'r' ,
  827. '_tableColAlignCenter' : 'c' ,
  828. '_tableCellAlignLeft' : 'l' ,
  829. '_tableCellAlignRight' : 'r' ,
  830. '_tableCellAlignCenter': 'c' ,
  831. '_tableCellColSpan' : '\a' ,
  832. '_tableCellMulticolOpen' : '\\multicolumn{\a}{|~C~|}{',
  833. '_tableCellMulticolClose' : '}',
  834. 'tableColAlignSep' : '|' ,
  835. 'comment' : '% \a' ,
  836. 'TOC' : '\\tableofcontents',
  837. 'pageBreak' : '\\clearpage',
  838. 'EOD' : '\\end{document}'
  839. },
  840. 'lout': {
  841. 'paragraphOpen' : '@LP' ,
  842. 'blockTitle1Open' : '@BeginSections' ,
  843. 'blockTitle1Close' : '@EndSections' ,
  844. 'blockTitle2Open' : ' @BeginSubSections' ,
  845. 'blockTitle2Close' : ' @EndSubSections' ,
  846. 'blockTitle3Open' : ' @BeginSubSubSections' ,
  847. 'blockTitle3Close' : ' @EndSubSubSections' ,
  848. 'title1Open' : '~A~@Section @Title { \a } @Begin',
  849. 'title1Close' : '@End @Section' ,
  850. 'title2Open' : '~A~ @SubSection @Title { \a } @Begin',
  851. 'title2Close' : ' @End @SubSection' ,
  852. 'title3Open' : '~A~ @SubSubSection @Title { \a } @Begin',
  853. 'title3Close' : ' @End @SubSubSection' ,
  854. 'title4Open' : '~A~@LP @LeftDisplay @B { \a }',
  855. 'title5Open' : '~A~@LP @LeftDisplay @B { \a }',
  856. 'anchor' : '@Tag { \a }\n' ,
  857. 'blockVerbOpen' : '@LP @ID @F @RawVerbatim @Begin',
  858. 'blockVerbClose' : '@End @RawVerbatim' ,
  859. 'blockQuoteOpen' : '@QD {' ,
  860. 'blockQuoteClose' : '}' ,
  861. # enclosed inside {} to deal with joined**words**
  862. 'fontMonoOpen' : '{@F {' ,
  863. 'fontMonoClose' : '}}' ,
  864. 'fontBoldOpen' : '{@B {' ,
  865. 'fontBoldClose' : '}}' ,
  866. 'fontItalicOpen' : '{@II {' ,
  867. 'fontItalicClose' : '}}' ,
  868. 'fontUnderlineOpen' : '{@Underline{' ,
  869. 'fontUnderlineClose' : '}}' ,
  870. # the full form is more readable, but could be BL EL LI NL TL DTI
  871. 'listOpen' : '@BulletList' ,
  872. 'listClose' : '@EndList' ,
  873. 'listItemOpen' : '@ListItem{' ,
  874. 'listItemClose' : '}' ,
  875. 'numlistOpen' : '@NumberedList' ,
  876. 'numlistClose' : '@EndList' ,
  877. 'numlistItemOpen' : '@ListItem{' ,
  878. 'numlistItemClose' : '}' ,
  879. 'deflistOpen' : '@TaggedList' ,
  880. 'deflistClose' : '@EndList' ,
  881. 'deflistItem1Open' : '@DropTagItem {' ,
  882. 'deflistItem1Close' : '}' ,
  883. 'deflistItem2Open' : '{' ,
  884. 'deflistItem2Close' : '}' ,
  885. 'bar1' : '@DP @FullWidthRule' ,
  886. 'url' : '{blue @Colour { \a }}' ,
  887. 'urlMark' : '\a ({blue @Colour { \a }})' ,
  888. 'email' : '{blue @Colour { \a }}' ,
  889. 'emailMark' : '\a ({blue Colour{ \a }})' ,
  890. 'img' : '~A~@IncludeGraphic { \a }' , # eps only!
  891. '_imgAlignLeft' : '@LeftDisplay ' ,
  892. '_imgAlignRight' : '@RightDisplay ' ,
  893. '_imgAlignCenter' : '@CentredDisplay ' ,
  894. # lout tables are *way* complicated, no support for now
  895. #'tableOpen' : '~A~@Tbl~B~\naformat{ @Cell A | @Cell B } {',
  896. #'tableClose' : '}' ,
  897. #'tableRowOpen' : '@Rowa\n' ,
  898. #'tableTitleRowOpen' : '@HeaderRowa' ,
  899. #'tableCenterAlign' : '@CentredDisplay ' ,
  900. #'tableCellOpen' : '\a {' , # A, B, ...
  901. #'tableCellClose' : '}' ,
  902. #'_tableBorder' : '\nrule {yes}' ,
  903. 'comment' : '# \a' ,
  904. # @MakeContents must be on the config file
  905. 'TOC' : '@DP @ContentsGoesHere @DP',
  906. 'pageBreak' : '@NP' ,
  907. 'EOD' : '@End @Text'
  908. },
  909. # http://moinmo.in/SyntaxReference
  910. 'moin': {
  911. 'title1' : '= \a =' ,
  912. 'title2' : '== \a ==' ,
  913. 'title3' : '=== \a ===' ,
  914. 'title4' : '==== \a ====' ,
  915. 'title5' : '===== \a =====',
  916. 'blockVerbOpen' : '{{{' ,
  917. 'blockVerbClose' : '}}}' ,
  918. 'blockQuoteLine' : ' ' ,
  919. 'fontMonoOpen' : '{{{' ,
  920. 'fontMonoClose' : '}}}' ,
  921. 'fontBoldOpen' : "'''" ,
  922. 'fontBoldClose' : "'''" ,
  923. 'fontItalicOpen' : "''" ,
  924. 'fontItalicClose' : "''" ,
  925. 'fontUnderlineOpen' : '__' ,
  926. 'fontUnderlineClose' : '__' ,
  927. 'fontStrikeOpen' : '--(' ,
  928. 'fontStrikeClose' : ')--' ,
  929. 'listItemOpen' : ' * ' ,
  930. 'numlistItemOpen' : ' \a. ' ,
  931. 'deflistItem1Open' : ' ' ,
  932. 'deflistItem1Close' : '::' ,
  933. 'deflistItem2LinePrefix': ' :: ' ,
  934. 'bar1' : '----' ,
  935. 'bar2' : '--------' ,
  936. 'url' : '[\a]' ,
  937. 'urlMark' : '[\a \a]' ,
  938. 'email' : '[\a]' ,
  939. 'emailMark' : '[\a \a]' ,
  940. 'img' : '[\a]' ,
  941. 'tableRowOpen' : '||' ,
  942. 'tableCellOpen' : '~A~' ,
  943. 'tableCellClose' : '||' ,
  944. 'tableTitleCellClose' : '||' ,
  945. '_tableCellAlignRight' : '<)>' ,
  946. '_tableCellAlignCenter' : '<:>' ,
  947. 'comment' : '/* \a */' ,
  948. 'TOC' : '[[TableOfContents]]'
  949. },
  950. # http://code.google.com/p/support/wiki/WikiSyntax
  951. 'gwiki': {
  952. 'title1' : '= \a =' ,
  953. 'title2' : '== \a ==' ,
  954. 'title3' : '=== \a ===' ,
  955. 'title4' : '==== \a ====' ,
  956. 'title5' : '===== \a =====',
  957. 'blockVerbOpen' : '{{{' ,
  958. 'blockVerbClose' : '}}}' ,
  959. 'blockQuoteLine' : ' ' ,
  960. 'fontMonoOpen' : '{{{' ,
  961. 'fontMonoClose' : '}}}' ,
  962. 'fontBoldOpen' : '*' ,
  963. 'fontBoldClose' : '*' ,
  964. 'fontItalicOpen' : '_' , # underline == italic
  965. 'fontItalicClose' : '_' ,
  966. 'fontStrikeOpen' : '~~' ,
  967. 'fontStrikeClose' : '~~' ,
  968. 'listItemOpen' : ' * ' ,
  969. 'numlistItemOpen' : ' # ' ,
  970. 'url' : '\a' ,
  971. 'urlMark' : '[\a \a]' ,
  972. 'email' : 'mailto:\a' ,
  973. 'emailMark' : '[mailto:\a \a]',
  974. 'img' : '[\a]' ,
  975. 'tableRowOpen' : '|| ' ,
  976. 'tableRowClose' : ' ||' ,
  977. 'tableCellSep' : ' || ' ,
  978. },
  979. # http://powerman.name/doc/asciidoc
  980. 'adoc': {
  981. 'title1' : '== \a' ,
  982. 'title2' : '=== \a' ,
  983. 'title3' : '==== \a' ,
  984. 'title4' : '===== \a' ,
  985. 'title5' : '===== \a' ,
  986. 'blockVerbOpen' : '----' ,
  987. 'blockVerbClose' : '----' ,
  988. 'fontMonoOpen' : '+' ,
  989. 'fontMonoClose' : '+' ,
  990. 'fontBoldOpen' : '*' ,
  991. 'fontBoldClose' : '*' ,
  992. 'fontItalicOpen' : '_' ,
  993. 'fontItalicClose' : '_' ,
  994. 'listItemOpen' : '- ' ,
  995. 'listItemLine' : '\t' ,
  996. 'numlistItemOpen' : '. ' ,
  997. 'url' : '\a' ,
  998. 'urlMark' : '\a[\a]' ,
  999. 'email' : 'mailto:\a' ,
  1000. 'emailMark' : 'mailto:\a[\a]' ,
  1001. 'img' : 'image::\a[]' ,
  1002. },
  1003. # http://wiki.splitbrain.org/wiki:syntax
  1004. # Hint: <br> is \\ $
  1005. # Hint: You can add footnotes ((This is a footnote))
  1006. 'doku': {
  1007. 'title1' : '===== \a =====',
  1008. 'title2' : '==== \a ====' ,
  1009. 'title3' : '=== \a ===' ,
  1010. 'title4' : '== \a ==' ,
  1011. 'title5' : '= \a =' ,
  1012. # DokuWiki uses ' ' identation to mark verb blocks (see indentverbblock)
  1013. 'blockQuoteLine' : '>' ,
  1014. 'fontMonoOpen' : "''" ,
  1015. 'fontMonoClose' : "''" ,
  1016. 'fontBoldOpen' : "**" ,
  1017. 'fontBoldClose' : "**" ,
  1018. 'fontItalicOpen' : "//" ,
  1019. 'fontItalicClose' : "//" ,
  1020. 'fontUnderlineOpen' : "__" ,
  1021. 'fontUnderlineClose' : "__" ,
  1022. 'fontStrikeOpen' : '<del>' ,
  1023. 'fontStrikeClose' : '</del>' ,
  1024. 'listItemOpen' : ' * ' ,
  1025. 'numlistItemOpen' : ' - ' ,
  1026. 'bar1' : '----' ,
  1027. 'url' : '[[\a]]' ,
  1028. 'urlMark' : '[[\a|\a]]' ,
  1029. 'email' : '[[\a]]' ,
  1030. 'emailMark' : '[[\a|\a]]' ,
  1031. 'img' : '{{\a}}' ,
  1032. 'imgAlignLeft' : '{{\a }}' ,
  1033. 'imgAlignRight' : '{{ \a}}' ,
  1034. 'imgAlignCenter' : '{{ \a }}' ,
  1035. 'tableTitleRowOpen' : '^ ' ,
  1036. 'tableTitleRowClose' : ' ^' ,
  1037. 'tableTitleCellSep' : ' ^ ' ,
  1038. 'tableRowOpen' : '| ' ,
  1039. 'tableRowClose' : ' |' ,
  1040. 'tableCellSep' : ' | ' ,
  1041. # DokuWiki has no attributes. The content must be aligned!
  1042. # '_tableCellAlignRight' : '<)>' , # ??
  1043. # '_tableCellAlignCenter': '<:>' , # ??
  1044. # DokuWiki colspan is the same as txt2tags' with multiple |||
  1045. # 'comment' : '## \a' , # ??
  1046. # TOC is automatic
  1047. },
  1048. # http://www.pmwiki.org/wiki/PmWiki/TextFormattingRules
  1049. 'pmw': {
  1050. 'title1' : '~A~! \a ' ,
  1051. 'title2' : '~A~!! \a ' ,
  1052. 'title3' : '~A~!!! \a ' ,
  1053. 'title4' : '~A~!!!! \a ' ,
  1054. 'title5' : '~A~!!!!! \a ' ,
  1055. 'blockQuoteOpen' : '->' ,
  1056. 'blockQuoteClose' : '\n' ,
  1057. # In-text font
  1058. 'fontLargeOpen' : "[+" ,
  1059. 'fontLargeClose' : "+]" ,
  1060. 'fontLargerOpen' : "[++" ,
  1061. 'fontLargerClose' : "++]" ,
  1062. 'fontSmallOpen' : "[-" ,
  1063. 'fontSmallClose' : "-]" ,
  1064. 'fontLargerOpen' : "[--" ,
  1065. 'fontLargerClose' : "--]" ,
  1066. 'fontMonoOpen' : "@@" ,
  1067. 'fontMonoClose' : "@@" ,
  1068. 'fontBoldOpen' : "'''" ,
  1069. 'fontBoldClose' : "'''" ,
  1070. 'fontItalicOpen' : "''" ,
  1071. 'fontItalicClose' : "''" ,
  1072. 'fontUnderlineOpen' : "{+" ,
  1073. 'fontUnderlineClose' : "+}" ,
  1074. 'fontStrikeOpen' : '{-' ,
  1075. 'fontStrikeClose' : '-}' ,
  1076. # Lists
  1077. 'listItemLine' : '*' ,
  1078. 'numlistItemLine' : '#' ,
  1079. 'deflistItem1Open' : ': ' ,
  1080. 'deflistItem1Close' : ':' ,
  1081. 'deflistItem2LineOpen' : '::' ,
  1082. 'deflistItem2LineClose' : ':' ,
  1083. # Verbatim block
  1084. 'blockVerbOpen' : '[@' ,
  1085. 'blockVerbClose' : '@]' ,
  1086. 'bar1' : '----' ,
  1087. # URL, email and anchor
  1088. 'url' : '\a' ,
  1089. 'urlMark' : '[[\a -> \a]]' ,
  1090. 'email' : '\a' ,
  1091. 'emailMark' : '[[\a -> mailto:\a]]',
  1092. 'anchor' : '[[#\a]]\n' ,
  1093. # Image markup
  1094. 'img' : '\a' ,
  1095. #'imgAlignLeft' : '{{\a }}' ,
  1096. #'imgAlignRight' : '{{ \a}}' ,
  1097. #'imgAlignCenter' : '{{ \a }}' ,
  1098. # Table attributes
  1099. 'tableTitleRowOpen' : '||! ' ,
  1100. 'tableTitleRowClose' : '||' ,
  1101. 'tableTitleCellSep' : ' ||!' ,
  1102. 'tableRowOpen' : '||' ,
  1103. 'tableRowClose' : '||' ,
  1104. 'tableCellSep' : ' ||' ,
  1105. },
  1106. # http://en.wikipedia.org/wiki/Help:Editing
  1107. 'wiki': {
  1108. 'title1' : '== \a ==' ,
  1109. 'title2' : '=== \a ===' ,
  1110. 'title3' : '==== \a ====' ,
  1111. 'title4' : '===== \a =====' ,
  1112. 'title5' : '====== \a ======',
  1113. 'blockVerbOpen' : '<pre>' ,
  1114. 'blockVerbClose' : '</pre>' ,
  1115. 'blockQuoteOpen' : '<blockquote>' ,
  1116. 'blockQuoteClose' : '</blockquote>' ,
  1117. 'fontMonoOpen' : '<tt>' ,
  1118. 'fontMonoClose' : '</tt>' ,
  1119. 'fontBoldOpen' : "'''" ,
  1120. 'fontBoldClose' : "'''" ,
  1121. 'fontItalicOpen' : "''" ,
  1122. 'fontItalicClose' : "''" ,
  1123. 'fontUnderlineOpen' : '<u>' ,
  1124. 'fontUnderlineClose' : '</u>' ,
  1125. 'fontStrikeOpen' : '<s>' ,
  1126. 'fontStrikeClose' : '</s>' ,
  1127. #XXX Mixed lists not working: *#* list inside numlist inside list
  1128. 'listItemLine' : '*' ,
  1129. 'numlistItemLine' : '#' ,
  1130. 'deflistItem1Open' : '; ' ,
  1131. 'deflistItem2LinePrefix': ': ' ,
  1132. 'bar1' : '----' ,
  1133. 'url' : '[\a]' ,
  1134. 'urlMark' : '[\a \a]' ,
  1135. 'email' : 'mailto:\a' ,
  1136. 'emailMark' : '[mailto:\a \a]' ,
  1137. # [[Image:foo.png|right|Optional alt/caption text]] (right, left, center, none)
  1138. 'img' : '[[Image:\a~A~]]' ,
  1139. '_imgAlignLeft' : '|left' ,
  1140. '_imgAlignCenter' : '|center' ,
  1141. '_imgAlignRight' : '|right' ,
  1142. # {| border="1" cellspacing="0" cellpadding="4" align="center"
  1143. 'tableOpen' : '{|~A~~B~ cellpadding="4"',
  1144. 'tableClose' : '|}' ,
  1145. 'tableRowOpen' : '|-\n| ' ,
  1146. 'tableTitleRowOpen' : '|-\n! ' ,
  1147. 'tableCellSep' : ' || ' ,
  1148. 'tableTitleCellSep' : ' !! ' ,
  1149. '_tableBorder' : ' border="1"' ,
  1150. '_tableAlignCenter' : ' align="center"' ,
  1151. 'comment' : '<!-- \a -->' ,
  1152. 'TOC' : '__TOC__' ,
  1153. },
  1154. # http://www.inference.phy.cam.ac.uk/mackay/mgp/SYNTAX
  1155. # http://en.wikipedia.org/wiki/MagicPoint
  1156. 'mgp': {
  1157. 'paragraphOpen' : '%font "normal", size 5' ,
  1158. 'title1' : '%page\n\n\a\n' ,
  1159. 'title2' : '%page\n\n\a\n' ,
  1160. 'title3' : '%page\n\n\a\n' ,
  1161. 'title4' : '%page\n\n\a\n' ,
  1162. 'title5' : '%page\n\n\a\n' ,
  1163. 'blockVerbOpen' : '%font "mono"' ,
  1164. 'blockVerbClose' : '%font "normal"' ,
  1165. 'blockQuoteOpen' : '%prefix " "' ,
  1166. 'blockQuoteClose' : '%prefix " "' ,
  1167. 'fontMonoOpen' : '\n%cont, font "mono"\n' ,
  1168. 'fontMonoClose' : '\n%cont, font "normal"\n' ,
  1169. 'fontBoldOpen' : '\n%cont, font "normal-b"\n' ,
  1170. 'fontBoldClose' : '\n%cont, font "normal"\n' ,
  1171. 'fontItalicOpen' : '\n%cont, font "normal-i"\n' ,
  1172. 'fontItalicClose' : '\n%cont, font "normal"\n' ,
  1173. 'fontUnderlineOpen' : '\n%cont, fore "cyan"\n' ,
  1174. 'fontUnderlineClose' : '\n%cont, fore "white"\n' ,
  1175. 'listItemLine' : '\t' ,
  1176. 'numlistItemLine' : '\t' ,
  1177. 'numlistItemOpen' : '\a. ' ,
  1178. 'deflistItem1Open' : '\t\n%cont, font "normal-b"\n',
  1179. 'deflistItem1Close' : '\n%cont, font "normal"\n' ,
  1180. 'bar1' : '%bar "white" 5' ,
  1181. 'bar2' : '%pause' ,
  1182. 'url' : '\n%cont, fore "cyan"\n\a' +\
  1183. '\n%cont, fore "white"\n' ,
  1184. 'urlMark' : '\a \n%cont, fore "cyan"\n\a'+\
  1185. '\n%cont, fore "white"\n' ,
  1186. 'email' : '\n%cont, fore "cyan"\n\a' +\
  1187. '\n%cont, fore "white"\n' ,
  1188. 'emailMark' : '\a \n%cont, fore "cyan"\n\a'+\
  1189. '\n%cont, fore "white"\n' ,
  1190. 'img' : '~A~\n%newimage "\a"\n%left\n',
  1191. '_imgAlignLeft' : '\n%left' ,
  1192. '_imgAlignRight' : '\n%right' ,
  1193. '_imgAlignCenter' : '\n%center' ,
  1194. 'comment' : '%% \a' ,
  1195. 'pageBreak' : '%page\n\n\n' ,
  1196. 'EOD' : '%%EOD'
  1197. },
  1198. # man groff_man ; man 7 groff
  1199. 'man': {
  1200. 'paragraphOpen' : '.P' ,
  1201. 'title1' : '.SH \a' ,
  1202. 'title2' : '.SS \a' ,
  1203. 'title3' : '.SS \a' ,
  1204. 'title4' : '.SS \a' ,
  1205. 'title5' : '.SS \a' ,
  1206. 'blockVerbOpen' : '.nf' ,
  1207. 'blockVerbClose' : '.fi\n' ,
  1208. 'blockQuoteOpen' : '.RS' ,
  1209. 'blockQuoteClose' : '.RE' ,
  1210. 'fontBoldOpen' : '\\fB' ,
  1211. 'fontBoldClose' : '\\fR' ,
  1212. 'fontItalicOpen' : '\\fI' ,
  1213. 'fontItalicClose' : '\\fR' ,
  1214. 'listOpen' : '.RS' ,
  1215. 'listItemOpen' : '.IP \(bu 3\n',
  1216. 'listClose' : '.RE' ,
  1217. 'numlistOpen' : '.RS' ,
  1218. 'numlistItemOpen' : '.IP \a. 3\n',
  1219. 'numlistClose' : '.RE' ,
  1220. 'deflistItem1Open' : '.TP\n' ,
  1221. 'bar1' : '\n\n' ,
  1222. 'url' : '\a' ,
  1223. 'urlMark' : '\a (\a)',
  1224. 'email' : '\a' ,
  1225. 'emailMark' : '\a (\a)',
  1226. 'img' : '\a' ,
  1227. 'tableOpen' : '.TS\n~A~~B~tab(^); ~C~.',
  1228. 'tableClose' : '.TE' ,
  1229. 'tableRowOpen' : ' ' ,
  1230. 'tableCellSep' : '^' ,
  1231. '_tableAlignCenter' : 'center, ',
  1232. '_tableBorder' : 'allbox, ',
  1233. '_tableColAlignLeft' : 'l' ,
  1234. '_tableColAlignRight' : 'r' ,
  1235. '_tableColAlignCenter' : 'c' ,
  1236. 'comment' : '.\\" \a'
  1237. },
  1238. 'pm6': {
  1239. 'paragraphOpen' : '<@Normal:>' ,
  1240. 'title1' : '<@Title1:>\a',
  1241. 'title2' : '<@Title2:>\a',
  1242. 'title3' : '<@Title3:>\a',
  1243. 'title4' : '<@Title4:>\a',
  1244. 'title5' : '<@Title5:>\a',
  1245. 'blockVerbOpen' : '<@PreFormat:>' ,
  1246. 'blockQuoteLine' : '<@Quote:>' ,
  1247. 'fontMonoOpen' : '<FONT "Lucida Console"><SIZE 9>' ,
  1248. 'fontMonoClose' : '<SIZE$><FONT$>',
  1249. 'fontBoldOpen' : '<B>' ,
  1250. 'fontBoldClose' : '<P>' ,
  1251. 'fontItalicOpen' : '<I>' ,
  1252. 'fontItalicClose' : '<P>' ,
  1253. 'fontUnderlineOpen' : '<U>' ,
  1254. 'fontUnderlineClose' : '<P>' ,
  1255. 'listOpen' : '<@Bullet:>' ,
  1256. 'listItemOpen' : '\x95\t' , # \x95 == ~U
  1257. 'numlistOpen' : '<@Bullet:>' ,
  1258. 'numlistItemOpen' : '\x95\t' ,
  1259. 'bar1' : '\a' ,
  1260. 'url' : '<U>\a<P>' , # underline
  1261. 'urlMark' : '\a <U>\a<P>' ,
  1262. 'email' : '\a' ,
  1263. 'emailMark' : '\a \a' ,
  1264. 'img' : '\a'
  1265. },
  1266. # http://www.wikicreole.org/wiki/AllMarkup
  1267. 'creole': {
  1268. 'title1' : '= \a =' ,
  1269. 'title2' : '== \a ==' ,
  1270. 'title3' : '=== \a ===' ,
  1271. 'title4' : '==== \a ====' ,
  1272. 'title5' : '===== \a =====',
  1273. 'blockVerbOpen' : '{{{' ,
  1274. 'blockVerbClose' : '}}}' ,
  1275. 'blockQuoteLine' : ' ' ,
  1276. # 'fontMonoOpen' : '##' , # planned for 2.0,
  1277. # 'fontMonoClose' : '##' , # meanwhile we disable it
  1278. 'fontBoldOpen' : '**' ,
  1279. 'fontBoldClose' : '**' ,
  1280. 'fontItalicOpen' : '//' ,
  1281. 'fontItalicClose' : '//' ,
  1282. 'fontUnderlineOpen' : '//' , # no underline in 1.0, planned for 2.0,
  1283. 'fontUnderlineClose' : '//' , # meanwhile we can use italic (emphasized)
  1284. # 'fontStrikeOpen' : '--' , # planned for 2.0,
  1285. # 'fontStrikeClose' : '--' , # meanwhile we disable it
  1286. 'listItemLine' : '*' ,
  1287. 'numlistItemLine' : '#' ,
  1288. 'deflistItem2LinePrefix': ':' ,
  1289. 'bar1' : '----' ,
  1290. 'url' : '[[\a]]' ,
  1291. 'urlMark' : '[[\a|\a]]' ,
  1292. 'img' : '{{\a}}' ,
  1293. 'tableTitleRowOpen' : '|= ' ,
  1294. 'tableTitleRowClose' : '|' ,
  1295. 'tableTitleCellSep' : ' |= ' ,
  1296. 'tableRowOpen' : '| ' ,
  1297. 'tableRowClose' : ' |' ,
  1298. 'tableCellSep' : ' | ' ,
  1299. # TODO: placeholder (mark for unknown syntax)
  1300. # if possible: http://www.wikicreole.org/wiki/Placeholder
  1301. }
  1302. }
  1303. # Exceptions for --css-sugar
  1304. if config['css-sugar'] and config['target'] in ('html','xhtml'):
  1305. # Change just HTML because XHTML inherits it
  1306. htmltags = alltags['html']
  1307. # Table with no cellpadding
  1308. htmltags['tableOpen'] = htmltags['tableOpen'].replace(' CELLPADDING="4"', '')
  1309. # DIVs
  1310. htmltags['tocOpen' ] = '<DIV CLASS="toc">'
  1311. htmltags['tocClose'] = '</DIV>'
  1312. htmltags['bodyOpen'] = '<DIV CLASS="body" ID="body">'
  1313. htmltags['bodyClose']= '</DIV>'
  1314. # Make the HTML -> XHTML inheritance
  1315. xhtml = alltags['html'].copy()
  1316. for key in xhtml.keys(): xhtml[key] = xhtml[key].lower()
  1317. # Some like HTML tags as lowercase, some don't... (headers out)
  1318. if HTML_LOWER: alltags['html'] = xhtml.copy()
  1319. xhtml.update(alltags['xhtml'])
  1320. alltags['xhtml'] = xhtml.copy()
  1321. # Compose the target tags dictionary
  1322. tags = {}
  1323. target_tags = alltags[config['target']].copy()
  1324. for key in keys: tags[key] = '' # create empty keys
  1325. for key in target_tags.keys():
  1326. tags[key] = maskEscapeChar(target_tags[key]) # populate
  1327. # Map strong line to pagebreak
  1328. if rules['mapbar2pagebreak'] and tags['pageBreak']:
  1329. tags['bar2'] = tags['pageBreak']
  1330. # Map strong line to separator if not defined
  1331. if not tags['bar2'] and tags['bar1']:
  1332. tags['bar2'] = tags['bar1']
  1333. return tags
  1334. ##############################################################################
  1335. def getRules(config):
  1336. "Returns all the target-specific syntax rules"
  1337. ret = {}
  1338. allrules = [
  1339. # target rules (ON/OFF)
  1340. 'linkable', # target supports external links
  1341. 'tableable', # target supports tables
  1342. 'imglinkable', # target supports images as links
  1343. 'imgalignable', # target supports image alignment
  1344. 'imgasdefterm', # target supports image as definition term
  1345. 'autonumberlist', # target supports numbered lists natively
  1346. 'autonumbertitle', # target supports numbered titles natively
  1347. 'stylable', # target supports external style files
  1348. 'parainsidelist', # lists items supports paragraph
  1349. 'compactlist', # separate enclosing tags for compact lists
  1350. 'spacedlistitem', # lists support blank lines between items
  1351. 'listnotnested', # lists cannot be nested
  1352. 'quotenotnested', # quotes cannot be nested
  1353. 'verbblocknotescaped', # don't escape specials in verb block
  1354. 'verbblockfinalescape', # do final escapes in verb block
  1355. 'escapeurl', # escape special in link URL
  1356. 'labelbeforelink', # label comes before the link on the tag
  1357. 'onelinepara', # dump paragraph as a single long line
  1358. 'tabletitlerowinbold', # manually bold any cell on table titles
  1359. 'tablecellstrip', # strip extra spaces from each table cell
  1360. 'tablecellspannable', # the table cells can have span attribute
  1361. 'tablecellmulticol', # separate open+close tags for multicol cells
  1362. 'barinsidequote', # bars are allowed inside quote blocks
  1363. 'finalescapetitle', # perform final escapes on title lines
  1364. 'autotocnewpagebefore', # break page before automatic TOC
  1365. 'autotocnewpageafter', # break page after automatic TOC
  1366. 'autotocwithbars', # automatic TOC surrounded by bars
  1367. 'mapbar2pagebreak', # map the strong bar to a page break
  1368. 'titleblocks', # titles must be on open/close section blocks
  1369. # Target code beautify (ON/OFF)
  1370. 'indentverbblock', # add leading spaces to verb block lines
  1371. 'breaktablecell', # break lines after any table cell
  1372. 'breaktablelineopen', # break line after opening table line
  1373. 'notbreaklistopen', # don't break line after opening a new list
  1374. 'keepquoteindent', # don't remove the leading TABs on quotes
  1375. 'keeplistindent', # don't remove the leading spaces on lists
  1376. 'blankendautotoc', # append a blank line at the auto TOC end
  1377. 'tagnotindentable', # tags must be placed at the line beginning
  1378. 'spacedlistitemopen', # append a space after the list item open tag
  1379. 'spacednumlistitemopen',# append a space after the numlist item open tag
  1380. 'deflisttextstrip', # strip the contents of the deflist text
  1381. 'blanksaroundpara', # put a blank line before and after paragraphs
  1382. 'blanksaroundverb', # put a blank line before and after verb blocks
  1383. 'blanksaroundquote', # put a blank line before and after quotes
  1384. 'blanksaroundlist', # put a blank line before and after lists
  1385. 'blanksaroundnumlist', # put a blank line before and after numlists
  1386. 'blanksarounddeflist', # put a blank line before and after deflists
  1387. 'blanksaroundtable', # put a blank line before and after tables
  1388. 'blanksaroundbar', # put a blank line before and after bars
  1389. 'blanksaroundtitle', # put a blank line before and after titles
  1390. 'blanksaroundnumtitle', # put a blank line before and after numtitles
  1391. # Value settings
  1392. 'listmaxdepth', # maximum depth for lists
  1393. 'quotemaxdepth', # maximum depth for quotes
  1394. 'tablecellaligntype', # type of table cell align: cell, column
  1395. ]
  1396. rules_bank = {
  1397. 'txt': {
  1398. 'indentverbblock':1,
  1399. 'spacedlistitem':1,
  1400. 'parainsidelist':1,
  1401. 'keeplistindent':1,
  1402. 'barinsidequote':1,
  1403. 'autotocwithbars':1,
  1404. 'blanksaroundpara':1,
  1405. 'blanksaroundverb':1,
  1406. 'blanksaroundquote':1,
  1407. 'blanksaroundlist':1,
  1408. 'blanksaroundnumlist':1,
  1409. 'blanksarounddeflist':1,
  1410. 'blanksaroundtable':1,
  1411. 'blanksaroundbar':1,
  1412. 'blanksaroundtitle':1,
  1413. 'blanksaroundnumtitle':1,
  1414. },
  1415. 'art': {
  1416. #TIP art inherits all TXT rules
  1417. },
  1418. 'html': {
  1419. 'indentverbblock':1,
  1420. 'linkable':1,
  1421. 'stylable':1,
  1422. 'escapeurl':1,
  1423. 'imglinkable':1,
  1424. 'imgalignable':1,
  1425. 'imgasdefterm':1,
  1426. 'autonumberlist':1,
  1427. 'spacedlistitem':1,
  1428. 'parainsidelist':1,
  1429. 'tableable':1,
  1430. 'tablecellstrip':1,
  1431. 'breaktablecell':1,
  1432. 'breaktablelineopen':1,
  1433. 'keeplistindent':1,
  1434. 'keepquoteindent':1,
  1435. 'barinsidequote':1,
  1436. 'autotocwithbars':1,
  1437. 'tablecellspannable':1,
  1438. 'tablecellaligntype':'cell',
  1439. # 'blanksaroundpara':1,
  1440. 'blanksaroundverb':1,
  1441. # 'blanksaroundquote':1,
  1442. 'blanksaroundlist':1,
  1443. 'blanksaroundnumlist':1,
  1444. 'blanksarounddeflist':1,
  1445. 'blanksaroundtable':1,
  1446. 'blanksaroundbar':1,
  1447. 'blanksaroundtitle':1,
  1448. 'blanksaroundnumtitle':1,
  1449. },
  1450. 'xhtml': {
  1451. #TIP xhtml inherits all HTML rules
  1452. },
  1453. 'sgml': {
  1454. 'linkable':1,
  1455. 'escapeurl':1,
  1456. 'autonumberlist':1,
  1457. 'spacedlistitem':1,
  1458. 'tableable':1,
  1459. 'tablecellstrip':1,
  1460. 'blankendautotoc':1,
  1461. 'quotenotnested':1,
  1462. 'keeplistindent':1,
  1463. 'keepquoteindent':1,
  1464. 'barinsidequote':1,
  1465. 'finalescapetitle':1,
  1466. 'tablecellaligntype':'column',
  1467. 'blanksaroundpara':1,
  1468. 'blanksaroundverb':1,
  1469. 'blanksaroundquote':1,
  1470. 'blanksaroundlist':1,
  1471. 'blanksaroundnumlist':1,
  1472. 'blanksarounddeflist':1,
  1473. 'blanksaroundtable':1,
  1474. 'blanksaroundbar':1,
  1475. 'blanksaroundtitle':1,
  1476. 'blanksaroundnumtitle':1,
  1477. },
  1478. 'dbk': {
  1479. 'linkable':1,
  1480. 'tableable':0, # activate when table tags are ready
  1481. 'imglinkable':1,
  1482. 'imgalignable':1,
  1483. 'imgasdefterm':1,
  1484. 'autonumberlist':1,
  1485. 'autonumbertitle':1,
  1486. 'parainsidelist':1,
  1487. 'spacedlistitem':1,
  1488. 'titleblocks':1,
  1489. },
  1490. 'mgp': {
  1491. 'tagnotindentable':1,
  1492. 'spacedlistitem':1,
  1493. 'imgalignable':1,
  1494. 'autotocnewpagebefore':1,
  1495. 'blanksaroundpara':1,
  1496. 'blanksaroundverb':1,
  1497. # 'blanksaroundquote':1,
  1498. 'blanksaroundlist':1,
  1499. 'blanksaroundnumlist':1,
  1500. 'blanksarounddeflist':1,
  1501. 'blanksaroundtable':1,
  1502. 'blanksaroundbar':1,
  1503. # 'blanksaroundtitle':1,
  1504. # 'blanksaroundnumtitle':1,
  1505. },
  1506. 'tex': {
  1507. 'stylable':1,
  1508. 'escapeurl':1,
  1509. 'autonumberlist':1,
  1510. 'autonumbertitle':1,
  1511. 'spacedlistitem':1,
  1512. 'compactlist':1,
  1513. 'parainsidelist':1,
  1514. 'tableable':1,
  1515. 'tablecellstrip':1,
  1516. 'tabletitlerowinbold':1,
  1517. 'verbblocknotescaped':1,
  1518. 'keeplistindent':1,
  1519. 'listmaxdepth':4, # deflist is 6
  1520. 'quotemaxdepth':6,
  1521. 'barinsidequote':1,
  1522. 'finalescapetitle':1,
  1523. 'autotocnewpageafter':1,
  1524. 'mapbar2pagebreak':1,
  1525. 'tablecellaligntype':'column',
  1526. 'tablecellmulticol':1,
  1527. 'blanksaroundpara':1,
  1528. 'blanksaroundverb':1,
  1529. # 'blanksaroundquote':1,
  1530. 'blanksaroundlist':1,
  1531. 'blanksaroundnumlist':1,
  1532. 'blanksarounddeflist':1,
  1533. 'blanksaroundtable':1,
  1534. 'blanksaroundbar':1,
  1535. 'blanksaroundtitle':1,
  1536. 'blanksaroundnumtitle':1,
  1537. },
  1538. 'lout': {
  1539. 'keepquoteindent':1,
  1540. 'deflisttextstrip':1,
  1541. 'escapeurl':1,
  1542. 'verbblocknotescaped':1,
  1543. 'imgalignable':1,
  1544. 'mapbar2pagebreak':1,
  1545. 'titleblocks':1,
  1546. 'autonumberlist':1,
  1547. 'parainsidelist':1,
  1548. 'blanksaroundpara':1,
  1549. 'blanksaroundverb':1,
  1550. # 'blanksaroundquote':1,
  1551. 'blanksaroundlist':1,
  1552. 'blanksaroundnumlist':1,
  1553. 'blanksarounddeflist':1,
  1554. 'blanksaroundtable':1,
  1555. 'blanksaroundbar':1,
  1556. 'blanksaroundtitle':1,
  1557. 'blanksaroundnumtitle':1,
  1558. },
  1559. 'moin': {
  1560. 'spacedlistitem':1,
  1561. 'linkable':1,
  1562. 'keeplistindent':1,
  1563. 'tableable':1,
  1564. 'barinsidequote':1,
  1565. 'tabletitlerowinbold':1,
  1566. 'tablecellstrip':1,
  1567. 'autotocwithbars':1,
  1568. 'tablecellaligntype':'cell',
  1569. 'deflisttextstrip':1,
  1570. 'blanksaroundpara':1,
  1571. 'blanksaroundverb':1,
  1572. # 'blanksaroundquote':1,
  1573. 'blanksaroundlist':1,
  1574. 'blanksaroundnumlist':1,
  1575. 'blanksarounddeflist':1,
  1576. 'blanksaroundtable':1,
  1577. # 'blanksaroundbar':1,
  1578. 'blanksaroundtitle':1,
  1579. 'blanksaroundnumtitle':1,
  1580. },
  1581. 'gwiki': {
  1582. 'spacedlistitem':1,
  1583. 'linkable':1,
  1584. 'keeplistindent':1,
  1585. 'tableable':1,
  1586. 'tabletitlerowinbold':1,
  1587. 'tablecellstrip':1,
  1588. 'autonumberlist':1,
  1589. 'blanksaroundpara':1,
  1590. 'blanksaroundverb':1,
  1591. # 'blanksaroundquote':1,
  1592. 'blanksaroundlist':1,
  1593. 'blanksaroundnumlist':1,
  1594. 'blanksarounddeflist':1,
  1595. 'blanksaroundtable':1,
  1596. # 'blanksaroundbar':1,
  1597. 'blanksaroundtitle':1,
  1598. 'blanksaroundnumtitle':1,
  1599. },
  1600. 'adoc': {
  1601. 'spacedlistitem':1,
  1602. 'linkable':1,
  1603. 'keeplistindent':1,
  1604. 'autonumberlist':1,
  1605. 'autonumbertitle':1,
  1606. 'listnotnested':1,
  1607. 'blanksaroundpara':1,
  1608. 'blanksaroundverb':1,
  1609. 'blanksaroundlist':1,
  1610. 'blanksaroundnumlist':1,
  1611. 'blanksarounddeflist':1,
  1612. 'blanksaroundtable':1,
  1613. 'blanksaroundtitle':1,
  1614. 'blanksaroundnumtitle':1,
  1615. },
  1616. 'doku': {
  1617. 'indentverbblock':1, # DokuWiki uses ' ' to mark verb blocks
  1618. 'spacedlistitem':1,
  1619. 'linkable':1,
  1620. 'keeplistindent':1,
  1621. 'tableable':1,
  1622. 'barinsidequote':1,
  1623. 'tablecellstrip':1,
  1624. 'autotocwithbars':1,
  1625. 'autonumberlist':1,
  1626. 'imgalignable':1,
  1627. 'tablecellaligntype':'cell',
  1628. 'blanksaroundpara':1,
  1629. 'blanksaroundverb':1,
  1630. # 'blanksaroundquote':1,
  1631. 'blanksaroundlist':1,
  1632. 'blanksaroundnumlist':1,
  1633. 'blanksarounddeflist':1,
  1634. 'blanksaroundtable':1,
  1635. 'blanksaroundbar':1,
  1636. 'blanksaroundtitle':1,
  1637. 'blanksaroundnumtitle':1,
  1638. },
  1639. 'pmw': {
  1640. 'indentverbblock':1,
  1641. 'spacedlistitem':1,
  1642. 'linkable':1,
  1643. 'labelbeforelink':1,
  1644. # 'keeplistindent':1,
  1645. 'tableable':1,
  1646. 'barinsidequote':1,
  1647. 'tablecellstrip':1,
  1648. 'autotocwithbars':1,
  1649. 'autonumberlist':1,
  1650. 'spacedlistitemopen':1,
  1651. 'spacednumlistitemopen':1,
  1652. 'imgalignable':1,
  1653. 'tabletitlerowinbold':1,
  1654. 'tablecellaligntype':'cell',
  1655. 'blanksaroundpara':1,
  1656. 'blanksaroundverb':1,
  1657. 'blanksaroundquote':1,
  1658. 'blanksaroundlist':1,
  1659. 'blanksaroundnumlist':1,
  1660. 'blanksarounddeflist':1,
  1661. 'blanksaroundtable':1,
  1662. 'blanksaroundbar':1,
  1663. 'blanksaroundtitle':1,
  1664. 'blanksaroundnumtitle':1,
  1665. },
  1666. 'wiki': {
  1667. 'linkable':1,
  1668. 'tableable':1,
  1669. 'tablecellstrip':1,
  1670. 'autotocwithbars':1,
  1671. 'spacedlistitemopen':1,
  1672. 'spacednumlistitemopen':1,
  1673. 'deflisttextstrip':1,
  1674. 'autonumberlist':1,
  1675. 'imgalignable':1,
  1676. 'blanksaroundpara':1,
  1677. 'blanksaroundverb':1,
  1678. # 'blanksaroundquote':1,
  1679. 'blanksaroundlist':1,
  1680. 'blanksaroundnumlist':1,
  1681. 'blanksarounddeflist':1,
  1682. 'blanksaroundtable':1,
  1683. 'blanksaroundbar':1,
  1684. 'blanksaroundtitle':1,
  1685. 'blanksaroundnumtitle':1,
  1686. },
  1687. 'man': {
  1688. 'spacedlistitem':1,
  1689. 'tagnotindentable':1,
  1690. 'tableable':1,
  1691. 'tablecellaligntype':'column',
  1692. 'tabletitlerowinbold':1,
  1693. 'tablecellstrip':1,
  1694. 'barinsidequote':1,
  1695. 'parainsidelist':0,
  1696. 'blanksaroundpara':1,
  1697. 'blanksaroundverb':1,
  1698. # 'blanksaroundquote':1,
  1699. 'blanksaroundlist':1,
  1700. 'blanksaroundnumlist':1,
  1701. 'blanksarounddeflist':1,
  1702. 'blanksaroundtable':1,
  1703. # 'blanksaroundbar':1,
  1704. 'blanksaroundtitle':1,
  1705. 'blanksaroundnumtitle':1,
  1706. },
  1707. 'pm6': {
  1708. 'keeplistindent':1,
  1709. 'verbblockfinalescape':1,
  1710. #TODO add support for these
  1711. # maybe set a JOINNEXT char and do it on addLineBreaks()
  1712. 'notbreaklistopen':1,
  1713. 'barinsidequote':1,
  1714. 'autotocwithbars':1,
  1715. 'onelinepara':1,
  1716. 'blanksaroundpara':1,
  1717. 'blanksaroundverb':1,
  1718. # 'blanksaroundquote':1,
  1719. 'blanksaroundlist':1,
  1720. 'blanksaroundnumlist':1,
  1721. 'blanksarounddeflist':1,
  1722. # 'blanksaroundtable':1,
  1723. # 'blanksaroundbar':1,
  1724. 'blanksaroundtitle':1,
  1725. 'blanksaroundnumtitle':1,
  1726. },
  1727. 'creole': {
  1728. 'linkable':1,
  1729. 'tableable':1,
  1730. 'imglinkable':1,
  1731. 'tablecellstrip':1,
  1732. 'autotocwithbars':1,
  1733. 'spacedlistitemopen':1,
  1734. 'spacednumlistitemopen':1,
  1735. 'deflisttextstrip':1,
  1736. 'verbblocknotescaped':1,
  1737. 'blanksaroundpara':1,
  1738. 'blanksaroundverb':1,
  1739. 'blanksaroundquote':1,
  1740. 'blanksaroundlist':1,
  1741. 'blanksaroundnumlist':1,
  1742. 'blanksarounddeflist':1,
  1743. 'blanksaroundtable':1,
  1744. 'blanksaroundbar':1,
  1745. 'blanksaroundtitle':1,
  1746. },
  1747. }
  1748. # Exceptions for --css-sugar
  1749. if config['css-sugar'] and config['target'] in ('html','xhtml'):
  1750. rules_bank['html']['indentverbblock'] = 0
  1751. rules_bank['html']['autotocwithbars'] = 0
  1752. # Get the target specific rules
  1753. if config['target'] == 'xhtml':
  1754. myrules = rules_bank['html'].copy() # inheritance
  1755. myrules.update(rules_bank['xhtml']) # get XHTML specific
  1756. elif config['target'] == 'art':
  1757. myrules = rules_bank['txt'].copy() # inheritance
  1758. if config['slides']:
  1759. myrules['blanksaroundtitle'] = 0
  1760. myrules['blanksaroundnumtitle'] = 0
  1761. else:
  1762. myrules = rules_bank[config['target']].copy()
  1763. # Populate return dictionary
  1764. for key in allrules: ret[key] = 0 # reset all
  1765. ret.update(myrules) # get rules
  1766. return ret
  1767. ##############################################################################
  1768. def getRegexes():
  1769. "Returns all the regexes used to find the t2t marks"
  1770. bank = {
  1771. 'blockVerbOpen':
  1772. re.compile(r'^```\s*$'),
  1773. 'blockVerbClose':
  1774. re.compile(r'^```\s*$'),
  1775. 'blockRawOpen':
  1776. re.compile(r'^"""\s*$'),
  1777. 'blockRawClose':
  1778. re.compile(r'^"""\s*$'),
  1779. 'blockTaggedOpen':
  1780. re.compile(r"^'''\s*$"),
  1781. 'blockTaggedClose':
  1782. re.compile(r"^'''\s*$"),
  1783. 'blockCommentOpen':
  1784. re.compile(r'^%%%\s*$'),
  1785. 'blockCommentClose':
  1786. re.compile(r'^%%%\s*$'),
  1787. 'quote':
  1788. re.compile(r'^\t+'),
  1789. '1lineVerb':
  1790. re.compile(r'^``` (?=.)'),
  1791. '1lineRaw':
  1792. re.compile(r'^""" (?=.)'),
  1793. '1lineTagged':
  1794. re.compile(r"^''' (?=.)"),
  1795. # mono, raw, bold, italic, underline:
  1796. # - marks must be glued with the contents, no boundary spaces
  1797. # - they are greedy, so in ****bold****, turns to <b>**bold**</b>
  1798. 'fontMono':
  1799. re.compile( r'``([^\s](|.*?[^\s])`*)``'),
  1800. 'raw':
  1801. re.compile( r'""([^\s](|.*?[^\s])"*)""'),
  1802. 'tagged':
  1803. re.compile( r"''([^\s](|.*?[^\s])'*)''"),
  1804. 'fontBold':
  1805. re.compile(r'\*\*([^\s](|.*?[^\s])\**)\*\*'),
  1806. 'fontItalic':
  1807. re.compile( r'//([^\s](|.*?[^\s])/*)//'),
  1808. 'fontUnderline':
  1809. re.compile( r'__([^\s](|.*?[^\s])_*)__'),
  1810. 'fontStrike':
  1811. re.compile( r'--([^\s](|.*?[^\s])-*)--'),
  1812. 'list':
  1813. re.compile(r'^( *)(-) (?=[^ ])'),
  1814. 'numlist':
  1815. re.compile(r'^( *)(\+) (?=[^ ])'),
  1816. 'deflist':
  1817. re.compile(r'^( *)(:) (.*)$'),
  1818. 'listclose':
  1819. re.compile(r'^( *)([-+:])\s*$'),
  1820. 'bar':
  1821. re.compile(r'^(\s*)([_=-]{20,})\s*$'),
  1822. 'table':
  1823. re.compile(r'^ *\|\|? '),
  1824. 'blankline':
  1825. re.compile(r'^\s*$'),
  1826. 'comment':
  1827. re.compile(r'^%'),
  1828. # Auxiliary tag regexes
  1829. '_imgAlign' : re.compile(r'~A~', re.I),
  1830. '_tableAlign' : re.compile(r'~A~', re.I),
  1831. '_anchor' : re.compile(r'~A~', re.I),
  1832. '_tableBorder' : re.compile(r'~B~', re.I),
  1833. '_tableColAlign' : re.compile(r'~C~', re.I),
  1834. '_tableCellColSpan': re.compile(r'~S~', re.I),
  1835. '_tableCellAlign' : re.compile(r'~A~', re.I),
  1836. }
  1837. # Special char to place data on TAGs contents (\a == bell)
  1838. bank['x'] = re.compile('\a')
  1839. # %%macroname [ (formatting) ]
  1840. bank['macros'] = re.compile(r'%%%%(?P<name>%s)\b(\((?P<fmt>.*?)\))?' % (
  1841. '|'.join(MACROS.keys())), re.I)
  1842. # %%TOC special macro for TOC positioning
  1843. bank['toc'] = re.compile(r'^ *%%toc\s*$', re.I)
  1844. # Almost complicated title regexes ;)
  1845. titskel = r'^ *(?P<id>%s)(?P<txt>%s)\1(\[(?P<label>[\w-]*)\])?\s*$'
  1846. bank[ 'title'] = re.compile(titskel%('[=]{1,5}','[^=](|.*[^=])'))
  1847. bank['numtitle'] = re.compile(titskel%('[+]{1,5}','[^+](|.*[^+])'))
  1848. ### Complicated regexes begin here ;)
  1849. #
  1850. # Textual descriptions on --help's style: [...] is optional, | is OR
  1851. ### First, some auxiliary variables
  1852. #
  1853. # [image.EXT]
  1854. patt_img = r'\[([\w_,.+%$#@!?+~/-]+\.(png|jpe?g|gif|eps|bmp))\]'
  1855. # Link things
  1856. # http://www.gbiv.com/protocols/uri/rfc/rfc3986.html
  1857. # pchar: A-Za-z._~- / %FF / !$&'()*+,;= / :@
  1858. # Recomended order: scheme://user:pass@domain/path?query=foo#anchor
  1859. # Also works : scheme://user:pass@domain/path#anchor?query=foo
  1860. # TODO form: !'():
  1861. urlskel = {
  1862. 'proto' : r'(https?|ftp|news|telnet|gopher|wais)://',
  1863. 'guess' : r'(www[23]?|ftp)\.', # w/out proto, try to guess
  1864. 'login' : r'A-Za-z0-9_.-', # for ftp://login@domain.com
  1865. 'pass' : r'[^ @]*', # for ftp://login:pass@dom.com
  1866. 'chars' : r'A-Za-z0-9%._/~:,=$@&+-', # %20(space), :80(port), D&D
  1867. 'anchor': r'A-Za-z0-9%._-', # %nn(encoded)
  1868. 'form' : r'A-Za-z0-9/%&=+:;.,$@*_-', # .,@*_-(as is)
  1869. 'punct' : r'.,;:!?'
  1870. }
  1871. # username [ :password ] @
  1872. patt_url_login = r'([%s]+(:%s)?@)?'%(urlskel['login'],urlskel['pass'])
  1873. # [ http:// ] [ username:password@ ] domain.com [ / ]
  1874. # [ #anchor | ?form=data ]
  1875. retxt_url = r'\b(%s%s|%s)[%s]+\b/*(\?[%s]+)?(#[%s]*)?'%(
  1876. urlskel['proto'],patt_url_login, urlskel['guess'],
  1877. urlskel['chars'],urlskel['form'],urlskel['anchor'])
  1878. # filename | [ filename ] #anchor
  1879. retxt_url_local = r'[%s]+|[%s]*(#[%s]*)'%(
  1880. urlskel['chars'],urlskel['chars'],urlskel['anchor'])
  1881. # user@domain [ ?form=data ]
  1882. patt_email = r'\b[%s]+@([A-Za-z0-9_-]+\.)+[A-Za-z]{2,4}\b(\?[%s]+)?'%(
  1883. urlskel['login'],urlskel['form'])
  1884. # Saving for future use
  1885. bank['_urlskel'] = urlskel
  1886. ### And now the real regexes
  1887. #
  1888. bank['email'] = re.compile(patt_email,re.I)
  1889. # email | url
  1890. bank['link'] = re.compile(r'%s|%s'%(retxt_url,patt_email), re.I)
  1891. # \[ label | imagetag url | email | filename \]
  1892. bank['linkmark'] = re.compile(
  1893. r'\[(?P<label>%s|[^]]+) (?P<link>%s|%s|%s)\]'%(
  1894. patt_img, retxt_url, patt_email, retxt_url_local),
  1895. re.L+re.I)
  1896. # Image
  1897. bank['img'] = re.compile(patt_img, re.L+re.I)
  1898. # Special things
  1899. bank['special'] = re.compile(r'^%!\s*')
  1900. return bank
  1901. ### END OF regex nightmares
  1902. ################# functions for the ASCII Art backend ########################
  1903. def aa_line(char, length):
  1904. return char * length
  1905. def aa_box(txt, length):
  1906. len_txt = len(txt)
  1907. nspace = (length - len_txt - 4) / 2
  1908. line_box = " " * nspace + AA['corner'] + AA['border'] * (len_txt + 2) + AA['corner']
  1909. # <----- nspace " " -----> "+" <----- len_txt+2 "-" -----> "+"
  1910. # +-------------------------------+
  1911. # | all theeeeeeeeeeeeeeeeee text |
  1912. # <----- nspace " " -----> "| " <--------- txt ---------> " |"
  1913. line_txt = " " * nspace + AA['side'] + ' ' + txt + ' ' + AA['side']
  1914. return [line_box, line_txt, line_box]
  1915. def aa_header(header_data, length, n, end):
  1916. header = [aa_line(AA['bar2'], length)]
  1917. header.extend(['']*n)
  1918. for h in 'HEADER1', 'HEADER2', 'HEADER3':
  1919. if header_data[h]:
  1920. header.extend(aa_box(header_data[h], length))
  1921. header.extend(['']*n)
  1922. header.extend(['']*end)
  1923. header.append(aa_line(AA['bar2'], length))
  1924. return header
  1925. def aa_slide(title, length):
  1926. res = [aa_line(AA['bar2'], length)]
  1927. res.append('')
  1928. res.append(title.center(length))
  1929. res.append('')
  1930. res.append(aa_line(AA['bar2'], length))
  1931. return res
  1932. def aa_table(table):
  1933. data = [row[2:-2].split(' | ') for row in table]
  1934. n = max([len(line) for line in data])
  1935. data = [line + (n - len(line)) * [''] for line in data]
  1936. tab = []
  1937. for i in range(n):
  1938. tab.append([line[i] for line in data])
  1939. length = [max([len(el) for el in line]) for line in tab]
  1940. res = "+"
  1941. for i in range(n):
  1942. res = res + (length[i] + 2) * "-" + '+'
  1943. ret = []
  1944. for line in data:
  1945. aff = "|"
  1946. ret.append(res)
  1947. for j,el in enumerate(line):
  1948. aff = aff + " " + el + (length[j] - len(el) + 1) * " " + "|"
  1949. ret.append(aff)
  1950. ret.append(res)
  1951. return ret
  1952. ##############################################################################
  1953. class error(Exception):
  1954. pass
  1955. def echo(msg): # for quick debug
  1956. print '\033[32;1m%s\033[m'%msg
  1957. def Quit(msg=''):
  1958. if msg: print msg
  1959. sys.exit(0)
  1960. def Error(msg):
  1961. msg = _("%s: Error: ")%my_name + msg
  1962. raise error, msg
  1963. def getTraceback():
  1964. try:
  1965. from traceback import format_exception
  1966. etype, value, tb = sys.exc_info()
  1967. return ''.join(format_exception(etype, value, tb))
  1968. except: pass
  1969. def getUnknownErrorMessage():
  1970. msg = '%s\n%s (%s):\n\n%s'%(
  1971. _('Sorry! Txt2tags aborted by an unknown error.'),
  1972. _('Please send the following Error Traceback to the author'),
  1973. my_email, getTraceback())
  1974. return msg
  1975. def Message(msg,level):
  1976. if level <= VERBOSE and not QUIET:
  1977. prefix = '-'*5
  1978. print "%s %s"%(prefix*level, msg)
  1979. def Debug(msg,id_=0,linenr=None):
  1980. "Show debug messages, categorized (colored or not)"
  1981. if QUIET or not DEBUG: return
  1982. if int(id_) not in range(8): id_ = 0
  1983. # 0:black 1:red 2:green 3:yellow 4:blue 5:pink 6:cyan 7:white ;1:light
  1984. ids = ['INI','CFG','SRC','BLK','HLD','GUI','OUT','DET']
  1985. colors_bgdark = ['7;1','1;1','3;1','6;1','4;1','5;1','2;1','7;1']
  1986. colors_bglight = ['0' ,'1' ,'3' ,'6' ,'4' ,'5' ,'2' ,'0' ]
  1987. if linenr is not None: msg = "LINE %04d: %s"%(linenr,msg)
  1988. if COLOR_DEBUG:
  1989. if BG_LIGHT: color = colors_bglight[id_]
  1990. else : color = colors_bgdark[id_]
  1991. msg = '\033[3%sm%s\033[m'%(color,msg)
  1992. print "++ %s: %s"%(ids[id_],msg)
  1993. def Readfile(file_path, remove_linebreaks=0, ignore_error=0):
  1994. data = []
  1995. if file_path == '-':
  1996. try:
  1997. data = sys.stdin.readlines()
  1998. except:
  1999. if not ignore_error:
  2000. Error(_('You must feed me with data on STDIN!'))
  2001. else:
  2002. try:
  2003. f = open(file_path)
  2004. data = f.readlines()
  2005. f.close()
  2006. except:
  2007. if not ignore_error:
  2008. Error(_("Cannot read file:") + ' ' + file_path)
  2009. if remove_linebreaks:
  2010. data = map(lambda x:re.sub('[\n\r]+$', '', x), data)
  2011. Message(_("File read (%d lines): %s") % (len(data), file_path), 2)
  2012. return data
  2013. def Savefile(file_path, contents):
  2014. try:
  2015. f = open(file_path, 'wb')
  2016. except:
  2017. Error(_("Cannot open file for writing:") + ' ' + file_path)
  2018. if type(contents) == type([]):
  2019. doit = f.writelines
  2020. else:
  2021. doit = f.write
  2022. doit(contents)
  2023. f.close()
  2024. def showdic(dic):
  2025. for k in dic.keys(): print "%15s : %s" % (k,dic[k])
  2026. def dotted_spaces(txt=''):
  2027. return txt.replace(' ', '.')
  2028. # TIP: win env vars http://www.winnetmag.com/Article/ArticleID/23873/23873.html
  2029. def get_rc_path():
  2030. "Return the full path for the users' RC file"
  2031. # Try to get the path from an env var. if yes, we're done
  2032. user_defined = os.environ.get('T2TCONFIG')
  2033. if user_defined: return user_defined
  2034. # Env var not found, so perform automatic path composing
  2035. # Set default filename according system platform
  2036. rc_names = {'default':'.txt2tagsrc', 'win':'_t2trc'}
  2037. rc_file = rc_names.get(sys.platform[:3]) or rc_names['default']
  2038. # The file must be on the user directory, but where is this dir?
  2039. rc_dir_search = ['HOME', 'HOMEPATH']
  2040. for var in rc_dir_search:
  2041. rc_dir = os.environ.get(var)
  2042. if rc_dir: break
  2043. # rc dir found, now we must join dir+file to compose the full path
  2044. if rc_dir:
  2045. # Compose path and return it if the file exists
  2046. rc_path = os.path.join(rc_dir, rc_file)
  2047. # On windows, prefix with the drive (%homedrive%: 2k/XP/NT)
  2048. if sys.platform.startswith('win'):
  2049. rc_drive = os.environ.get('HOMEDRIVE')
  2050. rc_path = os.path.join(rc_drive,rc_path)
  2051. return rc_path
  2052. # Sorry, not found
  2053. return ''
  2054. ##############################################################################
  2055. class CommandLine:
  2056. """
  2057. Command Line class - Masters command line
  2058. This class checks and extract data from the provided command line.
  2059. The --long options and flags are taken from the global OPTIONS,
  2060. FLAGS and ACTIONS dictionaries. The short options are registered
  2061. here, and also their equivalence to the long ones.
  2062. _compose_short_opts() -> str
  2063. _compose_long_opts() -> list
  2064. Compose the valid short and long options list, on the
  2065. 'getopt' format.
  2066. parse() -> (opts, args)
  2067. Call getopt to check and parse the command line.
  2068. It expects to receive the command line as a list, and
  2069. without the program name (sys.argv[1:]).
  2070. get_raw_config() -> [RAW config]
  2071. Scans command line and convert the data to the RAW config
  2072. format. See ConfigMaster class to the RAW format description.
  2073. Optional 'ignore' and 'filter_' arguments are used to filter
  2074. in or out specified keys.
  2075. compose_cmdline(dict) -> [Command line]
  2076. Compose a command line list from an already parsed config
  2077. dictionary, generated from RAW by ConfigMaster(). Use
  2078. this to compose an optimal command line for a group of
  2079. options.
  2080. The get_raw_config() calls parse(), so the typical use of this
  2081. class is:
  2082. raw = CommandLine().get_raw_config(sys.argv[1:])
  2083. """
  2084. def __init__(self):
  2085. self.all_options = OPTIONS.keys()
  2086. self.all_flags = FLAGS.keys()
  2087. self.all_actions = ACTIONS.keys()
  2088. # short:long options equivalence
  2089. self.short_long = {
  2090. 'C':'config-file',
  2091. 'h':'help',
  2092. 'H':'no-headers',
  2093. 'i':'infile',
  2094. 'n':'enum-title',
  2095. 'o':'outfile',
  2096. 'q':'quiet',
  2097. 't':'target',
  2098. 'v':'verbose',
  2099. 'V':'version',
  2100. }
  2101. # Compose valid short and long options data for getopt
  2102. self.short_opts = self._compose_short_opts()
  2103. self.long_opts = self._compose_long_opts()
  2104. def _compose_short_opts(self):
  2105. "Returns a string like 'hVt:o' with all short options/flags"
  2106. ret = []
  2107. for opt in self.short_long.keys():
  2108. long_ = self.short_long[opt]
  2109. if long_ in self.all_options: # is flag or option?
  2110. opt = opt+':' # option: have param
  2111. ret.append(opt)
  2112. #Debug('Valid SHORT options: %s'%ret)
  2113. return ''.join(ret)
  2114. def _compose_long_opts(self):
  2115. "Returns a list with all the valid long options/flags"
  2116. ret = map(lambda x:x+'=', self.all_options) # add =
  2117. ret.extend(self.all_flags) # flag ON
  2118. ret.extend(self.all_actions) # actions
  2119. ret.extend(map(lambda x:'no-'+x, self.all_flags)) # add no-*
  2120. ret.extend(['no-style','no-encoding']) # turn OFF
  2121. ret.extend(['no-outfile','no-infile']) # turn OFF
  2122. ret.extend(['no-dump-config', 'no-dump-source']) # turn OFF
  2123. ret.extend(['no-targets']) # turn OFF
  2124. #Debug('Valid LONG options: %s'%ret)
  2125. return ret
  2126. def _tokenize(self, cmd_string=''):
  2127. "Convert a command line string to a list"
  2128. #TODO protect quotes contents -- Don't use it, pass cmdline as list
  2129. return cmd_string.split()
  2130. def parse(self, cmdline=[]):
  2131. "Check/Parse a command line list TIP: no program name!"
  2132. # Get the valid options
  2133. short, long_ = self.short_opts, self.long_opts
  2134. # Parse it!
  2135. try:
  2136. opts, args = getopt.getopt(cmdline, short, long_)
  2137. except getopt.error, errmsg:
  2138. Error(_("%s (try --help)")%errmsg)
  2139. return (opts, args)
  2140. def get_raw_config(self, cmdline=[], ignore=[], filter_=[], relative=0):
  2141. "Returns the options/arguments found as RAW config"
  2142. if not cmdline: return []
  2143. ret = []
  2144. # We need lists, not strings (such as from %!options)
  2145. if type(cmdline) in (type(''), type(u'')):
  2146. cmdline = self._tokenize(cmdline)
  2147. # Extract name/value pair of all configs, check for invalid names
  2148. options, arguments = self.parse(cmdline[:])
  2149. # Some cleanup on the raw config
  2150. for name, value in options:
  2151. # Remove leading - and --
  2152. name = re.sub('^--?', '', name)
  2153. # Fix old misspelled --suGGar, --no-suGGar
  2154. name = name.replace('suggar', 'sugar')
  2155. # Translate short option to long
  2156. if len(name) == 1:
  2157. name = self.short_long[name]
  2158. # Outfile exception: path relative to PWD
  2159. if name == 'outfile' and relative and value not in [STDOUT, MODULEOUT]:
  2160. value = os.path.abspath(value)
  2161. # -C, --config-file inclusion, path relative to PWD
  2162. if name == 'config-file':
  2163. ret.extend(ConfigLines().include_config_file(value))
  2164. continue
  2165. # Save this config
  2166. ret.append(['all', name, value])
  2167. # All configuration was read and saved
  2168. # Get infile, if any
  2169. while arguments:
  2170. infile = arguments.pop(0)
  2171. ret.append(['all', 'infile', infile])
  2172. # Apply 'ignore' and 'filter_' rules (filter_ is stronger)
  2173. if (ignore or filter_):
  2174. filtered = []
  2175. for target, name, value in ret:
  2176. if (filter_ and name in filter_) or \
  2177. (ignore and name not in ignore):
  2178. filtered.append([target, name, value])
  2179. ret = filtered[:]
  2180. # Add the original command line string as 'realcmdline'
  2181. ret.append( ['all', 'realcmdline', cmdline] )
  2182. return ret
  2183. def compose_cmdline(self, conf={}, no_check=0):
  2184. "compose a full (and diet) command line from CONF dict"
  2185. if not conf: return []
  2186. args = []
  2187. dft_options = OPTIONS.copy()
  2188. cfg = conf.copy()
  2189. valid_opts = self.all_options + self.all_flags
  2190. use_short = {'no-headers':'H', 'enum-title':'n'}
  2191. # Remove useless options
  2192. if not no_check and cfg.get('toc-only'):
  2193. if cfg.has_key('no-headers'):
  2194. del cfg['no-headers']
  2195. if cfg.has_key('outfile'):
  2196. del cfg['outfile'] # defaults to STDOUT
  2197. if cfg.get('target') == 'txt':
  2198. del cfg['target'] # already default
  2199. args.append('--toc-only') # must be the first
  2200. del cfg['toc-only']
  2201. # Add target type
  2202. if cfg.has_key('target'):
  2203. args.append('-t '+cfg['target'])
  2204. del cfg['target']
  2205. # Add other options
  2206. for key in cfg.keys():
  2207. if key not in valid_opts: continue # may be a %!setting
  2208. if key == 'outfile' or key == 'infile': continue # later
  2209. val = cfg[key]
  2210. if not val: continue
  2211. # Default values are useless on cmdline
  2212. if val == dft_options.get(key): continue
  2213. # -short format
  2214. if key in use_short.keys():
  2215. args.append('-'+use_short[key])
  2216. continue
  2217. # --long format
  2218. if key in self.all_flags: # add --option
  2219. args.append('--'+key)
  2220. else: # add --option=value
  2221. args.append('--%s=%s'%(key,val))
  2222. # The outfile using -o
  2223. if cfg.has_key('outfile') and \
  2224. cfg['outfile'] != dft_options.get('outfile'):
  2225. args.append('-o '+cfg['outfile'])
  2226. # Place input file(s) always at the end
  2227. if cfg.has_key('infile'):
  2228. args.append(' '.join(cfg['infile']))
  2229. # Return as a nice list
  2230. Debug("Diet command line: %s"%' '.join(args), 1)
  2231. return args
  2232. ##############################################################################
  2233. class SourceDocument:
  2234. """
  2235. SourceDocument class - scan document structure, extract data
  2236. It knows about full files. It reads a file and identify all
  2237. the areas beginning (Head,Conf,Body). With this info it can
  2238. extract each area contents.
  2239. Note: the original line break is removed.
  2240. DATA:
  2241. self.arearef - Save Head, Conf, Body init line number
  2242. self.areas - Store the area names which are not empty
  2243. self.buffer - The full file contents (with NO \\r, \\n)
  2244. METHODS:
  2245. get() - Access the contents of an Area. Example:
  2246. config = SourceDocument(file).get('conf')
  2247. split() - Get all the document Areas at once. Example:
  2248. head, conf, body = SourceDocument(file).split()
  2249. RULES:
  2250. * The document parts are sequential: Head, Conf and Body.
  2251. * One ends when the next begins.
  2252. * The Conf Area is optional, so a document can have just
  2253. Head and Body Areas.
  2254. These are the Areas limits:
  2255. - Head Area: the first three lines
  2256. - Body Area: from the first valid text line to the end
  2257. - Conf Area: the comments between Head and Body Areas
  2258. Exception: If the first line is blank, this means no
  2259. header info, so the Head Area is just the first line.
  2260. """
  2261. def __init__(self, filename='', contents=[]):
  2262. self.areas = ['head','conf','body']
  2263. self.arearef = []
  2264. self.areas_fancy = ''
  2265. self.filename = filename
  2266. self.buffer = []
  2267. if filename:
  2268. self.scan_file(filename)
  2269. elif contents:
  2270. self.scan(contents)
  2271. def split(self):
  2272. "Returns all document parts, splitted into lists."
  2273. return self.get('head'), self.get('conf'), self.get('body')
  2274. def get(self, areaname):
  2275. "Returns head|conf|body contents from self.buffer"
  2276. # Sanity
  2277. if areaname not in self.areas: return []
  2278. if not self.buffer : return []
  2279. # Go get it
  2280. bufini = 1
  2281. bufend = len(self.buffer)
  2282. if areaname == 'head':
  2283. ini = bufini
  2284. end = self.arearef[1] or self.arearef[2] or bufend
  2285. elif areaname == 'conf':
  2286. ini = self.arearef[1]
  2287. end = self.arearef[2] or bufend
  2288. elif areaname == 'body':
  2289. ini = self.arearef[2]
  2290. end = bufend
  2291. else:
  2292. Error("Unknown Area name '%s'"%areaname)
  2293. lines = self.buffer[ini:end]
  2294. # Make sure head will always have 3 lines
  2295. while areaname == 'head' and len(lines) < 3:
  2296. lines.append('')
  2297. return lines
  2298. def scan_file(self, filename):
  2299. Debug("source file: %s"%filename)
  2300. Message(_("Loading source document"),1)
  2301. buf = Readfile(filename, remove_linebreaks=1)
  2302. self.scan(buf)
  2303. def scan(self, lines):
  2304. "Run through source file and identify head/conf/body areas"
  2305. buf = lines
  2306. if len(buf) == 0:
  2307. Error(_('The input file is empty: %s')%self.filename)
  2308. cfg_parser = ConfigLines().parse_line
  2309. buf.insert(0, '') # text start at pos 1
  2310. ref = [1,4,0]
  2311. if not buf[1].strip(): # no header
  2312. ref[0] = 0 ; ref[1] = 2
  2313. rgx = getRegexes()
  2314. on_comment_block = 0
  2315. for i in xrange(ref[1],len(buf)): # find body init:
  2316. # Handle comment blocks inside config area
  2317. if not on_comment_block \
  2318. and rgx['blockCommentOpen'].search(buf[i]):
  2319. on_comment_block = 1
  2320. continue
  2321. if on_comment_block \
  2322. and rgx['blockCommentOpen'].search(buf[i]):
  2323. on_comment_block = 0
  2324. continue
  2325. if on_comment_block: continue
  2326. if buf[i].strip() and ( # ... not blank and
  2327. buf[i][0] != '%' or # ... not comment or
  2328. rgx['macros'].match(buf[i]) or # ... %%macro
  2329. rgx['toc'].match(buf[i]) or # ... %%toc
  2330. cfg_parser(buf[i],'include')[1] or # ... %!include
  2331. cfg_parser(buf[i],'csv')[1] # ... %!csv
  2332. ):
  2333. ref[2] = i ; break
  2334. if ref[1] == ref[2]: ref[1] = 0 # no conf area
  2335. for i in 0,1,2: # del !existent
  2336. if ref[i] >= len(buf): ref[i] = 0 # title-only
  2337. if not ref[i]: self.areas[i] = ''
  2338. Debug('Head,Conf,Body start line: %s'%ref)
  2339. self.arearef = ref # save results
  2340. self.buffer = buf
  2341. # Fancyness sample: head conf body (1 4 8)
  2342. self.areas_fancy = "%s (%s)"%(
  2343. ' '.join(self.areas),
  2344. ' '.join(map(str, map(lambda x:x or '', ref))))
  2345. Message(_("Areas found: %s")%self.areas_fancy, 2)
  2346. def get_raw_config(self):
  2347. "Handy method to get the CONF area RAW config (if any)"
  2348. if not self.areas.count('conf'): return []
  2349. Message(_("Scanning source document CONF area"),1)
  2350. raw = ConfigLines(
  2351. file_=self.filename, lines=self.get('conf'),
  2352. first_line=self.arearef[1]).get_raw_config()
  2353. Debug("document raw config: %s"%raw, 1)
  2354. return raw
  2355. ##############################################################################
  2356. class ConfigMaster:
  2357. """
  2358. ConfigMaster class - the configuration wizard
  2359. This class is the configuration master. It knows how to handle
  2360. the RAW and PARSED config format. It also performs the sanity
  2361. checking for a given configuration.
  2362. DATA:
  2363. self.raw - Stores the config on the RAW format
  2364. self.parsed - Stores the config on the PARSED format
  2365. self.defaults - Stores the default values for all keys
  2366. self.off - Stores the OFF values for all keys
  2367. self.multi - List of keys which can have multiple values
  2368. self.numeric - List of keys which value must be a number
  2369. self.incremental - List of keys which are incremental
  2370. RAW FORMAT:
  2371. The RAW format is a list of lists, being each mother list item
  2372. a full configuration entry. Any entry is a 3 item list, on
  2373. the following format: [ TARGET, KEY, VALUE ]
  2374. Being a list, the order is preserved, so it's easy to use
  2375. different kinds of configs, as CONF area and command line,
  2376. respecting the precedence.
  2377. The special target 'all' is used when no specific target was
  2378. defined on the original config.
  2379. PARSED FORMAT:
  2380. The PARSED format is a dictionary, with all the 'key : value'
  2381. found by reading the RAW config. The self.target contents
  2382. matters, so this dictionary only contains the target's
  2383. config. The configs of other targets are ignored.
  2384. The CommandLine and ConfigLines classes have the get_raw_config()
  2385. method which convert the configuration found to the RAW format.
  2386. Just feed it to parse() and get a brand-new ready-to-use config
  2387. dictionary. Example:
  2388. >>> raw = CommandLine().get_raw_config(['-n', '-H'])
  2389. >>> print raw
  2390. [['all', 'enum-title', ''], ['all', 'no-headers', '']]
  2391. >>> parsed = ConfigMaster(raw).parse()
  2392. >>> print parsed
  2393. {'enum-title': 1, 'headers': 0}
  2394. """
  2395. def __init__(self, raw=[], target=''):
  2396. self.raw = raw
  2397. self.target = target
  2398. self.parsed = {}
  2399. self.dft_options = OPTIONS.copy()
  2400. self.dft_flags = FLAGS.copy()
  2401. self.dft_actions = ACTIONS.copy()
  2402. self.dft_settings = SETTINGS.copy()
  2403. self.defaults = self._get_defaults()
  2404. self.off = self._get_off()
  2405. self.incremental = ['verbose']
  2406. self.numeric = ['toc-level', 'split', 'width', 'height']
  2407. self.multi = ['infile', 'preproc', 'postproc', 'options', 'style']
  2408. def _get_defaults(self):
  2409. "Get the default values for all config/options/flags"
  2410. empty = {}
  2411. for kw in CONFIG_KEYWORDS: empty[kw] = ''
  2412. empty.update(self.dft_options)
  2413. empty.update(self.dft_flags)
  2414. empty.update(self.dft_actions)
  2415. empty.update(self.dft_settings)
  2416. empty['realcmdline'] = '' # internal use only
  2417. empty['sourcefile'] = '' # internal use only
  2418. return empty
  2419. def _get_off(self):
  2420. "Turns OFF all the config/options/flags"
  2421. off = {}
  2422. for key in self.defaults.keys():
  2423. kind = type(self.defaults[key])
  2424. if kind == type(9):
  2425. off[key] = 0
  2426. elif kind == type('') or kind == type(u''):
  2427. off[key] = ''
  2428. elif kind == type([]):
  2429. off[key] = []
  2430. else:
  2431. Error('ConfigMaster: %s: Unknown type' % key)
  2432. return off
  2433. def _check_target(self):
  2434. "Checks if the target is already defined. If not, do it"
  2435. if not self.target:
  2436. self.target = self.find_value('target')
  2437. def get_target_raw(self):
  2438. "Returns the raw config for self.target or 'all'"
  2439. ret = []
  2440. self._check_target()
  2441. for entry in self.raw:
  2442. if entry[0] == self.target or entry[0] == 'all':
  2443. ret.append(entry)
  2444. return ret
  2445. def add(self, key, val):
  2446. "Adds the key:value pair to the config dictionary (if needed)"
  2447. # %!options
  2448. if key == 'options':
  2449. ignoreme = self.dft_actions.keys() + ['target']
  2450. ignoreme.remove('dump-config')
  2451. ignoreme.remove('dump-source')
  2452. ignoreme.remove('targets')
  2453. raw_opts = CommandLine().get_raw_config(
  2454. val, ignore=ignoreme)
  2455. for target, key, val in raw_opts:
  2456. self.add(key, val)
  2457. return
  2458. # The no- prefix turns OFF this key
  2459. if key.startswith('no-'):
  2460. key = key[3:] # remove prefix
  2461. val = self.off.get(key) # turn key OFF
  2462. # Is this key valid?
  2463. if key not in self.defaults.keys():
  2464. Debug('Bogus Config %s:%s'%(key,val),1)
  2465. return
  2466. # Is this value the default one?
  2467. if val == self.defaults.get(key):
  2468. # If default value, remove previous key:val
  2469. if self.parsed.has_key(key):
  2470. del self.parsed[key]
  2471. # Nothing more to do
  2472. return
  2473. # Flags ON comes empty. we'll add the 1 value now
  2474. if val == '' and (
  2475. key in self.dft_flags.keys() or
  2476. key in self.dft_actions.keys()):
  2477. val = 1
  2478. # Multi value or single?
  2479. if key in self.multi:
  2480. # First one? start new list
  2481. if not self.parsed.has_key(key):
  2482. self.parsed[key] = []
  2483. self.parsed[key].append(val)
  2484. # Incremental value? so let's add it
  2485. elif key in self.incremental:
  2486. self.parsed[key] = (self.parsed.get(key) or 0) + val
  2487. else:
  2488. self.parsed[key] = val
  2489. fancykey = dotted_spaces("%12s"%key)
  2490. Message(_("Added config %s : %s")%(fancykey,val),3)
  2491. def get_outfile_name(self, config={}):
  2492. "Dirname is the same for {in,out}file"
  2493. infile, outfile = config['sourcefile'], config['outfile']
  2494. if outfile and outfile not in (STDOUT, MODULEOUT) \
  2495. and not os.path.isabs(outfile):
  2496. outfile = os.path.join(os.path.dirname(infile), outfile)
  2497. if infile == STDIN and not outfile: outfile = STDOUT
  2498. if infile == MODULEIN and not outfile: outfile = MODULEOUT
  2499. if not outfile and (infile and config.get('target')):
  2500. basename = re.sub('\.(txt|t2t)$','',infile)
  2501. outfile = "%s.%s"%(basename, config['target'])
  2502. Debug(" infile: '%s'"%infile , 1)
  2503. Debug("outfile: '%s'"%outfile, 1)
  2504. return outfile
  2505. def sanity(self, config, gui=0):
  2506. "Basic config sanity checking"
  2507. global AA
  2508. if not config: return {}
  2509. target = config.get('target')
  2510. # Some actions don't require target specification
  2511. if not target:
  2512. for action in NO_TARGET:
  2513. if config.get(action):
  2514. target = 'txt'
  2515. break
  2516. # On GUI, some checking are skipped
  2517. if not gui:
  2518. # We *need* a target
  2519. if not target:
  2520. Error(_('No target specified (try --help)') + '\n\n' +
  2521. _('Please inform a target using the -t option or the %!target command.') + '\n' +
  2522. _('Example:') + ' %s -t html %s' % (my_name, _('file.t2t')) + '\n\n' +
  2523. _("Run 'txt2tags --targets' to see all the available targets."))
  2524. # And of course, an infile also
  2525. # TODO#1: It seems that this checking is never reached
  2526. if not config.get('infile'):
  2527. Error(_('Missing input file (try --help)'))
  2528. # Is the target valid?
  2529. if not TARGETS.count(target):
  2530. Error(_("Invalid target '%s'") % target + '\n\n' +
  2531. _("Run 'txt2tags --targets' to see all the available targets."))
  2532. # Ensure all keys are present
  2533. empty = self.defaults.copy() ; empty.update(config)
  2534. config = empty.copy()
  2535. # Check integers options
  2536. for key in config.keys():
  2537. if key in self.numeric:
  2538. try:
  2539. config[key] = int(config[key])
  2540. except ValueError:
  2541. Error(_('--%s value must be a number') % key)
  2542. # Check split level value
  2543. if config['split'] not in (0,1,2):
  2544. Error(_('Option --split must be 0, 1 or 2'))
  2545. # Slides needs width and height
  2546. if config['slides'] and target == 'art':
  2547. if not config['width']:
  2548. config['width'] = DFT_SLIDE_WIDTH
  2549. if not config['height']:
  2550. config['height'] = DFT_SLIDE_HEIGHT
  2551. # ASCII Art needs a width
  2552. if target == 'art' and not config['width']:
  2553. config['width'] = DFT_TEXT_WIDTH
  2554. # Check/set user ASCII Art formatting characters
  2555. if config['art-chars']:
  2556. if len(config['art-chars']) != len(AA_VALUES):
  2557. Error(_("--art-chars: Expected %i chars, got %i") % (
  2558. len(AA_VALUES), len(config['art-chars'])))
  2559. else:
  2560. AA = dict(zip(AA_KEYS, config['art-chars']))
  2561. # --toc-only is stronger than others
  2562. if config['toc-only']:
  2563. config['headers'] = 0
  2564. config['toc'] = 0
  2565. config['split'] = 0
  2566. config['gui'] = 0
  2567. config['outfile'] = config['outfile'] or STDOUT
  2568. # Splitting is disable for now (future: HTML only, no STDOUT)
  2569. config['split'] = 0
  2570. # Restore target
  2571. config['target'] = target
  2572. # Set output file name
  2573. config['outfile'] = self.get_outfile_name(config)
  2574. # Checking suicide
  2575. if config['sourcefile'] == config['outfile'] and \
  2576. config['outfile'] not in [STDOUT,MODULEOUT] and not gui:
  2577. Error(_("Input and Output files are the same: %s") % config['outfile'])
  2578. return config
  2579. def parse(self):
  2580. "Returns the parsed config for the current target"
  2581. raw = self.get_target_raw()
  2582. for target, key, value in raw:
  2583. self.add(key, value)
  2584. Message(_("Added the following keys: %s") % ', '.join(self.parsed.keys()), 2)
  2585. return self.parsed.copy()
  2586. def find_value(self, key='', target=''):
  2587. "Scans ALL raw config to find the desired key"
  2588. ret = []
  2589. # Scan and save all values found
  2590. for targ, k, val in self.raw:
  2591. if k == key and (targ == target or targ == 'all'):
  2592. ret.append(val)
  2593. if not ret: return ''
  2594. # If not multi value, return only the last found
  2595. if key in self.multi: return ret
  2596. else : return ret[-1]
  2597. ########################################################################
  2598. class ConfigLines:
  2599. """
  2600. ConfigLines class - the config file data extractor
  2601. This class reads and parse the config lines on the %!key:val
  2602. format, converting it to RAW config. It deals with user
  2603. config file (RC file), source document CONF area and
  2604. %!includeconf directives.
  2605. Call it passing a file name or feed the desired config lines.
  2606. Then just call the get_raw_config() method and wait to
  2607. receive the full config data on the RAW format. This method
  2608. also follows the possible %!includeconf directives found on
  2609. the config lines. Example:
  2610. raw = ConfigLines(file=".txt2tagsrc").get_raw_config()
  2611. The parse_line() method is also useful to be used alone,
  2612. to identify and tokenize a single config line. For example,
  2613. to get the %!include command components, on the source
  2614. document BODY:
  2615. target, key, value = ConfigLines().parse_line(body_line)
  2616. """
  2617. def __init__(self, file_='', lines=[], first_line=1):
  2618. self.file = file_ or 'NOFILE'
  2619. self.lines = lines
  2620. self.first_line = first_line
  2621. def load_lines(self):
  2622. "Make sure we've loaded the file contents into buffer"
  2623. if not self.lines and not self.file:
  2624. Error("ConfigLines: No file or lines provided")
  2625. if not self.lines:
  2626. self.lines = self.read_config_file(self.file)
  2627. def read_config_file(self, filename=''):
  2628. "Read a Config File contents, aborting on invalid line"
  2629. if not filename: return []
  2630. errormsg = _("Invalid CONFIG line on %s")+"\n%03d:%s"
  2631. lines = Readfile(filename, remove_linebreaks=1)
  2632. # Sanity: try to find invalid config lines
  2633. for i in xrange(len(lines)):
  2634. line = lines[i].rstrip()
  2635. if not line: continue # empty
  2636. if line[0] != '%': Error(errormsg%(filename,i+1,line))
  2637. return lines
  2638. def include_config_file(self, file_=''):
  2639. "Perform the %!includeconf action, returning RAW config"
  2640. if not file_: return []
  2641. # Current dir relative to the current file (self.file)
  2642. current_dir = os.path.dirname(self.file)
  2643. file_ = os.path.join(current_dir, file_)
  2644. # Read and parse included config file contents
  2645. lines = self.read_config_file(file_)
  2646. return ConfigLines(file_=file_, lines=lines).get_raw_config()
  2647. def get_raw_config(self):
  2648. "Scan buffer and extract all config as RAW (including includes)"
  2649. ret = []
  2650. self.load_lines()
  2651. first = self.first_line
  2652. for i in xrange(len(self.lines)):
  2653. line = self.lines[i]
  2654. Message(_("Processing line %03d: %s")%(first+i,line),2)
  2655. target, key, val = self.parse_line(line)
  2656. if not key: continue # no config on this line
  2657. if key == 'includeconf':
  2658. err = _('A file cannot include itself (loop!)')
  2659. if val == self.file:
  2660. Error("%s: %%!includeconf: %s" % (err, self.file))
  2661. more_raw = self.include_config_file(val)
  2662. ret.extend(more_raw)
  2663. Message(_("Finished Config file inclusion: %s") % val, 2)
  2664. else:
  2665. ret.append([target, key, val])
  2666. Message(_("Added %s")%key,3)
  2667. return ret
  2668. def parse_line(self, line='', keyname='', target=''):
  2669. "Detects %!key:val config lines and extract data from it"
  2670. empty = ['', '', '']
  2671. if not line: return empty
  2672. no_target = ['target', 'includeconf']
  2673. re_name = keyname or '[a-z]+'
  2674. re_target = target or '[a-z]*'
  2675. # XXX TODO <value>\S.+? requires TWO chars, breaks %!include:a
  2676. cfgregex = re.compile("""
  2677. ^%%!\s* # leading id with opt spaces
  2678. (?P<name>%s)\s* # config name
  2679. (\((?P<target>%s)\))? # optional target spec inside ()
  2680. \s*:\s* # key:value delimiter with opt spaces
  2681. (?P<value>\S.+?) # config value
  2682. \s*$ # rstrip() spaces and hit EOL
  2683. """%(re_name, re_target), re.I+re.VERBOSE)
  2684. prepostregex = re.compile("""
  2685. # ---[ PATTERN ]---
  2686. ^( "([^"]*)" # "double quoted" or
  2687. | '([^']*)' # 'single quoted' or
  2688. | ([^\s]+) # single_word
  2689. )
  2690. \s+ # separated by spaces
  2691. # ---[ REPLACE ]---
  2692. ( "([^"]*)" # "double quoted" or
  2693. | '([^']*)' # 'single quoted' or
  2694. | (.*) # anything
  2695. )
  2696. \s*$
  2697. """, re.VERBOSE)
  2698. guicolors = re.compile("^([^\s]+\s+){3}[^\s]+") # 4 tokens
  2699. # Give me a match or get out
  2700. match = cfgregex.match(line)
  2701. if not match: return empty
  2702. # Save information about this config
  2703. name = (match.group('name') or '').lower()
  2704. target = (match.group('target') or 'all').lower()
  2705. value = match.group('value')
  2706. # %!keyword(target) not allowed for these
  2707. if name in no_target and match.group('target'):
  2708. Error(
  2709. _("You can't use (target) with %s") % ('%!' + name)
  2710. + "\n%s" % line)
  2711. # Force no_target keywords to be valid for all targets
  2712. if name in no_target:
  2713. target = 'all'
  2714. # Special config for GUI colors
  2715. if name == 'guicolors':
  2716. valmatch = guicolors.search(value)
  2717. if not valmatch: return empty
  2718. value = re.split('\s+', value)
  2719. # Special config with two quoted values (%!preproc: "foo" 'bar')
  2720. if name == 'preproc' or name == 'postproc':
  2721. valmatch = prepostregex.search(value)
  2722. if not valmatch: return empty
  2723. getval = valmatch.group
  2724. patt = getval(2) or getval(3) or getval(4) or ''
  2725. repl = getval(6) or getval(7) or getval(8) or ''
  2726. value = (patt, repl)
  2727. return [target, name, value]
  2728. ##############################################################################
  2729. class MaskMaster:
  2730. "(Un)Protect important structures from escaping and formatting"
  2731. def __init__(self):
  2732. self.linkmask = 'vvvLINKvvv'
  2733. self.monomask = 'vvvMONOvvv'
  2734. self.macromask = 'vvvMACROvvv'
  2735. self.rawmask = 'vvvRAWvvv'
  2736. self.taggedmask= 'vvvTAGGEDvvv'
  2737. self.tocmask = 'vvvTOCvvv'
  2738. self.macroman = MacroMaster()
  2739. self.reset()
  2740. def reset(self):
  2741. self.linkbank = []
  2742. self.monobank = []
  2743. self.macrobank = []
  2744. self.rawbank = []
  2745. self.taggedbank = []
  2746. def mask(self, line=''):
  2747. global AUTOTOC
  2748. # The verbatim, raw and tagged inline marks are mutually exclusive.
  2749. # This means that one can't appear inside the other.
  2750. # If found, the inner marks must be ignored.
  2751. # Example: ``foo ""bar"" ''baz''``
  2752. # In HTML: <code>foo ""bar"" ''baz''</code>
  2753. #
  2754. # The trick here is to protect the mark who appears first on the line.
  2755. # The three regexes are tried and the one with the lowest index wins.
  2756. # If none is found (else), we get out of the loop.
  2757. #
  2758. while True:
  2759. # Try to match the line for the three marks
  2760. # Note: 'z' > 999999
  2761. #
  2762. t = r = v = 'z'
  2763. try: t = regex['tagged'].search(line).start()
  2764. except: pass
  2765. try: r = regex['raw'].search(line).start()
  2766. except: pass
  2767. try: v = regex['fontMono'].search(line).start()
  2768. except: pass
  2769. # Protect tagged text
  2770. if t >= 0 and t < r and t < v:
  2771. txt = regex['tagged'].search(line).group(1)
  2772. self.taggedbank.append(txt)
  2773. line = regex['tagged'].sub(self.taggedmask,line,1)
  2774. # Protect raw text
  2775. elif r >= 0 and r < t and r < v:
  2776. txt = regex['raw'].search(line).group(1)
  2777. txt = doEscape(TARGET,txt)
  2778. self.rawbank.append(txt)
  2779. line = regex['raw'].sub(self.rawmask,line,1)
  2780. # Protect verbatim text
  2781. elif v >= 0 and v < t and v < r:
  2782. txt = regex['fontMono'].search(line).group(1)
  2783. txt = doEscape(TARGET,txt)
  2784. self.monobank.append(txt)
  2785. line = regex['fontMono'].sub(self.monomask,line,1)
  2786. else:
  2787. break
  2788. # Protect macros
  2789. while regex['macros'].search(line):
  2790. txt = regex['macros'].search(line).group()
  2791. self.macrobank.append(txt)
  2792. line = regex['macros'].sub(self.macromask,line,1)
  2793. # Protect TOC location
  2794. while regex['toc'].search(line):
  2795. line = regex['toc'].sub(self.tocmask,line)
  2796. AUTOTOC = 0
  2797. # Protect URLs and emails
  2798. while regex['linkmark'].search(line) or \
  2799. regex['link' ].search(line):
  2800. # Try to match plain or named links
  2801. match_link = regex['link'].search(line)
  2802. match_named = regex['linkmark'].search(line)
  2803. # Define the current match
  2804. if match_link and match_named:
  2805. # Both types found, which is the first?
  2806. m = match_link
  2807. if match_named.start() < match_link.start():
  2808. m = match_named
  2809. else:
  2810. # Just one type found, we're fine
  2811. m = match_link or match_named
  2812. # Extract link data and apply mask
  2813. if m == match_link: # plain link
  2814. link = m.group()
  2815. label = ''
  2816. link_re = regex['link']
  2817. else: # named link
  2818. link = m.group('link')
  2819. label = m.group('label').rstrip()
  2820. link_re = regex['linkmark']
  2821. line = link_re.sub(self.linkmask,line,1)
  2822. # Save link data to the link bank
  2823. self.linkbank.append((label, link))
  2824. return line
  2825. def undo(self, line):
  2826. # url & email
  2827. for label,url in self.linkbank:
  2828. link = get_tagged_link(label, url)
  2829. line = line.replace(self.linkmask, link, 1)
  2830. # Expand macros
  2831. for macro in self.macrobank:
  2832. macro = self.macroman.expand(macro)
  2833. line = line.replace(self.macromask, macro, 1)
  2834. # Expand verb
  2835. for mono in self.monobank:
  2836. open_,close = TAGS['fontMonoOpen'],TAGS['fontMonoClose']
  2837. line = line.replace(self.monomask, open_+mono+close, 1)
  2838. # Expand raw
  2839. for raw in self.rawbank:
  2840. line = line.replace(self.rawmask, raw, 1)
  2841. # Expand tagged
  2842. for tagged in self.taggedbank:
  2843. line = line.replace(self.taggedmask, tagged, 1)
  2844. return line
  2845. ##############################################################################
  2846. class TitleMaster:
  2847. "Title things"
  2848. def __init__(self):
  2849. self.count = ['',0,0,0,0,0]
  2850. self.toc = []
  2851. self.level = 0
  2852. self.kind = ''
  2853. self.txt = ''
  2854. self.label = ''
  2855. self.tag = ''
  2856. self.tag_hold = []
  2857. self.last_level = 0
  2858. self.count_id = ''
  2859. self.user_labels = {}
  2860. self.anchor_count = 0
  2861. self.anchor_prefix = 'toc'
  2862. def _open_close_blocks(self):
  2863. "Open new title blocks, closing the previous (if any)"
  2864. if not rules['titleblocks']: return
  2865. tag = ''
  2866. last = self.last_level
  2867. curr = self.level
  2868. # Same level, just close the previous
  2869. if curr == last:
  2870. tag = TAGS.get('title%dClose'%last)
  2871. if tag: self.tag_hold.append(tag)
  2872. # Section -> subsection, more depth
  2873. while curr > last:
  2874. last += 1
  2875. # Open the new block of subsections
  2876. tag = TAGS.get('blockTitle%dOpen'%last)
  2877. if tag: self.tag_hold.append(tag)
  2878. # Jump from title1 to title3 or more
  2879. # Fill the gap with an empty section
  2880. if curr - last > 0:
  2881. tag = TAGS.get('title%dOpen'%last)
  2882. tag = regex['x'].sub('', tag) # del \a
  2883. if tag: self.tag_hold.append(tag)
  2884. # Section <- subsection, less depth
  2885. while curr < last:
  2886. # Close the current opened subsection
  2887. tag = TAGS.get('title%dClose'%last)
  2888. if tag: self.tag_hold.append(tag)
  2889. # Close the current opened block of subsections
  2890. tag = TAGS.get('blockTitle%dClose'%last)
  2891. if tag: self.tag_hold.append(tag)
  2892. last -= 1
  2893. # Close the previous section of the same level
  2894. # The subsections were under it
  2895. if curr == last:
  2896. tag = TAGS.get('title%dClose'%last)
  2897. if tag: self.tag_hold.append(tag)
  2898. def add(self, line):
  2899. "Parses a new title line."
  2900. if not line: return
  2901. self._set_prop(line)
  2902. self._open_close_blocks()
  2903. self._set_count_id()
  2904. self._set_label()
  2905. self._save_toc_info()
  2906. def close_all(self):
  2907. "Closes all opened title blocks"
  2908. ret = []
  2909. ret.extend(self.tag_hold)
  2910. while self.level:
  2911. tag = TAGS.get('title%dClose'%self.level)
  2912. if tag: ret.append(tag)
  2913. tag = TAGS.get('blockTitle%dClose'%self.level)
  2914. if tag: ret.append(tag)
  2915. self.level -= 1
  2916. return ret
  2917. def _save_toc_info(self):
  2918. "Save TOC info, used by self.dump_marked_toc()"
  2919. self.toc.append((self.level, self.count_id, self.txt, self.label))
  2920. def _set_prop(self, line=''):
  2921. "Extract info from original line and set data holders."
  2922. # Detect title type (numbered or not)
  2923. id_ = line.lstrip()[0]
  2924. if id_ == '=': kind = 'title'
  2925. elif id_ == '+': kind = 'numtitle'
  2926. else: Error("Unknown Title ID '%s'"%id_)
  2927. # Extract line info
  2928. match = regex[kind].search(line)
  2929. level = len(match.group('id'))
  2930. txt = match.group('txt').strip()
  2931. label = match.group('label')
  2932. # Parse info & save
  2933. if CONF['enum-title']: kind = 'numtitle' # force
  2934. if rules['titleblocks']:
  2935. self.tag = TAGS.get('%s%dOpen'%(kind,level)) or \
  2936. TAGS.get('title%dOpen'%level)
  2937. else:
  2938. self.tag = TAGS.get(kind+`level`) or \
  2939. TAGS.get('title'+`level`)
  2940. self.last_level = self.level
  2941. self.kind = kind
  2942. self.level = level
  2943. self.txt = txt
  2944. self.label = label
  2945. def _set_count_id(self):
  2946. "Compose and save the title count identifier (if needed)."
  2947. count_id = ''
  2948. if self.kind == 'numtitle' and not rules['autonumbertitle']:
  2949. # Manually increase title count
  2950. self.count[self.level] += 1
  2951. # Reset sublevels count (if any)
  2952. max_levels = len(self.count)
  2953. if self.level < max_levels-1:
  2954. for i in xrange(self.level+1, max_levels):
  2955. self.count[i] = 0
  2956. # Compose count id from hierarchy
  2957. for i in xrange(self.level):
  2958. count_id= "%s%d."%(count_id, self.count[i+1])
  2959. self.count_id = count_id
  2960. def _set_label(self):
  2961. "Compose and save title label, used by anchors."
  2962. # Remove invalid chars from label set by user
  2963. self.label = re.sub('[^A-Za-z0-9_-]', '', self.label or '')
  2964. # Generate name as 15 first :alnum: chars
  2965. #TODO how to translate safely accented chars to plain?
  2966. #self.label = re.sub('[^A-Za-z0-9]', '', self.txt)[:15]
  2967. # 'tocN' label - sequential count, ignoring 'toc-level'
  2968. #self.label = self.anchor_prefix + str(len(self.toc)+1)
  2969. def _get_tagged_anchor(self):
  2970. "Return anchor if user defined a label, or TOC is on."
  2971. ret = ''
  2972. label = self.label
  2973. if CONF['toc'] and self.level <= CONF['toc-level']:
  2974. # This count is needed bcos self.toc stores all
  2975. # titles, regardless of the 'toc-level' setting,
  2976. # so we can't use self.toc length to number anchors
  2977. self.anchor_count += 1
  2978. # Autonumber label (if needed)
  2979. label = label or '%s%s' % (self.anchor_prefix, self.anchor_count)
  2980. if label and TAGS['anchor']:
  2981. ret = regex['x'].sub(label,TAGS['anchor'])
  2982. return ret
  2983. def _get_full_title_text(self):
  2984. "Returns the full title contents, already escaped."
  2985. ret = self.txt
  2986. # Insert count_id (if any) before text
  2987. if self.count_id:
  2988. ret = '%s %s'%(self.count_id, ret)
  2989. # Escape specials
  2990. ret = doEscape(TARGET, ret)
  2991. # Same targets needs final escapes on title lines
  2992. # It's here because there is a 'continue' after title
  2993. if rules['finalescapetitle']:
  2994. ret = doFinalEscape(TARGET, ret)
  2995. return ret
  2996. def get(self):
  2997. "Returns the tagged title as a list."
  2998. global AA_TITLE
  2999. ret = []
  3000. # Maybe some anchoring before?
  3001. anchor = self._get_tagged_anchor()
  3002. self.tag = regex['_anchor'].sub(anchor, self.tag)
  3003. ### Compose & escape title text (TOC uses unescaped)
  3004. full_title = self._get_full_title_text()
  3005. # Close previous section area
  3006. ret.extend(self.tag_hold)
  3007. self.tag_hold = []
  3008. tagged = regex['x'].sub(full_title, self.tag)
  3009. # Adds "underline" on TXT target
  3010. if TARGET == 'txt':
  3011. if BLOCK.count > 1: ret.append('') # blank line before
  3012. ret.append(tagged)
  3013. # Get the right letter count for UTF
  3014. if CONF['encoding'].lower() == 'utf-8':
  3015. i = len(full_title.decode('utf-8'))
  3016. else:
  3017. i = len(full_title)
  3018. ret.append(regex['x'].sub('='*i, self.tag))
  3019. elif TARGET == 'art' and self.level == 1:
  3020. if CONF['slides'] :
  3021. AA_TITLE = tagged
  3022. else :
  3023. if BLOCK.count > 1: ret.append('') # blank line before
  3024. ret.extend(aa_box(tagged, CONF['width']))
  3025. elif TARGET == 'art':
  3026. level = 'level'+str(self.level)
  3027. if BLOCK.count > 1: ret.append('') # blank line before
  3028. ret.append(tagged)
  3029. ret.append(AA[level] * len(full_title))
  3030. else:
  3031. ret.append(tagged)
  3032. return ret
  3033. def dump_marked_toc(self, max_level=99):
  3034. "Dumps all toc itens as a valid t2t-marked list"
  3035. ret = []
  3036. toc_count = 1
  3037. for level, count_id, txt, label in self.toc:
  3038. if level > max_level: continue # ignore
  3039. indent = ' '*level
  3040. id_txt = ('%s %s'%(count_id, txt)).lstrip()
  3041. label = label or self.anchor_prefix+`toc_count`
  3042. toc_count += 1
  3043. # TOC will have crosslinks to anchors
  3044. if TAGS['anchor']:
  3045. if CONF['enum-title'] and level == 1:
  3046. # 1. [Foo #anchor] is more readable than [1. Foo #anchor] in level 1.
  3047. # This is a stoled idea from Windows .CHM help files.
  3048. tocitem = '%s+ [""%s"" #%s]' % (indent, txt, label)
  3049. else:
  3050. tocitem = '%s- [""%s"" #%s]' % (indent, id_txt, label)
  3051. # TOC will be plain text (no links)
  3052. else:
  3053. if TARGET in ['txt', 'man', 'art']:
  3054. # For these, the list is not necessary, just dump the text
  3055. tocitem = '%s""%s""' % (indent, id_txt)
  3056. else:
  3057. tocitem = '%s- ""%s""' % (indent, id_txt)
  3058. ret.append(tocitem)
  3059. return ret
  3060. ##############################################################################
  3061. #TODO check all this table mess
  3062. # It uses parse_row properties for table lines
  3063. # BLOCK.table() replaces the cells by the parsed content
  3064. class TableMaster:
  3065. def __init__(self, line=''):
  3066. self.rows = []
  3067. self.border = 0
  3068. self.align = 'Left'
  3069. self.cellalign = []
  3070. self.colalign = []
  3071. self.cellspan = []
  3072. if line:
  3073. prop = self.parse_row(line)
  3074. self.border = prop['border']
  3075. self.align = prop['align']
  3076. self.cellalign = prop['cellalign']
  3077. self.cellspan = prop['cellspan']
  3078. self.colalign = self._get_col_align()
  3079. def _get_col_align(self):
  3080. colalign = []
  3081. for cell in range(0,len(self.cellalign)):
  3082. align = self.cellalign[cell]
  3083. span = self.cellspan[cell]
  3084. colalign.extend([align] * span)
  3085. return colalign
  3086. def _get_open_tag(self):
  3087. topen = TAGS['tableOpen']
  3088. tborder = TAGS['_tableBorder']
  3089. talign = TAGS['_tableAlign'+self.align]
  3090. calignsep = TAGS['tableColAlignSep']
  3091. calign = ''
  3092. # The first line defines if table has border or not
  3093. if not self.border: tborder = ''
  3094. # Set the columns alignment
  3095. if rules['tablecellaligntype'] == 'column':
  3096. calign = map(lambda x: TAGS['_tableColAlign%s'%x], self.colalign)
  3097. calign = calignsep.join(calign)
  3098. # Align full table, set border and Column align (if any)
  3099. topen = regex['_tableAlign' ].sub(talign , topen)
  3100. topen = regex['_tableBorder' ].sub(tborder, topen)
  3101. topen = regex['_tableColAlign'].sub(calign , topen)
  3102. # Tex table spec, border or not: {|l|c|r|} , {lcr}
  3103. if calignsep and not self.border:
  3104. # Remove cell align separator
  3105. topen = topen.replace(calignsep, '')
  3106. return topen
  3107. def _get_cell_align(self, cells):
  3108. ret = []
  3109. for cell in cells:
  3110. align = 'Left'
  3111. if cell.strip():
  3112. if cell[0] == ' ' and cell[-1] == ' ':
  3113. align = 'Center'
  3114. elif cell[0] == ' ':
  3115. align = 'Right'
  3116. ret.append(align)
  3117. return ret
  3118. def _get_cell_span(self, cells):
  3119. ret = []
  3120. for cell in cells:
  3121. span = 1
  3122. m = re.search('\a(\|+)$', cell)
  3123. if m: span = len(m.group(1))+1
  3124. ret.append(span)
  3125. return ret
  3126. def _tag_cells(self, rowdata):
  3127. row = []
  3128. cells = rowdata['cells']
  3129. open_ = TAGS['tableCellOpen']
  3130. close = TAGS['tableCellClose']
  3131. sep = TAGS['tableCellSep']
  3132. calign = map(lambda x: TAGS['_tableCellAlign'+x], rowdata['cellalign'])
  3133. calignsep = TAGS['tableColAlignSep']
  3134. ncolumns = len(self.colalign)
  3135. # Populate the span and multicol open tags
  3136. cspan = []
  3137. multicol = []
  3138. colindex = 0
  3139. for cellindex in range(0,len(rowdata['cellspan'])):
  3140. span = rowdata['cellspan'][cellindex]
  3141. align = rowdata['cellalign'][cellindex]
  3142. if span > 1:
  3143. cspan.append(regex['x'].sub(
  3144. str(span), TAGS['_tableCellColSpan']))
  3145. mcopen = regex['x'].sub(str(span), TAGS['_tableCellMulticolOpen'])
  3146. multicol.append(mcopen)
  3147. else:
  3148. cspan.append('')
  3149. if colindex < ncolumns and align != self.colalign[colindex]:
  3150. mcopen = regex['x'].sub('1', TAGS['_tableCellMulticolOpen'])
  3151. multicol.append(mcopen)
  3152. else:
  3153. multicol.append('')
  3154. if not self.border:
  3155. multicol[-1] = multicol[-1].replace(calignsep, '')
  3156. colindex += span
  3157. # Maybe is it a title row?
  3158. if rowdata['title']:
  3159. open_ = TAGS['tableTitleCellOpen'] or open_
  3160. close = TAGS['tableTitleCellClose'] or close
  3161. sep = TAGS['tableTitleCellSep'] or sep
  3162. # Should we break the line on *each* table cell?
  3163. if rules['breaktablecell']: close = close+'\n'
  3164. # Cells pre processing
  3165. if rules['tablecellstrip']:
  3166. cells = map(lambda x: x.strip(), cells)
  3167. if rowdata['title'] and rules['tabletitlerowinbold']:
  3168. cells = map(lambda x: enclose_me('fontBold',x), cells)
  3169. # Add cell BEGIN/END tags
  3170. for cell in cells:
  3171. copen = open_
  3172. cclose = close
  3173. # Make sure we will pop from some filled lists
  3174. # Fixes empty line bug '| |'
  3175. this_align = this_span = this_mcopen = ''
  3176. if calign: this_align = calign.pop(0)
  3177. if cspan : this_span = cspan.pop(0)
  3178. if multicol: this_mcopen = multicol.pop(0)
  3179. # Insert cell align into open tag (if cell is alignable)
  3180. if rules['tablecellaligntype'] == 'cell':
  3181. copen = regex['_tableCellAlign'].sub(
  3182. this_align, copen)
  3183. # Insert cell span into open tag (if cell is spannable)
  3184. if rules['tablecellspannable']:
  3185. copen = regex['_tableCellColSpan'].sub(
  3186. this_span, copen)
  3187. # Use multicol tags instead (if multicol supported, and if
  3188. # cell has a span or is aligned differently to column)
  3189. if rules['tablecellmulticol']:
  3190. if this_mcopen:
  3191. copen = regex['_tableColAlign'].sub(this_align, this_mcopen)
  3192. cclose = TAGS['_tableCellMulticolClose']
  3193. row.append(copen + cell + cclose)
  3194. # Maybe there are cell separators?
  3195. return sep.join(row)
  3196. def add_row(self, cells):
  3197. self.rows.append(cells)
  3198. def parse_row(self, line):
  3199. # Default table properties
  3200. ret = {
  3201. 'border':0, 'title':0, 'align':'Left',
  3202. 'cells':[], 'cellalign':[], 'cellspan':[]
  3203. }
  3204. # Detect table align (and remove spaces mark)
  3205. if line[0] == ' ': ret['align'] = 'Center'
  3206. line = line.lstrip()
  3207. # Detect title mark
  3208. if line[1] == '|': ret['title'] = 1
  3209. # Detect border mark and normalize the EOL
  3210. m = re.search(' (\|+) *$', line)
  3211. if m: line = line+' ' ; ret['border'] = 1
  3212. else: line = line+' | '
  3213. # Delete table mark
  3214. line = regex['table'].sub('', line)
  3215. # Detect colspan | foo | bar baz |||
  3216. line = re.sub(' (\|+)\| ', '\a\\1 | ', line)
  3217. # Split cells (the last is fake)
  3218. ret['cells'] = line.split(' | ')[:-1]
  3219. # Find cells span
  3220. ret['cellspan'] = self._get_cell_span(ret['cells'])
  3221. # Remove span ID
  3222. ret['cells'] = map(lambda x:re.sub('\a\|+$','',x),ret['cells'])
  3223. # Find cells align
  3224. ret['cellalign'] = self._get_cell_align(ret['cells'])
  3225. # Hooray!
  3226. Debug('Table Prop: %s' % ret, 7)
  3227. return ret
  3228. def dump(self):
  3229. open_ = self._get_open_tag()
  3230. rows = self.rows
  3231. close = TAGS['tableClose']
  3232. rowopen = TAGS['tableRowOpen']
  3233. rowclose = TAGS['tableRowClose']
  3234. rowsep = TAGS['tableRowSep']
  3235. titrowopen = TAGS['tableTitleRowOpen'] or rowopen
  3236. titrowclose = TAGS['tableTitleRowClose'] or rowclose
  3237. if rules['breaktablelineopen']:
  3238. rowopen = rowopen + '\n'
  3239. titrowopen = titrowopen + '\n'
  3240. # Tex gotchas
  3241. if TARGET == 'tex':
  3242. if not self.border:
  3243. rowopen = titrowopen = ''
  3244. else:
  3245. close = rowopen + close
  3246. # Now we tag all the table cells on each row
  3247. #tagged_cells = map(lambda x: self._tag_cells(x), rows) #!py15
  3248. tagged_cells = []
  3249. for cell in rows: tagged_cells.append(self._tag_cells(cell))
  3250. # Add row separator tags between lines
  3251. tagged_rows = []
  3252. if rowsep:
  3253. #!py15
  3254. #tagged_rows = map(lambda x:x+rowsep, tagged_cells)
  3255. for cell in tagged_cells:
  3256. tagged_rows.append(cell+rowsep)
  3257. # Remove last rowsep, because the table is over
  3258. tagged_rows[-1] = tagged_rows[-1].replace(rowsep, '')
  3259. # Add row BEGIN/END tags for each line
  3260. else:
  3261. for rowdata in rows:
  3262. if rowdata['title']:
  3263. o,c = titrowopen, titrowclose
  3264. else:
  3265. o,c = rowopen, rowclose
  3266. row = tagged_cells.pop(0)
  3267. tagged_rows.append(o + row + c)
  3268. # Join the pieces together
  3269. fulltable = []
  3270. if open_: fulltable.append(open_)
  3271. fulltable.extend(tagged_rows)
  3272. if close: fulltable.append(close)
  3273. return fulltable
  3274. ##############################################################################
  3275. class BlockMaster:
  3276. "TIP: use blockin/out to add/del holders"
  3277. def __init__(self):
  3278. self.BLK = []
  3279. self.HLD = []
  3280. self.PRP = []
  3281. self.depth = 0
  3282. self.count = 0
  3283. self.last = ''
  3284. self.tableparser = None
  3285. self.contains = {
  3286. 'para' :['comment','raw','tagged'],
  3287. 'verb' :[],
  3288. 'table' :['comment'],
  3289. 'raw' :[],
  3290. 'tagged' :[],
  3291. 'comment' :[],
  3292. 'quote' :['quote','comment','raw','tagged'],
  3293. 'list' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
  3294. 'numlist' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
  3295. 'deflist' :['list','numlist','deflist','para','verb','comment','raw','tagged'],
  3296. 'bar' :[],
  3297. 'title' :[],
  3298. 'numtitle':[],
  3299. }
  3300. self.allblocks = self.contains.keys()
  3301. # If one is found inside another, ignore the marks
  3302. self.exclusive = ['comment','verb','raw','tagged']
  3303. # May we include bars inside quotes?
  3304. if rules['barinsidequote']:
  3305. self.contains['quote'].append('bar')
  3306. def block(self):
  3307. if not self.BLK: return ''
  3308. return self.BLK[-1]
  3309. def isblock(self, name=''):
  3310. return self.block() == name
  3311. def prop(self, key):
  3312. if not self.PRP: return ''
  3313. return self.PRP[-1].get(key) or ''
  3314. def propset(self, key, val):
  3315. self.PRP[-1][key] = val
  3316. #Debug('BLOCK prop ++: %s->%s'%(key,repr(val)), 1)
  3317. #Debug('BLOCK props: %s'%(repr(self.PRP)), 1)
  3318. def hold(self):
  3319. if not self.HLD: return []
  3320. return self.HLD[-1]
  3321. def holdadd(self, line):
  3322. if self.block().endswith('list'): line = [line]
  3323. self.HLD[-1].append(line)
  3324. Debug('HOLD add: %s'%repr(line), 4)
  3325. Debug('FULL HOLD: %s'%self.HLD, 4)
  3326. def holdaddsub(self, line):
  3327. self.HLD[-1][-1].append(line)
  3328. Debug('HOLD addsub: %s'%repr(line), 4)
  3329. Debug('FULL HOLD: %s'%self.HLD, 4)
  3330. def holdextend(self, lines):
  3331. if self.block().endswith('list'): lines = [lines]
  3332. self.HLD[-1].extend(lines)
  3333. Debug('HOLD extend: %s'%repr(lines), 4)
  3334. Debug('FULL HOLD: %s'%self.HLD, 4)
  3335. def blockin(self, block):
  3336. ret = []
  3337. if block not in self.allblocks:
  3338. Error("Invalid block '%s'"%block)
  3339. # First, let's close other possible open blocks
  3340. while self.block() and block not in self.contains[self.block()]:
  3341. ret.extend(self.blockout())
  3342. # Now we can gladly add this new one
  3343. self.BLK.append(block)
  3344. self.HLD.append([])
  3345. self.PRP.append({})
  3346. self.count += 1
  3347. if block == 'table': self.tableparser = TableMaster()
  3348. # Deeper and deeper
  3349. self.depth = len(self.BLK)
  3350. Debug('block ++ (%s): %s' % (block,self.BLK), 3)
  3351. return ret
  3352. def blockout(self):
  3353. global AA_COUNT
  3354. if not self.BLK: Error('No block to pop')
  3355. blockname = self.BLK.pop()
  3356. result = getattr(self, blockname)()
  3357. parsed = self.HLD.pop()
  3358. self.PRP.pop()
  3359. self.depth = len(self.BLK)
  3360. if blockname == 'table': del self.tableparser
  3361. # Inserting a nested block into mother
  3362. if self.block():
  3363. if blockname != 'comment': # ignore comment blocks
  3364. if self.block().endswith('list'):
  3365. self.HLD[-1][-1].append(result)
  3366. else:
  3367. self.HLD[-1].append(result)
  3368. # Reset now. Mother block will have it all
  3369. result = []
  3370. Debug('block -- (%s): %s' % (blockname,self.BLK), 3)
  3371. Debug('RELEASED (%s): %s' % (blockname,parsed), 3)
  3372. # Save this top level block name (produced output)
  3373. # The next block will use it
  3374. if result:
  3375. self.last = blockname
  3376. Debug('BLOCK: %s'%result, 6)
  3377. # ASCII Art processing
  3378. if TARGET == 'art' and CONF['slides'] and not CONF['toc-only'] and not CONF.get('art-no-title'):
  3379. n = (CONF['height'] - 1) - (AA_COUNT % (CONF['height'] - 1) + 1)
  3380. if n < len(result) and not (TITLE.level == 1 and blockname in ["title", "numtitle"]):
  3381. result = ([''] * n) + [aa_line(AA['bar1'], CONF['width'])] + aa_slide(AA_TITLE, CONF['width']) + [''] + result
  3382. if blockname in ["title", "numtitle"] and TITLE.level == 1:
  3383. aa_title = aa_slide(AA_TITLE, CONF['width']) + ['']
  3384. if AA_COUNT:
  3385. aa_title = ([''] * n) + [aa_line(AA['bar2'], CONF['width'])] + aa_title
  3386. result = aa_title + result
  3387. AA_COUNT += len(result)
  3388. return result
  3389. def _last_escapes(self, line):
  3390. return doFinalEscape(TARGET, line)
  3391. def _get_escaped_hold(self):
  3392. ret = []
  3393. for line in self.hold():
  3394. linetype = type(line)
  3395. if linetype == type('') or linetype == type(u''):
  3396. ret.append(self._last_escapes(line))
  3397. elif linetype == type([]):
  3398. ret.extend(line)
  3399. else:
  3400. Error("BlockMaster: Unknown HOLD item type: %s" % linetype)
  3401. return ret
  3402. def _remove_twoblanks(self, lastitem):
  3403. if len(lastitem) > 1 and lastitem[-2:] == ['','']:
  3404. return lastitem[:-2]
  3405. return lastitem
  3406. def _should_add_blank_line(self, where, blockname):
  3407. "Validates the blanksaround* rules"
  3408. # Nestable blocks: only mother blocks (level 1) are spaced
  3409. if blockname.endswith('list') and self.depth > 1:
  3410. return False
  3411. # The blank line after the block is always added
  3412. if where == 'after' \
  3413. and rules['blanksaround'+blockname]:
  3414. return True
  3415. # # No blank before if it's the first block of the body
  3416. # elif where == 'before' \
  3417. # and BLOCK.count == 1:
  3418. # return False
  3419. # # No blank before if it's the first block of this level (nested)
  3420. # elif where == 'before' \
  3421. # and self.count == 1:
  3422. # return False
  3423. # The blank line before the block is only added if
  3424. # the previous block haven't added a blank line
  3425. # (to avoid consecutive blanks)
  3426. elif where == 'before' \
  3427. and rules['blanksaround'+blockname] \
  3428. and not rules.get('blanksaround'+self.last):
  3429. return True
  3430. # Nested quotes are handled here,
  3431. # because the mother quote isn't closed yet
  3432. elif where == 'before' \
  3433. and blockname == 'quote' \
  3434. and rules['blanksaround'+blockname] \
  3435. and self.depth > 1:
  3436. return True
  3437. return False
  3438. def comment(self):
  3439. return ''
  3440. def raw(self):
  3441. lines = self.hold()
  3442. return map(lambda x: doEscape(TARGET, x), lines)
  3443. def tagged(self):
  3444. return self.hold()
  3445. def para(self):
  3446. result = []
  3447. open_ = TAGS['paragraphOpen']
  3448. close = TAGS['paragraphClose']
  3449. lines = self._get_escaped_hold()
  3450. # Blank line before?
  3451. if self._should_add_blank_line('before', 'para'): result.append('')
  3452. # Open tag
  3453. if open_: result.append(open_)
  3454. # Pagemaker likes a paragraph as a single long line
  3455. if rules['onelinepara']:
  3456. result.append(' '.join(lines))
  3457. # Others are normal :)
  3458. else:
  3459. result.extend(lines)
  3460. # Close tag
  3461. if close: result.append(close)
  3462. # Blank line after?
  3463. if self._should_add_blank_line('after', 'para'): result.append('')
  3464. # Very very very very very very very very very UGLY fix
  3465. # Needed because <center> can't appear inside <p>
  3466. try:
  3467. if len(lines) == 1 and \
  3468. TARGET in ('html', 'xhtml') and \
  3469. re.match('^\s*<center>.*</center>\s*$', lines[0]):
  3470. result = [lines[0]]
  3471. except: pass
  3472. return result
  3473. def verb(self):
  3474. "Verbatim lines are not masked, so there's no need to unmask"
  3475. result = []
  3476. open_ = TAGS['blockVerbOpen']
  3477. close = TAGS['blockVerbClose']
  3478. # Blank line before?
  3479. if self._should_add_blank_line('before', 'verb'): result.append('')
  3480. # Open tag
  3481. if open_: result.append(open_)
  3482. # Get contents
  3483. for line in self.hold():
  3484. if self.prop('mapped') == 'table':
  3485. line = MacroMaster().expand(line)
  3486. if not rules['verbblocknotescaped']:
  3487. line = doEscape(TARGET,line)
  3488. if rules['indentverbblock']:
  3489. line = ' '+line
  3490. if rules['verbblockfinalescape']:
  3491. line = doFinalEscape(TARGET, line)
  3492. result.append(line)
  3493. # Close tag
  3494. if close: result.append(close)
  3495. # Blank line after?
  3496. if self._should_add_blank_line('after', 'verb'): result.append('')
  3497. return result
  3498. def numtitle(self): return self.title('numtitle')
  3499. def title(self, name='title'):
  3500. result = []
  3501. # Blank line before?
  3502. if self._should_add_blank_line('before', name): result.append('')
  3503. # Get contents
  3504. result.extend(TITLE.get())
  3505. # Blank line after?
  3506. if self._should_add_blank_line('after', name): result.append('')
  3507. return result
  3508. def table(self):
  3509. result = []
  3510. # Blank line before?
  3511. if self._should_add_blank_line('before', 'table'): result.append('')
  3512. # Rewrite all table cells by the unmasked and escaped data
  3513. lines = self._get_escaped_hold()
  3514. for i in xrange(len(lines)):
  3515. cells = lines[i].split(SEPARATOR)
  3516. self.tableparser.rows[i]['cells'] = cells
  3517. result.extend(self.tableparser.dump())
  3518. # Blank line after?
  3519. if self._should_add_blank_line('after', 'table'): result.append('')
  3520. return result
  3521. def quote(self):
  3522. result = []
  3523. open_ = TAGS['blockQuoteOpen'] # block based
  3524. close = TAGS['blockQuoteClose']
  3525. qline = TAGS['blockQuoteLine'] # line based
  3526. indent = tagindent = '\t'*self.depth
  3527. # Apply rules
  3528. if rules['tagnotindentable']: tagindent = ''
  3529. if not rules['keepquoteindent']: indent = ''
  3530. # Blank line before?
  3531. if self._should_add_blank_line('before', 'quote'): result.append('')
  3532. # Open tag
  3533. if open_: result.append(tagindent+open_)
  3534. # Get contents
  3535. for item in self.hold():
  3536. if type(item) == type([]):
  3537. result.extend(item) # subquotes
  3538. else:
  3539. item = regex['quote'].sub('', item) # del TABs
  3540. item = self._last_escapes(item)
  3541. item = qline*self.depth + item
  3542. result.append(indent+item) # quote line
  3543. # Close tag
  3544. if close: result.append(tagindent+close)
  3545. # Blank line after?
  3546. if self._should_add_blank_line('after', 'quote'): result.append('')
  3547. return result
  3548. def bar(self):
  3549. result = []
  3550. bar_tag = ''
  3551. # Blank line before?
  3552. if self._should_add_blank_line('before', 'bar'): result.append('')
  3553. # Get the original bar chars
  3554. bar_chars = self.hold()[0].strip()
  3555. # Set bar type
  3556. if bar_chars.startswith('='): bar_tag = TAGS['bar2']
  3557. else : bar_tag = TAGS['bar1']
  3558. # To avoid comment tag confusion like <!-- ------ --> (sgml)
  3559. if TAGS['comment'].count('--'):
  3560. bar_chars = bar_chars.replace('--', '__')
  3561. # Get the bar tag (may contain \a)
  3562. result.append(regex['x'].sub(bar_chars, bar_tag))
  3563. # Blank line after?
  3564. if self._should_add_blank_line('after', 'bar'): result.append('')
  3565. return result
  3566. def deflist(self): return self.list('deflist')
  3567. def numlist(self): return self.list('numlist')
  3568. def list(self, name='list'):
  3569. result = []
  3570. items = self.hold()
  3571. indent = self.prop('indent')
  3572. tagindent = indent
  3573. listline = TAGS.get(name+'ItemLine')
  3574. itemcount = 0
  3575. if name == 'deflist':
  3576. itemopen = TAGS[name+'Item1Open']
  3577. itemclose = TAGS[name+'Item2Close']
  3578. itemsep = TAGS[name+'Item1Close']+\
  3579. TAGS[name+'Item2Open']
  3580. else:
  3581. itemopen = TAGS[name+'ItemOpen']
  3582. itemclose = TAGS[name+'ItemClose']
  3583. itemsep = ''
  3584. # Apply rules
  3585. if rules['tagnotindentable']: tagindent = ''
  3586. if not rules['keeplistindent']: indent = tagindent = ''
  3587. # ItemLine: number of leading chars identifies list depth
  3588. if listline:
  3589. itemopen = listline*self.depth + itemopen
  3590. # Adds trailing space on opening tags
  3591. if (name == 'list' and rules['spacedlistitemopen']) or \
  3592. (name == 'numlist' and rules['spacednumlistitemopen']):
  3593. itemopen = itemopen + ' '
  3594. # Remove two-blanks from list ending mark, to avoid <p>
  3595. items[-1] = self._remove_twoblanks(items[-1])
  3596. # Blank line before?
  3597. if self._should_add_blank_line('before', name): result.append('')
  3598. # Tag each list item (multiline items), store in listbody
  3599. itemopenorig = itemopen
  3600. listbody = []
  3601. widelist = 0
  3602. for item in items:
  3603. # Add "manual" item count for noautonum targets
  3604. itemcount += 1
  3605. if name == 'numlist' and not rules['autonumberlist']:
  3606. n = str(itemcount)
  3607. itemopen = regex['x'].sub(n, itemopenorig)
  3608. del n
  3609. # Tag it
  3610. item[0] = self._last_escapes(item[0])
  3611. if name == 'deflist':
  3612. z,term,rest = item[0].split(SEPARATOR, 2)
  3613. item[0] = rest
  3614. if not item[0]: del item[0] # to avoid <p>
  3615. listbody.append(tagindent+itemopen+term+itemsep)
  3616. else:
  3617. fullitem = tagindent+itemopen
  3618. listbody.append(item[0].replace(SEPARATOR, fullitem))
  3619. del item[0]
  3620. # Process next lines for this item (if any)
  3621. for line in item:
  3622. if type(line) == type([]): # sublist inside
  3623. listbody.extend(line)
  3624. else:
  3625. line = self._last_escapes(line)
  3626. # Blank lines turns to <p>
  3627. if not line and rules['parainsidelist']:
  3628. line = indent + TAGS['paragraphOpen'] + TAGS['paragraphClose']
  3629. line = line.rstrip()
  3630. widelist = 1
  3631. # Some targets don't like identation here (wiki)
  3632. if not rules['keeplistindent'] or (name == 'deflist' and rules['deflisttextstrip']):
  3633. line = line.lstrip()
  3634. # Maybe we have a line prefix to add? (wiki)
  3635. if name == 'deflist' and TAGS['deflistItem2LinePrefix']:
  3636. line = TAGS['deflistItem2LinePrefix'] + line
  3637. listbody.append(line)
  3638. # Close item (if needed)
  3639. if itemclose: listbody.append(tagindent+itemclose)
  3640. if not widelist and rules['compactlist']:
  3641. listopen = TAGS.get(name+'OpenCompact')
  3642. listclose = TAGS.get(name+'CloseCompact')
  3643. else:
  3644. listopen = TAGS.get(name+'Open')
  3645. listclose = TAGS.get(name+'Close')
  3646. # Open list (not nestable lists are only opened at mother)
  3647. if listopen and not \
  3648. (rules['listnotnested'] and BLOCK.depth != 1):
  3649. result.append(tagindent+listopen)
  3650. result.extend(listbody)
  3651. # Close list (not nestable lists are only closed at mother)
  3652. if listclose and not \
  3653. (rules['listnotnested'] and self.depth != 1):
  3654. result.append(tagindent+listclose)
  3655. # Blank line after?
  3656. if self._should_add_blank_line('after', name): result.append('')
  3657. return result
  3658. ##############################################################################
  3659. class MacroMaster:
  3660. def __init__(self, config={}):
  3661. self.name = ''
  3662. self.config = config or CONF
  3663. self.infile = self.config['sourcefile']
  3664. self.outfile = self.config['outfile']
  3665. self.currdate = time.localtime(time.time())
  3666. self.rgx = regex.get('macros') or getRegexes()['macros']
  3667. self.fileinfo = { 'infile': None, 'outfile': None }
  3668. self.dft_fmt = MACROS
  3669. def walk_file_format(self, fmt):
  3670. "Walks the %%{in/out}file format string, expanding the % flags"
  3671. i = 0; ret = '' # counter/hold
  3672. while i < len(fmt): # char by char
  3673. c = fmt[i]; i += 1
  3674. if c == '%': # hot char!
  3675. if i == len(fmt): # % at the end
  3676. ret = ret + c
  3677. break
  3678. c = fmt[i]; i += 1 # read next
  3679. ret = ret + self.expand_file_flag(c)
  3680. else:
  3681. ret = ret +c # common char
  3682. return ret
  3683. def expand_file_flag(self, flag):
  3684. "%f: filename %F: filename (w/o extension)"
  3685. "%d: dirname %D: dirname (only parent dir)"
  3686. "%p: file path %e: extension"
  3687. info = self.fileinfo[self.name] # get dict
  3688. if flag == '%': x = '%' # %% -> %
  3689. elif flag == 'f': x = info['name']
  3690. elif flag == 'F': x = re.sub('\.[^.]*$','',info['name'])
  3691. elif flag == 'd': x = info['dir']
  3692. elif flag == 'D': x = os.path.split(info['dir'])[-1]
  3693. elif flag == 'p': x = info['path']
  3694. elif flag == 'e': x = re.search('.(\.([^.]+))?$', info['name']).group(2) or ''
  3695. #TODO simpler way for %e ?
  3696. else : x = '%'+flag # false alarm
  3697. return x
  3698. def set_file_info(self, macroname):
  3699. if self.fileinfo.get(macroname): return # already done
  3700. file_ = getattr(self, self.name) # self.infile
  3701. if file_ == STDOUT or file_ == MODULEOUT:
  3702. dir_ = ''
  3703. path = name = file_
  3704. else:
  3705. path = os.path.abspath(file_)
  3706. dir_ = os.path.dirname(path)
  3707. name = os.path.basename(path)
  3708. self.fileinfo[macroname] = {'path':path,'dir':dir_,'name':name}
  3709. def expand(self, line=''):
  3710. "Expand all macros found on the line"
  3711. while self.rgx.search(line):
  3712. m = self.rgx.search(line)
  3713. name = self.name = m.group('name').lower()
  3714. fmt = m.group('fmt') or self.dft_fmt.get(name)
  3715. if name == 'date':
  3716. txt = time.strftime(fmt,self.currdate)
  3717. elif name == 'mtime':
  3718. if self.infile in (STDIN, MODULEIN):
  3719. fdate = self.currdate
  3720. else:
  3721. mtime = os.path.getmtime(self.infile)
  3722. fdate = time.localtime(mtime)
  3723. txt = time.strftime(fmt,fdate)
  3724. elif name == 'infile' or name == 'outfile':
  3725. self.set_file_info(name)
  3726. txt = self.walk_file_format(fmt)
  3727. else:
  3728. Error("Unknown macro name '%s'"%name)
  3729. line = self.rgx.sub(txt,line,1)
  3730. return line
  3731. ##############################################################################
  3732. def listTargets():
  3733. """list all available targets"""
  3734. targets = TARGETS
  3735. targets.sort()
  3736. for target in targets:
  3737. print "%s\t%s" % (target, TARGET_NAMES.get(target))
  3738. def dumpConfig(source_raw, parsed_config):
  3739. onoff = {1:_('ON'), 0:_('OFF')}
  3740. data = [
  3741. (_('RC file') , RC_RAW ),
  3742. (_('source document'), source_raw ),
  3743. (_('command line') , CMDLINE_RAW)
  3744. ]
  3745. # First show all RAW data found
  3746. for label, cfg in data:
  3747. print _('RAW config for %s')%label
  3748. for target,key,val in cfg:
  3749. target = '(%s)'%target
  3750. key = dotted_spaces("%-14s"%key)
  3751. val = val or _('ON')
  3752. print ' %-8s %s: %s'%(target,key,val)
  3753. print
  3754. # Then the parsed results of all of them
  3755. print _('Full PARSED config')
  3756. keys = parsed_config.keys() ; keys.sort() # sorted
  3757. for key in keys:
  3758. val = parsed_config[key]
  3759. # Filters are the last
  3760. if key == 'preproc' or key == 'postproc':
  3761. continue
  3762. # Flag beautifier
  3763. if key in FLAGS.keys() or key in ACTIONS.keys():
  3764. val = onoff.get(val) or val
  3765. # List beautifier
  3766. if type(val) == type([]):
  3767. if key == 'options': sep = ' '
  3768. else : sep = ', '
  3769. val = sep.join(val)
  3770. print "%25s: %s"%(dotted_spaces("%-14s"%key),val)
  3771. print
  3772. print _('Active filters')
  3773. for filter_ in ['preproc', 'postproc']:
  3774. for rule in parsed_config.get(filter_) or []:
  3775. print "%25s: %s -> %s" % (
  3776. dotted_spaces("%-14s"%filter_), rule[0], rule[1])
  3777. def get_file_body(file_):
  3778. "Returns all the document BODY lines"
  3779. return process_source_file(file_, noconf=1)[1][2]
  3780. def finish_him(outlist, config):
  3781. "Writing output to screen or file"
  3782. outfile = config['outfile']
  3783. outlist = unmaskEscapeChar(outlist)
  3784. outlist = expandLineBreaks(outlist)
  3785. # Apply PostProc filters
  3786. if config['postproc']:
  3787. filters = compile_filters(config['postproc'],
  3788. _('Invalid PostProc filter regex'))
  3789. postoutlist = []
  3790. errmsg = _('Invalid PostProc filter replacement')
  3791. for line in outlist:
  3792. for rgx,repl in filters:
  3793. try: line = rgx.sub(repl, line)
  3794. except: Error("%s: '%s'"%(errmsg, repl))
  3795. postoutlist.append(line)
  3796. outlist = postoutlist[:]
  3797. if outfile == MODULEOUT:
  3798. return outlist
  3799. elif outfile == STDOUT:
  3800. if GUI:
  3801. return outlist, config
  3802. else:
  3803. for line in outlist: print line
  3804. else:
  3805. Savefile(outfile, addLineBreaks(outlist))
  3806. if not GUI and not QUIET:
  3807. print _('%s wrote %s')%(my_name,outfile)
  3808. if config['split']:
  3809. if not QUIET: print "--- html..."
  3810. sgml2html = 'sgml2html -s %s -l %s %s' % (
  3811. config['split'], config['lang'] or lang, outfile)
  3812. if not QUIET: print "Running system command:", sgml2html
  3813. os.system(sgml2html)
  3814. def toc_inside_body(body, toc, config):
  3815. ret = []
  3816. if AUTOTOC: return body # nothing to expand
  3817. toc_mark = MaskMaster().tocmask
  3818. # Expand toc mark with TOC contents
  3819. for line in body:
  3820. if line.count(toc_mark): # toc mark found
  3821. if config['toc']:
  3822. ret.extend(toc) # include if --toc
  3823. else:
  3824. pass # or remove %%toc line
  3825. else:
  3826. ret.append(line) # common line
  3827. return ret
  3828. def toc_tagger(toc, config):
  3829. "Returns the tagged TOC, as a single tag or a tagged list"
  3830. ret = []
  3831. # Convert the TOC list (t2t-marked) to the target's list format
  3832. if config['toc-only'] or (config['toc'] and not TAGS['TOC']):
  3833. fakeconf = config.copy()
  3834. fakeconf['headers'] = 0
  3835. fakeconf['toc-only'] = 0
  3836. fakeconf['mask-email'] = 0
  3837. fakeconf['preproc'] = []
  3838. fakeconf['postproc'] = []
  3839. fakeconf['css-sugar'] = 0
  3840. fakeconf['art-no-title'] = 1 # needed for --toc and --slides together, avoids slide title before TOC
  3841. ret,foo = convert(toc, fakeconf)
  3842. set_global_config(config) # restore config
  3843. # Our TOC list is not needed, the target already knows how to do a TOC
  3844. elif config['toc'] and TAGS['TOC']:
  3845. ret = [TAGS['TOC']]
  3846. return ret
  3847. def toc_formatter(toc, config):
  3848. "Formats TOC for automatic placement between headers and body"
  3849. if config['toc-only']: return toc # no formatting needed
  3850. if not config['toc'] : return [] # TOC disabled
  3851. ret = toc
  3852. # Art: An automatic "Table of Contents" header is added to the TOC slide
  3853. if config['target'] == 'art' and config['slides']:
  3854. n = (config['height'] - 1) - (len(toc) + 6) % (config['height'] - 1)
  3855. toc = aa_slide(_("Table of Contents"), config['width']) + toc + ([''] * n)
  3856. toc.append(aa_line(AA['bar2'], config['width']))
  3857. return toc
  3858. # TOC open/close tags (if any)
  3859. if TAGS['tocOpen' ]: ret.insert(0, TAGS['tocOpen'])
  3860. if TAGS['tocClose']: ret.append(TAGS['tocClose'])
  3861. # Autotoc specific formatting
  3862. if AUTOTOC:
  3863. if rules['autotocwithbars']: # TOC between bars
  3864. para = TAGS['paragraphOpen']+TAGS['paragraphClose']
  3865. bar = regex['x'].sub('-' * DFT_TEXT_WIDTH, TAGS['bar1'])
  3866. tocbar = [para, bar, para]
  3867. if config['target'] == 'art' and config['headers']:
  3868. # exception: header already printed a bar
  3869. ret = [para] + ret + tocbar
  3870. else:
  3871. ret = tocbar + ret + tocbar
  3872. if rules['blankendautotoc']: # blank line after TOC
  3873. ret.append('')
  3874. if rules['autotocnewpagebefore']: # page break before TOC
  3875. ret.insert(0,TAGS['pageBreak'])
  3876. if rules['autotocnewpageafter']: # page break after TOC
  3877. ret.append(TAGS['pageBreak'])
  3878. return ret
  3879. def doHeader(headers, config):
  3880. if not config['headers']: return []
  3881. if not headers: headers = ['','','']
  3882. target = config['target']
  3883. if not HEADER_TEMPLATE.has_key(target):
  3884. Error("doHeader: Unknown target '%s'"%target)
  3885. if target in ('html','xhtml') and config.get('css-sugar'):
  3886. template = HEADER_TEMPLATE[target+'css'].split('\n')
  3887. else:
  3888. template = HEADER_TEMPLATE[target].split('\n')
  3889. head_data = {'STYLE':[], 'ENCODING':''}
  3890. for key in head_data.keys():
  3891. val = config.get(key.lower())
  3892. # Remove .sty extension from each style filename (freaking tex)
  3893. # XXX Can't handle --style foo.sty,bar.sty
  3894. if target == 'tex' and key == 'STYLE':
  3895. val = map(lambda x:re.sub('(?i)\.sty$','',x), val)
  3896. if key == 'ENCODING':
  3897. val = get_encoding_string(val, target)
  3898. head_data[key] = val
  3899. # Parse header contents
  3900. for i in 0,1,2:
  3901. # Expand macros
  3902. contents = MacroMaster(config=config).expand(headers[i])
  3903. # Escapes - on tex, just do it if any \tag{} present
  3904. if target != 'tex' or \
  3905. (target == 'tex' and re.search(r'\\\w+{', contents)):
  3906. contents = doEscape(target, contents)
  3907. if target == 'lout':
  3908. contents = doFinalEscape(target, contents)
  3909. head_data['HEADER%d'%(i+1)] = contents
  3910. # css-inside removes STYLE line
  3911. #XXX In tex, this also removes the modules call (%!style:amsfonts)
  3912. if target in ('html','xhtml') and config.get('css-inside') and \
  3913. config.get('style'):
  3914. head_data['STYLE'] = []
  3915. Debug("Header Data: %s"%head_data, 1)
  3916. # ASCII Art does not use a header template, aa_header() formats the header
  3917. if target == 'art':
  3918. n_h = len([v for v in head_data if v.startswith("HEADER") and head_data[v]])
  3919. if not n_h :
  3920. return []
  3921. if config['slides']:
  3922. x = config['height'] - 3 - (n_h * 3)
  3923. n = x / (n_h + 1)
  3924. end = x % (n_h + 1)
  3925. template = aa_header(head_data, config['width'], n, end)
  3926. else:
  3927. template = [''] + aa_header(head_data, config['width'], 2, 0)
  3928. # Header done, let's get out
  3929. return template
  3930. # Scan for empty dictionary keys
  3931. # If found, scan template lines for that key reference
  3932. # If found, remove the reference
  3933. # If there isn't any other key reference on the same line, remove it
  3934. #TODO loop by template line > key
  3935. for key in head_data.keys():
  3936. if head_data.get(key): continue
  3937. for line in template:
  3938. if line.count('%%(%s)s'%key):
  3939. sline = line.replace('%%(%s)s'%key, '')
  3940. if not re.search(r'%\([A-Z0-9]+\)s', sline):
  3941. template.remove(line)
  3942. # Style is a multiple tag.
  3943. # - If none or just one, use default template
  3944. # - If two or more, insert extra lines in a loop (and remove original)
  3945. styles = head_data['STYLE']
  3946. if len(styles) == 1:
  3947. head_data['STYLE'] = styles[0]
  3948. elif len(styles) > 1:
  3949. style_mark = '%(STYLE)s'
  3950. for i in xrange(len(template)):
  3951. if template[i].count(style_mark):
  3952. while styles:
  3953. template.insert(i+1, template[i].replace(style_mark, styles.pop()))
  3954. del template[i]
  3955. break
  3956. # Populate template with data (dict expansion)
  3957. template = '\n'.join(template) % head_data
  3958. # Adding CSS contents into template (for --css-inside)
  3959. # This code sux. Dirty++
  3960. if target in ('html','xhtml') and config.get('css-inside') and \
  3961. config.get('style'):
  3962. set_global_config(config) # usually on convert(), needed here
  3963. for i in xrange(len(config['style'])):
  3964. cssfile = config['style'][i]
  3965. if not os.path.isabs(cssfile):
  3966. infile = config.get('sourcefile')
  3967. cssfile = os.path.join(
  3968. os.path.dirname(infile), cssfile)
  3969. try:
  3970. contents = Readfile(cssfile, 1)
  3971. css = "\n%s\n%s\n%s\n%s\n" % (
  3972. doCommentLine("Included %s" % cssfile),
  3973. TAGS['cssOpen'],
  3974. '\n'.join(contents),
  3975. TAGS['cssClose'])
  3976. # Style now is content, needs escaping (tex)
  3977. #css = maskEscapeChar(css)
  3978. except:
  3979. errmsg = "CSS include failed for %s" % cssfile
  3980. css = "\n%s\n" % (doCommentLine(errmsg))
  3981. # Insert this CSS file contents on the template
  3982. template = re.sub('(?i)(</HEAD>)', css+r'\1', template)
  3983. # template = re.sub(r'(?i)(\\begin{document})',
  3984. # css+'\n'+r'\1', template) # tex
  3985. # The last blank line to keep everything separated
  3986. template = re.sub('(?i)(</HEAD>)', '\n'+r'\1', template)
  3987. return template.split('\n')
  3988. def doCommentLine(txt):
  3989. # The -- string ends a (h|sg|xht)ml comment :(
  3990. txt = maskEscapeChar(txt)
  3991. if TAGS['comment'].count('--') and txt.count('--'):
  3992. txt = re.sub('-(?=-)', r'-\\', txt)
  3993. if TAGS['comment']:
  3994. return regex['x'].sub(txt, TAGS['comment'])
  3995. return ''
  3996. def doFooter(config):
  3997. ret = []
  3998. # No footer. The --no-headers option hides header AND footer
  3999. if not config['headers']:
  4000. return []
  4001. # Only add blank line before footer if last block doesn't added by itself
  4002. if not rules.get('blanksaround'+BLOCK.last):
  4003. ret.append('')
  4004. # Add txt2tags info at footer, if target supports comments
  4005. if TAGS['comment']:
  4006. # Not using TARGET_NAMES because it's i18n'ed.
  4007. # It's best to always present this info in english.
  4008. target = config['target']
  4009. if config['target'] == 'tex':
  4010. target = 'LaTeX2e'
  4011. t2t_version = '%s code generated by %s %s (%s)' % (target, my_name, my_version, my_url)
  4012. cmdline = 'cmdline: %s %s' % (my_name, ' '.join(config['realcmdline']))
  4013. ret.append(doCommentLine(t2t_version))
  4014. ret.append(doCommentLine(cmdline))
  4015. # Maybe we have a specific tag to close the document?
  4016. if TAGS['EOD']:
  4017. ret.append(TAGS['EOD'])
  4018. return ret
  4019. def doEscape(target,txt):
  4020. "Target-specific special escapes. Apply *before* insert any tag."
  4021. tmpmask = 'vvvvThisEscapingSuxvvvv'
  4022. if target in ('html','sgml','xhtml','dbk'):
  4023. txt = re.sub('&','&amp;',txt)
  4024. txt = re.sub('<','&lt;',txt)
  4025. txt = re.sub('>','&gt;',txt)
  4026. if target == 'sgml':
  4027. txt = re.sub('\xff','&yuml;',txt) # "+y
  4028. elif target == 'pm6':
  4029. txt = re.sub('<','<\#60>',txt)
  4030. elif target == 'mgp':
  4031. txt = re.sub('^%',' %',txt) # add leading blank to avoid parse
  4032. elif target == 'man':
  4033. txt = re.sub("^([.'])", '\\&\\1',txt) # command ID
  4034. txt = txt.replace(ESCCHAR, ESCCHAR+'e') # \e
  4035. elif target == 'lout':
  4036. # TIP: / moved to FinalEscape to avoid //italic//
  4037. # TIP: these are also converted by lout: ... --- --
  4038. txt = txt.replace(ESCCHAR, tmpmask) # \
  4039. txt = txt.replace('"', '"%s""'%ESCCHAR) # "\""
  4040. txt = re.sub('([|&{}@#^~])', '"\\1"', txt) # "@"
  4041. txt = txt.replace(tmpmask, '"%s"'%(ESCCHAR*2)) # "\\"
  4042. elif target == 'tex':
  4043. # Mark literal \ to be changed to $\backslash$ later
  4044. txt = txt.replace(ESCCHAR, tmpmask)
  4045. txt = re.sub('([#$&%{}])', ESCCHAR+r'\1' , txt) # \%
  4046. txt = re.sub('([~^])' , ESCCHAR+r'\1{}', txt) # \~{}
  4047. txt = re.sub('([<|>])' , r'$\1$', txt) # $>$
  4048. txt = txt.replace(tmpmask, maskEscapeChar(r'$\backslash$'))
  4049. # TIP the _ is escaped at the end
  4050. return txt
  4051. # TODO man: where - really needs to be escaped?
  4052. def doFinalEscape(target, txt):
  4053. "Last escapes of each line"
  4054. if target == 'pm6' : txt = txt.replace(ESCCHAR+'<', r'<\#92><')
  4055. elif target == 'man' : txt = txt.replace('-', r'\-')
  4056. elif target == 'sgml': txt = txt.replace('[', '&lsqb;')
  4057. elif target == 'lout': txt = txt.replace('/', '"/"')
  4058. elif target == 'tex' :
  4059. txt = txt.replace('_', r'\_')
  4060. txt = txt.replace('vvvvTexUndervvvv', '_') # shame!
  4061. return txt
  4062. def EscapeCharHandler(action, data):
  4063. "Mask/Unmask the Escape Char on the given string"
  4064. if not data.strip(): return data
  4065. if action not in ('mask','unmask'):
  4066. Error("EscapeCharHandler: Invalid action '%s'"%action)
  4067. if action == 'mask': return data.replace('\\', ESCCHAR)
  4068. else: return data.replace(ESCCHAR, '\\')
  4069. def maskEscapeChar(data):
  4070. "Replace any Escape Char \ with a text mask (Input: str or list)"
  4071. if type(data) == type([]):
  4072. return map(lambda x: EscapeCharHandler('mask', x), data)
  4073. return EscapeCharHandler('mask',data)
  4074. def unmaskEscapeChar(data):
  4075. "Undo the Escape char \ masking (Input: str or list)"
  4076. if type(data) == type([]):
  4077. return map(lambda x: EscapeCharHandler('unmask', x), data)
  4078. return EscapeCharHandler('unmask',data)
  4079. def addLineBreaks(mylist):
  4080. "use LB to respect sys.platform"
  4081. ret = []
  4082. for line in mylist:
  4083. line = line.replace('\n', LB) # embedded \n's
  4084. ret.append(line+LB) # add final line break
  4085. return ret
  4086. # Convert ['foo\nbar'] to ['foo', 'bar']
  4087. def expandLineBreaks(mylist):
  4088. ret = []
  4089. for line in mylist:
  4090. ret.extend(line.split('\n'))
  4091. return ret
  4092. def compile_filters(filters, errmsg='Filter'):
  4093. if filters:
  4094. for i in xrange(len(filters)):
  4095. patt,repl = filters[i]
  4096. try: rgx = re.compile(patt)
  4097. except: Error("%s: '%s'"%(errmsg, patt))
  4098. filters[i] = (rgx,repl)
  4099. return filters
  4100. def enclose_me(tagname, txt):
  4101. return TAGS.get(tagname+'Open') + txt + TAGS.get(tagname+'Close')
  4102. def beautify_me(name, line):
  4103. "where name is: bold, italic, underline or strike"
  4104. # Exception: Doesn't parse an horizontal bar as strike
  4105. if name == 'strike' and regex['bar'].search(line): return line
  4106. name = 'font%s' % name.capitalize()
  4107. open_ = TAGS['%sOpen'%name]
  4108. close = TAGS['%sClose'%name]
  4109. txt = r'%s\1%s'%(open_, close)
  4110. line = regex[name].sub(txt,line)
  4111. return line
  4112. def get_tagged_link(label, url):
  4113. ret = ''
  4114. target = CONF['target']
  4115. image_re = regex['img']
  4116. # Set link type
  4117. if regex['email'].match(url):
  4118. linktype = 'email'
  4119. else:
  4120. linktype = 'url';
  4121. # Escape specials from TEXT parts
  4122. label = doEscape(target,label)
  4123. # Escape specials from link URL
  4124. if not rules['linkable'] or rules['escapeurl']:
  4125. url = doEscape(target, url)
  4126. # Adding protocol to guessed link
  4127. guessurl = ''
  4128. if linktype == 'url' and \
  4129. re.match('(?i)'+regex['_urlskel']['guess'], url):
  4130. if url[0] in 'Ww': guessurl = 'http://' +url
  4131. else : guessurl = 'ftp://' +url
  4132. # Not link aware targets -> protocol is useless
  4133. if not rules['linkable']: guessurl = ''
  4134. # Simple link (not guessed)
  4135. if not label and not guessurl:
  4136. if CONF['mask-email'] and linktype == 'email':
  4137. # Do the email mask feature (no TAGs, just text)
  4138. url = url.replace('@', ' (a) ')
  4139. url = url.replace('.', ' ')
  4140. url = "<%s>" % url
  4141. if rules['linkable']: url = doEscape(target, url)
  4142. ret = url
  4143. else:
  4144. # Just add link data to tag
  4145. tag = TAGS[linktype]
  4146. ret = regex['x'].sub(url,tag)
  4147. # Named link or guessed simple link
  4148. else:
  4149. # Adjusts for guessed link
  4150. if not label: label = url # no protocol
  4151. if guessurl : url = guessurl # with protocol
  4152. # Image inside link!
  4153. if image_re.match(label):
  4154. if rules['imglinkable']: # get image tag
  4155. label = parse_images(label)
  4156. else: # img@link !supported
  4157. label = "(%s)"%image_re.match(label).group(1)
  4158. # Putting data on the right appearance order
  4159. if rules['labelbeforelink'] or not rules['linkable']:
  4160. urlorder = [label, url] # label before link
  4161. else:
  4162. urlorder = [url, label] # link before label
  4163. # Add link data to tag (replace \a's)
  4164. ret = TAGS["%sMark"%linktype]
  4165. for data in urlorder:
  4166. ret = regex['x'].sub(data,ret,1)
  4167. return ret
  4168. def parse_deflist_term(line):
  4169. "Extract and parse definition list term contents"
  4170. img_re = regex['img']
  4171. term = regex['deflist'].search(line).group(3)
  4172. # Mask image inside term as (image.jpg), where not supported
  4173. if not rules['imgasdefterm'] and img_re.search(term):
  4174. while img_re.search(term):
  4175. imgfile = img_re.search(term).group(1)
  4176. term = img_re.sub('(%s)'%imgfile, term, 1)
  4177. #TODO tex: escape ] on term. \], \rbrack{} and \verb!]! don't work :(
  4178. return term
  4179. def get_image_align(line):
  4180. "Return the image (first found) align for the given line"
  4181. # First clear marks that can mess align detection
  4182. line = re.sub(SEPARATOR+'$', '', line) # remove deflist sep
  4183. line = re.sub('^'+SEPARATOR, '', line) # remove list sep
  4184. line = re.sub('^[\t]+' , '', line) # remove quote mark
  4185. # Get image position on the line
  4186. m = regex['img'].search(line)
  4187. ini = m.start() ; head = 0
  4188. end = m.end() ; tail = len(line)
  4189. # The align detection algorithm
  4190. if ini == head and end != tail: align = 'left' # ^img + text$
  4191. elif ini != head and end == tail: align = 'right' # ^text + img$
  4192. else : align = 'center' # default align
  4193. # Some special cases
  4194. if BLOCK.isblock('table'): align = 'center' # ignore when table
  4195. # if TARGET == 'mgp' and align == 'center': align = 'center'
  4196. return align
  4197. # Reference: http://www.iana.org/assignments/character-sets
  4198. # http://www.drclue.net/F1.cgi/HTML/META/META.html
  4199. def get_encoding_string(enc, target):
  4200. if not enc: return ''
  4201. # Target specific translation table
  4202. translate = {
  4203. 'tex': {
  4204. # missing: ansinew , applemac , cp437 , cp437de , cp865
  4205. 'utf-8' : 'utf8',
  4206. 'us-ascii' : 'ascii',
  4207. 'windows-1250': 'cp1250',
  4208. 'windows-1252': 'cp1252',
  4209. 'ibm850' : 'cp850',
  4210. 'ibm852' : 'cp852',
  4211. 'iso-8859-1' : 'latin1',
  4212. 'iso-8859-2' : 'latin2',
  4213. 'iso-8859-3' : 'latin3',
  4214. 'iso-8859-4' : 'latin4',
  4215. 'iso-8859-5' : 'latin5',
  4216. 'iso-8859-9' : 'latin9',
  4217. 'koi8-r' : 'koi8-r'
  4218. }
  4219. }
  4220. # Normalization
  4221. enc = re.sub('(?i)(us[-_]?)?ascii|us|ibm367','us-ascii' , enc)
  4222. enc = re.sub('(?i)(ibm|cp)?85([02])' ,'ibm85\\2' , enc)
  4223. enc = re.sub('(?i)(iso[_-]?)?8859[_-]?' ,'iso-8859-' , enc)
  4224. enc = re.sub('iso-8859-($|[^1-9]).*' ,'iso-8859-1', enc)
  4225. # Apply translation table
  4226. try: enc = translate[target][enc.lower()]
  4227. except: pass
  4228. return enc
  4229. ##############################################################################
  4230. ##MerryChristmas,IdontwanttofighttonightwithyouImissyourbodyandIneedyourlove##
  4231. ##############################################################################
  4232. def process_source_file(file_='', noconf=0, contents=[]):
  4233. """
  4234. Find and Join all the configuration available for a source file.
  4235. No sanity checking is done on this step.
  4236. It also extracts the source document parts into separate holders.
  4237. The config scan order is:
  4238. 1. The user configuration file (i.e. $HOME/.txt2tagsrc)
  4239. 2. The source document's CONF area
  4240. 3. The command line options
  4241. The return data is a tuple of two items:
  4242. 1. The parsed config dictionary
  4243. 2. The document's parts, as a (head, conf, body) tuple
  4244. All the conversion process will be based on the data and
  4245. configuration returned by this function.
  4246. The source files is read on this step only.
  4247. """
  4248. if contents:
  4249. source = SourceDocument(contents=contents)
  4250. else:
  4251. source = SourceDocument(file_)
  4252. head, conf, body = source.split()
  4253. Message(_("Source document contents stored"),2)
  4254. if not noconf:
  4255. # Read document config
  4256. source_raw = source.get_raw_config()
  4257. # Join all the config directives found, then parse it
  4258. full_raw = RC_RAW + source_raw + CMDLINE_RAW
  4259. Message(_("Parsing and saving all config found (%03d items)") % (len(full_raw)), 1)
  4260. full_parsed = ConfigMaster(full_raw).parse()
  4261. # Add manually the filename to the conf dic
  4262. if contents:
  4263. full_parsed['sourcefile'] = MODULEIN
  4264. full_parsed['infile'] = MODULEIN
  4265. full_parsed['outfile'] = MODULEOUT
  4266. else:
  4267. full_parsed['sourcefile'] = file_
  4268. # Maybe should we dump the config found?
  4269. if full_parsed.get('dump-config'):
  4270. dumpConfig(source_raw, full_parsed)
  4271. Quit()
  4272. # The user just want to know a single config value (hidden feature)
  4273. #TODO pick a better name than --show-config-value
  4274. elif full_parsed.get('show-config-value'):
  4275. config_value = full_parsed.get(full_parsed['show-config-value'])
  4276. if config_value:
  4277. if type(config_value) == type([]):
  4278. print '\n'.join(config_value)
  4279. else:
  4280. print config_value
  4281. Quit()
  4282. # Okay, all done
  4283. Debug("FULL config for this file: %s"%full_parsed, 1)
  4284. else:
  4285. full_parsed = {}
  4286. return full_parsed, (head,conf,body)
  4287. def get_infiles_config(infiles):
  4288. """
  4289. Find and Join into a single list, all configuration available
  4290. for each input file. This function is supposed to be the very
  4291. first one to be called, before any processing.
  4292. """
  4293. return map(process_source_file, infiles)
  4294. def convert_this_files(configs):
  4295. global CONF
  4296. for myconf,doc in configs: # multifile support
  4297. target_head = []
  4298. target_toc = []
  4299. target_body = []
  4300. target_foot = []
  4301. source_head, source_conf, source_body = doc
  4302. myconf = ConfigMaster().sanity(myconf)
  4303. # Compose the target file Headers
  4304. #TODO escape line before?
  4305. #TODO see exceptions by tex and mgp
  4306. Message(_("Composing target Headers"),1)
  4307. target_head = doHeader(source_head, myconf)
  4308. # Parse the full marked body into tagged target
  4309. first_body_line = (len(source_head) or 1)+ len(source_conf) + 1
  4310. Message(_("Composing target Body"),1)
  4311. target_body, marked_toc = convert(source_body, myconf, firstlinenr=first_body_line)
  4312. # If dump-source, we're done
  4313. if myconf['dump-source']:
  4314. for line in source_head+source_conf+target_body:
  4315. print line
  4316. return
  4317. # Close the last slide
  4318. if myconf['slides'] and not myconf['toc-only'] and myconf['target'] == 'art':
  4319. n = (myconf['height'] - 1) - (AA_COUNT % (myconf['height'] - 1) + 1)
  4320. target_body = target_body + ([''] * n) + [aa_line(AA['bar2'], myconf['width'])]
  4321. # Compose the target file Footer
  4322. Message(_("Composing target Footer"),1)
  4323. target_foot = doFooter(myconf)
  4324. # Make TOC (if needed)
  4325. Message(_("Composing target TOC"),1)
  4326. tagged_toc = toc_tagger(marked_toc, myconf)
  4327. target_toc = toc_formatter(tagged_toc, myconf)
  4328. target_body = toc_inside_body(target_body, target_toc, myconf)
  4329. if not AUTOTOC and not myconf['toc-only']: target_toc = []
  4330. # Finally, we have our document
  4331. outlist = target_head + target_toc + target_body + target_foot
  4332. # If on GUI, abort before finish_him
  4333. # If module, return finish_him as list
  4334. # Else, write results to file or STDOUT
  4335. if GUI:
  4336. return outlist, myconf
  4337. elif myconf.get('outfile') == MODULEOUT:
  4338. return finish_him(outlist, myconf), myconf
  4339. else:
  4340. Message(_("Saving results to the output file"),1)
  4341. finish_him(outlist, myconf)
  4342. def parse_images(line):
  4343. "Tag all images found"
  4344. while regex['img'].search(line) and TAGS['img'] != '[\a]':
  4345. txt = regex['img'].search(line).group(1)
  4346. tag = TAGS['img']
  4347. # If target supports image alignment, here we go
  4348. if rules['imgalignable']:
  4349. align = get_image_align(line) # right
  4350. align_name = align.capitalize() # Right
  4351. # The align is a full tag, or part of the image tag (~A~)
  4352. if TAGS['imgAlign'+align_name]:
  4353. tag = TAGS['imgAlign'+align_name]
  4354. else:
  4355. align_tag = TAGS['_imgAlign'+align_name]
  4356. tag = regex['_imgAlign'].sub(align_tag, tag, 1)
  4357. # Dirty fix to allow centered solo images
  4358. if align == 'center' and TARGET in ('html','xhtml'):
  4359. rest = regex['img'].sub('',line,1)
  4360. if re.match('^\s+$', rest):
  4361. tag = "<center>%s</center>" %tag
  4362. if TARGET == 'tex':
  4363. tag = re.sub(r'\\b',r'\\\\b',tag)
  4364. txt = txt.replace('_', 'vvvvTexUndervvvv')
  4365. # Ugly hack to avoid infinite loop when target's image tag contains []
  4366. tag = tag.replace('[', 'vvvvEscapeSquareBracketvvvv')
  4367. line = regex['img'].sub(tag,line,1)
  4368. line = regex['x'].sub(txt,line,1)
  4369. return line.replace('vvvvEscapeSquareBracketvvvv','[')
  4370. def add_inline_tags(line):
  4371. # Beautifiers
  4372. for beauti in ('bold', 'italic', 'underline', 'strike'):
  4373. if regex['font%s'%beauti.capitalize()].search(line):
  4374. line = beautify_me(beauti, line)
  4375. line = parse_images(line)
  4376. return line
  4377. def get_include_contents(file_, path=''):
  4378. "Parses %!include: value and extract file contents"
  4379. ids = {'`':'verb', '"':'raw', "'":'tagged' }
  4380. id_ = 't2t'
  4381. # Set include type and remove identifier marks
  4382. mark = file_[0]
  4383. if mark in ids.keys():
  4384. if file_[:2] == file_[-2:] == mark*2:
  4385. id_ = ids[mark] # set type
  4386. file_ = file_[2:-2] # remove marks
  4387. # Handle remote dir execution
  4388. filepath = os.path.join(path, file_)
  4389. # Read included file contents
  4390. lines = Readfile(filepath, remove_linebreaks=1)
  4391. # Default txt2tags marked text, just BODY matters
  4392. if id_ == 't2t':
  4393. lines = get_file_body(filepath)
  4394. #TODO fix images relative path if file has a path, ie.: chapter1/index.t2t (wait until tree parsing)
  4395. #TODO for the images path fix, also respect outfile path, if different from infile (wait until tree parsing)
  4396. lines.insert(0, '%%INCLUDED(%s) starts here: %s'%(id_,file_))
  4397. # This appears when included hit EOF with verbatim area open
  4398. #lines.append('%%INCLUDED(%s) ends here: %s'%(id_,file_))
  4399. return id_, lines
  4400. def set_global_config(config):
  4401. global CONF, TAGS, regex, rules, TARGET
  4402. CONF = config
  4403. rules = getRules(CONF)
  4404. TAGS = getTags(CONF)
  4405. regex = getRegexes()
  4406. TARGET = config['target'] # save for buggy functions that need global
  4407. def convert(bodylines, config, firstlinenr=1):
  4408. global BLOCK, TITLE
  4409. set_global_config(config)
  4410. target = config['target']
  4411. BLOCK = BlockMaster()
  4412. MASK = MaskMaster()
  4413. TITLE = TitleMaster()
  4414. ret = []
  4415. dump_source = []
  4416. f_lastwasblank = 0
  4417. # Compiling all PreProc regexes
  4418. pre_filter = compile_filters(
  4419. CONF['preproc'], _('Invalid PreProc filter regex'))
  4420. # Let's mark it up!
  4421. linenr = firstlinenr-1
  4422. lineref = 0
  4423. while lineref < len(bodylines):
  4424. # Defaults
  4425. MASK.reset()
  4426. results_box = ''
  4427. untouchedline = bodylines[lineref]
  4428. dump_source.append(untouchedline)
  4429. line = re.sub('[\n\r]+$','',untouchedline) # del line break
  4430. # Apply PreProc filters
  4431. if pre_filter:
  4432. errmsg = _('Invalid PreProc filter replacement')
  4433. for rgx,repl in pre_filter:
  4434. try: line = rgx.sub(repl, line)
  4435. except: Error("%s: '%s'"%(errmsg, repl))
  4436. line = maskEscapeChar(line) # protect \ char
  4437. linenr += 1
  4438. lineref += 1
  4439. Debug(repr(line), 2, linenr) # heavy debug: show each line
  4440. #------------------[ Comment Block ]------------------------
  4441. # We're already on a comment block
  4442. if BLOCK.block() == 'comment':
  4443. # Closing comment
  4444. if regex['blockCommentClose'].search(line):
  4445. ret.extend(BLOCK.blockout() or [])
  4446. continue
  4447. # Normal comment-inside line. Ignore it.
  4448. continue
  4449. # Detecting comment block init
  4450. if regex['blockCommentOpen'].search(line) \
  4451. and BLOCK.block() not in BLOCK.exclusive:
  4452. ret.extend(BLOCK.blockin('comment'))
  4453. continue
  4454. #-------------------------[ Tagged Text ]----------------------
  4455. # We're already on a tagged block
  4456. if BLOCK.block() == 'tagged':
  4457. # Closing tagged
  4458. if regex['blockTaggedClose'].search(line):
  4459. ret.extend(BLOCK.blockout())
  4460. continue
  4461. # Normal tagged-inside line
  4462. BLOCK.holdadd(line)
  4463. continue
  4464. # Detecting tagged block init
  4465. if regex['blockTaggedOpen'].search(line) \
  4466. and BLOCK.block() not in BLOCK.exclusive:
  4467. ret.extend(BLOCK.blockin('tagged'))
  4468. continue
  4469. # One line tagged text
  4470. if regex['1lineTagged'].search(line) \
  4471. and BLOCK.block() not in BLOCK.exclusive:
  4472. ret.extend(BLOCK.blockin('tagged'))
  4473. line = regex['1lineTagged'].sub('',line)
  4474. BLOCK.holdadd(line)
  4475. ret.extend(BLOCK.blockout())
  4476. continue
  4477. #-------------------------[ Raw Text ]----------------------
  4478. # We're already on a raw block
  4479. if BLOCK.block() == 'raw':
  4480. # Closing raw
  4481. if regex['blockRawClose'].search(line):
  4482. ret.extend(BLOCK.blockout())
  4483. continue
  4484. # Normal raw-inside line
  4485. BLOCK.holdadd(line)
  4486. continue
  4487. # Detecting raw block init
  4488. if regex['blockRawOpen'].search(line) \
  4489. and BLOCK.block() not in BLOCK.exclusive:
  4490. ret.extend(BLOCK.blockin('raw'))
  4491. continue
  4492. # One line raw text
  4493. if regex['1lineRaw'].search(line) \
  4494. and BLOCK.block() not in BLOCK.exclusive:
  4495. ret.extend(BLOCK.blockin('raw'))
  4496. line = regex['1lineRaw'].sub('',line)
  4497. BLOCK.holdadd(line)
  4498. ret.extend(BLOCK.blockout())
  4499. continue
  4500. #------------------------[ Verbatim ]----------------------
  4501. #TIP We'll never support beautifiers inside verbatim
  4502. # Closing table mapped to verb
  4503. if BLOCK.block() == 'verb' \
  4504. and BLOCK.prop('mapped') == 'table' \
  4505. and not regex['table'].search(line):
  4506. ret.extend(BLOCK.blockout())
  4507. # We're already on a verb block
  4508. if BLOCK.block() == 'verb':
  4509. # Closing verb
  4510. if regex['blockVerbClose'].search(line):
  4511. ret.extend(BLOCK.blockout())
  4512. continue
  4513. # Normal verb-inside line
  4514. BLOCK.holdadd(line)
  4515. continue
  4516. # Detecting verb block init
  4517. if regex['blockVerbOpen'].search(line) \
  4518. and BLOCK.block() not in BLOCK.exclusive:
  4519. ret.extend(BLOCK.blockin('verb'))
  4520. f_lastwasblank = 0
  4521. continue
  4522. # One line verb-formatted text
  4523. if regex['1lineVerb'].search(line) \
  4524. and BLOCK.block() not in BLOCK.exclusive:
  4525. ret.extend(BLOCK.blockin('verb'))
  4526. line = regex['1lineVerb'].sub('',line)
  4527. BLOCK.holdadd(line)
  4528. ret.extend(BLOCK.blockout())
  4529. f_lastwasblank = 0
  4530. continue
  4531. # Tables are mapped to verb when target is not table-aware
  4532. if not rules['tableable'] and regex['table'].search(line):
  4533. if not BLOCK.isblock('verb'):
  4534. ret.extend(BLOCK.blockin('verb'))
  4535. BLOCK.propset('mapped', 'table')
  4536. BLOCK.holdadd(line)
  4537. continue
  4538. #---------------------[ blank lines ]-----------------------
  4539. if regex['blankline'].search(line):
  4540. # Close open paragraph
  4541. if BLOCK.isblock('para'):
  4542. ret.extend(BLOCK.blockout())
  4543. f_lastwasblank = 1
  4544. continue
  4545. # Close all open tables
  4546. if BLOCK.isblock('table'):
  4547. ret.extend(BLOCK.blockout())
  4548. f_lastwasblank = 1
  4549. continue
  4550. # Close all open quotes
  4551. while BLOCK.isblock('quote'):
  4552. ret.extend(BLOCK.blockout())
  4553. # Closing all open lists
  4554. if f_lastwasblank: # 2nd consecutive blank
  4555. if BLOCK.block().endswith('list'):
  4556. BLOCK.holdaddsub('') # helps parser
  4557. while BLOCK.depth: # closes list (if any)
  4558. ret.extend(BLOCK.blockout())
  4559. continue # ignore consecutive blanks
  4560. # Paragraph (if any) is wanted inside lists also
  4561. if BLOCK.block().endswith('list'):
  4562. BLOCK.holdaddsub('')
  4563. f_lastwasblank = 1
  4564. continue
  4565. #---------------------[ special ]---------------------------
  4566. if regex['special'].search(line):
  4567. targ, key, val = ConfigLines().parse_line(line, None, target)
  4568. if key:
  4569. Debug("Found config '%s', value '%s'" % (key, val), 1, linenr)
  4570. else:
  4571. Debug('Bogus Special Line', 1, linenr)
  4572. # %!include command
  4573. if key == 'include':
  4574. incpath = os.path.dirname(CONF['sourcefile'])
  4575. incfile = val
  4576. err = _('A file cannot include itself (loop!)')
  4577. if CONF['sourcefile'] == incfile:
  4578. Error("%s: %s"%(err,incfile))
  4579. inctype, inclines = get_include_contents(incfile, incpath)
  4580. # Verb, raw and tagged are easy
  4581. if inctype != 't2t':
  4582. ret.extend(BLOCK.blockin(inctype))
  4583. BLOCK.holdextend(inclines)
  4584. ret.extend(BLOCK.blockout())
  4585. else:
  4586. # Insert include lines into body
  4587. #TODO include maxdepth limit
  4588. bodylines = bodylines[:lineref] + inclines + bodylines[lineref:]
  4589. #TODO fix path if include@include
  4590. # Remove %!include call
  4591. if CONF['dump-source']:
  4592. dump_source.pop()
  4593. # This line is done, go to next
  4594. continue
  4595. # %!csv command
  4596. elif key == 'csv':
  4597. if not csv:
  4598. Error("Python module 'csv' not found, but needed for %!csv")
  4599. table = []
  4600. filename = val
  4601. reader = csv.reader(Readfile(filename))
  4602. # Convert each CSV line to a txt2tags' table line
  4603. # foo,bar,baz -> | foo | bar | baz |
  4604. try:
  4605. for row in reader:
  4606. table.append('| %s |' % ' | '.join(row))
  4607. except csv.Error, e:
  4608. Error('CSV: file %s: %s' % (filename, e))
  4609. # Parse and convert the new table
  4610. # Note: cell contents is raw, no t2t marks are parsed
  4611. if rules['tableable']:
  4612. ret.extend(BLOCK.blockin('table'))
  4613. if table:
  4614. BLOCK.tableparser.__init__(table[0])
  4615. for row in table:
  4616. tablerow = TableMaster().parse_row(row)
  4617. BLOCK.tableparser.add_row(tablerow)
  4618. # Very ugly, but necessary for escapes
  4619. line = SEPARATOR.join(tablerow['cells'])
  4620. BLOCK.holdadd(doEscape(target, line))
  4621. ret.extend(BLOCK.blockout())
  4622. # Tables are mapped to verb when target is not table-aware
  4623. else:
  4624. if target == 'art' and table:
  4625. table = aa_table(table)
  4626. ret.extend(BLOCK.blockin('verb'))
  4627. BLOCK.propset('mapped', 'table')
  4628. for row in table:
  4629. BLOCK.holdadd(row)
  4630. ret.extend(BLOCK.blockout())
  4631. # This line is done, go to next
  4632. continue
  4633. #---------------------[ dump-source ]-----------------------
  4634. # We don't need to go any further
  4635. if CONF['dump-source']:
  4636. continue
  4637. #---------------------[ Comments ]--------------------------
  4638. # Just skip them (if not macro)
  4639. if regex['comment'].search(line) and not \
  4640. regex['macros'].match(line) and not \
  4641. regex['toc'].match(line):
  4642. continue
  4643. #---------------------[ Triggers ]--------------------------
  4644. # Valid line, reset blank status
  4645. f_lastwasblank = 0
  4646. # Any NOT quote line closes all open quotes
  4647. if BLOCK.isblock('quote') and not regex['quote'].search(line):
  4648. while BLOCK.isblock('quote'):
  4649. ret.extend(BLOCK.blockout())
  4650. # Any NOT table line closes an open table
  4651. if BLOCK.isblock('table') and not regex['table'].search(line):
  4652. ret.extend(BLOCK.blockout())
  4653. #---------------------[ Horizontal Bar ]--------------------
  4654. if regex['bar'].search(line):
  4655. # Bars inside quotes are handled on the Quote processing
  4656. # Otherwise we parse the bars right here
  4657. #
  4658. if not (BLOCK.isblock('quote') or regex['quote'].search(line)) \
  4659. or (BLOCK.isblock('quote') and not rules['barinsidequote']):
  4660. # Close all the opened blocks
  4661. ret.extend(BLOCK.blockin('bar'))
  4662. # Extract the bar chars (- or =)
  4663. m = regex['bar'].search(line)
  4664. bar_chars = m.group(2)
  4665. # Process and dump the tagged bar
  4666. BLOCK.holdadd(bar_chars)
  4667. ret.extend(BLOCK.blockout())
  4668. Debug("BAR: %s"%line, 6)
  4669. # We're done, nothing more to process
  4670. continue
  4671. #---------------------[ Title ]-----------------------------
  4672. if (regex['title'].search(line) or regex['numtitle'].search(line)) \
  4673. and not BLOCK.block().endswith('list'):
  4674. if regex['title'].search(line):
  4675. name = 'title'
  4676. else:
  4677. name = 'numtitle'
  4678. # Close all the opened blocks
  4679. ret.extend(BLOCK.blockin(name))
  4680. # Process title
  4681. TITLE.add(line)
  4682. ret.extend(BLOCK.blockout())
  4683. # We're done, nothing more to process
  4684. continue
  4685. #---------------------[ %%toc ]-----------------------
  4686. # %%toc line closes paragraph
  4687. if BLOCK.block() == 'para' and regex['toc'].search(line):
  4688. ret.extend(BLOCK.blockout())
  4689. #---------------------[ apply masks ]-----------------------
  4690. line = MASK.mask(line)
  4691. #XXX from here, only block-inside lines will pass
  4692. #---------------------[ Quote ]-----------------------------
  4693. if regex['quote'].search(line):
  4694. # Store number of leading TABS
  4695. quotedepth = len(regex['quote'].search(line).group(0))
  4696. # SGML doesn't support nested quotes
  4697. if rules['quotenotnested']: quotedepth = 1
  4698. # Don't cross depth limit
  4699. maxdepth = rules['quotemaxdepth']
  4700. if maxdepth and quotedepth > maxdepth:
  4701. quotedepth = maxdepth
  4702. # New quote
  4703. if not BLOCK.isblock('quote'):
  4704. ret.extend(BLOCK.blockin('quote'))
  4705. # New subquotes
  4706. while BLOCK.depth < quotedepth:
  4707. BLOCK.blockin('quote')
  4708. # Closing quotes
  4709. while quotedepth < BLOCK.depth:
  4710. ret.extend(BLOCK.blockout())
  4711. # Bar inside quote
  4712. if regex['bar'].search(line) and rules['barinsidequote']:
  4713. tempBlock = BlockMaster()
  4714. tagged_bar = []
  4715. tagged_bar.extend(tempBlock.blockin('bar'))
  4716. tempBlock.holdadd(line)
  4717. tagged_bar.extend(tempBlock.blockout())
  4718. BLOCK.holdextend(tagged_bar)
  4719. continue
  4720. #---------------------[ Lists ]-----------------------------
  4721. # An empty item also closes the current list
  4722. if BLOCK.block().endswith('list'):
  4723. m = regex['listclose'].match(line)
  4724. if m:
  4725. listindent = m.group(1)
  4726. listtype = m.group(2)
  4727. currlisttype = BLOCK.prop('type')
  4728. currlistindent = BLOCK.prop('indent')
  4729. if listindent == currlistindent and \
  4730. listtype == currlisttype:
  4731. ret.extend(BLOCK.blockout())
  4732. continue
  4733. if regex['list'].search(line) or \
  4734. regex['numlist'].search(line) or \
  4735. regex['deflist'].search(line):
  4736. listindent = BLOCK.prop('indent')
  4737. listids = ''.join(LISTNAMES.keys())
  4738. m = re.match('^( *)([%s]) '%listids, line)
  4739. listitemindent = m.group(1)
  4740. listtype = m.group(2)
  4741. listname = LISTNAMES[listtype]
  4742. results_box = BLOCK.holdadd
  4743. # Del list ID (and separate term from definition)
  4744. if listname == 'deflist':
  4745. term = parse_deflist_term(line)
  4746. line = regex['deflist'].sub(
  4747. SEPARATOR+term+SEPARATOR,line)
  4748. else:
  4749. line = regex[listname].sub(SEPARATOR,line)
  4750. # Don't cross depth limit
  4751. maxdepth = rules['listmaxdepth']
  4752. if maxdepth and BLOCK.depth == maxdepth:
  4753. if len(listitemindent) > len(listindent):
  4754. listitemindent = listindent
  4755. # List bumping (same indent, diff mark)
  4756. # Close the currently open list to clear the mess
  4757. if BLOCK.block().endswith('list') \
  4758. and listname != BLOCK.block() \
  4759. and len(listitemindent) == len(listindent):
  4760. ret.extend(BLOCK.blockout())
  4761. listindent = BLOCK.prop('indent')
  4762. # Open mother list or sublist
  4763. if not BLOCK.block().endswith('list') or \
  4764. len(listitemindent) > len(listindent):
  4765. ret.extend(BLOCK.blockin(listname))
  4766. BLOCK.propset('indent',listitemindent)
  4767. BLOCK.propset('type',listtype)
  4768. # Closing sublists
  4769. while len(listitemindent) < len(BLOCK.prop('indent')):
  4770. ret.extend(BLOCK.blockout())
  4771. # O-oh, sublist before list ("\n\n - foo\n- foo")
  4772. # Fix: close sublist (as mother), open another list
  4773. if not BLOCK.block().endswith('list'):
  4774. ret.extend(BLOCK.blockin(listname))
  4775. BLOCK.propset('indent',listitemindent)
  4776. BLOCK.propset('type',listtype)
  4777. #---------------------[ Table ]-----------------------------
  4778. #TODO escape undesired format inside table
  4779. #TODO add pm6 target
  4780. if regex['table'].search(line):
  4781. if not BLOCK.isblock('table'): # first table line!
  4782. ret.extend(BLOCK.blockin('table'))
  4783. BLOCK.tableparser.__init__(line)
  4784. tablerow = TableMaster().parse_row(line)
  4785. BLOCK.tableparser.add_row(tablerow) # save config
  4786. # Maintain line to unmask and inlines
  4787. # XXX Bug: | **bo | ld** | turns **bo\x01ld** and gets converted :(
  4788. # TODO isolate unmask+inlines parsing to use here
  4789. line = SEPARATOR.join(tablerow['cells'])
  4790. #---------------------[ Paragraph ]-------------------------
  4791. if not BLOCK.block() and \
  4792. not line.count(MASK.tocmask): # new para!
  4793. ret.extend(BLOCK.blockin('para'))
  4794. ############################################################
  4795. ############################################################
  4796. ############################################################
  4797. #---------------------[ Final Parses ]----------------------
  4798. # The target-specific special char escapes for body lines
  4799. line = doEscape(target,line)
  4800. line = add_inline_tags(line)
  4801. line = MASK.undo(line)
  4802. #---------------------[ Hold or Return? ]-------------------
  4803. ### Now we must choose where to put the parsed line
  4804. #
  4805. if not results_box:
  4806. # List item extra lines
  4807. if BLOCK.block().endswith('list'):
  4808. results_box = BLOCK.holdaddsub
  4809. # Other blocks
  4810. elif BLOCK.block():
  4811. results_box = BLOCK.holdadd
  4812. # No blocks
  4813. else:
  4814. line = doFinalEscape(target, line)
  4815. results_box = ret.append
  4816. results_box(line)
  4817. # EOF: close any open para/verb/lists/table/quotes
  4818. Debug('EOF',7)
  4819. while BLOCK.block():
  4820. ret.extend(BLOCK.blockout())
  4821. # Maybe close some opened title area?
  4822. if rules['titleblocks']:
  4823. ret.extend(TITLE.close_all())
  4824. # Maybe a major tag to enclose body? (like DIV for CSS)
  4825. if TAGS['bodyOpen' ]: ret.insert(0, TAGS['bodyOpen'])
  4826. if TAGS['bodyClose']: ret.append(TAGS['bodyClose'])
  4827. if CONF['toc-only']: ret = []
  4828. marked_toc = TITLE.dump_marked_toc(CONF['toc-level'])
  4829. # If dump-source, all parsing is ignored
  4830. if CONF['dump-source']: ret = dump_source[:]
  4831. return ret, marked_toc
  4832. ##############################################################################
  4833. ################################### GUI ######################################
  4834. ##############################################################################
  4835. #
  4836. # Tk help: http://python.org/topics/tkinter/
  4837. # Tuto: http://ibiblio.org/obp/py4fun/gui/tkPhone.html
  4838. # /usr/lib/python*/lib-tk/Tkinter.py
  4839. #
  4840. # grid table : row=0, column=0, columnspan=2, rowspan=2
  4841. # grid align : sticky='n,s,e,w' (North, South, East, West)
  4842. # pack place : side='top,bottom,right,left'
  4843. # pack fill : fill='x,y,both,none', expand=1
  4844. # pack align : anchor='n,s,e,w' (North, South, East, West)
  4845. # padding : padx=10, pady=10, ipadx=10, ipady=10 (internal)
  4846. # checkbox : offvalue is return if the _user_ deselected the box
  4847. # label align: justify=left,right,center
  4848. def load_GUI_resources():
  4849. "Load all extra modules and methods used by GUI"
  4850. global askopenfilename, showinfo, showwarning, showerror, Tkinter
  4851. from tkFileDialog import askopenfilename
  4852. from tkMessageBox import showinfo,showwarning,showerror
  4853. import Tkinter
  4854. class Gui:
  4855. "Graphical Tk Interface"
  4856. def __init__(self, conf={}):
  4857. self.root = Tkinter.Tk() # mother window, come to butthead
  4858. self.root.title(my_name) # window title bar text
  4859. self.window = self.root # variable "focus" for inclusion
  4860. self.row = 0 # row count for grid()
  4861. self.action_length = 150 # left column length (pixel)
  4862. self.frame_margin = 10 # frame margin size (pixel)
  4863. self.frame_border = 6 # frame border size (pixel)
  4864. # The default Gui colors, can be changed by %!guicolors
  4865. self.dft_gui_colors = ['#6c6','white','#cf9','#030']
  4866. self.gui_colors = []
  4867. self.bg1 = self.fg1 = self.bg2 = self.fg2 = ''
  4868. # On Tk, vars need to be set/get using setvar()/get()
  4869. self.infile = self.setvar('')
  4870. self.target = self.setvar('')
  4871. self.target_name = self.setvar('')
  4872. # The checks appearance order
  4873. self.checks = [
  4874. 'headers', 'enum-title', 'toc', 'mask-email', 'toc-only', 'stdout'
  4875. ]
  4876. # Creating variables for all checks
  4877. for check in self.checks:
  4878. setattr(self, 'f_'+check, self.setvar(''))
  4879. # Load RC config
  4880. self.conf = {}
  4881. if conf: self.load_config(conf)
  4882. def load_config(self, conf):
  4883. self.conf = conf
  4884. self.gui_colors = conf.get('guicolors') or self.dft_gui_colors
  4885. self.bg1, self.fg1, self.bg2, self.fg2 = self.gui_colors
  4886. self.root.config(bd=15,bg=self.bg1)
  4887. ### Config as dic for python 1.5 compat (**opts don't work :( )
  4888. def entry(self, **opts): return Tkinter.Entry(self.window, opts)
  4889. def label(self, txt='', bg=None, **opts):
  4890. opts.update({'text':txt,'bg':bg or self.bg1})
  4891. return Tkinter.Label(self.window, opts)
  4892. def button(self,name,cmd,**opts):
  4893. opts.update({'text':name,'command':cmd})
  4894. return Tkinter.Button(self.window, opts)
  4895. def check(self,name,checked=0,**opts):
  4896. bg, fg = self.bg2, self.fg2
  4897. opts.update({
  4898. 'text':name,
  4899. 'onvalue':1,
  4900. 'offvalue':0,
  4901. 'activeforeground':fg,
  4902. 'activebackground':bg,
  4903. 'highlightbackground':bg,
  4904. 'fg':fg,
  4905. 'bg':bg,
  4906. 'anchor':'w'
  4907. })
  4908. chk = Tkinter.Checkbutton(self.window, opts)
  4909. if checked: chk.select()
  4910. chk.grid(columnspan=2, sticky='w', padx=0)
  4911. def menu(self,sel,items):
  4912. return apply(Tkinter.OptionMenu,(self.window,sel)+tuple(items))
  4913. # Handy auxiliary functions
  4914. def action(self, txt):
  4915. self.label(
  4916. txt,
  4917. fg=self.fg1,
  4918. bg=self.bg1,
  4919. wraplength=self.action_length).grid(column=0,row=self.row)
  4920. def frame_open(self):
  4921. self.window = Tkinter.Frame(
  4922. self.root,
  4923. bg=self.bg2,
  4924. borderwidth=self.frame_border)
  4925. def frame_close(self):
  4926. self.window.grid(
  4927. column=1,
  4928. row=self.row,
  4929. sticky='w',
  4930. padx=self.frame_margin)
  4931. self.window = self.root
  4932. self.label('').grid()
  4933. self.row += 2 # update row count
  4934. def target_name2key(self):
  4935. name = self.target_name.get()
  4936. target = filter(lambda x: TARGET_NAMES[x] == name, TARGETS)
  4937. try : key = target[0]
  4938. except: key = ''
  4939. self.target = self.setvar(key)
  4940. def target_key2name(self):
  4941. key = self.target.get()
  4942. name = TARGET_NAMES.get(key) or key
  4943. self.target_name = self.setvar(name)
  4944. def exit(self): self.root.destroy()
  4945. def setvar(self, val): z = Tkinter.StringVar() ; z.set(val) ; return z
  4946. def askfile(self):
  4947. ftypes= [(_('txt2tags files'), ('*.t2t','*.txt')), (_('All files'),'*')]
  4948. newfile = askopenfilename(filetypes=ftypes)
  4949. if newfile:
  4950. self.infile.set(newfile)
  4951. newconf = process_source_file(newfile)[0]
  4952. newconf = ConfigMaster().sanity(newconf, gui=1)
  4953. # Restate all checkboxes after file selection
  4954. #TODO how to make a refresh without killing it?
  4955. self.root.destroy()
  4956. self.__init__(newconf)
  4957. self.mainwindow()
  4958. def scrollwindow(self, txt='no text!', title=''):
  4959. # Create components
  4960. win = Tkinter.Toplevel() ; win.title(title)
  4961. frame = Tkinter.Frame(win)
  4962. scroll = Tkinter.Scrollbar(frame)
  4963. text = Tkinter.Text(frame,yscrollcommand=scroll.set)
  4964. button = Tkinter.Button(win)
  4965. # Config
  4966. text.insert(Tkinter.END, '\n'.join(txt))
  4967. scroll.config(command=text.yview)
  4968. button.config(text=_('Close'), command=win.destroy)
  4969. button.focus_set()
  4970. # Packing
  4971. text.pack(side='left', fill='both', expand=1)
  4972. scroll.pack(side='right', fill='y')
  4973. frame.pack(fill='both', expand=1)
  4974. button.pack(ipadx=30)
  4975. def runprogram(self):
  4976. global CMDLINE_RAW
  4977. # Prepare
  4978. self.target_name2key()
  4979. infile, target = self.infile.get(), self.target.get()
  4980. # Sanity
  4981. if not target:
  4982. showwarning(my_name,_("You must select a target type!"))
  4983. return
  4984. if not infile:
  4985. showwarning(my_name,_("You must provide the source file location!"))
  4986. return
  4987. # Compose cmdline
  4988. guiflags = []
  4989. real_cmdline_conf = ConfigMaster(CMDLINE_RAW).parse()
  4990. if real_cmdline_conf.has_key('infile'):
  4991. del real_cmdline_conf['infile']
  4992. if real_cmdline_conf.has_key('target'):
  4993. del real_cmdline_conf['target']
  4994. real_cmdline = CommandLine().compose_cmdline(real_cmdline_conf)
  4995. default_outfile = ConfigMaster().get_outfile_name(
  4996. {'sourcefile':infile, 'outfile':'', 'target':target})
  4997. for opt in self.checks:
  4998. val = int(getattr(self, 'f_%s'%opt).get() or "0")
  4999. if opt == 'stdout': opt = 'outfile'
  5000. on_config = self.conf.get(opt) or 0
  5001. on_cmdline = real_cmdline_conf.get(opt) or 0
  5002. if opt == 'outfile':
  5003. if on_config == STDOUT: on_config = 1
  5004. else: on_config = 0
  5005. if on_cmdline == STDOUT: on_cmdline = 1
  5006. else: on_cmdline = 0
  5007. if val != on_config or (
  5008. val == on_config == on_cmdline and
  5009. real_cmdline_conf.has_key(opt)):
  5010. if val:
  5011. # Was not set, but user selected on GUI
  5012. Debug("user turned ON: %s"%opt)
  5013. if opt == 'outfile': opt = '-o-'
  5014. else: opt = '--%s'%opt
  5015. else:
  5016. # Was set, but user deselected on GUI
  5017. Debug("user turned OFF: %s"%opt)
  5018. if opt == 'outfile':
  5019. opt = "-o%s"%default_outfile
  5020. else: opt = '--no-%s'%opt
  5021. guiflags.append(opt)
  5022. cmdline = [my_name, '-t', target] + real_cmdline + guiflags + [infile]
  5023. Debug('Gui/Tk cmdline: %s' % cmdline, 5)
  5024. # Run!
  5025. cmdline_raw_orig = CMDLINE_RAW
  5026. try:
  5027. # Fake the GUI cmdline as the real one, and parse file
  5028. CMDLINE_RAW = CommandLine().get_raw_config(cmdline[1:])
  5029. data = process_source_file(infile)
  5030. # On GUI, convert_* returns the data, not finish_him()
  5031. outlist, config = convert_this_files([data])
  5032. # On GUI and STDOUT, finish_him() returns the data
  5033. result = finish_him(outlist, config)
  5034. # Show outlist in s a nice new window
  5035. if result:
  5036. outlist, config = result
  5037. title = _('%s: %s converted to %s') % (
  5038. my_name,
  5039. os.path.basename(infile),
  5040. config['target'].upper())
  5041. self.scrollwindow(outlist, title)
  5042. # Show the "file saved" message
  5043. else:
  5044. msg = "%s\n\n %s\n%s\n\n %s\n%s"%(
  5045. _('Conversion done!'),
  5046. _('FROM:'), infile,
  5047. _('TO:'), config['outfile'])
  5048. showinfo(my_name, msg)
  5049. except error: # common error (windowed), not quit
  5050. pass
  5051. except: # fatal error (windowed and printed)
  5052. errormsg = getUnknownErrorMessage()
  5053. print errormsg
  5054. showerror(_('%s FATAL ERROR!')%my_name,errormsg)
  5055. self.exit()
  5056. CMDLINE_RAW = cmdline_raw_orig
  5057. def mainwindow(self):
  5058. self.infile.set(self.conf.get('sourcefile') or '')
  5059. self.target.set(self.conf.get('target') or _('-- select one --'))
  5060. outfile = self.conf.get('outfile')
  5061. if outfile == STDOUT: # map -o-
  5062. self.conf['stdout'] = 1
  5063. if self.conf.get('headers') == None:
  5064. self.conf['headers'] = 1 # map default
  5065. action1 = _("Enter the source file location:")
  5066. action2 = _("Choose the target document type:")
  5067. action3 = _("Some options you may check:")
  5068. action4 = _("Some extra options:")
  5069. checks_txt = {
  5070. 'headers' : _("Include headers on output"),
  5071. 'enum-title': _("Number titles (1, 1.1, 1.1.1, etc)"),
  5072. 'toc' : _("Do TOC also (Table of Contents)"),
  5073. 'mask-email': _("Hide e-mails from SPAM robots"),
  5074. 'toc-only' : _("Just do TOC, nothing more"),
  5075. 'stdout' : _("Dump to screen (Don't save target file)")
  5076. }
  5077. targets_menu = map(lambda x: TARGET_NAMES[x], TARGETS)
  5078. # Header
  5079. self.label("%s %s"%(my_name.upper(), my_version),
  5080. bg=self.bg2, fg=self.fg2).grid(columnspan=2, ipadx=10)
  5081. self.label(_("ONE source, MULTI targets")+'\n%s\n'%my_url,
  5082. bg=self.bg1, fg=self.fg1).grid(columnspan=2)
  5083. self.row = 2
  5084. # Choose input file
  5085. self.action(action1) ; self.frame_open()
  5086. e_infile = self.entry(textvariable=self.infile,width=25)
  5087. e_infile.grid(row=self.row, column=0, sticky='e')
  5088. if not self.infile.get(): e_infile.focus_set()
  5089. self.button(_("Browse"), self.askfile).grid(
  5090. row=self.row, column=1, sticky='w', padx=10)
  5091. # Show outfile name, style and encoding (if any)
  5092. txt = ''
  5093. if outfile:
  5094. txt = outfile
  5095. if outfile == STDOUT: txt = _('<screen>')
  5096. l_output = self.label(_('Output: ')+txt, fg=self.fg2, bg=self.bg2)
  5097. l_output.grid(columnspan=2, sticky='w')
  5098. for setting in ['style','encoding']:
  5099. if self.conf.get(setting):
  5100. name = setting.capitalize()
  5101. val = self.conf[setting]
  5102. self.label('%s: %s'%(name, val),
  5103. fg=self.fg2, bg=self.bg2).grid(
  5104. columnspan=2, sticky='w')
  5105. # Choose target
  5106. self.frame_close() ; self.action(action2)
  5107. self.frame_open()
  5108. self.target_key2name()
  5109. self.menu(self.target_name, targets_menu).grid(
  5110. columnspan=2, sticky='w')
  5111. # Options checkboxes label
  5112. self.frame_close() ; self.action(action3)
  5113. self.frame_open()
  5114. # Compose options check boxes, example:
  5115. # self.check(checks_txt['toc'],1,variable=self.f_toc)
  5116. for check in self.checks:
  5117. # Extra options label
  5118. if check == 'toc-only':
  5119. self.frame_close() ; self.action(action4)
  5120. self.frame_open()
  5121. txt = checks_txt[check]
  5122. var = getattr(self, 'f_'+check)
  5123. checked = self.conf.get(check)
  5124. self.check(txt,checked,variable=var)
  5125. self.frame_close()
  5126. # Spacer and buttons
  5127. self.label('').grid() ; self.row += 1
  5128. b_quit = self.button(_("Quit"), self.exit)
  5129. b_quit.grid(row=self.row, column=0, sticky='w', padx=30)
  5130. b_conv = self.button(_("Convert!"), self.runprogram)
  5131. b_conv.grid(row=self.row, column=1, sticky='e', padx=30)
  5132. if self.target.get() and self.infile.get():
  5133. b_conv.focus_set()
  5134. # As documentation told me
  5135. if sys.platform.startswith('win'):
  5136. self.root.iconify()
  5137. self.root.update()
  5138. self.root.deiconify()
  5139. self.root.mainloop()
  5140. ##############################################################################
  5141. ##############################################################################
  5142. def exec_command_line(user_cmdline=[]):
  5143. global CMDLINE_RAW, RC_RAW, DEBUG, VERBOSE, QUIET, GUI, Error
  5144. # Extract command line data
  5145. cmdline_data = user_cmdline or sys.argv[1:]
  5146. CMDLINE_RAW = CommandLine().get_raw_config(cmdline_data, relative=1)
  5147. cmdline_parsed = ConfigMaster(CMDLINE_RAW).parse()
  5148. DEBUG = cmdline_parsed.get('debug' ) or 0
  5149. VERBOSE = cmdline_parsed.get('verbose') or 0
  5150. QUIET = cmdline_parsed.get('quiet' ) or 0
  5151. GUI = cmdline_parsed.get('gui' ) or 0
  5152. infiles = cmdline_parsed.get('infile' ) or []
  5153. Message(_("Txt2tags %s processing begins")%my_version,1)
  5154. # The easy ones
  5155. if cmdline_parsed.get('help' ): Quit(USAGE)
  5156. if cmdline_parsed.get('version'): Quit(VERSIONSTR)
  5157. if cmdline_parsed.get('targets'):
  5158. listTargets()
  5159. Quit()
  5160. # Multifile haters
  5161. if len(infiles) > 1:
  5162. errmsg=_("Option --%s can't be used with multiple input files")
  5163. for option in NO_MULTI_INPUT:
  5164. if cmdline_parsed.get(option):
  5165. Error(errmsg%option)
  5166. Debug("system platform: %s"%sys.platform)
  5167. Debug("python version: %s"%(sys.version.split('(')[0]))
  5168. Debug("line break char: %s"%repr(LB))
  5169. Debug("command line: %s"%sys.argv)
  5170. Debug("command line raw config: %s"%CMDLINE_RAW,1)
  5171. # Extract RC file config
  5172. if cmdline_parsed.get('rc') == 0:
  5173. Message(_("Ignoring user configuration file"),1)
  5174. else:
  5175. rc_file = get_rc_path()
  5176. if os.path.isfile(rc_file):
  5177. Message(_("Loading user configuration file"),1)
  5178. RC_RAW = ConfigLines(file_=rc_file).get_raw_config()
  5179. Debug("rc file: %s"%rc_file)
  5180. Debug("rc file raw config: %s"%RC_RAW,1)
  5181. # Get all infiles config (if any)
  5182. infiles_config = get_infiles_config(infiles)
  5183. # Is GUI available?
  5184. # Try to load and start GUI interface for --gui
  5185. if GUI:
  5186. try:
  5187. load_GUI_resources()
  5188. Debug("GUI resources OK (Tk module is installed)")
  5189. winbox = Gui()
  5190. Debug("GUI display OK")
  5191. GUI = 1
  5192. except:
  5193. Debug("GUI Error: no Tk module or no DISPLAY")
  5194. GUI = 0
  5195. # User forced --gui, but it's not available
  5196. if cmdline_parsed.get('gui') and not GUI:
  5197. print getTraceback(); print
  5198. Error(
  5199. "Sorry, I can't run my Graphical Interface - GUI\n"
  5200. "- Check if Python Tcl/Tk module is installed (Tkinter)\n"
  5201. "- Make sure you are in a graphical environment (like X)")
  5202. # Okay, we will use GUI
  5203. if GUI:
  5204. Message(_("We are on GUI interface"),1)
  5205. # Redefine Error function to raise exception instead sys.exit()
  5206. def Error(msg):
  5207. showerror(_('txt2tags ERROR!'), msg)
  5208. raise error
  5209. # If no input file, get RC+cmdline config, else full config
  5210. if not infiles:
  5211. gui_conf = ConfigMaster(RC_RAW+CMDLINE_RAW).parse()
  5212. else:
  5213. try : gui_conf = infiles_config[0][0]
  5214. except: gui_conf = {}
  5215. # Sanity is needed to set outfile and other things
  5216. gui_conf = ConfigMaster().sanity(gui_conf, gui=1)
  5217. Debug("GUI config: %s"%gui_conf,5)
  5218. # Insert config and populate the nice window!
  5219. winbox.load_config(gui_conf)
  5220. winbox.mainwindow()
  5221. # Console mode rocks forever!
  5222. else:
  5223. Message(_("We are on Command Line interface"),1)
  5224. # Called with no arguments, show error
  5225. # TODO#1: this checking should be only in ConfigMaster.sanity()
  5226. if not infiles:
  5227. Error(_('Missing input file (try --help)') + '\n\n' +
  5228. _('Please inform an input file (.t2t) at the end of the command.') + '\n' +
  5229. _('Example:') + ' %s -t html %s' % (my_name, _('file.t2t')))
  5230. convert_this_files(infiles_config)
  5231. Message(_("Txt2tags finished successfully"),1)
  5232. if __name__ == '__main__':
  5233. try:
  5234. exec_command_line()
  5235. except error, msg:
  5236. sys.stderr.write("%s\n"%msg)
  5237. sys.stderr.flush()
  5238. sys.exit(1)
  5239. except SystemExit:
  5240. pass
  5241. except:
  5242. sys.stderr.write(getUnknownErrorMessage())
  5243. sys.stderr.flush()
  5244. sys.exit(1)
  5245. Quit()
  5246. # The End.