3 asciidoc - converts an AsciiDoc text file to DocBook, HTML or LinuxDoc
5 Copyright (C) 2002-2005 Stuart Rackham. Free use of this software is granted
6 under the terms of the GNU General Public License (GPL).
9 import sys
, os
, re
, string
, time
, traceback
, tempfile
, popen2
, codecs
12 VERSION
= '7.1.2' # See CHANGLOG file for version history.
14 #---------------------------------------------------------------------------
16 #---------------------------------------------------------------------------
17 DEFAULT_BACKEND
= 'xhtml11'
18 DEFAULT_DOCTYPE
= 'article'
19 # Allowed substitution options for List, Paragraph and DelimitedBlock
20 # definition subs entry.
21 SUBS_OPTIONS
= ('specialcharacters','quotes','specialwords','replacements',
22 'attributes','macros','callouts','normal','verbatim','none','passthroughs')
23 # Default value for unspecified subs and presubs configuration file entries.
24 SUBS_NORMAL
= ('specialcharacters','quotes','specialwords','replacements',
25 'attributes','macros','passthroughs')
26 SUBS_VERBATIM
= ('specialcharacters','callouts')
28 NAME_RE
= r
'(?u)[^\W\d][-\w]*' # Valid section or attrbibute name.
31 #---------------------------------------------------------------------------
32 # Utility functions and classes.
33 #---------------------------------------------------------------------------
35 class EAsciiDoc(Exception):
39 from UserDict
import UserDict
41 class OrderedDict(UserDict
):
42 '''Python Cookbook: Ordered Dictionary, Submitter: David Benjamin'''
43 def __init__(self
, d
= None):
45 UserDict
.__init
__(self
, d
)
46 def __delitem__(self
, key
):
47 UserDict
.__delitem
__(self
, key
)
48 self
._keys
.remove(key
)
49 def __setitem__(self
, key
, item
):
50 UserDict
.__setitem
__(self
, key
, item
)
51 if key
not in self
._keys
: self
._keys
.append(key
)
56 d
= UserDict
.copy(self
)
57 d
._keys
= self
._keys
[:]
60 return zip(self
._keys
, self
.values())
67 raise KeyError('dictionary is empty')
71 def setdefault(self
, key
, failobj
= None):
72 UserDict
.setdefault(self
, key
, failobj
)
73 if key
not in self
._keys
: self
._keys
.append(key
)
75 UserDict
.update(self
, d
)
77 if key
not in self
._keys
: self
._keys
.append(key
)
79 return map(self
.get
, self
._keys
)
81 def print_stderr(line
):
82 sys
.stderr
.write(line
+os
.linesep
)
84 def verbose(msg
,linenos
=True):
85 '''-v option messages.'''
87 console(msg
,linenos
=linenos
)
89 def warning(msg
,linenos
=True):
90 console(msg
,'WARNING: ',linenos
)
92 def deprecated(old
, new
, linenos
=True):
93 console("%s: %s" % (old
,new
), 'DEPRECATED: ', linenos
)
96 '''Report fatal error but don't exit application, continue in the hope of
97 reporting all fatal errors finishing with a non-zero exit code.'''
98 console(msg
,'ERROR: ')
99 document
.has_errors
= True
101 def console(msg
, prefix
='', linenos
=True):
102 '''Print message to stderr. 'offset' is added to reported line number for
103 warnings emitted when reading ahead.'''
105 if linenos
and reader
.cursor
:
106 s
= s
+ "%s: line %d: " \
107 % (os
.path
.basename(reader
.cursor
[0]),reader
.cursor
[1])
111 def file_in(fname
,dir):
112 '''Return True if file fname resides inside directory dir.'''
113 assert os
.path
.isfile(fname
)
114 assert os
.path
.isdir(dir)
115 dir = os
.path
.abspath(dir)
116 fname
= realpath(fname
)
117 return os
.path
.commonprefix((dir,fname
)) == dir
122 def is_safe_file(fname
, dir=None):
123 # A safe file must reside in directory dir (defaults to the source file
126 dir = os
.path
.dirname(document
.infile
)
127 return not safe() or file_in(fname
,dir)
129 # Return file name which must reside in the parent file directory.
130 # Return None if file is not found or not safe.
131 def safe_filename(fname
, parentdir
):
132 if not os
.path
.isabs(fname
):
133 # Include files are relative to parent document
135 fname
= os
.path
.join(parentdir
,fname
)
136 if not os
.path
.isfile(fname
):
137 warning('include file not found: %s' % fname
)
139 if not is_safe_file(fname
, parentdir
):
140 unsafe_error('include file: %s' % fname
)
144 def unsafe_error(msg
):
145 error('unsafe: '+msg
)
148 '''Return the absolute pathname of the file fname. Follow symbolic links.
149 os.realpath() not available in Python prior to 2.2 and not portable.'''
150 # Follow symlinks to the actual executable.
153 while os
.path
.islink(fname
):
154 linkdir
= os
.path
.dirname(fname
)
155 fname
= os
.readlink(fname
)
156 if linkdir
: os
.chdir(linkdir
) # Symlinks can be relative.
157 fname
= os
.path
.abspath(fname
)
160 return os
.path
.normpath(fname
)
163 # Run shell command and return stdout.
164 child
= os
.popen(cmd
)
173 '''Assign all attributes from 'src' object to 'dst' object.'''
174 for a
,v
in src
.__dict
__.items():
178 '''Trim white space and, if necessary, quote characters from s.'''
180 # Strip quotation mark characters from quoted strings.
181 if len(s
) >= 3 and s
[0] == '"' and s
[-1] == '"':
186 '''Return True if s is a valid regular expression else return False.'''
191 def join_regexp(relist
):
192 '''Join list of regular expressions re1,re2,... to single regular
193 expression (re1)|(re2)|...'''
197 # Delete named groups to avoid ambiguity.
199 result
.append(re
.sub(r
'\?P<\S+?>','',s
))
200 result
= string
.join(result
,')|(')
201 result
= '('+result
+')'
204 def validate(value
,rule
,errmsg
):
205 '''Validate value against rule expression. Throw EAsciiDoc exception with
206 errmsg if validation fails.'''
208 if not eval(string
.replace(rule
,'$',str(value
))):
209 raise EAsciiDoc
,errmsg
211 raise EAsciiDoc
,errmsg
214 def join_lines(lines
):
215 '''Return a list in which lines terminated with the backslash line
216 continuation character are joined.'''
221 if line
and line
[-1] == '\\':
226 result
.append(s
+line
)
235 def dovetail(lines1
, lines2
):
236 '''Append list or tuple of strings 'lines2' to list 'lines1'. Join the
237 last string in 'lines1' with the first string in 'lines2' into a single
239 assert isinstance(lines1
,list) or isinstance(lines1
,tuple)
240 assert isinstance(lines2
,list) or isinstance(lines2
,tuple)
241 if not lines1
or not lines2
:
242 return list(lines1
) + list(lines2
)
243 result
= list(lines1
[:-1])
244 result
.append(lines1
[-1] + lines2
[0])
245 result
+= list(lines2
[1:])
248 def dovetail_tags(stag
,content
,etag
):
249 '''Merge the end tag with the first content line and the last
250 content line with the end tag. This ensures verbatim elements don't
251 include extraneous opening and closing line breaks.'''
252 return dovetail(dovetail(stag
,content
), etag
)
254 def parse_attributes(attrs
,dict):
255 '''Update a dictionary with name/value attributes from the attrs string.
256 The attrs string is a comma separated list of values and keyword name=value
257 pairs. Values must preceed keywords and are named '1','2'... The entire
258 attributes list is named '0'. If keywords are specified string values must
265 dict: {'2': 'world', '0': 'hello,world', '1': 'hello'}
267 attrs: '"hello",planet="earth"'
268 dict: {'planet': 'earth', '0': '"hello",planet="earth"', '1': 'hello'}
270 def f(*args
,**keywords
):
271 # Name and add aguments '1','2'... to keywords.
272 for i
in range(len(args
)):
273 if not keywords
.has_key(str(i
+1)):
274 keywords
[str(i
+1)] = args
[i
]
280 # Replace line separators with spaces so line spanning works.
281 s
= re
.sub(r
'\s', ' ', attrs
)
286 # Try quoting the attrs.
287 s
= string
.replace(s
,'"',r
'\"') # Escape double-quotes.
288 s
= string
.split(s
,',')
289 s
= map(lambda x
: '"'+string
.strip(x
)+'"',s
)
290 s
= string
.join(s
,',')
294 return # If there's a syntax error leave with {0}=attrs.
295 for k
in d
.keys(): # Drop any empty positional arguments.
296 if d
[k
] == '': del d
[k
]
300 def parse_named_attributes(s
,attrs
):
301 '''Update a attrs dictionary with name="value" attributes from the s string.
302 Returns False if invalid syntax.
304 attrs: 'star="sun",planet="earth"'
305 dict: {'planet':'earth', 'star':'sun'}
307 def f(**keywords
): return keywords
317 '''Parse comma separated string of Python literals. Return a tuple of of
320 result
= eval('tuple(['+s
+'])')
322 raise EAsciiDoc
,'malformed list: '+s
325 def parse_options(options
,allowed
,errmsg
):
326 '''Parse comma separated string of unquoted option names and return as a
327 tuple of valid options. 'allowed' is a list of allowed option values.
328 If allowed=() then all legitimate names are allowed.
329 'errmsg' isan error message prefix if an illegal option error is thrown.'''
332 for s
in re
.split(r
'\s*,\s*',options
):
333 if (allowed
and s
not in allowed
) or (s
== '' or not is_name(s
)):
334 raise EAsciiDoc
,'%s: %s' % (errmsg
,s
)
339 '''Drop non-symbol characters and convert to lowercase.'''
340 return string
.lower(re
.sub(r
'(?u)[^\w\-_]', '', s
))
343 '''Return True if s is valid attribute, macro or tag name
344 (starts with alpha containing alphanumeric and dashes only).'''
345 return re
.match(NAME_RE
,s
) is not None
347 def subs_quotes(text
):
348 '''Quoted text is marked up and the resulting text is
350 quotes
= config
.quotes
.keys()
351 # The quotes are iterated in reverse sort order to avoid ambiguity,
352 # for example, '' is processed before '.
356 i
= string
.find(quote
,'|')
357 if i
!= -1 and quote
!= '|' and quote
!= '||':
362 # Non-word (\W) characters are allowed at boundaries to accomodate
363 # enveloping quotes. re.S flag for line spanning.
364 reo
= re
.compile(r
'(?msu)(^|\W)(\[(?P<attrs>[^[]+?)\])?'\
365 + r
'(?:' + re
.escape(lq
) + r
')' \
366 + r
'(?P<content>.*?)(?:'+re
.escape(rq
)+r
')(?=\W|$)')
369 mo
= reo
.search(text
,pos
)
371 if text
[mo
.start()] == '\\':
375 parse_attributes(mo
.group('attrs'), attrs
)
376 stag
,etag
= config
.tag(config
.quotes
[quote
], attrs
)
377 s
= mo
.group(1) + stag
+ mo
.group('content') + etag
378 text
= text
[:mo
.start()] + s
+ text
[mo
.end():]
379 pos
= mo
.start() + len(s
)
380 # Unescape escaped quotes.
381 text
= string
.replace(text
,'\\'+lq
, lq
)
383 text
= string
.replace(text
,'\\'+rq
, rq
)
386 def subs_tag(tag
,dict={}):
387 '''Perform attribute substitution and split tag string returning start, end
388 tag tuple (c.f. Config.tag()).'''
389 s
= subs_attrs(tag
,dict)
391 warning('tag "%s" dropped: contains undefined attribute' % tag
)
393 result
= string
.split(s
,'|')
396 elif len(result
) == 2:
399 raise EAsciiDoc
,'malformed tag: %s' % tag
401 def parse_entry(entry
, dict=None, unquote
=False, unique_values
=False,
402 allow_name_only
=False):
403 '''Parse name=value entry to dictionary 'dict'. Return tuple (name,value)
404 or None if illegal entry.
405 If the syntax is name= then value is set to ''.
406 If the syntax is name and allow_name_only=True then value is set to ''.
407 If the syntax is name! and allow_name_only=True then value is set to None.
408 Leading and trailing white space is striped from 'name' and 'value'.
409 'name' can contain any printable characters. If 'name includes the equals
410 '=' character it must be escaped with a backslash.
411 If 'unquote' is True leading and trailing double-quotes are stripped from
413 If unique_values' is True then dictionary entries with the same value are
414 removed before the parsed entry is added.'''
415 mo
=re
.search(r
'[^\\](=)',entry
)
416 if mo
: # name=value entry.
417 name
= entry
[:mo
.start(1)]
418 value
= entry
[mo
.end(1):]
419 elif allow_name_only
and entry
: # name or name! entry.
429 name
= strip_quotes(name
)
430 if value
is not None:
431 value
= strip_quotes(value
)
433 name
= string
.strip(name
)
434 if value
is not None:
435 value
= string
.strip(value
)
440 for k
,v
in dict.items():
441 if v
== value
: del dict[k
]
445 def parse_entries(entries
, dict, unquote
=False, unique_values
=False,
446 allow_name_only
=False):
447 '''Parse name=value entries from from lines of text in 'entries' into
448 dictionary 'dict'. Blank lines are skipped.'''
449 for entry
in entries
:
450 if entry
and not parse_entry(entry
, dict, unquote
, unique_values
,
452 raise EAsciiDoc
,'malformed section entry: %s' % entry
454 def load_sections(sections
, fname
, dir=None, namepat
=NAME_RE
):
455 '''Loads sections dictionary with sections from file fname.
456 Existing sections are overlaid. Silently skips missing configuration
459 fname
= os
.path
.join(dir, fname
)
460 # Sliently skip missing configuration file.
461 if not os
.path
.isfile(fname
):
463 lines
= open(fname
).readlines()
464 reo
= re
.compile(r
'^\[(?P<section>'+namepat
+')\]\s*$')
465 section
,contents
= '',[]
467 if line
and line
[0] == '#': # Skip comment lines.
469 line
= string
.rstrip(line
)
470 found
= reo
.findall(line
)
472 if section
: # Store previous section.
473 sections
[section
] = contents
474 section
= found
[0].lower()
477 contents
.append(line
)
478 if section
and contents
: # Store last section.
479 sections
[section
] = contents
481 def dump_section(name
,dict,f
=sys
.stdout
):
482 '''Write parameters in 'dict' as in configuration file section format with
484 f
.write('[%s]%s' % (name
,writer
.newline
))
485 for k
,v
in dict.items():
487 # Quote if necessary.
488 if len(k
) != len(string
.strip(k
)):
490 if v
and len(v
) != len(string
.strip(v
)):
493 # Don't dump undefined attributes.
498 s
= '\\' + s
# Escape so not treated as comment lines.
499 f
.write('%s%s' % (s
,writer
.newline
))
500 f
.write(writer
.newline
)
502 def update_attrs(attrs
,dict):
503 '''Update 'attrs' dictionary with parsed attributes in dictionary 'dict'.'''
504 for k
,v
in dict.items():
506 raise EAsciiDoc
,'illegal attribute name: %s' % k
509 def readlines(fname
):
510 '''Read lines from file named 'fname' and strip trailing white space.'''
514 lines
= f
.readlines()
518 for i
in range(len(lines
)):
519 lines
[i
] = string
.rstrip(lines
[i
])
522 def filter_lines(filter,lines
,dict={}):
523 '''Run 'lines' through the 'filter' shell command and return the result. The
524 'dict' dictionary contains additional filter attributes.'''
525 # BUG: Has problems finding filters with spaces in command name.
528 if os
.name
!= 'posix':
529 warning('filters do not work in a non-posix environment')
531 # Perform attributes substitution on the filter command.
532 s
= subs_attrs(filter,dict)
534 raise EAsciiDoc
,'filter has undefined attribute: %s' % filter
536 # Search for the filter command in both user and application 'filters'
538 mo
= re
.match(r
'^(?P<cmd>\S+)(?P<tail>.*)$', filter)
540 cmd
= mo
.group('cmd')
542 if not os
.path
.dirname(cmd
):
543 # Check in asciidoc user and application directories for unqualified
546 cmd2
= os
.path
.join(USER_DIR
,'filters',cmd
)
547 if os
.path
.isfile(cmd2
):
550 cmd2
= os
.path
.join(GLOBAL_CONFIG_DIR
,'filters',cmd
)
551 if os
.path
.isfile(cmd2
):
554 cmd2
= os
.path
.join(APP_DIR
,'filters',cmd
)
555 if os
.path
.isfile(cmd2
):
558 filter = cmd2
+ mo
.group('tail')
560 if os
.uname()[0][:6] == 'CYGWIN':
561 # popen2() does not like non-drive letter path names under
563 s
= string
.strip(syseval('cygpath -m "'+cmd
+'"'))
566 if not os
.path
.isfile(cmd
):
567 warning('filter not found: %s' % cmd
)
569 # Put the real filter path into the filter command.
570 verbose('filtering: '+filter)
574 r
,w
= popen2
.popen2(filter)
575 # Polled I/O loop to alleviate full buffer deadlocks.
577 while i
< len(lines
):
579 if select
.select([],[w
.fileno()],[],0)[1]:
580 w
.write(line
+os
.linesep
) # Use platform line terminator.
582 if select
.select([r
.fileno()],[],[],0)[0]:
584 if not s
: break # Exit if filter output closes.
585 result
.append(string
.rstrip(s
))
587 for s
in r
.readlines():
588 result
.append(string
.rstrip(s
))
591 raise EAsciiDoc
,'filter error: %s' % filter
592 # There's no easy way to guage whether popen2() found and executed the
593 # filter, so guess that if it produced no output there is probably a
595 if lines
and not result
:
596 warning('no output from filter: %s' % filter)
599 def system(name
, args
, is_macro
=False):
600 '''Evaluate a system attribute ({name:args}) or system block macro
601 (name::[args]). If is_macro is True then we are processing a system
602 block macro otherwise it's a system attribute.
603 NOTE: The include1 attribute is used internally by the include1::[] macro
604 and is not for public use.'''
610 separator
= writer
.newline
611 if name
not in ('eval','sys','sys2','include','include1'):
612 msg
= 'illegal '+syntax
% (name
,args
)
614 msg
+= ': macro name'
616 msg
+= ': executable attribute name'
619 if name
!= 'include1':
620 verbose(('evaluating: '+syntax
) % (name
,args
))
621 if safe() and name
not in ('include','include1'):
622 unsafe_error(syntax
% (name
,args
))
630 elif result
is False:
632 elif result
is not None:
635 warning((syntax
+': expression evaluation error') % (name
,args
))
636 elif name
in ('sys','sys2'):
638 fd
,tmp
= tempfile
.mkstemp()
642 cmd
= cmd
+ (' > %s' % tmp
)
646 warning((syntax
+': non-zero exit status') % (name
,args
))
648 if os
.path
.isfile(tmp
):
649 lines
= readlines(tmp
)
653 raise EAsciiDoc
,(syntax
+': temp file read error') % (name
,args
)
654 result
= string
.join(lines
, separator
)
656 if os
.path
.isfile(tmp
):
658 elif name
== 'include':
659 if not os
.path
.exists(args
):
660 warning((syntax
+': file does not exist') % (name
,args
))
661 elif not is_safe_file(args
):
662 unsafe_error(syntax
% (name
,args
))
664 result
= readlines(args
)
666 result
= subs_attrs(result
)
667 result
= string
.join(result
, separator
)
668 result
= string
.expandtabs(result
, reader
.tabsize
)
671 elif name
== 'include1':
672 result
= string
.join(config
.include1
[args
], separator
)
677 def subs_attrs(lines
,dict={}):
678 '''Substitute 'lines' of text with attributes from the global
679 document.attributes dictionary and from the 'dict' dictionary ('dict'
680 entries take precedence). Return a tuple of the substituted lines. 'lines'
681 containing undefined attributes are deleted. If 'lines' is a string then
684 - Attribute references are substituted in the following order: simple,
686 - If a 'dict' value includes an attribute reference it won't be substituted
687 unless it's substitution order follows that of the source attribute
690 The moral is that any attribute references in 'dict' attribute values
691 should be substituted beforehand.'''
693 def end_brace(text
,start
):
694 '''Return index following end brace that matches brace at start in
696 assert text
[start
] == '{'
699 for c
in text
[start
:]:
700 # Skip braces that are followed by a backslash.
701 if result
== len(text
)-1 or text
[result
+1] != '\\':
702 if c
== '{': n
= n
+ 1
703 elif c
== '}': n
= n
- 1
708 if isinstance(lines
,StringType
):
712 string_result
= False
714 attrs
= document
.attributes
.copy()
716 # Substitute all attributes in all lines.
717 for i
in range(len(lines
)-1,-1,-1): # Reverse iterate lines.
719 # Make it easier for regular expressions.
720 text
= string
.replace(text
,'\\{','{\\')
721 text
= string
.replace(text
,'\\}','}\\')
722 # Expand simple attributes ({name}).
723 # Nested attributes not allowed.
724 reo
= re
.compile(r
'(?su)\{(?P<name>[^\\\W][-\w]*?)\}(?!\\)')
727 mo
= reo
.search(text
,pos
)
729 s
= attrs
.get(mo
.group('name'))
734 text
= text
[:mo
.start()] + s
+ text
[mo
.end():]
735 pos
= mo
.start() + len(s
)
736 # Expand conditional attributes.
737 reo
= re
.compile(r
'(?su)\{(?P<name>[^\\\W][-\w]*?)' \
738 r
'(?P<op>\=|\?|!|#|%|@|\$)' \
739 r
'(?P<value>.*?)\}(?!\\)')
742 mo
= reo
.search(text
,pos
)
745 name
= mo
.group('name')
746 lval
= attrs
.get(name
)
748 # mo.end() is not good enough because '{x={y}}' matches '{x={y}'.
749 end
= end_brace(text
,mo
.start())
750 rval
= text
[mo
.start('value'):end
-1]
752 if op
== '=': s
= rval
753 elif op
== '?': s
= ''
754 elif op
== '!': s
= rval
755 elif op
== '#': s
= '{'+name
+'}' # So the line is dropped.
756 elif op
== '%': s
= rval
757 elif op
in ('@','$'):
758 s
= '{'+name
+'}' # So the line is dropped.
760 assert False, 'illegal attribute: %s' % attr
762 if op
== '=': s
= lval
763 elif op
== '?': s
= rval
764 elif op
== '!': s
= ''
765 elif op
== '#': s
= rval
766 elif op
== '%': s
= '{zzzzz}' # So the line is dropped.
767 elif op
in ('@','$'):
768 v
= re
.split(r
'(?<!\\):',rval
)
769 if len(v
) not in (2,3):
770 error('illegal attribute syntax: %s' % attr
)
772 elif not is_regexp('^'+v
[0]+'$'):
773 error('illegal attribute regexp: %s' % attr
)
776 v
= [s
.replace('\\:',':') for s
in v
]
777 re_mo
= re
.match('^'+v
[0]+'$',lval
)
780 s
= v
[1] # {<name>@<re>:<v1>[:<v2>]}
782 if len(v
) == 3: # {<name>@<re>:<v1>:<v2>}
784 else: # {<name>@<re>:<v1>}
788 if len(v
) == 2: # {<name>$<re>:<v1>}
790 elif v
[1] == '': # {<name>$<re>::<v2>}
791 s
= '{zzzzz}' # So the line is dropped.
792 else: # {<name>$<re>:<v1>:<v2>}
795 if len(v
) == 2: # {<name>$<re>:<v1>}
796 s
= '{zzzzz}' # So the line is dropped.
797 else: # {<name>$<re>:<v1>:<v2>}
800 assert False, 'illegal attribute: %s' % attr
802 text
= text
[:mo
.start()] + s
+ text
[end
:]
803 pos
= mo
.start() + len(s
)
804 # Drop line if it contains unsubstituted {name} references.
805 skipped
= re
.search(r
'(?su)\{[^\\\W][-\w]*?\}(?!\\)', text
)
809 # Expand system attributes.
810 reo
= re
.compile(r
'(?su)\{(?P<action>[^\\\W][-\w]*?):(?P<expr>.*?)\}(?!\\)')
814 mo
= reo
.search(text
,pos
)
816 expr
= mo
.group('expr')
817 expr
= expr
.replace('{\\','{')
818 expr
= expr
.replace('}\\','}')
819 s
= system(mo
.group('action'),expr
)
823 text
= text
[:mo
.start()] + s
+ text
[mo
.end():]
824 pos
= mo
.start() + len(s
)
825 # Drop line if the action returns None.
829 # Remove backslash from escaped entries.
830 text
= text
.replace('{\\','{')
831 text
= text
.replace('}\\','}')
835 return string
.join(lines
,'\n')
842 encoding
= document
.attributes
.get('encoding')
845 codecs
.lookup(encoding
)
846 except LookupError,e
:
847 raise EAsciiDoc
,str(e
)
851 return len(char_decode(s
))
856 return s
.decode(char_encoding())
859 "'%s' codec can't decode \"%s\"" % (char_encoding(), s
)
865 return s
.encode(char_encoding())
870 '''Lexical analysis routines. Static methods and attributes only.'''
874 raise AssertionError,'no class instances allowed'
876 '''Returns class of next element on the input (None if EOF). The
877 reader is assumed to be at the first line following a previous element,
878 end of file or line one. Exits with the reader pointing to the first
879 line of the next element or EOF (leading blank lines are skipped).'''
880 reader
.skip_blank_lines()
881 if reader
.eof(): return None
882 # Optimization: If we've already checked for an element at this
883 # position return the element.
884 if Lex
.prev_element
and Lex
.prev_cursor
== reader
.cursor
:
885 return Lex
.prev_element
888 if not result
and Title
.isnext():
890 # Check for Block Macro.
891 if not result
and macros
.isnext():
892 result
= macros
.current
894 if not result
and lists
.isnext():
895 result
= lists
.current
896 # Check for DelimitedBlock.
897 if not result
and blocks
.isnext():
898 # Skip comment blocks.
899 if 'skip' in blocks
.current
.options
:
900 blocks
.current
.translate()
903 result
= blocks
.current
905 if not result
and tables
.isnext():
906 result
= tables
.current
907 # Check for AttributeEntry.
908 if not result
and AttributeEntry
.isnext():
909 result
= AttributeEntry
910 # Check for AttributeList.
911 if not result
and AttributeList
.isnext():
912 result
= AttributeList
913 # Check for BlockTitle.
914 if not result
and BlockTitle
.isnext():
916 # If it's none of the above then it must be an Paragraph.
918 if not paragraphs
.isnext():
919 raise EAsciiDoc
,'paragraph expected'
920 result
= paragraphs
.current
922 Lex
.prev_cursor
= reader
.cursor
923 Lex
.prev_element
= result
925 next
= staticmethod(next
)
927 # Extract the passthrough text and replace with temporary placeholders.
928 def extract_passthroughs(text
, passthroughs
):
930 lq1
= r
'(?P<lq>\+{3})'
932 reo1
= re
.compile(r
'(?msu)(^|[^\w+])(' + lq1
+ r
')' \
933 + r
'(?P<content>.+?)(' + rq1
+ r
')(?=[^\w+]|$)')
935 lq2
= r
'(\[(?P<attrs>[^[]+?)\])?(?P<lq>\${2})'
937 reo2
= re
.compile(r
'(?msu)(^|[^\w$\]])(' + lq2
+ r
')' \
938 + r
'(?P<content>.+?)(' + rq2
+ r
')(?=[^\w$]|$)')
942 mo
= reo
.search(text
,pos
)
950 if text
[mo
.start()] == '\\':
953 content
= mo
.group('content')
954 if mo
.group('lq') == '$$':
955 content
= config
.subs_specialchars(content
)
957 parse_attributes(mo
.group('attrs'), attrs
)
958 stag
,etag
= config
.tag('$$passthrough', attrs
)
960 etag
= '' # Drop end tag if start tag has been.
961 content
= stag
+ content
+ etag
962 passthroughs
.append(content
)
963 # Tabs are expanded when the source is read so using them here
964 # guarantees the placeholders are unambiguous.
965 s
= mo
.group(1) + '\t' + str(len(passthroughs
)-1) + '\t'
966 text
= text
[:mo
.start()] + s
+ text
[mo
.end():]
967 pos
= mo
.start() + len(s
)
968 # Unescape escaped passthroughs.
969 text
= string
.replace(text
,'\\+++', '+++')
970 text
= string
.replace(text
,'\\$$', '$$')
972 extract_passthroughs
= staticmethod(extract_passthroughs
)
974 # Replace passthough placeholders with the original passthrough text.
975 def restore_passthroughs(text
, passthroughs
):
976 for i
,v
in enumerate(passthroughs
):
977 text
= string
.replace(text
, '\t'+str(i
)+'\t', passthroughs
[i
], 1)
979 restore_passthroughs
= staticmethod(restore_passthroughs
)
981 def subs_1(s
,options
):
982 '''Perform substitution specified in 'options' (in 'options' order) on
983 a single line 's' of text. Returns the substituted string.'''
988 if o
== 'specialcharacters':
989 result
= config
.subs_specialchars(result
)
992 result
= subs_quotes(result
)
994 elif o
== 'specialwords':
995 result
= config
.subs_specialwords(result
)
997 elif o
== 'replacements':
998 result
= config
.subs_replacements(result
)
1001 result
= macros
.subs(result
)
1002 elif o
== 'callouts':
1003 result
= macros
.subs(result
,callouts
=True)
1004 elif o
== 'passthroughs':
1007 raise EAsciiDoc
,'illegal substitution option: %s' % o
1009 subs_1
= staticmethod(subs_1
)
1011 def subs(lines
,options
):
1012 '''Perform inline processing specified by 'options' (in 'options'
1013 order) on sequence of 'lines'.'''
1014 if len(options
) == 1:
1015 if options
[0] == 'none':
1017 elif options
[0] == 'normal':
1018 options
= SUBS_NORMAL
1019 elif options
[0] == 'verbatim':
1020 options
= SUBS_VERBATIM
1021 if not lines
or not options
:
1023 # Join lines so quoting can span multiple lines.
1024 para
= string
.join(lines
,"\n")
1025 if 'passthroughs' in options
:
1027 para
= Lex
.extract_passthroughs(para
,passthroughs
)
1029 if o
== 'attributes':
1030 # If we don't substitute attributes line-by-line then a single
1031 # undefined attribute will drop the entire paragraph.
1032 lines
= subs_attrs(para
.split("\n"))
1033 para
= string
.join(lines
,"\n")
1035 para
= Lex
.subs_1(para
,(o
,))
1036 if 'passthroughs' in options
:
1037 para
= Lex
.restore_passthroughs(para
,passthroughs
)
1038 return para
.split("\n")
1039 subs
= staticmethod(subs
)
1041 def set_margin(lines
, margin
=0):
1042 '''Utility routine that sets the left margin to 'margin' space in a
1043 block of non-blank lines.'''
1044 # Calculate width of block margin.
1046 width
= len(lines
[0])
1048 i
= re
.search(r
'\S',s
).start()
1049 if i
< width
: width
= i
1050 # Strip margin width from all lines.
1051 for i
in range(len(lines
)):
1052 lines
[i
] = ' '*margin
+ lines
[i
][width
:]
1054 set_margin
= staticmethod(set_margin
)
1056 #---------------------------------------------------------------------------
1057 # Document element classes parse AsciiDoc reader input and write DocBook writer
1059 #---------------------------------------------------------------------------
1062 self
.doctype
= None # 'article','manpage' or 'book'.
1063 self
.backend
= None # -b option argument.
1064 self
.infile
= None # Source file name.
1065 self
.outfile
= None # Output file name.
1066 self
.attributes
= {}
1067 self
.level
= 0 # 0 => front matter. 1,2,3 => sect1,2,3.
1068 self
.has_errors
= False # Set true if processing errors were flagged.
1069 self
.safe
= True # Default safe mode.
1070 def init_attrs(self
):
1071 # Set implicit attributes.
1072 d
= time
.localtime(time
.time())
1073 self
.attributes
['localdate'] = time
.strftime('%d-%b-%Y',d
)
1074 s
= time
.strftime('%H:%M:%S',d
)
1076 self
.attributes
['localtime'] = s
+ ' ' + time
.tzname
[1]
1078 self
.attributes
['localtime'] = s
+ ' ' + time
.tzname
[0]
1079 self
.attributes
['asciidoc-version'] = VERSION
1080 self
.attributes
['backend'] = document
.backend
1081 self
.attributes
['doctype'] = document
.doctype
1082 self
.attributes
['backend-'+document
.backend
] = ''
1083 self
.attributes
['doctype-'+document
.doctype
] = ''
1084 self
.attributes
[document
.backend
+'-'+document
.doctype
] = ''
1085 self
.attributes
['asciidoc-dir'] = APP_DIR
1086 self
.attributes
['user-dir'] = USER_DIR
1087 if self
.infile
!= '<stdin>':
1088 self
.attributes
['infile'] = self
.infile
1089 self
.attributes
['docdir'] = os
.path
.dirname(self
.infile
)
1090 self
.attributes
['docname'] = os
.path
.splitext(
1091 os
.path
.basename(self
.infile
))[0]
1092 # Update with configuration file attributes.
1093 self
.attributes
.update(config
.conf_attrs
)
1094 # Update with command-line attributes.
1095 self
.attributes
.update(config
.cmd_attrs
)
1096 # Filter miscellaneous configuration section entries from attributes.
1097 config
.load_miscellaneous(config
.conf_attrs
)
1098 config
.load_miscellaneous(config
.cmd_attrs
)
1099 self
.attributes
['newline'] = config
.newline
# Use raw (unescaped) value.
1101 if self
.outfile
!= '<stdout>':
1102 self
.attributes
['outfile'] = self
.outfile
1103 ext
= os
.path
.splitext(self
.outfile
)[1][1:]
1104 elif config
.outfilesuffix
:
1105 ext
= config
.outfilesuffix
[1:]
1109 self
.attributes
['filetype'] = ext
1110 self
.attributes
['filetype-'+ext
] = ''
1111 def translate(self
):
1112 assert self
.doctype
in ('article','manpage','book'), \
1113 'illegal document type'
1114 assert self
.level
== 0
1115 config
.expand_all_templates()
1116 # Process document header.
1117 AttributeEntry
.translate_all()
1118 has_header
= Lex
.next() is Title
and Title
.level
== 0
1119 if self
.doctype
== 'manpage' and not has_header
:
1120 error('manpage document title is mandatory')
1123 # Command-line entries override header derived entries.
1124 self
.attributes
.update(config
.cmd_attrs
)
1125 if config
.header_footer
:
1126 hdr
= config
.subs_section('header',{})
1128 if self
.doctype
in ('article','book'):
1129 # Translate 'preamble' (untitled elements between header
1130 # and first section title).
1131 if Lex
.next() is not Title
:
1132 stag
,etag
= config
.section2tags('preamble')
1134 Section
.translate_body()
1137 # Translate manpage SYNOPSIS.
1138 if Lex
.next() is not Title
:
1139 error('SYNOPSIS section expected')
1142 if string
.upper(Title
.dict['title']) <> 'SYNOPSIS':
1143 error('second section must be named SYNOPSIS')
1144 if Title
.level
!= 1:
1145 error('SYNOPSIS section title must be at level 1')
1147 d
.update(Title
.dict)
1148 AttributeList
.consume(d
)
1149 stag
,etag
= config
.section2tags('sect-synopsis',d
)
1151 Section
.translate_body()
1154 if config
.header_footer
:
1155 hdr
= config
.subs_section('header',{})
1157 if Lex
.next() is not Title
:
1158 Section
.translate_body()
1159 # Process remaining sections.
1160 while not reader
.eof():
1161 if Lex
.next() is not Title
:
1162 raise EAsciiDoc
,'section title expected'
1164 Section
.setlevel(0) # Write remaining unwritten section close tags.
1165 # Substitute document parameters and write document footer.
1166 if config
.header_footer
:
1167 ftr
= config
.subs_section('footer',{})
1169 def parse_author(self
,s
):
1170 attrs
= self
.attributes
# Alias for readability.
1172 if not s
: # An undefined attribute has dropped the author line.
1175 mo
= re
.match(r
'^(?P<name1>[^<>\s]+)'
1176 '(\s+(?P<name2>[^<>\s]+))?'
1177 '(\s+(?P<name3>[^<>\s]+))?'
1178 '(\s+<(?P<email>\S+)>)?$',s
)
1180 error('malformed author line')
1182 firstname
= mo
.group('name1')
1183 if mo
.group('name3'):
1184 middlename
= mo
.group('name2')
1185 lastname
= mo
.group('name3')
1188 lastname
= mo
.group('name2')
1189 email
= mo
.group('email')
1191 initials
= firstname
[0]
1193 author
+= ' '+middlename
1194 initials
+= middlename
[0]
1196 author
+= ' '+lastname
1197 initials
+= lastname
[0]
1198 initials
= string
.upper(initials
)
1200 attrs
['firstname'] = config
.subs_specialchars(firstname
)
1202 attrs
['middlename'] = config
.subs_specialchars(middlename
)
1204 attrs
['lastname'] = config
.subs_specialchars(lastname
)
1206 attrs
['author'] = config
.subs_specialchars(author
)
1208 attrs
['authorinitials'] = config
.subs_specialchars(initials
)
1210 attrs
['email'] = email
1213 '''Static methods and attributes only.'''
1215 raise AssertionError,'no class instances allowed'
1217 assert Lex
.next() is Title
and Title
.level
== 0
1219 attrs
= document
.attributes
# Alias for readability.
1220 attrs
['doctitle'] = Title
.dict['title']
1221 if document
.doctype
== 'manpage':
1222 # manpage title formatted like mantitle(manvolnum).
1223 mo
= re
.match(r
'^(?P<mantitle>.*)\((?P<manvolnum>.*)\)$',
1226 error('malformed manpage title')
1228 attrs
['mantitle'] = mo
.group('mantitle').lower().strip()
1229 attrs
['manvolnum'] = mo
.group('manvolnum').strip()
1230 AttributeEntry
.translate_all()
1231 s
= reader
.read_next()
1234 document
.parse_author(s
)
1235 AttributeEntry
.translate_all()
1236 if reader
.read_next():
1237 # Parse revision line.
1241 # Match RCS/CVS $Id$ marker format.
1242 mo
= re
.match(r
'^\$Id: \S+ (?P<revision>\S+)'
1243 ' (?P<date>\S+) \S+ \S+ \S+ \$$',s
)
1245 # Match AsciiDoc revision,date format.
1246 mo
= re
.match(r
'^\D*(?P<revision>.*?),(?P<date>.+)$',s
)
1248 revision
= mo
.group('revision').strip()
1249 date
= mo
.group('date').strip()
1254 attrs
['revision'] = config
.subs_specialchars(revision
)
1256 attrs
['date'] = config
.subs_specialchars(date
)
1257 AttributeEntry
.translate_all()
1258 if document
.backend
== 'linuxdoc' and not attrs
.has_key('author'):
1259 warning('linuxdoc requires author name')
1260 if document
.doctype
== 'manpage':
1261 # Translate mandatory NAME section.
1262 if Lex
.next() is not Title
:
1263 error('NAME section expected')
1266 if string
.upper(Title
.dict['title']) <> 'NAME':
1267 error('first section must be named NAME')
1268 if Title
.level
!= 1:
1269 error('NAME section title must be at level 1')
1270 if not isinstance(Lex
.next(),Paragraph
):
1271 error('malformed NAME section body')
1272 lines
= reader
.read_until(r
'^$')
1273 s
= string
.join(lines
)
1274 mo
= re
.match(r
'^(?P<manname>.*?)\s+-\s+(?P<manpurpose>.*)$',s
)
1276 error('malformed NAME section body')
1277 attrs
['manname'] = string
.strip(mo
.group('manname'))
1278 attrs
['manpurpose'] = string
.strip(mo
.group('manpurpose'))
1279 if attrs
.get('author',None) or attrs
.get('email',None):
1280 attrs
['authored'] = ''
1281 translate
= staticmethod(translate
)
1283 class AttributeEntry
:
1284 '''Static methods and attributes only.'''
1289 raise AssertionError,'no class instances allowed'
1291 result
= False # Assume not next.
1292 if not AttributeEntry
.pattern
:
1293 pat
= document
.attributes
.get('attributeentry-pattern')
1295 error("[attributes] missing 'attributeentry-pattern' entry")
1296 AttributeEntry
.pattern
= pat
1297 line
= reader
.read_next()
1299 mo
= re
.match(AttributeEntry
.pattern
,line
)
1301 name
= mo
.group('attrname').strip()
1302 if name
[-1] == '!': # Names like name! are None.
1306 value
= mo
.group('attrvalue').strip()
1307 # Strip white space and illegal name chars.
1308 name
= re
.sub(r
'(?u)[^\w\-_]', '', name
).lower()
1309 AttributeEntry
.name
= name
1310 AttributeEntry
.value
= value
1313 isnext
= staticmethod(isnext
)
1315 assert Lex
.next() is AttributeEntry
1316 attr
= AttributeEntry
# Alias for brevity.
1317 reader
.read() # Discard attribute from reader.
1318 # Don't override command-line attributes.
1319 if config
.cmd_attrs
.has_key(attr
.name
):
1321 # Update document.attributes from previously parsed attribute.
1323 attr
.value
= config
.subs_specialchars(attr
.value
)
1324 attr
.value
= subs_attrs(attr
.value
)
1325 if attr
.value
is not None:
1326 document
.attributes
[attr
.name
] = attr
.value
1327 # Some document Header attributes get special treatment.
1328 if attr
.name
== 'author':
1329 document
.parse_author(attr
.value
)
1330 elif document
.attributes
.has_key(attr
.name
):
1331 del document
.attributes
[attr
.name
]
1332 translate
= staticmethod(translate
)
1333 def translate_all():
1334 ''' Process all contiguous attribute lines on reader.'''
1335 while AttributeEntry
.isnext():
1336 AttributeEntry
.translate()
1337 translate_all
= staticmethod(translate_all
)
1339 class AttributeList
:
1340 '''Static methods and attributes only.'''
1345 raise AssertionError,'no class instances allowed'
1347 result
= False # Assume not next.
1348 if not AttributeList
.pattern
:
1349 if not document
.attributes
.has_key('attributelist-pattern'):
1350 error("[miscellaneous] missing 'attributelist-pattern' entry")
1351 AttributeList
.pattern
= document
.attributes
['attributelist-pattern']
1352 line
= reader
.read_next()
1354 mo
= re
.match(AttributeList
.pattern
, line
)
1356 AttributeList
.match
= mo
1359 isnext
= staticmethod(isnext
)
1361 assert Lex
.next() is AttributeList
1362 reader
.read() # Discard attribute list from reader.
1363 d
= AttributeList
.match
.groupdict()
1364 for k
,v
in d
.items():
1369 parse_attributes(v
, AttributeList
.attrs
)
1371 AttributeList
.attrs
[k
] = v
1372 translate
= staticmethod(translate
)
1374 '''Add attribute list to the dictionary 'd' and reset the
1376 if AttributeList
.attrs
:
1377 d
.update(AttributeList
.attrs
)
1378 AttributeList
.attrs
= {}
1379 consume
= staticmethod(consume
)
1382 '''Static methods and attributes only.'''
1386 raise AssertionError,'no class instances allowed'
1388 result
= False # Assume not next.
1389 line
= reader
.read_next()
1391 mo
= re
.match(BlockTitle
.pattern
,line
)
1393 BlockTitle
.title
= mo
.group('title')
1396 isnext
= staticmethod(isnext
)
1398 assert Lex
.next() is BlockTitle
1399 reader
.read() # Discard title from reader.
1400 # Perform title substitutions.
1401 s
= Lex
.subs((BlockTitle
.title
,), Title
.subs
)
1402 s
= string
.join(s
,writer
.newline
)
1404 warning('blank block title')
1405 BlockTitle
.title
= s
1406 translate
= staticmethod(translate
)
1408 '''If there is a title add it to dictionary 'd' then reset title.'''
1409 if BlockTitle
.title
:
1410 d
['title'] = BlockTitle
.title
1411 BlockTitle
.title
= None
1412 consume
= staticmethod(consume
)
1415 '''Processes Header and Section titles. Static methods and attributes
1418 underlines
= ('==','--','~~','^^','++') # Levels 0,1,2,3,4.
1419 subs
= ('specialcharacters','quotes','replacements','attributes','macros')
1424 section_numbers
= [0]*len(underlines
)
1426 linecount
= None # Number of lines in title (1 or 2).
1428 raise AssertionError,'no class instances allowed'
1430 '''Parse the Title.dict and Title.level from the reader. The
1431 real work has already been done by parse().'''
1432 assert Lex
.next() is Title
1433 # Discard title from reader.
1434 for i
in range(Title
.linecount
):
1437 # Perform title substitutions.
1438 s
= Lex
.subs((Title
.dict['title'],), Title
.subs
)
1439 s
= string
.join(s
,writer
.newline
)
1441 warning('blank section title')
1442 Title
.dict['title'] = s
1443 translate
= staticmethod(translate
)
1445 lines
= reader
.read_ahead(2)
1446 return Title
.parse(lines
)
1447 isnext
= staticmethod(isnext
)
1449 '''Parse title at start of lines tuple.'''
1450 if len(lines
) == 0: return False
1451 if len(lines
[0]) == 0: return False # Title can't be blank.
1452 # Check for single-line titles.
1454 for level
in range(len(Title
.underlines
)):
1455 k
= 'sect%s' % level
1456 if Title
.dump_dict
.has_key(k
):
1457 mo
= re
.match(Title
.dump_dict
[k
], lines
[0])
1459 Title
.dict = mo
.groupdict()
1465 # Check for double-line titles.
1466 if not Title
.pattern
: return False # Single-line titles only.
1467 if len(lines
) < 2: return False
1468 title
,ul
= lines
[:2]
1469 title_len
= char_len(title
)
1470 ul_len
= char_len(ul
)
1471 if ul_len
< 2: return False
1472 # Fast elimination check.
1473 if ul
[:2] not in Title
.underlines
: return False
1474 # Length of underline must be within +-3 of title.
1475 if not (ul_len
-3 < title_len
< ul_len
+3): return False
1476 # Check for valid repetition of underline character pairs.
1477 s
= ul
[:2]*((ul_len
+1)/2)
1478 if ul
!= s
[:ul_len
]: return False
1479 # Don't be fooled by back-to-back delimited blocks, require at
1480 # least one alphanumeric character in title.
1481 if not re
.search(r
'(?u)\w',title
): return False
1482 mo
= re
.match(Title
.pattern
, title
)
1484 Title
.dict = mo
.groupdict()
1485 Title
.level
= list(Title
.underlines
).index(ul
[:2])
1488 # Check for expected pattern match groups.
1490 if not Title
.dict.has_key('title'):
1491 warning('[titles] entry has no <title> group')
1492 Title
.dict['title'] = lines
[0]
1493 for k
,v
in Title
.dict.items():
1494 if v
is None: del Title
.dict[k
]
1496 parse
= staticmethod(parse
)
1498 '''Load and validate [titles] section entries from dict.'''
1499 if dict.has_key('underlines'):
1500 errmsg
= 'malformed [titles] underlines entry'
1502 underlines
= parse_list(dict['underlines'])
1504 raise EAsciiDoc
,errmsg
1505 if len(underlines
) != len(Title
.underlines
):
1506 raise EAsciiDoc
,errmsg
1507 for s
in underlines
:
1509 raise EAsciiDoc
,errmsg
1510 Title
.underlines
= tuple(underlines
)
1511 Title
.dump_dict
['underlines'] = dict['underlines']
1512 if dict.has_key('subs'):
1513 Title
.subs
= parse_options(dict['subs'], SUBS_OPTIONS
,
1514 'illegal [titles] subs entry')
1515 Title
.dump_dict
['subs'] = dict['subs']
1516 if dict.has_key('sectiontitle'):
1517 pat
= dict['sectiontitle']
1518 if not pat
or not is_regexp(pat
):
1519 raise EAsciiDoc
,'malformed [titles] sectiontitle entry'
1521 Title
.dump_dict
['sectiontitle'] = pat
1522 if dict.has_key('blocktitle'):
1523 pat
= dict['blocktitle']
1524 if not pat
or not is_regexp(pat
):
1525 raise EAsciiDoc
,'malformed [titles] blocktitle entry'
1526 BlockTitle
.pattern
= pat
1527 Title
.dump_dict
['blocktitle'] = pat
1528 # Load single-line title patterns.
1529 for k
in ('sect0','sect1','sect2','sect3','sect4'):
1532 if not pat
or not is_regexp(pat
):
1533 raise EAsciiDoc
,'malformed [titles] %s entry' % k
1534 Title
.dump_dict
[k
] = pat
1535 # TODO: Check we have either a Title.pattern or at least one
1536 # single-line title pattern -- can this be done here or do we need
1537 # check routine like the other block checkers?
1538 load
= staticmethod(load
)
1540 dump_section('titles',Title
.dump_dict
)
1541 dump
= staticmethod(dump
)
1543 '''Set Title section name. First search for section title in
1544 [specialsections], if not found use default 'sect<level>' name.'''
1545 for pat
,sect
in config
.specialsections
.items():
1546 mo
= re
.match(pat
,Title
.dict['title'])
1548 title
= mo
.groupdict().get('title')
1549 if title
is not None:
1550 Title
.dict['title'] = string
.strip(title
)
1552 Title
.dict['title'] = string
.strip(mo
.group())
1553 Title
.sectname
= sect
1556 Title
.sectname
= 'sect%d' % Title
.level
1557 setsectname
= staticmethod(setsectname
)
1558 def getnumber(level
):
1559 '''Return next section number at section 'level' formatted like
1562 for l
in range(len(Title
.section_numbers
)):
1563 n
= Title
.section_numbers
[l
]
1567 number
= '%s%d.' % (number
, n
)
1569 number
= '%s%d.' % (number
, n
+ 1)
1570 Title
.section_numbers
[l
] = n
+ 1
1572 # Reset unprocessed section levels.
1573 Title
.section_numbers
[l
] = 0
1575 getnumber
= staticmethod(getnumber
)
1579 '''Static methods and attributes only.'''
1580 endtags
= [] # Stack of currently open section (level,endtag) tuples.
1582 raise AssertionError,'no class instances allowed'
1583 def savetag(level
,etag
):
1584 '''Save section end.'''
1585 Section
.endtags
.append((level
,etag
))
1586 savetag
= staticmethod(savetag
)
1587 def setlevel(level
):
1588 '''Set document level and write open section close tags up to level.'''
1589 while Section
.endtags
and Section
.endtags
[-1][0] >= level
:
1590 writer
.write(Section
.endtags
.pop()[1])
1591 document
.level
= level
1592 setlevel
= staticmethod(setlevel
)
1594 assert Lex
.next() is Title
1595 prev_sectname
= Title
.sectname
1597 if Title
.level
== 0 and document
.doctype
!= 'book':
1598 error('only book doctypes can contain level 0 sections')
1599 if Title
.level
> document
.level \
1600 and document
.backend
== 'docbook' \
1601 and prev_sectname
in ('sect-colophon','sect-abstract', \
1602 'sect-dedication','sect-glossary','sect-bibliography'):
1603 error('%s section cannot contain sub-sections' % prev_sectname
)
1604 if Title
.level
> document
.level
+1:
1605 # Sub-sections of multi-part book level zero Preface and Appendices
1606 # are meant to be out of sequence.
1607 if document
.doctype
== 'book' \
1608 and document
.level
== 0 \
1609 and Title
.level
== 2 \
1610 and prev_sectname
in ('sect-preface','sect-appendix'):
1613 warning('section title out of sequence: '
1614 'expected level %d, got level %d'
1615 % (document
.level
+1, Title
.level
))
1616 Section
.setlevel(Title
.level
)
1617 Title
.dict['sectnum'] = Title
.getnumber(document
.level
)
1618 AttributeList
.consume(Title
.dict)
1619 stag
,etag
= config
.section2tags(Title
.sectname
,Title
.dict)
1620 Section
.savetag(Title
.level
,etag
)
1622 Section
.translate_body()
1623 translate
= staticmethod(translate
)
1624 def translate_body(terminator
=Title
):
1627 while next
and next
is not terminator
:
1628 if next
is Title
and isinstance(terminator
,DelimitedBlock
):
1629 error('title not permitted in sidebar body')
1630 if document
.backend
== 'linuxdoc' \
1631 and document
.level
== 0 \
1632 and not isinstance(next
,Paragraph
):
1633 warning('only paragraphs are permitted in linuxdoc synopsis')
1637 # The section is not empty if contains a subsection.
1638 if next
and isempty
and Title
.level
> document
.level
:
1640 # Report empty sections if invalid markup will result.
1642 if document
.backend
== 'docbook' and Title
.sectname
!= 'sect-index':
1643 error('empty section is not valid')
1644 translate_body
= staticmethod(translate_body
)
1646 class AbstractBlock
:
1648 self
.OPTIONS
= () # The set of allowed options values
1649 # Configuration parameter names common to all blocks.
1650 self
.CONF_ENTRIES
= ('options','subs','presubs','postsubs',
1651 'posattrs','style','.*-style')
1652 # Configuration parameters.
1653 self
.name
=None # Configuration file section name.
1654 self
.delimiter
=None # Regular expression matching block delimiter.
1655 self
.template
=None # template section entry.
1656 self
.options
=() # options entry list.
1657 self
.presubs
=SUBS_NORMAL
# presubs/subs entry list.
1658 self
.postsubs
=() # postsubs entry list.
1659 self
.filter=None # filter entry.
1660 self
.posattrs
=() # posattrs entry list.
1661 self
.style
=None # Default style.
1662 self
.styles
=OrderedDict() # Styles dictionary.
1663 # Before a block is processed it's attributes (from it's
1664 # attributes list) are merged with the block configuration parameters
1665 # (by self.process_attributes()) resulting in the template substitution
1666 # dictionary (self.attributes) and the block's procssing parameters
1667 # (self.parameters).
1669 # The names of block parameters.
1670 self
.PARAM_NAMES
=('template','options','presubs','postsubs','filter')
1672 # Leading delimiter match object.
1674 def is_conf_entry(self
,param
):
1675 '''Return True if param matches an allowed configuration file entry
1677 for s
in self
.CONF_ENTRIES
:
1678 if re
.match('^'+s
+'$',param
):
1681 def load(self
,name
,entries
):
1682 '''Update block definition from section 'entries' dictionary.'''
1683 for k
in entries
.keys():
1684 if not self
.is_conf_entry(k
):
1685 raise EAsciiDoc
,'illegal [%s] entry name: %s' % (name
,k
)
1687 for k
,v
in entries
.items():
1690 'malformed [%s] entry name: %s' % (name
,k
)
1691 if k
== 'delimiter':
1692 if v
and is_regexp(v
):
1695 raise EAsciiDoc
,'malformed [%s] regexp: %s' % (name
,v
)
1696 elif k
== 'template':
1699 'malformed [%s] template name: %s' % (name
,v
)
1704 'malformed [%s] style name: %s' % (name
,v
)
1706 elif k
== 'posattrs':
1707 self
.posattrs
= parse_options(v
, (),
1708 'illegal [%s] %s: %s' % (name
,k
,v
))
1709 elif k
== 'options':
1710 self
.options
= parse_options(v
,self
.OPTIONS
,
1711 'illegal [%s] %s: %s' % (name
,k
,v
))
1712 elif k
== 'presubs' or k
== 'subs':
1713 self
.presubs
= parse_options(v
,SUBS_OPTIONS
,
1714 'illegal [%s] %s: %s' % (name
,k
,v
))
1715 elif k
== 'postsubs':
1716 self
.postsubs
= parse_options(v
,SUBS_OPTIONS
,
1717 'illegal [%s] %s: %s' % (name
,k
,v
))
1721 mo
= re
.match(r
'^(?P<style>.*)-style$',k
)
1724 raise EAsciiDoc
, 'empty [%s] style: %s' % (name
,k
)
1725 style
= mo
.group('style')
1727 if not parse_named_attributes(v
,d
):
1728 raise EAsciiDoc
,'malformed [%s] style: %s' % (name
,v
)
1729 self
.styles
[style
] = d
1731 '''Write block definition to stdout.'''
1732 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
1733 write('['+self
.name
+']')
1734 if self
.is_conf_entry('delimiter'):
1735 write('delimiter='+self
.delimiter
)
1737 write('template='+self
.template
)
1739 write('options='+string
.join(self
.options
,','))
1742 write('presubs='+string
.join(self
.presubs
,','))
1744 write('subs='+string
.join(self
.presubs
,','))
1746 write('postsubs='+string
.join(self
.postsubs
,','))
1748 write('filter='+self
.filter)
1750 write('posattrs='+string
.join(self
.posattrs
,','))
1752 write('style='+self
.style
)
1754 for style
,d
in self
.styles
.items():
1756 for k
,v
in d
.items():
1757 if isinstance(v
,tuple):
1758 v
= string
.join(v
,',')
1760 write(style
+'-style='+s
[:-1])
1762 '''Validate block after the complete configuration has been loaded.'''
1763 if self
.is_conf_entry('delimiter') and not self
.delimiter
:
1764 raise EAsciiDoc
,'[%s] missing delimiter' % self
.name
1766 if not self
.styles
.has_key(self
.style
):
1767 warning(' missing [%s] %s-style entry' % (self
.name
,self
.style
))
1768 # Check all styles for missing templates.
1769 all_styles_have_template
= True
1770 for k
,v
in self
.styles
.items():
1771 t
= v
.get('template')
1772 if t
and not config
.sections
.has_key(t
):
1773 warning('[%s] missing template section' % t
)
1775 all_styles_have_template
= False
1776 # Check we have a valid template entry or alternatively that all the
1777 # styles have templates.
1778 if self
.is_conf_entry('template') and not 'skip' in self
.options
:
1780 if not config
.sections
.has_key(self
.template
):
1781 warning('[%s] missing template section' % self
.template
)
1782 elif not all_styles_have_template
:
1783 warning('[%s] styles missing templates' % self
.name
)
1785 '''Check if this block is next in document reader.'''
1787 reader
.skip_blank_lines()
1788 if reader
.read_next():
1789 mo
= re
.match(self
.delimiter
,reader
.read_next())
1794 def translate(self
):
1795 '''Translate block from document reader.'''
1796 raise AssertionError,'no class instances allowed'
1797 def update_params(self
,src
,dst
):
1798 '''Copy block processing parameters from src to dst dictionaries.'''
1799 for k
,v
in src
.items():
1802 elif k
== 'options':
1803 dst
[k
] = parse_options(v
,self
.OPTIONS
,
1804 'illegal [%s] %s: %s' % (self
.name
,k
,v
))
1805 elif k
in ('subs','presubs','postsubs'):
1806 subs
= parse_options(v
,SUBS_OPTIONS
,
1807 'illegal [%s] %s: %s' % (self
.name
,k
,v
))
1809 dst
['presubs'] = subs
1814 def merge_attributes(self
,attrs
):
1815 '''Merge block attributes 'attrs' dictionary with the block
1816 configuration parameters setting self.attributes (template substitution
1817 attributes) and self.parameters (block processing parameters).'''
1818 self
.attributes
= {}
1819 self
.attributes
.update(attrs
)
1820 # Configure positional attributes.
1821 for i
,v
in enumerate(self
.posattrs
):
1822 if self
.attributes
.has_key(str(i
+1)):
1823 self
.attributes
[v
] = self
.attributes
[str(i
+1)]
1824 # Calculate dynamic block parameters.
1825 # Start with configuration file defaults.
1826 self
.parameters
['template'] = self
.template
1827 self
.parameters
['options'] = self
.options
1828 self
.parameters
['presubs'] = self
.presubs
1829 self
.parameters
['postsubs'] = self
.postsubs
1830 self
.parameters
['filter'] = self
.filter
1831 # Load the selected style attributes.
1832 style
= self
.attributes
.get('style',self
.style
)
1833 if style
is not None:
1834 if not self
.styles
.has_key(style
):
1835 warning('missing [%s] %s-style entry' % (self
.name
,style
))
1837 self
.attributes
['style'] = style
1838 for k
,v
in self
.styles
[style
].items():
1839 if k
in self
.PARAM_NAMES
:
1840 self
.parameters
[k
] = v
1841 elif not self
.attributes
.has_key(k
):
1842 # Style attributes don't take precedence over explicit.
1843 self
.attributes
[k
] = v
1844 # Override config and style attributes with document attributes.
1845 self
.update_params(self
.attributes
,self
.parameters
)
1846 assert isinstance(self
.parameters
['options'],tuple)
1847 assert isinstance(self
.parameters
['presubs'],tuple)
1848 assert isinstance(self
.parameters
['postsubs'],tuple)
1849 def get_options(self
):
1850 return self
.parameters
['options']
1852 return (self
.parameters
['presubs'], self
.parameters
['postsubs'])
1853 def get_template(self
):
1854 return self
.parameters
['template']
1855 def get_filter(self
):
1856 return self
.parameters
['filter']
1858 class AbstractBlocks
:
1859 '''List of block definitions.'''
1860 PREFIX
= '' # Conf file section name prefix set in derived classes.
1861 BLOCK_TYPE
= None # Block type set in derived classes.
1864 self
.blocks
= [] # List of Block objects.
1865 self
.default
= None # Default Block.
1866 self
.delimiter
= None # Combined tables delimiter regular expression.
1867 def load(self
,sections
):
1868 '''Load block definition from 'sections' dictionary.'''
1869 for k
in sections
.keys():
1870 if re
.match(r
'^'+ self
.PREFIX
+ r
'.+$',k
):
1872 parse_entries(sections
.get(k
,()),d
)
1873 for b
in self
.blocks
:
1877 b
= self
.BLOCK_TYPE()
1878 self
.blocks
.append(b
)
1882 raise EAsciiDoc
,'[%s] %s' % (k
,str(e
))
1884 for b
in self
.blocks
:
1887 for b
in self
.blocks
:
1893 '''Validate the block definitions.'''
1894 # Validate delimiters and build combined lists delimiter pattern.
1895 for b
in self
.blocks
:
1896 assert b
.__class
__ is self
.BLOCK_TYPE
1898 if b
.is_conf_entry('delimiter'):
1900 for b
in self
.blocks
:
1903 delimiters
.append(b
.delimiter
)
1904 self
.delimiter
= join_regexp(delimiters
)
1906 class Paragraph(AbstractBlock
):
1908 AbstractBlock
.__init
__(self
)
1909 self
.CONF_ENTRIES
+= ('delimiter','template','filter')
1910 self
.OPTIONS
= ('listelement',)
1911 self
.text
=None # Text in first line of paragraph.
1912 def load(self
,name
,entries
):
1913 AbstractBlock
.load(self
,name
,entries
)
1915 AbstractBlock
.dump(self
)
1916 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
1919 result
= AbstractBlock
.isnext(self
)
1921 self
.text
= self
.mo
.groupdict().get('text')
1923 def translate(self
):
1925 attrs
.update(self
.mo
.groupdict())
1926 BlockTitle
.consume(attrs
)
1927 AttributeList
.consume(attrs
)
1928 self
.merge_attributes(attrs
)
1929 reader
.read() # Discard (already parsed item first line).
1930 body
= reader
.read_until(r
'^\+$|^$|'+blocks
.delimiter
+r
'|'+tables
.delimiter
)
1931 body
= [self
.text
] + list(body
)
1932 presubs
,postsubs
= self
.get_subs()
1933 # Don't join verbatim paragraphs.
1934 if 'verbatim' not in (presubs
+ postsubs
):
1935 body
= join_lines(body
)
1936 body
= Lex
.set_margin(body
) # Move body to left margin.
1937 body
= Lex
.subs(body
,presubs
)
1938 if self
.get_filter():
1939 body
= filter_lines(self
.get_filter(),body
,self
.attributes
)
1940 body
= Lex
.subs(body
,postsubs
)
1941 template
= self
.get_template()
1942 stag
,etag
= config
.section2tags(template
, self
.attributes
)
1943 # Write start tag, content, end tag.
1944 writer
.write(dovetail_tags(stag
,body
,etag
))
1946 class Paragraphs(AbstractBlocks
):
1947 '''List of paragraph definitions.'''
1948 BLOCK_TYPE
= Paragraph
1951 AbstractBlocks
.__init
__(self
)
1952 def load(self
,sections
):
1953 AbstractBlocks
.load(self
,sections
)
1955 AbstractBlocks
.validate(self
)
1956 # Check we have a default paragraph definition, put it last in list.
1957 for b
in self
.blocks
:
1958 if b
.name
== 'paradef-default':
1959 self
.blocks
.append(b
)
1961 self
.blocks
.remove(b
)
1964 raise EAsciiDoc
,'missing [paradef-default] section'
1966 class List(AbstractBlock
):
1967 TAGS
= ('listtag','itemtag','texttag','entrytag','labeltag')
1968 TYPES
= ('bulleted','numbered','labeled','callout')
1970 AbstractBlock
.__init
__(self
)
1971 self
.CONF_ENTRIES
+= ('delimiter','type') + self
.TAGS
1974 self
.texttag
=None # Tag for list item text.
1975 self
.labeltag
=None # Variable lists only.
1976 self
.entrytag
=None # Variable lists only.
1977 self
.label
=None # List item label (labeled lists).
1978 self
.text
=None # Text in first line of list item.
1979 self
.index
=None # Matched delimiter 'index' group (numbered lists).
1980 self
.type=None # List type.
1981 def load(self
,name
,entries
):
1982 AbstractBlock
.load(self
,name
,entries
)
1983 for k
,v
in entries
.items():
1988 raise EAsciiDoc
,'illegal list type: %s' % v
1989 elif k
in self
.TAGS
:
1993 raise EAsciiDoc
,'illegal list %s name: %s' % (k
,v
)
1995 AbstractBlock
.dump(self
)
1996 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
1997 write('type='+self
.type)
1998 write('listtag='+self
.listtag
)
1999 write('itemtag='+self
.itemtag
)
2000 write('texttag='+self
.texttag
)
2001 if self
.type == 'labeled':
2002 write('entrytag='+self
.entrytag
)
2003 write('labeltag='+self
.labeltag
)
2006 result
= AbstractBlock
.isnext(self
)
2008 self
.label
= self
.mo
.groupdict().get('label')
2009 self
.text
= self
.mo
.groupdict().get('text')
2010 self
.index
= self
.mo
.groupdict().get('index')
2012 def translate_entry(self
):
2013 assert self
.type == 'labeled'
2014 stag
,etag
= config
.tag(self
.entrytag
, self
.attributes
)
2018 # Horizontal label list.
2019 reader
.read() # Discard (already parsed item first line).
2020 writer
.write_tag(self
.labeltag
, [self
.label
],
2021 self
.presubs
, self
.attributes
)
2023 # Write multiple labels (vertical label list).
2024 while Lex
.next() is self
:
2025 reader
.read() # Discard (already parsed item first line).
2026 writer
.write_tag(self
.labeltag
, [self
.label
],
2027 self
.presubs
, self
.attributes
)
2029 self
.translate_item()
2032 def iscontinued(self
):
2033 if reader
.read_next() == '+':
2034 reader
.read() # Discard.
2035 # Allow attribute list to precede continued list item element.
2036 while Lex
.next() is AttributeList
:
2037 Lex
.next().translate()
2041 def translate_item(self
,listindex
=None):
2043 self
.translate_item_2(listindex
)
2045 self
.translate_item_1(listindex
)
2046 def translate_item_1(self
,listindex
=None):
2047 '''Translation for '+' style list continuation.'''
2048 if self
.type == 'callout':
2049 self
.attributes
['coids'] = calloutmap
.calloutids(listindex
)
2050 stag
,etag
= config
.tag(self
.itemtag
, self
.attributes
)
2053 if self
.text
and self
.text
== '+':
2054 # Pathalogical case: continued Horizontal Labeled List with no
2057 elif not self
.text
and self
.iscontinued():
2058 # Pathalogical case: continued Vertical Labeled List with no
2063 text
= reader
.read_until(lists
.delimiter
+ r
'|^\+$|^$|' +
2064 blocks
.delimiter
+ r
'|' + tables
.delimiter
)
2065 if self
.text
is not None:
2066 text
= [self
.text
] + list(text
)
2067 text
= join_lines(text
)
2068 writer
.write_tag(self
.texttag
, text
, self
.presubs
, self
.attributes
)
2069 continued
= self
.iscontinued()
2072 if next
in lists
.open:
2074 elif isinstance(next
,List
):
2076 elif isinstance(next
,Paragraph
) and 'listelement' in next
.options
:
2079 if next
is Title
or next
is BlockTitle
:
2080 error('title not allowed in list item continuation')
2084 continued
= self
.iscontinued()
2087 def translate_item_2(self
,listindex
=None):
2088 '''Translation for List block style lists.'''
2089 if self
.type == 'callout':
2090 self
.attributes
['coids'] = calloutmap
.calloutids(listindex
)
2091 stag
,etag
= config
.tag(self
.itemtag
, self
.attributes
)
2094 if self
.text
or reader
.read_next():
2096 text
= reader
.read_until(lists
.delimiter
+ r
'|^$|' +
2097 blocks
.delimiter
+ r
'|' + tables
.delimiter
)
2098 if self
.text
is not None:
2099 text
= [self
.text
] + list(text
)
2100 text
= join_lines(text
)
2101 writer
.write_tag(self
.texttag
, text
, self
.presubs
, self
.attributes
)
2104 if next
in lists
.open:
2106 elif next
is lists
.listblock
:
2108 elif isinstance(next
,List
):
2110 elif isinstance(next
,Paragraph
) and 'listelement' in next
.options
:
2112 elif lists
.listblock
:
2113 if next
is Title
or next
is BlockTitle
:
2114 error('title not allowed in list item continuation')
2120 def check_index(self
,listindex
):
2121 ''' Check calculated listindex (1,2,...) against the item index in the
2122 document (self.index).'''
2123 assert self
.type in ('numbered','callout')
2126 if re
.match(r
'\d+', self
.index
):
2129 elif re
.match(r
'[a-z]', self
.index
):
2130 i
= ord(self
.index
) - ord('a') + 1
2132 if matched
and i
!= listindex
:
2133 print 'type: ',self
.type,': expected ',listindex
,' got ',i
2134 warning("list item %s out of sequence" % self
.index
)
2135 def translate(self
):
2136 lists
.open.append(self
)
2138 attrs
.update(self
.mo
.groupdict())
2139 BlockTitle
.consume(attrs
)
2140 AttributeList
.consume(attrs
)
2141 self
.merge_attributes(attrs
)
2142 stag
,etag
= config
.tag(self
.listtag
, self
.attributes
)
2146 while Lex
.next() is self
:
2147 if self
.type in ('numbered','callout'):
2149 self
.check_index(listindex
)
2150 if self
.type in ('bulleted','numbered','callout'):
2151 reader
.read() # Discard (already parsed item first line).
2152 self
.translate_item(listindex
)
2153 elif self
.type == 'labeled':
2154 self
.translate_entry()
2156 raise AssertionError,'illegal [%s] list type"' % self
.name
2159 if self
.type == 'callout':
2160 calloutmap
.validate(listindex
)
2161 calloutmap
.listclose()
2164 class Lists(AbstractBlocks
):
2165 '''List of List objects.'''
2169 AbstractBlocks
.__init
__(self
)
2170 self
.open = [] # A stack of the current and parent lists.
2171 self
.listblock
= None # Current list is in list block.
2172 def load(self
,sections
):
2173 AbstractBlocks
.load(self
,sections
)
2175 AbstractBlocks
.validate(self
)
2176 for b
in self
.blocks
:
2177 # Check list has valid type.
2178 if not b
.type in b
.TYPES
:
2179 raise EAsciiDoc
,'[%s] illegal type' % b
.name
2180 # Check all list tags.
2181 if not b
.listtag
or not config
.tags
.has_key(b
.listtag
):
2182 warning('[%s] missing listtag' % b
.name
)
2183 if not b
.itemtag
or not config
.tags
.has_key(b
.itemtag
):
2184 warning('[%s] missing tag itemtag' % b
.name
)
2185 if not b
.texttag
or not config
.tags
.has_key(b
.texttag
):
2186 warning('[%s] missing tag texttag' % b
.name
)
2187 if b
.type == 'labeled':
2188 if not b
.entrytag
or not config
.tags
.has_key(b
.entrytag
):
2189 warning('[%s] missing entrytag' % b
.name
)
2190 if not b
.labeltag
or not config
.tags
.has_key(b
.labeltag
):
2191 warning('[%s] missing labeltag' % b
.name
)
2193 class DelimitedBlock(AbstractBlock
):
2195 AbstractBlock
.__init
__(self
)
2196 self
.CONF_ENTRIES
+= ('delimiter','template','filter')
2197 self
.OPTIONS
= ('skip','sectionbody','list')
2198 def load(self
,name
,entries
):
2199 AbstractBlock
.load(self
,name
,entries
)
2201 AbstractBlock
.dump(self
)
2202 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
2205 return AbstractBlock
.isnext(self
)
2206 def translate(self
):
2207 if 'list' in self
.options
:
2208 lists
.listblock
= self
2209 reader
.read() # Discard delimiter.
2211 # Leave list block attributes for the list element.
2212 if lists
.listblock
is not self
:
2213 BlockTitle
.consume(attrs
)
2214 AttributeList
.consume(attrs
)
2215 self
.merge_attributes(attrs
)
2216 options
= self
.get_options()
2217 if safe() and self
.name
== 'blockdef-backend':
2218 unsafe_error('Backend Block')
2219 # Discard block body.
2220 reader
.read_until(self
.delimiter
,same_file
=True)
2221 elif 'skip' in options
:
2222 # Discard block body.
2223 reader
.read_until(self
.delimiter
,same_file
=True)
2225 template
= self
.get_template()
2226 stag
,etag
= config
.section2tags(template
,self
.attributes
)
2227 if 'sectionbody' in options
or 'list' in options
:
2228 # The body is treated like a SimpleSection.
2230 Section
.translate_body(self
)
2233 body
= reader
.read_until(self
.delimiter
,same_file
=True)
2234 presubs
,postsubs
= self
.get_subs()
2235 body
= Lex
.subs(body
,presubs
)
2236 if self
.get_filter():
2237 body
= filter_lines(self
.get_filter(),body
,self
.attributes
)
2238 body
= Lex
.subs(body
,postsubs
)
2239 # Write start tag, content, end tag.
2240 writer
.write(dovetail_tags(stag
,body
,etag
))
2241 if 'list' in options
:
2242 lists
.listblock
= None
2244 error('closing [%s] delimiter expected' % self
.name
)
2246 delimiter
= reader
.read() # Discard delimiter line.
2247 assert re
.match(self
.delimiter
,delimiter
)
2249 class DelimitedBlocks(AbstractBlocks
):
2250 '''List of delimited blocks.'''
2251 BLOCK_TYPE
= DelimitedBlock
2252 PREFIX
= 'blockdef-'
2254 AbstractBlocks
.__init
__(self
)
2255 def load(self
,sections
):
2256 '''Update blocks defined in 'sections' dictionary.'''
2257 AbstractBlocks
.load(self
,sections
)
2259 AbstractBlocks
.validate(self
)
2264 self
.colalign
= None # 'left','right','center'
2265 self
.rulerwidth
= None
2266 self
.colwidth
= None # Output width in page units.
2268 class Table(AbstractBlock
):
2269 COL_STOP
= r
"(`|'|\.)" # RE.
2270 ALIGNMENTS
= {'`':'left', "'":'right', '.':'center'}
2271 FORMATS
= ('fixed','csv','dsv')
2273 AbstractBlock
.__init
__(self
)
2274 self
.CONF_ENTRIES
+= ('template','fillchar','format','colspec',
2275 'headrow','footrow','bodyrow','headdata',
2276 'footdata', 'bodydata')
2277 # Configuration parameters.
2279 self
.format
=None # 'fixed','csv','dsv'
2287 # Calculated parameters.
2288 self
.underline
=None # RE matching current table underline.
2289 self
.isnumeric
=False # True if numeric ruler.
2290 self
.tablewidth
=None # Optional table width scale factor.
2291 self
.columns
=[] # List of Columns.
2293 self
.check_msg
='' # Message set by previous self.validate() call.
2294 def load(self
,name
,entries
):
2295 AbstractBlock
.load(self
,name
,entries
)
2296 '''Update table definition from section entries in 'entries'.'''
2297 for k
,v
in entries
.items():
2299 if v
and len(v
) == 1:
2302 raise EAsciiDoc
,'malformed table fillchar: %s' % v
2304 if v
in Table
.FORMATS
:
2307 raise EAsciiDoc
,'illegal table format: %s' % v
2308 elif k
== 'colspec':
2310 elif k
== 'headrow':
2312 elif k
== 'footrow':
2314 elif k
== 'bodyrow':
2316 elif k
== 'headdata':
2318 elif k
== 'footdata':
2320 elif k
== 'bodydata':
2323 AbstractBlock
.dump(self
)
2324 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
2325 write('fillchar='+self
.fillchar
)
2326 write('format='+self
.format
)
2328 write('colspec='+self
.colspec
)
2330 write('headrow='+self
.headrow
)
2332 write('footrow='+self
.footrow
)
2333 write('bodyrow='+self
.bodyrow
)
2335 write('headdata='+self
.headdata
)
2337 write('footdata='+self
.footdata
)
2338 write('bodydata='+self
.bodydata
)
2341 AbstractBlock
.validate(self
)
2342 '''Check table definition and set self.check_msg if invalid else set
2343 self.check_msg to blank string.'''
2344 # Check global table parameters.
2345 if config
.textwidth
is None:
2346 self
.check_msg
= 'missing [miscellaneous] textwidth entry'
2347 elif config
.pagewidth
is None:
2348 self
.check_msg
= 'missing [miscellaneous] pagewidth entry'
2349 elif config
.pageunits
is None:
2350 self
.check_msg
= 'missing [miscellaneous] pageunits entry'
2351 elif self
.headrow
is None:
2352 self
.check_msg
= 'missing headrow entry'
2353 elif self
.footrow
is None:
2354 self
.check_msg
= 'missing footrow entry'
2355 elif self
.bodyrow
is None:
2356 self
.check_msg
= 'missing bodyrow entry'
2357 elif self
.headdata
is None:
2358 self
.check_msg
= 'missing headdata entry'
2359 elif self
.footdata
is None:
2360 self
.check_msg
= 'missing footdata entry'
2361 elif self
.bodydata
is None:
2362 self
.check_msg
= 'missing bodydata entry'
2367 return AbstractBlock
.isnext(self
)
2368 def parse_ruler(self
,ruler
):
2369 '''Parse ruler calculating underline and ruler column widths.'''
2370 fc
= re
.escape(self
.fillchar
)
2371 # Strip and save optional tablewidth from end of ruler.
2372 mo
= re
.match(r
'^(.*'+fc
+r
'+)([\d\.]+)$',ruler
)
2375 self
.tablewidth
= float(mo
.group(2))
2376 self
.attributes
['tablewidth'] = str(float(self
.tablewidth
))
2378 self
.tablewidth
= None
2379 self
.attributes
['tablewidth'] = '100.0'
2380 # Guess whether column widths are specified numerically or not.
2381 if ruler
[1] != self
.fillchar
:
2382 # If the first column does not start with a fillchar then numeric.
2383 self
.isnumeric
= True
2384 elif ruler
[1:] == self
.fillchar
*len(ruler
[1:]):
2385 # The case of one column followed by fillchars is numeric.
2386 self
.isnumeric
= True
2388 self
.isnumeric
= False
2389 # Underlines must be 3 or more fillchars.
2390 self
.underline
= r
'^' + fc
+ r
'{3,}$'
2391 splits
= re
.split(self
.COL_STOP
,ruler
)[1:]
2392 # Build self.columns.
2393 for i
in range(0,len(splits
),2):
2395 c
.colalign
= self
.ALIGNMENTS
[splits
[i
]]
2398 # Strip trailing fillchars.
2399 s
= re
.sub(fc
+r
'+$','',s
)
2403 c
.rulerwidth
= int(validate(s
,'int($)>0',
2404 'malformed ruler: bad width'))
2405 else: # Calculate column width from inter-fillchar intervals.
2406 if not re
.match(r
'^'+fc
+r
'+$',s
):
2407 raise EAsciiDoc
,'malformed ruler: illegal fillchars'
2408 c
.rulerwidth
= len(s
)+1
2409 self
.columns
.append(c
)
2410 # Fill in unspecified ruler widths.
2412 if self
.columns
[0].rulerwidth
is None:
2414 for c
in self
.columns
:
2415 if c
.rulerwidth
is None:
2416 c
.rulerwidth
= prevwidth
2417 prevwidth
= c
.rulerwidth
2418 def build_colspecs(self
):
2419 '''Generate colwidths and colspecs. This can only be done after the
2420 table arguments have been parsed since we use the table format.'''
2421 self
.attributes
['cols'] = len(self
.columns
)
2422 # Calculate total ruler width.
2424 for c
in self
.columns
:
2425 totalwidth
= totalwidth
+ c
.rulerwidth
2427 raise EAsciiDoc
,'zero width table'
2428 # Calculate marked up colwidths from rulerwidths.
2429 for c
in self
.columns
:
2430 # Convert ruler width to output page width.
2431 width
= float(c
.rulerwidth
)
2432 if self
.format
== 'fixed':
2433 if self
.tablewidth
is None:
2434 # Size proportional to ruler width.
2435 colfraction
= width
/config
.textwidth
2437 # Size proportional to page width.
2438 colfraction
= width
/totalwidth
2440 # Size proportional to page width.
2441 colfraction
= width
/totalwidth
2442 c
.colwidth
= colfraction
* config
.pagewidth
# To page units.
2443 if self
.tablewidth
is not None:
2444 c
.colwidth
= c
.colwidth
* self
.tablewidth
# Scale factor.
2445 if self
.tablewidth
> 1:
2446 c
.colwidth
= c
.colwidth
/100 # tablewidth is in percent.
2450 for c
in self
.columns
:
2451 self
.attributes
['colalign'] = c
.colalign
2452 self
.attributes
['colwidth'] = str(int(c
.colwidth
))
2453 s
= subs_attrs(self
.colspec
,self
.attributes
)
2455 warning('colspec dropped: contains undefined attribute')
2458 self
.attributes
['colspecs'] = string
.join(cols
,writer
.newline
)
2459 def split_rows(self
,rows
):
2460 '''Return a two item tuple containing a list of lines up to but not
2461 including the next underline (continued lines are joined ) and the
2462 tuple of all lines after the underline.'''
2463 reo
= re
.compile(self
.underline
)
2465 while not reo
.match(rows
[i
]):
2468 raise EAsciiDoc
,'missing table rows'
2470 raise EAsciiDoc
,'closing [%s] underline expected' % self
.name
2471 return (join_lines(rows
[:i
]), rows
[i
+1:])
2472 def parse_rows(self
, rows
, rtag
, dtag
):
2473 '''Parse rows list using the row and data tags. Returns a substituted
2474 list of output lines.'''
2476 # Source rows are parsed as single block, rather than line by line, to
2477 # allow the CSV reader to handle multi-line rows.
2478 if self
.format
== 'fixed':
2479 rows
= self
.parse_fixed(rows
)
2480 elif self
.format
== 'csv':
2481 rows
= self
.parse_csv(rows
)
2482 elif self
.format
== 'dsv':
2483 rows
= self
.parse_dsv(rows
)
2485 assert True,'illegal table format'
2486 # Substitute and indent all data in all rows.
2487 stag
,etag
= subs_tag(rtag
,self
.attributes
)
2489 result
.append(' '+stag
)
2490 for data
in self
.subs_row(row
,dtag
):
2491 result
.append(' '+data
)
2492 result
.append(' '+etag
)
2494 def subs_row(self
, data
, dtag
):
2495 '''Substitute the list of source row data elements using the data tag.
2496 Returns a substituted list of output table data items.'''
2498 if len(data
) < len(self
.columns
):
2499 warning('fewer row data items then table columns')
2500 if len(data
) > len(self
.columns
):
2501 warning('more row data items than table columns')
2502 for i
in range(len(self
.columns
)):
2503 if i
> len(data
) - 1:
2504 d
= '' # Fill missing column data with blanks.
2508 self
.attributes
['colalign'] = c
.colalign
2509 self
.attributes
['colwidth'] = str(int(c
.colwidth
))+config
.pageunits
2510 stag
,etag
= subs_tag(dtag
,self
.attributes
)
2511 # Insert AsciiDoc line break (' +') where row data has newlines
2512 # ('\n'). This is really only useful when the table format is csv
2513 # and the output markup is HTML. It's also a bit dubious in that it
2514 # assumes the user has not modified the shipped line break pattern.
2515 subs
= self
.get_subs()[0]
2516 if 'replacements' in subs
:
2517 # Insert line breaks in cell data.
2518 d
= re
.sub(r
'(?m)\n',r
' +\n',d
)
2519 d
= string
.split(d
,'\n') # So writer.newline is written.
2522 result
= result
+ [stag
] + Lex
.subs(d
,subs
) + [etag
]
2524 def parse_fixed(self
,rows
):
2525 '''Parse the list of source table rows. Each row item in the returned
2526 list contains a list of cell data elements.'''
2531 # build an encoded representation
2532 row
= char_decode(row
)
2533 for c
in self
.columns
:
2534 end
= start
+ c
.rulerwidth
2535 if c
is self
.columns
[-1]:
2536 # Text in last column can continue forever.
2537 # Use the encoded string to slice, but convert back
2538 # to plain string before further processing
2539 data
.append(string
.strip(char_encode(row
[start
:])))
2541 data
.append(string
.strip(char_encode(row
[start
:end
])))
2545 def parse_csv(self
,rows
):
2546 '''Parse the list of source table rows. Each row item in the returned
2547 list contains a list of cell data elements.'''
2551 rdr
= csv
.reader(StringIO
.StringIO(string
.join(rows
,'\r\n')),
2552 skipinitialspace
=True)
2557 raise EAsciiDoc
,'csv parse error: %s' % row
2559 def parse_dsv(self
,rows
):
2560 '''Parse the list of source table rows. Each row item in the returned
2561 list contains a list of cell data elements.'''
2562 separator
= self
.attributes
.get('separator',':')
2563 separator
= eval('"'+separator
+'"')
2564 if len(separator
) != 1:
2565 raise EAsciiDoc
,'malformed dsv separator: %s' % separator
2566 # TODO If separator is preceeded by an odd number of backslashes then
2567 # it is escaped and should not delimit.
2571 if row
== '': continue
2572 # Unescape escaped characters.
2573 row
= eval('"'+row
.replace('"','\\"')+'"')
2574 data
= row
.split(separator
)
2575 data
= [s
.strip() for s
in data
]
2578 def translate(self
):
2579 # Reset instance specific properties.
2580 self
.underline
= None
2583 BlockTitle
.consume(attrs
)
2584 # Add relevant globals to table substitutions.
2585 attrs
['pagewidth'] = str(config
.pagewidth
)
2586 attrs
['pageunits'] = config
.pageunits
2587 # Mix in document attribute list.
2588 AttributeList
.consume(attrs
)
2589 # Validate overridable attributes.
2590 for k
,v
in attrs
.items():
2592 if v
not in self
.FORMATS
:
2593 raise EAsciiDoc
, 'illegal [%s] %s: %s' % (self
.name
,k
,v
)
2595 elif k
== 'tablewidth':
2597 self
.tablewidth
= float(attrs
['tablewidth'])
2599 raise EAsciiDoc
, 'illegal [%s] %s: %s' % (self
.name
,k
,v
)
2600 self
.merge_attributes(attrs
)
2601 # Parse table ruler.
2602 ruler
= reader
.read()
2603 assert re
.match(self
.delimiter
,ruler
)
2604 self
.parse_ruler(ruler
)
2605 # Read the entire table.
2608 line
= reader
.read_next()
2609 # Table terminated by underline followed by a blank line or EOF.
2610 if len(table
) > 0 and re
.match(self
.underline
,table
[-1]):
2611 if line
in ('',None):
2614 raise EAsciiDoc
,'closing [%s] underline expected' % self
.name
2615 table
.append(reader
.read())
2616 #TODO: Inherited validate() doesn't set check_msg, needs checking.
2617 if self
.check_msg
: # Skip if table definition was marked invalid.
2618 warning('skipping %s table: %s' % (self
.name
,self
.check_msg
))
2620 # Generate colwidths and colspecs.
2621 self
.build_colspecs()
2622 # Generate headrows, footrows, bodyrows.
2623 # Headrow, footrow and bodyrow data replaces same named attributes in
2624 # the table markup template. In order to ensure this data does not get
2625 # a second attribute substitution (which would interfere with any
2626 # already substituted inline passthroughs) unique placeholders are used
2627 # (the tab character does not appear elsewhere since it is expanded on
2628 # input) which are replaced after template attribute substitution.
2629 headrows
= footrows
= []
2630 bodyrows
,table
= self
.split_rows(table
)
2633 bodyrows
,table
= self
.split_rows(table
)
2635 footrows
,table
= self
.split_rows(table
)
2637 headrows
= self
.parse_rows(headrows
, self
.headrow
, self
.headdata
)
2638 headrows
= string
.join(headrows
,writer
.newline
)
2639 self
.attributes
['headrows'] = '\theadrows\t'
2641 footrows
= self
.parse_rows(footrows
, self
.footrow
, self
.footdata
)
2642 footrows
= string
.join(footrows
,writer
.newline
)
2643 self
.attributes
['footrows'] = '\tfootrows\t'
2644 bodyrows
= self
.parse_rows(bodyrows
, self
.bodyrow
, self
.bodydata
)
2645 bodyrows
= string
.join(bodyrows
,writer
.newline
)
2646 self
.attributes
['bodyrows'] = '\tbodyrows\t'
2647 table
= subs_attrs(config
.sections
[self
.template
],self
.attributes
)
2648 table
= string
.join(table
, writer
.newline
)
2649 # Before we finish replace the table head, foot and body place holders
2650 # with the real data.
2652 table
= table
.replace('\theadrows\t', headrows
, 1)
2654 table
= table
.replace('\tfootrows\t', footrows
, 1)
2655 table
= table
.replace('\tbodyrows\t', bodyrows
, 1)
2658 class Tables(AbstractBlocks
):
2659 '''List of tables.'''
2661 PREFIX
= 'tabledef-'
2663 AbstractBlocks
.__init
__(self
)
2664 def load(self
,sections
):
2665 AbstractBlocks
.load(self
,sections
)
2666 '''Update tables defined in 'sections' dictionary.'''
2668 # Does not call AbstractBlocks.validate().
2669 # Check we have a default table definition,
2670 for i
in range(len(self
.blocks
)):
2671 if self
.blocks
[i
].name
== 'tabledef-default':
2672 default
= self
.blocks
[i
]
2675 raise EAsciiDoc
,'missing [table-default] section'
2676 # Set default table defaults.
2677 if default
.format
is None: default
.subs
= 'fixed'
2678 # Propagate defaults to unspecified table parameters.
2679 for b
in self
.blocks
:
2680 if b
is not default
:
2681 if b
.fillchar
is None: b
.fillchar
= default
.fillchar
2682 if b
.format
is None: b
.format
= default
.format
2683 if b
.template
is None: b
.template
= default
.template
2684 if b
.colspec
is None: b
.colspec
= default
.colspec
2685 if b
.headrow
is None: b
.headrow
= default
.headrow
2686 if b
.footrow
is None: b
.footrow
= default
.footrow
2687 if b
.bodyrow
is None: b
.bodyrow
= default
.bodyrow
2688 if b
.headdata
is None: b
.headdata
= default
.headdata
2689 if b
.footdata
is None: b
.footdata
= default
.footdata
2690 if b
.bodydata
is None: b
.bodydata
= default
.bodydata
2691 # Check all tables have valid fill character.
2692 for b
in self
.blocks
:
2693 if not b
.fillchar
or len(b
.fillchar
) != 1:
2694 raise EAsciiDoc
,'[%s] missing or illegal fillchar' % b
.name
2695 # Build combined tables delimiter patterns and assign defaults.
2697 for b
in self
.blocks
:
2699 # (ColStop,(ColWidth,FillChar+)?)+, FillChar+, TableWidth?
2700 b
.delimiter
= r
'^(' + Table
.COL_STOP \
2701 + r
'(\d*|' + re
.escape(b
.fillchar
) + r
'*)' \
2703 + re
.escape(b
.fillchar
) + r
'+' \
2705 delimiters
.append(b
.delimiter
)
2707 b
.headrow
= b
.bodyrow
2709 b
.footrow
= b
.bodyrow
2711 b
.headdata
= b
.bodydata
2713 b
.footdata
= b
.bodydata
2714 self
.delimiter
= join_regexp(delimiters
)
2715 # Check table definitions are valid.
2716 for b
in self
.blocks
:
2720 warning('[%s] table definition: %s' % (b
.name
,b
.check_msg
))
2723 # Default system macro syntax.
2724 SYS_DEFAULT
= r
'(?u)^(?P<name>\w(\w|-)*?)::(?P<target>\S*?)' + \
2725 r
'(\[(?P<attrlist>.*?)\])$'
2727 self
.macros
= [] # List of Macros.
2728 self
.current
= None # The last matched block macro.
2729 # Initialize default system macro.
2731 m
.pattern
= self
.SYS_DEFAULT
2733 m
.reo
= re
.compile(m
.pattern
)
2734 self
.macros
.append(m
)
2735 def load(self
,entries
):
2736 for entry
in entries
:
2740 # Delete undefined macro.
2741 for i
in range(len(self
.macros
)-1,-1,-1):
2742 if self
.macros
[i
].pattern
== m
.pattern
:
2745 # Check for duplicates.
2746 for m2
in self
.macros
:
2748 verbose('duplicate macro: '+entry
)
2751 self
.macros
.append(m
)
2753 write
= lambda s
: sys
.stdout
.write('%s%s' % (s
,writer
.newline
))
2755 # Dump all macros except the first (built-in system) macro.
2756 for m
in self
.macros
[1:]:
2757 write('%s=%s%s' % (m
.pattern
,m
.prefix
,m
.name
))
2760 # Check all named sections exist.
2762 for m
in self
.macros
:
2763 if m
.name
and m
.prefix
!= '+':
2765 def subs(self
,text
,prefix
='',callouts
=False):
2766 # If callouts is True then only callout macros are processed, if False
2767 # then all non-callout macros are processed.
2769 for m
in self
.macros
:
2770 if m
.prefix
== prefix
:
2771 if callouts ^
(m
.name
!= 'callout'):
2772 result
= m
.subs(result
)
2775 '''Return matching macro if block macro is next on reader.'''
2776 reader
.skip_blank_lines()
2777 line
= reader
.read_next()
2779 for m
in self
.macros
:
2781 if m
.reo
.match(line
):
2785 def match(self
,prefix
,name
,text
):
2786 '''Return re match object matching 'text' with macro type 'prefix',
2787 macro name 'name'.'''
2788 for m
in self
.macros
:
2789 if m
.prefix
== prefix
:
2790 mo
= m
.reo
.match(text
)
2794 if re
.match(name
,mo
.group('name')):
2798 # Macro set just prior to calling _subs_macro(). Ugly but there's no way
2799 # to pass optional arguments with _subs_macro().
2802 def _subs_macro(mo
):
2803 '''Function called to perform inline macro substitution. Uses matched macro
2804 regular expression object and returns string containing the substituted
2805 macro body. Called by Macros().subs().'''
2806 # Check if macro reference is escaped.
2807 if mo
.group()[0] == '\\':
2808 return mo
.group()[1:] # Strip leading backslash.
2810 # Delete groups that didn't participate in match.
2811 for k
,v
in d
.items():
2812 if v
is None: del d
[k
]
2816 if not d
.has_key('name'):
2817 warning('missing macro name group: %s' % mo
.re
.pattern
)
2820 section_name
= _macro
.section_name(name
)
2821 if not section_name
:
2823 # If we're dealing with a block macro get optional block ID and block title.
2824 if _macro
.prefix
== '#':
2825 AttributeList
.consume(d
)
2826 BlockTitle
.consume(d
)
2827 # Parse macro attributes.
2828 if d
.has_key('attrlist'):
2829 if d
['attrlist'] in (None,''):
2832 parse_attributes(d
['attrlist'],d
)
2833 if name
== 'callout':
2834 listindex
=int(d
['index'])
2835 d
['coid'] = calloutmap
.add(listindex
)
2836 # BUG: We've already done attribute substitution on the macro which means
2837 # that any escaped attribute references are now unescaped and will be
2838 # substituted by config.subs_section() below. As a partial fix have witheld
2839 # {0} from substitution but this kludge doesn't fix it for other attributes
2840 # containg unescaped references.
2843 d
['0'] = chr(0) # Replace temporarily with unused character.
2844 body
= config
.subs_section(section_name
,d
)
2847 elif len(body
) == 1:
2850 if _macro
.prefix
== '#':
2851 result
= string
.join(body
,writer
.newline
)
2853 # Internally processed inline macros use UNIX line separator.
2854 result
= string
.join(body
,'\n')
2856 result
= string
.replace(result
, chr(0), a0
)
2861 self
.pattern
= None # Matching regular expression.
2862 self
.name
= '' # Conf file macro name (None if implicit).
2863 self
.prefix
= '' # '' if inline, '+' if system, '#' if block.
2864 self
.reo
= None # Compiled pattern re object.
2865 def section_name(self
,name
=None):
2866 '''Return macro markup template section name based on macro name and
2867 prefix. Return None section not found.'''
2868 assert self
.prefix
!= '+'
2872 if self
.prefix
== '#':
2873 suffix
= '-blockmacro'
2875 suffix
= '-inlinemacro'
2876 if config
.sections
.has_key(name
+suffix
):
2879 warning('missing macro section: [%s]' % name
+suffix
)
2882 if self
.pattern
!= m
.pattern
:
2884 if self
.name
!= m
.name
:
2886 if self
.prefix
!= m
.prefix
:
2889 def load(self
,entry
):
2890 e
= parse_entry(entry
)
2892 raise EAsciiDoc
,'malformed macro entry: %s' % entry
2893 self
.pattern
, self
.name
= e
2894 if not is_regexp(self
.pattern
):
2895 raise EAsciiDoc
,'illegal regular expression in macro entry: %s' \
2897 self
.reo
= re
.compile(self
.pattern
)
2899 if self
.name
[0] in ('+','#'):
2900 self
.prefix
, self
.name
= self
.name
[0], self
.name
[1:]
2901 if self
.name
and not is_name(self
.name
):
2902 raise EAsciiDoc
,'illegal section name in macro entry: %s' % entry
2903 def subs(self
,text
):
2905 _macro
= self
# Pass the macro to _subs_macro().
2906 return self
.reo
.sub(_subs_macro
,text
)
2907 def translate(self
):
2908 ''' Block macro translation.'''
2909 assert self
.prefix
== '#'
2911 s
= subs_attrs(s
) # Substitute global attributes.
2919 self
.comap
= {} # key = list index, value = callouts list.
2920 self
.calloutindex
= 0 # Current callout index number.
2921 self
.listnumber
= 1 # Current callout list number.
2922 def listclose(self
):
2923 # Called when callout list is closed.
2924 self
.listnumber
+= 1
2925 self
.calloutindex
= 0
2927 def add(self
,listindex
):
2928 # Add next callout index to listindex map entry. Return the callout id.
2929 self
.calloutindex
+= 1
2930 # Append the coindex to a list in the comap dictionary.
2931 if not self
.comap
.has_key(listindex
):
2932 self
.comap
[listindex
] = [self
.calloutindex
]
2934 self
.comap
[listindex
].append(self
.calloutindex
)
2935 return self
.calloutid(self
.listnumber
, self
.calloutindex
)
2936 def calloutid(listnumber
,calloutindex
):
2937 return 'CO%d-%d' % (listnumber
,calloutindex
)
2938 calloutid
= staticmethod(calloutid
)
2939 def calloutids(self
,listindex
):
2940 # Retieve list of callout indexes that refer to listindex.
2941 if self
.comap
.has_key(listindex
):
2943 for coindex
in self
.comap
[listindex
]:
2944 result
+= ' ' + self
.calloutid(self
.listnumber
,coindex
)
2945 return result
.strip()
2947 error('no callouts refer to list item '+str(listindex
))
2949 def validate(self
,maxlistindex
):
2950 # Check that all list indexes referenced by callouts exist.
2951 for listindex
in self
.comap
.keys():
2952 if listindex
> maxlistindex
:
2953 warning('callout refers to non-existent list item '
2956 #---------------------------------------------------------------------------
2957 # Input stream Reader and output stream writer classes.
2958 #---------------------------------------------------------------------------
2961 '''Line oriented AsciiDoc input file reader. Processes include and
2962 conditional inclusion system macros. Tabs are expanded and lines are right
2964 # This class is not used directly, use Reader class instead.
2965 READ_BUFFER_MIN
= 10 # Read buffer low level.
2967 self
.f
= None # Input file object.
2968 self
.fname
= None # Input file name.
2969 self
.next
= [] # Read ahead buffer containing
2970 # [filename,linenumber,linetext] lists.
2971 self
.cursor
= None # Last read() [filename,linenumber,linetext].
2972 self
.tabsize
= 8 # Tab expansion number of spaces.
2973 self
.parent
= None # Included reader's parent reader.
2974 self
._lineno
= 0 # The last line read from file object f.
2975 self
.include_depth
= 0 # Current include depth.
2976 self
.include_max
= 5 # Maxiumum allowed include depth.
2977 def open(self
,fname
):
2979 verbose('reading: '+fname
)
2980 if fname
== '<stdin>':
2983 self
.f
= open(fname
,"rb")
2984 self
._lineno
= 0 # The last line read from file object f.
2986 # Prefill buffer by reading the first line and then pushing it back.
2987 if Reader1
.read(self
):
2988 self
.unread(self
.cursor
)
2990 def closefile(self
):
2991 '''Used by class methods to close nested include files.'''
2997 def read(self
,skip
=False):
2998 '''Read next line. Return None if EOF. Expand tabs. Strip trailing
2999 white space. Maintain self.next read ahead buffer. If skip=True then
3000 conditional exclusion is active (ifdef and ifndef macros).'''
3002 if len(self
.next
) <= self
.READ_BUFFER_MIN
:
3003 s
= self
.f
.readline()
3005 self
._lineno
= self
._lineno
+ 1
3007 if self
.tabsize
!= 0:
3008 s
= string
.expandtabs(s
,self
.tabsize
)
3009 s
= string
.rstrip(s
)
3010 self
.next
.append([self
.fname
,self
._lineno
,s
])
3011 if len(self
.next
) > self
.READ_BUFFER_MIN
:
3013 s
= self
.f
.readline()
3015 self
._lineno
= self
._lineno
+ 1
3016 # Return first (oldest) buffer entry.
3017 if len(self
.next
) > 0:
3018 self
.cursor
= self
.next
[0]
3020 result
= self
.cursor
[2]
3021 # Check for include macro.
3022 mo
= macros
.match('+',r
'include[1]?',result
)
3024 # Perform attribute substitution on inlcude macro file name.
3025 fname
= subs_attrs(mo
.group('target'))
3027 return Reader1
.read(self
) # Return next input line.
3028 if self
.include_depth
>= self
.include_max
:
3029 raise EAsciiDoc
,'maxiumum inlcude depth exceeded'
3030 if self
.fname
!= '<stdin>':
3031 fname
= safe_filename(fname
, os
.path
.dirname(self
.fname
))
3033 return Reader1
.read(self
) # Return next input line.
3034 if mo
.group('name') == 'include1':
3035 if not config
.dumping
:
3036 # Store the include file in memory for later
3037 # retrieval by the {include1:} system attribute.
3038 config
.include1
[fname
] = readlines(fname
)
3039 return '{include1:%s}' % fname
3041 # This is a configuration dump, just pass the macro
3044 # Parse include macro attributes.
3046 parse_attributes(mo
.group('attrlist'),attrs
)
3047 # Clone self and set as parent (self assumes the role of child).
3050 self
.parent
= parent
3051 if attrs
.has_key('tabsize'):
3052 self
.tabsize
= int(validate(attrs
['tabsize'],'int($)>=0', \
3053 'illegal include macro tabsize argument'))
3055 self
.include_depth
= self
.include_depth
+ 1
3056 result
= Reader1
.read(self
)
3058 if not Reader1
.eof(self
):
3059 result
= Reader1
.read(self
)
3064 '''Returns True if all lines have been read.'''
3065 if len(self
.next
) == 0:
3066 # End of current file.
3069 assign(self
,self
.parent
) # Restore parent reader.
3070 return Reader1
.eof(self
)
3075 def read_next(self
):
3076 '''Like read() but does not advance file pointer.'''
3077 if Reader1
.eof(self
):
3080 return self
.next
[0][2]
3081 def unread(self
,cursor
):
3082 '''Push the line (filename,linenumber,linetext) tuple back into the read
3083 buffer. Note that it's up to the caller to restore the previous
3086 self
.next
.insert(0,cursor
)
3088 class Reader(Reader1
):
3089 ''' Wraps (well, sought of) Reader1 class and implements conditional text
3092 Reader1
.__init__(self
)
3093 self
.depth
= 0 # if nesting depth.
3094 self
.skip
= False # true if we're skipping ifdef...endif.
3095 self
.skipname
= '' # Name of current endif macro target.
3096 self
.skipto
= -1 # The depth at which skipping is reenabled.
3097 def read_super(self
):
3098 result
= Reader1
.read(self
,self
.skip
)
3099 if result
is None and self
.skip
:
3100 raise EAsciiDoc
,'missing endif::%s[]' % self
.skipname
3103 result
= self
.read_super()
3107 mo
= macros
.match('+',r
'ifdef|ifndef|endif',result
)
3109 name
= mo
.group('name')
3110 target
= mo
.group('target')
3112 self
.depth
= self
.depth
-1
3114 raise EAsciiDoc
,'mismatched macro: %s' % result
3115 if self
.depth
== self
.skipto
:
3117 if target
and self
.skipname
!= target
:
3118 raise EAsciiDoc
,'mismatched macro: %s' % result
3119 else: # ifdef or ifndef.
3121 raise EAsciiDoc
,'missing macro target: %s' % result
3122 self
.depth
= self
.depth
+1
3123 result
= self
.read_super()
3126 mo
= macros
.match('+',r
'ifdef|ifndef|endif',result
)
3128 name
= mo
.group('name')
3129 target
= mo
.group('target')
3131 self
.depth
= self
.depth
-1
3132 else: # ifdef or ifndef.
3134 raise EAsciiDoc
,'missing macro target: %s' % result
3135 defined
= document
.attributes
.get(target
) is not None
3137 self
.skip
= not defined
3141 self
.skipto
= self
.depth
3142 self
.skipname
= target
3143 self
.depth
= self
.depth
+1
3144 result
= self
.read()
3146 # Expand executable block macros.
3147 mo
= macros
.match('+',r
'eval|sys|sys2',result
)
3149 action
= mo
.group('name')
3150 cmd
= mo
.group('attrlist')
3151 s
= system(action
, cmd
, is_macro
=True)
3153 self
.cursor
[2] = s
# So we don't re-evaluate.
3157 return self
.read_next() is None
3158 def read_next(self
):
3159 save_cursor
= self
.cursor
3160 result
= self
.read()
3161 if result
is not None:
3162 self
.unread(self
.cursor
)
3163 self
.cursor
= save_cursor
3165 def read_all(self
,fname
):
3166 '''Read all lines from file fname and return as list. Use like class
3167 method: Reader().read_all(fname)'''
3171 while not self
.eof():
3172 result
.append(self
.read())
3176 def read_lines(self
,count
=1):
3177 '''Return tuple containing count lines.'''
3180 while i
< count
and not self
.eof():
3181 result
.append(self
.read())
3182 return tuple(result
)
3183 def read_ahead(self
,count
=1):
3184 '''Same as read_lines() but does not advance the file pointer.'''
3187 save_cursor
= self
.cursor
3190 while i
< count
and not self
.eof():
3191 result
.append(self
.read())
3192 putback
.append(self
.cursor
)
3195 self
.unread(putback
.pop())
3197 self
.cursor
= save_cursor
3198 return tuple(result
)
3199 def skip_blank_lines(self
):
3200 reader
.read_until(r
'\s*\S+')
3201 def read_until(self
,pattern
,same_file
=False):
3202 '''Like read() but reads lines up to (but not including) the first line
3203 that matches the pattern regular expression. If same_file is True
3204 then the terminating pattern must occur in the file the was being read
3205 when the routine was called.'''
3207 fname
= self
.cursor
[0]
3209 reo
= re
.compile(pattern
)
3210 while not self
.eof():
3211 save_cursor
= self
.cursor
3213 if (not same_file
or fname
== self
.cursor
[0]) and reo
.match(s
):
3214 self
.unread(self
.cursor
)
3215 self
.cursor
= save_cursor
3218 return tuple(result
)
3219 def read_continuation(self
):
3220 '''Like read() but treats trailing backslash as line continuation
3226 while s
is not None and len(s
) > 0 and s
[-1] == '\\':
3227 result
= result
+ s
[:-1]
3234 '''Writes lines to output file.'''
3235 newline
= '\r\n' # End of line terminator.
3236 f
= None # Output file object.
3237 fname
= None # Output file name.
3238 lines_out
= 0 # Number of lines written.
3239 def open(self
,fname
):
3240 self
.fname
= os
.path
.abspath(fname
)
3241 verbose('writing: '+fname
)
3242 if fname
== '<stdout>':
3245 self
.f
= open(fname
,"wb+")
3248 if self
.fname
!= '<stdout>':
3250 def write(self
,*args
):
3251 '''Iterates arguments, writes tuple and list arguments one line per
3252 element, else writes argument as single line. If no arguments writes
3253 blank line. If argument is None nothing is written. self.newline is
3254 appended to each line.'''
3256 self
.f
.write(self
.newline
)
3257 self
.lines_out
= self
.lines_out
+ 1
3260 if isinstance(arg
,list) or isinstance(arg
,tuple):
3262 self
.f
.write(s
+self
.newline
)
3263 self
.lines_out
= self
.lines_out
+ len(arg
)
3264 elif arg
is not None:
3265 self
.f
.write(arg
+self
.newline
)
3266 self
.lines_out
= self
.lines_out
+ 1
3267 def write_tag(self
,tagname
,content
,subs
=SUBS_NORMAL
,d
=None):
3268 '''Write content enveloped by configuration file tag tagname.
3269 Substitutions specified in the 'subs' list are perform on the
3271 stag
,etag
= config
.tag(tagname
,d
)
3275 self
.write(Lex
.subs(content
,subs
))
3279 #---------------------------------------------------------------------------
3280 # Configuration file processing.
3281 #---------------------------------------------------------------------------
3282 def _subs_specialwords(mo
):
3283 '''Special word substitution function called by
3284 Config.subs_specialwords().'''
3285 word
= mo
.re
.pattern
# The special word.
3286 template
= config
.specialwords
[word
] # The corresponding markup template.
3287 if not config
.sections
.has_key(template
):
3288 raise EAsciiDoc
,'missing special word template [%s]' % template
3289 if mo
.group()[0] == '\\':
3290 return mo
.group()[1:] # Return escaped word.
3292 args
['words'] = mo
.group() # The full match string is argument 'words'.
3293 args
.update(mo
.groupdict()) # Add other named match groups to the arguments.
3294 # Delete groups that didn't participate in match.
3295 for k
,v
in args
.items():
3296 if v
is None: del args
[k
]
3297 lines
= subs_attrs(config
.sections
[template
],args
)
3300 elif len(lines
) == 1:
3303 result
= string
.join(lines
,writer
.newline
)
3307 '''Methods to process configuration files.'''
3308 # Predefined section name regexp's.
3309 SPECIAL_SECTIONS
= ('tags','miscellaneous','attributes','specialcharacters',
3310 'specialwords','macros','replacements','quotes','titles',
3311 r
'paradef.+',r
'listdef.+',r
'blockdef.+',r
'tabledef.*')
3313 self
.sections
= OrderedDict() # Keyed by section name containing
3314 # lists of section lines.
3315 # Command-line options.
3316 self
.verbose
= False
3317 self
.header_footer
= True # -s, --no-header-footer option.
3318 # [miscellaneous] section.
3321 self
.newline
= '\r\n'
3322 self
.pagewidth
= None
3323 self
.pageunits
= None
3324 self
.outfilesuffix
= ''
3326 self
.tags
= {} # Values contain (stag,etag) tuples.
3327 self
.specialchars
= {} # Values of special character substitutions.
3328 self
.specialwords
= {} # Name is special word pattern, value is macro.
3329 self
.replacements
= OrderedDict() # Key is find pattern, value is
3331 self
.specialsections
= {} # Name is special section name pattern, value
3332 # is corresponding section name.
3333 self
.quotes
= {} # Values contain corresponding tag name.
3334 self
.fname
= '' # Most recently loaded configuration file name.
3335 self
.conf_attrs
= {} # Glossary entries from conf files.
3336 self
.cmd_attrs
= {} # Attributes from command-line -a options.
3337 self
.loaded
= [] # Loaded conf files.
3338 self
.include1
= {} # Holds include1::[] files for {include1:}.
3339 self
.dumping
= False # True if asciidoc -c option specified.
3341 def load(self
,fname
,dir=None):
3342 '''Loads sections dictionary with sections from file fname.
3343 Existing sections are overlaid. Silently skips missing configuration
3346 fname
= os
.path
.join(dir, fname
)
3347 # Sliently skip missing configuration file.
3348 if not os
.path
.isfile(fname
):
3350 # Don't load conf files twice (local and application conf files are the
3351 # same if the source file is in the application directory).
3352 if realpath(fname
) in self
.loaded
:
3354 rdr
= Reader() # Reader processes system macros.
3357 reo
= re
.compile(r
'(?u)^\[(?P<section>[^\W\d][\w-]*)\]\s*$')
3358 sections
= OrderedDict()
3359 section
,contents
= '',[]
3360 while not rdr
.eof():
3362 if s
and s
[0] == '#': # Skip comment lines.
3364 if s
[:2] == '\\#': # Unescape lines starting with '#'.
3366 s
= string
.rstrip(s
)
3367 found
= reo
.findall(s
)
3369 if section
: # Store previous section.
3370 if sections
.has_key(section
) \
3371 and self
.is_special_section(section
):
3372 # Merge line oriented special sections.
3373 contents
= sections
[section
] + contents
3374 sections
[section
] = contents
3375 section
= found
[0].lower()
3379 if section
and contents
: # Store last section.
3380 if sections
.has_key(section
) \
3381 and self
.is_special_section(section
):
3382 # Merge line oriented special sections.
3383 contents
= sections
[section
] + contents
3384 sections
[section
] = contents
3386 # Delete blank lines from sections.
3387 for k
in sections
.keys():
3388 for i
in range(len(sections
[k
])-1,-1,-1):
3389 if not sections
[k
][i
]:
3391 elif not self
.is_special_section(k
):
3392 break # Only trailing blanks from non-special sections.
3393 # Add/overwrite new sections.
3394 self
.sections
.update(sections
)
3396 # Internally [miscellaneous] section entries are just attributes.
3398 parse_entries(sections
.get('miscellaneous',()), d
, unquote
=True,
3399 allow_name_only
=True)
3400 update_attrs(self
.conf_attrs
,d
)
3402 parse_entries(sections
.get('attributes',()), d
, unquote
=True,
3403 allow_name_only
=True)
3404 update_attrs(self
.conf_attrs
,d
)
3405 # Update document attributes so they are available immediately.
3406 document
.init_attrs()
3408 parse_entries(sections
.get('titles',()),d
)
3410 parse_entries(sections
.get('specialcharacters',()),self
.specialchars
)
3411 parse_entries(sections
.get('quotes',()),self
.quotes
,unique_values
=True)
3412 self
.parse_specialwords()
3413 self
.parse_replacements()
3414 self
.parse_specialsections()
3415 paragraphs
.load(sections
)
3416 lists
.load(sections
)
3417 blocks
.load(sections
)
3418 tables
.load(sections
)
3419 macros
.load(sections
.get('macros',()))
3420 self
.loaded
.append(realpath(fname
))
3422 def load_all(self
,dir):
3423 '''Load the standard configuration files from directory 'dir'.'''
3424 self
.load('asciidoc.conf',dir)
3425 conf
= document
.backend
+ '.conf'
3427 conf
= document
.backend
+ '-' + document
.doctype
+ '.conf'
3429 # Load ./filters/*.conf files if they exist.
3430 filters
= os
.path
.join(dir,'filters')
3431 if os
.path
.isdir(filters
):
3432 for f
in os
.listdir(filters
):
3433 if re
.match(r
'^.+\.conf$',f
):
3434 self
.load(f
,filters
)
3436 def load_miscellaneous(self
,d
):
3437 '''Set miscellaneous configuration entries from dictionary 'd'.'''
3438 def set_misc(name
,rule
='True',intval
=False):
3440 errmsg
= 'illegal [miscellaneous] %s entry' % name
3442 setattr(self
, name
, int(validate(d
[name
],rule
,errmsg
)))
3444 setattr(self
, name
, validate(d
[name
],rule
,errmsg
))
3445 set_misc('tabsize','int($)>0',intval
=True)
3446 set_misc('textwidth','int($)>0',intval
=True)
3447 set_misc('pagewidth','int($)>0',intval
=True)
3448 set_misc('pageunits')
3449 set_misc('outfilesuffix')
3450 if d
.has_key('newline'):
3451 # Convert escape sequences to their character values.
3452 self
.newline
= eval('"'+d
['newline']+'"')
3455 '''Check the configuration for internal consistancy. Called after all
3456 configuration files have been loaded.'''
3457 # Heuristic validate that at least one configuration file was loaded.
3458 if not self
.specialchars
or not self
.tags
or not lists
:
3459 raise EAsciiDoc
,'incomplete configuration files'
3460 # Check special characters are only one character long.
3461 for k
in self
.specialchars
.keys():
3463 raise EAsciiDoc
,'[specialcharacters] ' \
3464 'must be a single character: %s' % k
3465 # Check all special words have a corresponding inline macro body.
3466 for macro
in self
.specialwords
.values():
3467 if not is_name(macro
):
3468 raise EAsciiDoc
,'illegal special word name: %s' % macro
3469 if not self
.sections
.has_key(macro
):
3470 warning('missing special word macro: [%s]' % macro
)
3471 # Check all text quotes have a corresponding tag.
3472 for q
in self
.quotes
.keys():
3473 tag
= self
.quotes
[q
]
3474 if not self
.tags
.has_key(tag
):
3475 warning('[quotes] %s missing tag definition: %s'
3477 # Check all specialsections section names exist.
3478 for k
,v
in self
.specialsections
.items():
3479 if not self
.sections
.has_key(v
):
3480 warning('[%s] missing specialsections section' % v
)
3481 paragraphs
.validate()
3487 def is_special_section(self
,section_name
):
3488 for name
in self
.SPECIAL_SECTIONS
:
3489 if re
.match(name
,section_name
):
3494 '''Dump configuration to stdout.'''
3497 hdr
= hdr
+ '#' + writer
.newline
3498 hdr
= hdr
+ '# Generated by AsciiDoc %s for %s %s.%s' % \
3499 (VERSION
,document
.backend
,document
.doctype
,writer
.newline
)
3500 t
= time
.asctime(time
.localtime(time
.time()))
3501 hdr
= hdr
+ '# %s%s' % (t
,writer
.newline
)
3502 hdr
= hdr
+ '#' + writer
.newline
3503 sys
.stdout
.write(hdr
)
3504 # Dump special sections.
3505 # Dump only the configuration file and command-line attributes.
3506 # [miscellanous] entries are dumped as part of the [attributes].
3508 d
.update(self
.conf_attrs
)
3509 d
.update(self
.cmd_attrs
)
3510 dump_section('attributes',d
)
3512 dump_section('quotes',self
.quotes
)
3513 dump_section('specialcharacters',self
.specialchars
)
3515 for k
,v
in self
.specialwords
.items():
3517 d
[v
] = '%s "%s"' % (d
[v
],k
) # Append word list.
3520 dump_section('specialwords',d
)
3521 dump_section('replacements',self
.replacements
)
3522 dump_section('specialsections',self
.specialsections
)
3524 for k
,v
in self
.tags
.items():
3526 dump_section('tags',d
)
3532 # Dump remaining sections.
3533 for k
in self
.sections
.keys():
3534 if not self
.is_special_section(k
):
3535 sys
.stdout
.write('[%s]%s' % (k
,writer
.newline
))
3536 for line
in self
.sections
[k
]:
3537 sys
.stdout
.write('%s%s' % (line
,writer
.newline
))
3538 sys
.stdout
.write(writer
.newline
)
3540 def subs_section(self
,section
,d
):
3541 '''Section attribute substitution using attributes from
3542 document.attributes and 'd'. Lines containing undefinded
3543 attributes are deleted.'''
3544 if self
.sections
.has_key(section
):
3545 return subs_attrs(self
.sections
[section
],d
)
3547 warning('missing [%s] section' % section
)
3550 def parse_tags(self
):
3551 '''Parse [tags] section entries into self.tags dictionary.'''
3553 parse_entries(self
.sections
.get('tags',()),d
)
3554 for k
,v
in d
.items():
3556 if self
.tags
.has_key(k
):
3559 self
.tags
[k
] = (None,None)
3561 mo
= re
.match(r
'(?P<stag>.*)\|(?P<etag>.*)',v
)
3563 self
.tags
[k
] = (mo
.group('stag'), mo
.group('etag'))
3565 raise EAsciiDoc
,'[tag] %s value malformed' % k
3567 def tag(self
, name
, d
=None):
3568 '''Returns (starttag,endtag) tuple named name from configuration file
3569 [tags] section. Raise error if not found. If a dictionary 'd' is
3570 passed then merge with document attributes and perform attribute
3571 substitution on tags.'''
3573 # TODO: Tags should be stored a single string, not split into start
3574 # and end tags since most are going to be substituted anyway (see
3575 # subs_tag() for how we should process them. parse_tags() (above)
3576 # should only validate i.e. parse_check(). This routine should be renamed
3577 # split_tag() and would call subs_tag(). self.tags dictionary values
3578 # would be strings not tuples.
3580 if not self
.tags
.has_key(name
):
3581 raise EAsciiDoc
, 'missing tag: %s' % name
3582 stag
,etag
= self
.tags
[name
]
3584 # TODO: Should we warn if substitution drops a tag?
3586 stag
= subs_attrs(stag
,d
)
3588 etag
= subs_attrs(etag
,d
)
3589 if stag
is None: stag
= ''
3590 if etag
is None: etag
= ''
3593 def parse_specialsections(self
):
3594 '''Parse specialsections section to self.specialsections dictionary.'''
3595 # TODO: This is virtually the same as parse_replacements() and should
3596 # be factored to single routine.
3598 parse_entries(self
.sections
.get('specialsections',()),d
,unquote
=True)
3599 for pat
,sectname
in d
.items():
3600 pat
= strip_quotes(pat
)
3601 if not is_regexp(pat
):
3602 raise EAsciiDoc
,'[specialsections] entry ' \
3603 'is not a valid regular expression: %s' % pat
3604 if sectname
is None:
3605 if self
.specialsections
.has_key(pat
):
3606 del self
.specialsections
[pat
]
3608 self
.specialsections
[pat
] = sectname
3610 def parse_replacements(self
):
3611 '''Parse replacements section into self.replacements dictionary.'''
3613 parse_entries(self
.sections
.get('replacements',()), d
, unquote
=True)
3614 for pat
,rep
in d
.items():
3615 pat
= strip_quotes(pat
)
3616 if not is_regexp(pat
):
3617 raise EAsciiDoc
,'[replacements] entry in %s ' \
3618 'is not a valid regular expression: %s' % (self
.fname
,pat
)
3620 if self
.replacements
.has_key(pat
):
3621 del self
.replacements
[pat
]
3623 self
.replacements
[pat
] =strip_quotes(rep
)
3625 def subs_replacements(self
,s
):
3626 '''Substitute patterns from self.replacements in 's'.'''
3628 for pat
,rep
in self
.replacements
.items():
3629 result
= re
.sub(pat
, rep
, result
)
3632 def parse_specialwords(self
):
3633 '''Parse special words section into self.specialwords dictionary.'''
3634 reo
= re
.compile(r
'(?:\s|^)(".+?"|[^"\s]+)(?=\s|$)')
3635 for line
in self
.sections
.get('specialwords',()):
3636 e
= parse_entry(line
)
3638 raise EAsciiDoc
,'[specialwords] entry in %s is malformed: %s' \
3641 if not is_name(name
):
3642 raise EAsciiDoc
,'[specialwords] name in %s is illegal: %s' \
3644 if wordlist
is None:
3645 # Undefine all words associated with 'name'.
3646 for k
,v
in self
.specialwords
.items():
3648 del self
.specialwords
[k
]
3650 words
= reo
.findall(wordlist
)
3652 word
= strip_quotes(word
)
3653 if not is_regexp(word
):
3654 raise EAsciiDoc
,'[specialwords] entry in %s ' \
3655 'is not a valid regular expression: %s' \
3657 self
.specialwords
[word
] = name
3659 def subs_specialchars(self
,s
):
3660 '''Perform special character substitution on string 's'.'''
3661 '''It may seem like a good idea to escape special characters with a '\'
3662 character, the reason we don't is because the escape character itself
3663 then has to be escaped and this makes including code listings
3664 problematic. Use the predefined {amp},{lt},{gt} attributes instead.'''
3667 result
= result
+ self
.specialchars
.get(ch
,ch
)
3670 def subs_specialwords(self
,s
):
3671 '''Search for word patterns from self.specialwords in 's' and
3672 substitute using corresponding macro.'''
3674 for word
in self
.specialwords
.keys():
3675 result
= re
.sub(word
, _subs_specialwords
, result
)
3678 def expand_templates(self
,section
):
3680 for line
in self
.sections
[section
]:
3681 mo
= macros
.match('+',r
'template',line
)
3683 s
= mo
.group('attrlist')
3684 if self
.sections
.has_key(s
):
3685 result
+= self
.sections
[s
]
3687 warning('missing [%s] section' % s
)
3692 def expand_all_templates(self
):
3693 for k
in self
.sections
.keys():
3694 self
.sections
[k
] = self
.expand_templates(k
)
3696 def section2tags(self
,section
,d
={}):
3697 '''Perform attribute substitution on 'section' using document
3698 attributes plus 'd' attributes. Return tuple (stag,etag) containing
3699 pre and post | placeholder tags.'''
3700 assert section
is not None
3701 if self
.sections
.has_key(section
):
3702 body
= self
.sections
[section
]
3704 warning('missing [%s] section' % section
)
3706 # Split macro body into start and end tag lists.
3712 mo
= re
.match(r
'(?P<stag>.*)\|(?P<etag>.*)',s
)
3714 if mo
.group('stag'):
3715 stag
.append(mo
.group('stag'))
3716 if mo
.group('etag'):
3717 etag
.append(mo
.group('etag'))
3723 # Do attribute substitution last so {brkbar} can be used to escape |.
3724 stag
= subs_attrs(stag
,d
)
3725 etag
= subs_attrs(etag
,d
)
3729 #---------------------------------------------------------------------------
3731 #---------------------------------------------------------------------------
3734 APP_DIR
= None # This file's directory.
3735 USER_DIR
= None # ~/.asciidoc
3736 GLOBAL_CONFIG_DIR
= "/etc/asciidoc"
3737 HELP_FILE
= 'help.conf'
3741 document
= Document() # The document being processed.
3742 config
= Config() # Configuration file reader.
3743 reader
= Reader() # Input stream line reader.
3744 writer
= Writer() # Output stream line writer.
3745 paragraphs
= Paragraphs() # Paragraph definitions.
3746 lists
= Lists() # List definitions.
3747 blocks
= DelimitedBlocks() # DelimitedBlock definitions.
3748 tables
= Tables() # Table definitions.
3749 macros
= Macros() # Macro definitions.
3750 calloutmap
= CalloutMap() # Coordinates callouts and callout list.
3752 def asciidoc(backend
, doctype
, confiles
, infile
, outfile
, options
):
3753 '''Convert AsciiDoc document to DocBook document of type doctype
3754 The AsciiDoc document is read from file object src the translated
3755 DocBook file written to file object dst.'''
3757 if doctype
not in ('article','manpage','book'):
3758 raise EAsciiDoc
,'illegal document type'
3759 if backend
== 'linuxdoc' and doctype
!= 'article':
3760 raise EAsciiDoc
,'%s %s documents are not supported' \
3762 document
.backend
= backend
3763 if not os
.path
.exists(os
.path
.join(APP_DIR
, backend
+'.conf')) and not \
3764 os
.path
.exists(os
.path
.join(GLOBAL_CONFIG_DIR
, backend
+'.conf')):
3765 warning('non-standard %s backend' % backend
, linenos
=False)
3766 document
.doctype
= doctype
3767 document
.infile
= infile
3768 document
.init_attrs()
3769 # Set processing options.
3771 if o
== '-c': config
.dumping
= True
3772 if o
== '-s': config
.header_footer
= False
3773 if o
== '-v': config
.verbose
= True
3774 # Check the infile exists.
3775 if infile
!= '<stdin>' and not os
.path
.isfile(infile
):
3776 raise EAsciiDoc
,'input file %s missing' % infile
3777 if '-e' not in options
:
3778 # Load global configuration from system configuration directory.
3779 config
.load_all(GLOBAL_CONFIG_DIR
)
3780 # Load global configuration files from asciidoc directory.
3781 config
.load_all(APP_DIR
)
3782 # Load configuration files from ~/.asciidoc if it exists.
3783 if USER_DIR
is not None:
3784 config
.load_all(USER_DIR
)
3785 # Load configuration files from document directory.
3786 config
.load_all(os
.path
.dirname(infile
))
3787 if infile
!= '<stdin>':
3788 # Load implicit document specific configuration files if they exist.
3789 config
.load(os
.path
.splitext(infile
)[0] + '.conf')
3790 config
.load(os
.path
.splitext(infile
)[0] + '-' + backend
+ '.conf')
3791 # If user specified configuration file(s) overlay the defaults.
3793 for conf
in confiles
:
3794 if os
.path
.isfile(conf
):
3797 raise EAsciiDoc
,'configuration file %s missing' % conf
3798 document
.init_attrs() # Add conf files.
3799 # Check configuration for consistency.
3801 # Build outfile name now all conf files have been read.
3803 outfile
= os
.path
.splitext(infile
)[0] + '.' + backend
3804 if config
.outfilesuffix
:
3805 # Change file extension.
3806 outfile
= os
.path
.splitext(outfile
)[0] + config
.outfilesuffix
3807 document
.outfile
= outfile
3811 reader
.tabsize
= config
.tabsize
3814 writer
.newline
= config
.newline
3815 writer
.open(outfile
)
3817 document
.init_attrs() # Add file name related entries.
3818 document
.translate()
3822 reader
.closefile() # Keep reader state for postmortem.
3823 except (KeyboardInterrupt, SystemExit):
3827 if outfile
and outfile
!= '<stdout>' and os
.path
.isfile(outfile
):
3829 # Build and print error description.
3832 msg
= msg
+ "%s: line %d: " % (reader
.cursor
[0],reader
.cursor
[1])
3833 if isinstance(e
,EAsciiDoc
):
3834 print_stderr(msg
+str(e
))
3836 print_stderr(msg
+'unexpected error:')
3837 print_stderr('-'*60)
3838 traceback
.print_exc(file=sys
.stderr
)
3839 print_stderr('-'*60)
3845 print_stderr('Usage: asciidoc [OPTIONS] FILE')
3846 print_stderr('Man page: asciidoc -h manpage')
3847 print_stderr('Syntax: asciidoc -h syntax')
3848 print_stderr('Help topics: asciidoc -h topics')
3850 def show_help(topic
):
3851 '''Print help topic to stdout.'''
3852 # Print [topic] section from help.conf files.
3853 topics
= OrderedDict()
3854 load_sections(topics
, HELP_FILE
, GLOBAL_CONFIG_DIR
)
3855 load_sections(topics
, HELP_FILE
, APP_DIR
)
3856 if USER_DIR
is not None:
3857 load_sections(topics
, HELP_FILE
, USER_DIR
)
3858 if len(topics
) == 0:
3859 print_stderr('no help topics found')
3861 lines
= topics
.get(topic
)
3863 print_stderr('help topic not found: %s' % topic
)
3864 s
= 'available help topics: '
3865 for k
in topics
.keys():
3867 print_stderr(s
[:-2])
3874 if float(sys
.version
[:3]) < 2.3:
3875 print_stderr('FAILED: Python 2.3 or better required.')
3877 # Locate the executable and configuration files directory.
3878 global APP_DIR
,USER_DIR
3879 APP_DIR
= os
.path
.dirname(realpath(sys
.argv
[0]))
3880 USER_DIR
= os
.environ
.get('HOME')
3881 if USER_DIR
is not None:
3882 USER_DIR
= os
.path
.join(USER_DIR
,'.asciidoc')
3883 if not os
.path
.isdir(USER_DIR
):
3885 # Process command line options.
3888 #DEPRECATED: --safe option.
3889 opts
,args
= getopt
.getopt(sys
.argv
[1:],
3890 'a:b:cd:ef:h:no:svw:',
3891 ['attribute=','backend=','conf-file=','doctype=','dump-conf',
3892 'help=','no-conf','no-header-footer','out-file=','profile',
3893 'section-numbers','verbose','version','safe','unsafe'])
3894 except getopt
.GetoptError
,e
:
3896 if re
.search(r
'^option (-h|--help) requires argument$', msg
):
3897 # It's OK not to supply help option argument.
3898 show_help('default')
3906 backend
= DEFAULT_BACKEND
3907 doctype
= DEFAULT_DOCTYPE
3913 if o
in ('--help','-h'):
3917 show_help('default')
3919 if o
== '--profile':
3922 document
.safe
= False
3923 if o
== '--version':
3924 print_stderr('asciidoc %s' % VERSION
)
3926 if o
in ('-b','--backend'):
3928 if o
in ('-c','--dump-conf'):
3929 options
.append('-c')
3930 if o
in ('-d','--doctype'):
3932 if o
in ('-e','--no-conf'):
3933 options
.append('-e')
3934 if o
in ('-f','--conf-file'):
3936 if o
in ('-n','--section-numbers'):
3939 if o
in ('-a','--attribute'):
3940 e
= parse_entry(v
, allow_name_only
=True)
3942 usage('Illegal -a option: %s' % v
)
3945 config
.cmd_attrs
[k
] = v
3946 if o
in ('-o','--out-file'):
3948 outfile
= '<stdout>'
3951 if o
in ('-s','--no-header-footer'):
3952 options
.append('-s')
3953 if o
in ('-v','--verbose'):
3954 options
.append('-v')
3955 if len(args
) == 0 and len(opts
) == 0:
3959 usage('No source file specified')
3962 usage('No --backend option specified')
3968 if infile
== '<stdin>' and not outfile
:
3969 outfile
= '<stdout>'
3970 # Convert in and out files to absolute paths.
3971 if infile
!= '<stdin>':
3972 infile
= os
.path
.abspath(infile
)
3973 if outfile
and outfile
!= '<stdout>':
3974 outfile
= os
.path
.abspath(outfile
)
3978 profile
.run("asciidoc('%s','%s',(),'%s',None,())"
3979 % (backend
,doctype
,infile
))
3981 asciidoc(backend
, doctype
, confiles
, infile
, outfile
, options
)
3982 if document
.has_errors
:
3985 if __name__
== "__main__":
3988 except KeyboardInterrupt:
3993 print_stderr('%s: unexpected error: %s' %
3994 (os
.path
.basename(sys
.argv
[0]), sys
.exc_info()[1]))
3995 print_stderr('-'*60)
3996 traceback
.print_exc(file=sys
.stderr
)
3997 print_stderr('-'*60)