PageRenderTime 65ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/dev/asciidoc/asciidoc.py

https://code.google.com/
Python | 5902 lines | 5697 code | 23 blank | 182 comment | 257 complexity | d61d36499cb6cece2a540d36a7d4ea0e MD5 | raw file
Possible License(s): GPL-2.0

Large files files are truncated, but you can click here to view the full file

  1. #!/usr/bin/env python
  2. """
  3. asciidoc - converts an AsciiDoc text file to HTML or DocBook
  4. Copyright (C) 2002-2010 Stuart Rackham. Free use of this software is granted
  5. under the terms of the GNU General Public License (GPL).
  6. """
  7. import sys, os, re, time, traceback, tempfile, subprocess, codecs, locale, unicodedata
  8. ### Used by asciidocapi.py ###
  9. VERSION = '8.6.5' # See CHANGLOG file for version history.
  10. MIN_PYTHON_VERSION = 2.4 # Require this version of Python or better.
  11. #---------------------------------------------------------------------------
  12. # Program constants.
  13. #---------------------------------------------------------------------------
  14. DEFAULT_BACKEND = 'html'
  15. DEFAULT_DOCTYPE = 'article'
  16. # Allowed substitution options for List, Paragraph and DelimitedBlock
  17. # definition subs entry.
  18. SUBS_OPTIONS = ('specialcharacters','quotes','specialwords',
  19. 'replacements', 'attributes','macros','callouts','normal','verbatim',
  20. 'none','replacements2')
  21. # Default value for unspecified subs and presubs configuration file entries.
  22. SUBS_NORMAL = ('specialcharacters','quotes','attributes',
  23. 'specialwords','replacements','macros','replacements2')
  24. SUBS_VERBATIM = ('specialcharacters','callouts')
  25. NAME_RE = r'(?u)[^\W\d][-\w]*' # Valid section or attribute name.
  26. OR, AND = ',', '+' # Attribute list separators.
  27. #---------------------------------------------------------------------------
  28. # Utility functions and classes.
  29. #---------------------------------------------------------------------------
  30. class EAsciiDoc(Exception): pass
  31. class OrderedDict(dict):
  32. """
  33. Dictionary ordered by insertion order.
  34. Python Cookbook: Ordered Dictionary, Submitter: David Benjamin.
  35. http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/107747
  36. """
  37. def __init__(self, d=None, **kwargs):
  38. self._keys = []
  39. if d is None: d = kwargs
  40. dict.__init__(self, d)
  41. def __delitem__(self, key):
  42. dict.__delitem__(self, key)
  43. self._keys.remove(key)
  44. def __setitem__(self, key, item):
  45. dict.__setitem__(self, key, item)
  46. if key not in self._keys: self._keys.append(key)
  47. def clear(self):
  48. dict.clear(self)
  49. self._keys = []
  50. def copy(self):
  51. d = dict.copy(self)
  52. d._keys = self._keys[:]
  53. return d
  54. def items(self):
  55. return zip(self._keys, self.values())
  56. def keys(self):
  57. return self._keys
  58. def popitem(self):
  59. try:
  60. key = self._keys[-1]
  61. except IndexError:
  62. raise KeyError('dictionary is empty')
  63. val = self[key]
  64. del self[key]
  65. return (key, val)
  66. def setdefault(self, key, failobj = None):
  67. dict.setdefault(self, key, failobj)
  68. if key not in self._keys: self._keys.append(key)
  69. def update(self, d=None, **kwargs):
  70. if d is None:
  71. d = kwargs
  72. dict.update(self, d)
  73. for key in d.keys():
  74. if key not in self._keys: self._keys.append(key)
  75. def values(self):
  76. return map(self.get, self._keys)
  77. class AttrDict(dict):
  78. """
  79. Like a dictionary except values can be accessed as attributes i.e. obj.foo
  80. can be used in addition to obj['foo'].
  81. If an item is not present None is returned.
  82. """
  83. def __getattr__(self, key):
  84. try: return self[key]
  85. except KeyError: return None
  86. def __setattr__(self, key, value):
  87. self[key] = value
  88. def __delattr__(self, key):
  89. try: del self[key]
  90. except KeyError, k: raise AttributeError, k
  91. def __repr__(self):
  92. return '<AttrDict ' + dict.__repr__(self) + '>'
  93. def __getstate__(self):
  94. return dict(self)
  95. def __setstate__(self,value):
  96. for k,v in value.items(): self[k]=v
  97. class InsensitiveDict(dict):
  98. """
  99. Like a dictionary except key access is case insensitive.
  100. Keys are stored in lower case.
  101. """
  102. def __getitem__(self, key):
  103. return dict.__getitem__(self, key.lower())
  104. def __setitem__(self, key, value):
  105. dict.__setitem__(self, key.lower(), value)
  106. def has_key(self, key):
  107. return dict.has_key(self,key.lower())
  108. def get(self, key, default=None):
  109. return dict.get(self, key.lower(), default)
  110. def update(self, dict):
  111. for k,v in dict.items():
  112. self[k] = v
  113. def setdefault(self, key, default = None):
  114. return dict.setdefault(self, key.lower(), default)
  115. class Trace(object):
  116. """
  117. Used in conjunction with the 'trace' attribute to generate diagnostic
  118. output. There is a single global instance of this class named trace.
  119. """
  120. SUBS_NAMES = ('specialcharacters','quotes','specialwords',
  121. 'replacements', 'attributes','macros','callouts',
  122. 'replacements2')
  123. def __init__(self):
  124. self.name_re = '' # Regexp pattern to match trace names.
  125. self.linenos = True
  126. self.offset = 0
  127. def __call__(self, name, before, after=None):
  128. """
  129. Print trace message if tracing is on and the trace 'name' matches the
  130. document 'trace' attribute (treated as a regexp).
  131. 'before' is the source text before substitution; 'after' text is the
  132. source text after substitutuion.
  133. The 'before' and 'after' messages are only printed if they differ.
  134. """
  135. name_re = document.attributes.get('trace')
  136. if name_re == 'subs': # Alias for all the inline substitutions.
  137. name_re = '|'.join(self.SUBS_NAMES)
  138. self.name_re = name_re
  139. if self.name_re is not None:
  140. msg = message.format(name, 'TRACE: ', self.linenos, offset=self.offset)
  141. if before != after and re.match(self.name_re,name):
  142. if is_array(before):
  143. before = '\n'.join(before)
  144. if after is None:
  145. msg += '\n%s\n' % before
  146. else:
  147. if is_array(after):
  148. after = '\n'.join(after)
  149. msg += '\n<<<\n%s\n>>>\n%s\n' % (before,after)
  150. message.stderr(msg)
  151. class Message:
  152. """
  153. Message functions.
  154. """
  155. PROG = os.path.basename(os.path.splitext(__file__)[0])
  156. def __init__(self):
  157. # Set to True or False to globally override line numbers method
  158. # argument. Has no effect when set to None.
  159. self.linenos = None
  160. self.messages = []
  161. def stdout(self,msg):
  162. print msg
  163. def stderr(self,msg=''):
  164. self.messages.append(msg)
  165. if __name__ == '__main__':
  166. sys.stderr.write('%s: %s%s' % (self.PROG, msg, os.linesep))
  167. def verbose(self, msg,linenos=True):
  168. if config.verbose:
  169. msg = self.format(msg,linenos=linenos)
  170. self.stderr(msg)
  171. def warning(self, msg,linenos=True,offset=0):
  172. msg = self.format(msg,'WARNING: ',linenos,offset=offset)
  173. document.has_warnings = True
  174. self.stderr(msg)
  175. def deprecated(self, msg, linenos=True):
  176. msg = self.format(msg, 'DEPRECATED: ', linenos)
  177. self.stderr(msg)
  178. def format(self, msg, prefix='', linenos=True, cursor=None, offset=0):
  179. """Return formatted message string."""
  180. if self.linenos is not False and ((linenos or self.linenos) and reader.cursor):
  181. if cursor is None:
  182. cursor = reader.cursor
  183. prefix += '%s: line %d: ' % (os.path.basename(cursor[0]),cursor[1]+offset)
  184. return prefix + msg
  185. def error(self, msg, cursor=None, halt=False):
  186. """
  187. Report fatal error.
  188. If halt=True raise EAsciiDoc exception.
  189. If halt=False don't exit application, continue in the hope of reporting
  190. all fatal errors finishing with a non-zero exit code.
  191. """
  192. if halt:
  193. raise EAsciiDoc, self.format(msg,linenos=False,cursor=cursor)
  194. else:
  195. msg = self.format(msg,'ERROR: ',cursor=cursor)
  196. self.stderr(msg)
  197. document.has_errors = True
  198. def unsafe(self, msg):
  199. self.error('unsafe: '+msg)
  200. def userdir():
  201. """
  202. Return user's home directory or None if it is not defined.
  203. """
  204. result = os.path.expanduser('~')
  205. if result == '~':
  206. result = None
  207. return result
  208. def localapp():
  209. """
  210. Return True if we are not executing the system wide version
  211. i.e. the configuration is in the executable's directory.
  212. """
  213. return os.path.isfile(os.path.join(APP_DIR, 'asciidoc.conf'))
  214. def file_in(fname, directory):
  215. """Return True if file fname resides inside directory."""
  216. assert os.path.isfile(fname)
  217. # Empty directory (not to be confused with None) is the current directory.
  218. if directory == '':
  219. directory = os.getcwd()
  220. else:
  221. assert os.path.isdir(directory)
  222. directory = os.path.realpath(directory)
  223. fname = os.path.realpath(fname)
  224. return os.path.commonprefix((directory, fname)) == directory
  225. def safe():
  226. return document.safe
  227. def is_safe_file(fname, directory=None):
  228. # A safe file must reside in directory directory (defaults to the source
  229. # file directory).
  230. if directory is None:
  231. if document.infile == '<stdin>':
  232. return not safe()
  233. directory = os.path.dirname(document.infile)
  234. elif directory == '':
  235. directory = '.'
  236. return (
  237. not safe()
  238. or file_in(fname, directory)
  239. or file_in(fname, APP_DIR)
  240. or file_in(fname, CONF_DIR)
  241. )
  242. def safe_filename(fname, parentdir):
  243. """
  244. Return file name which must reside in the parent file directory.
  245. Return None if file is not found or not safe.
  246. """
  247. if not os.path.isabs(fname):
  248. # Include files are relative to parent document
  249. # directory.
  250. fname = os.path.normpath(os.path.join(parentdir,fname))
  251. if not os.path.isfile(fname):
  252. message.warning('include file not found: %s' % fname)
  253. return None
  254. if not is_safe_file(fname, parentdir):
  255. message.unsafe('include file: %s' % fname)
  256. return None
  257. return fname
  258. def assign(dst,src):
  259. """Assign all attributes from 'src' object to 'dst' object."""
  260. for a,v in src.__dict__.items():
  261. setattr(dst,a,v)
  262. def strip_quotes(s):
  263. """Trim white space and, if necessary, quote characters from s."""
  264. s = s.strip()
  265. # Strip quotation mark characters from quoted strings.
  266. if len(s) >= 3 and s[0] == '"' and s[-1] == '"':
  267. s = s[1:-1]
  268. return s
  269. def is_re(s):
  270. """Return True if s is a valid regular expression else return False."""
  271. try: re.compile(s)
  272. except: return False
  273. else: return True
  274. def re_join(relist):
  275. """Join list of regular expressions re1,re2,... to single regular
  276. expression (re1)|(re2)|..."""
  277. if len(relist) == 0:
  278. return None
  279. result = []
  280. # Delete named groups to avoid ambiguity.
  281. for s in relist:
  282. result.append(re.sub(r'\?P<\S+?>','',s))
  283. result = ')|('.join(result)
  284. result = '('+result+')'
  285. return result
  286. def validate(value,rule,errmsg):
  287. """Validate value against rule expression. Throw EAsciiDoc exception with
  288. errmsg if validation fails."""
  289. try:
  290. if not eval(rule.replace('$',str(value))):
  291. raise EAsciiDoc,errmsg
  292. except Exception:
  293. raise EAsciiDoc,errmsg
  294. return value
  295. def lstrip_list(s):
  296. """
  297. Return list with empty items from start of list removed.
  298. """
  299. for i in range(len(s)):
  300. if s[i]: break
  301. else:
  302. return []
  303. return s[i:]
  304. def rstrip_list(s):
  305. """
  306. Return list with empty items from end of list removed.
  307. """
  308. for i in range(len(s)-1,-1,-1):
  309. if s[i]: break
  310. else:
  311. return []
  312. return s[:i+1]
  313. def strip_list(s):
  314. """
  315. Return list with empty items from start and end of list removed.
  316. """
  317. s = lstrip_list(s)
  318. s = rstrip_list(s)
  319. return s
  320. def is_array(obj):
  321. """
  322. Return True if object is list or tuple type.
  323. """
  324. return isinstance(obj,list) or isinstance(obj,tuple)
  325. def dovetail(lines1, lines2):
  326. """
  327. Append list or tuple of strings 'lines2' to list 'lines1'. Join the last
  328. non-blank item in 'lines1' with the first non-blank item in 'lines2' into a
  329. single string.
  330. """
  331. assert is_array(lines1)
  332. assert is_array(lines2)
  333. lines1 = strip_list(lines1)
  334. lines2 = strip_list(lines2)
  335. if not lines1 or not lines2:
  336. return list(lines1) + list(lines2)
  337. result = list(lines1[:-1])
  338. result.append(lines1[-1] + lines2[0])
  339. result += list(lines2[1:])
  340. return result
  341. def dovetail_tags(stag,content,etag):
  342. """Merge the end tag with the first content line and the last
  343. content line with the end tag. This ensures verbatim elements don't
  344. include extraneous opening and closing line breaks."""
  345. return dovetail(dovetail(stag,content), etag)
  346. def parse_attributes(attrs,dict):
  347. """Update a dictionary with name/value attributes from the attrs string.
  348. The attrs string is a comma separated list of values and keyword name=value
  349. pairs. Values must preceed keywords and are named '1','2'... The entire
  350. attributes list is named '0'. If keywords are specified string values must
  351. be quoted. Examples:
  352. attrs: ''
  353. dict: {}
  354. attrs: 'hello,world'
  355. dict: {'2': 'world', '0': 'hello,world', '1': 'hello'}
  356. attrs: '"hello", planet="earth"'
  357. dict: {'planet': 'earth', '0': '"hello",planet="earth"', '1': 'hello'}
  358. """
  359. def f(*args,**keywords):
  360. # Name and add aguments '1','2'... to keywords.
  361. for i in range(len(args)):
  362. if not str(i+1) in keywords:
  363. keywords[str(i+1)] = args[i]
  364. return keywords
  365. if not attrs:
  366. return
  367. dict['0'] = attrs
  368. # Replace line separators with spaces so line spanning works.
  369. s = re.sub(r'\s', ' ', attrs)
  370. try:
  371. d = eval('f('+s+')')
  372. # Attributes must evaluate to strings, numbers or None.
  373. for v in d.values():
  374. if not (isinstance(v,str) or isinstance(v,int) or isinstance(v,float) or v is None):
  375. raise Exception
  376. except Exception:
  377. s = s.replace('"','\\"')
  378. s = s.split(',')
  379. s = map(lambda x: '"' + x.strip() + '"', s)
  380. s = ','.join(s)
  381. try:
  382. d = eval('f('+s+')')
  383. except Exception:
  384. return # If there's a syntax error leave with {0}=attrs.
  385. for k in d.keys(): # Drop any empty positional arguments.
  386. if d[k] == '': del d[k]
  387. dict.update(d)
  388. assert len(d) > 0
  389. def parse_named_attributes(s,attrs):
  390. """Update a attrs dictionary with name="value" attributes from the s string.
  391. Returns False if invalid syntax.
  392. Example:
  393. attrs: 'star="sun",planet="earth"'
  394. dict: {'planet':'earth', 'star':'sun'}
  395. """
  396. def f(**keywords): return keywords
  397. try:
  398. d = eval('f('+s+')')
  399. attrs.update(d)
  400. return True
  401. except Exception:
  402. return False
  403. def parse_list(s):
  404. """Parse comma separated string of Python literals. Return a tuple of of
  405. parsed values."""
  406. try:
  407. result = eval('tuple(['+s+'])')
  408. except Exception:
  409. raise EAsciiDoc,'malformed list: '+s
  410. return result
  411. def parse_options(options,allowed,errmsg):
  412. """Parse comma separated string of unquoted option names and return as a
  413. tuple of valid options. 'allowed' is a list of allowed option values.
  414. If allowed=() then all legitimate names are allowed.
  415. 'errmsg' is an error message prefix if an illegal option error is thrown."""
  416. result = []
  417. if options:
  418. for s in re.split(r'\s*,\s*',options):
  419. if (allowed and s not in allowed) or not is_name(s):
  420. raise EAsciiDoc,'%s: %s' % (errmsg,s)
  421. result.append(s)
  422. return tuple(result)
  423. def symbolize(s):
  424. """Drop non-symbol characters and convert to lowercase."""
  425. return re.sub(r'(?u)[^\w\-_]', '', s).lower()
  426. def is_name(s):
  427. """Return True if s is valid attribute, macro or tag name
  428. (starts with alpha containing alphanumeric and dashes only)."""
  429. return re.match(r'^'+NAME_RE+r'$',s) is not None
  430. def subs_quotes(text):
  431. """Quoted text is marked up and the resulting text is
  432. returned."""
  433. keys = config.quotes.keys()
  434. for q in keys:
  435. i = q.find('|')
  436. if i != -1 and q != '|' and q != '||':
  437. lq = q[:i] # Left quote.
  438. rq = q[i+1:] # Right quote.
  439. else:
  440. lq = rq = q
  441. tag = config.quotes[q]
  442. if not tag: continue
  443. # Unconstrained quotes prefix the tag name with a hash.
  444. if tag[0] == '#':
  445. tag = tag[1:]
  446. # Unconstrained quotes can appear anywhere.
  447. reo = re.compile(r'(?msu)(^|.)(\[(?P<attrlist>[^[\]]+?)\])?' \
  448. + r'(?:' + re.escape(lq) + r')' \
  449. + r'(?P<content>.+?)(?:'+re.escape(rq)+r')')
  450. else:
  451. # The text within constrained quotes must be bounded by white space.
  452. # Non-word (\W) characters are allowed at boundaries to accomodate
  453. # enveloping quotes and punctuation e.g. a='x', ('x'), 'x', ['x'].
  454. reo = re.compile(r'(?msu)(^|[^\w;:}])(\[(?P<attrlist>[^[\]]+?)\])?' \
  455. + r'(?:' + re.escape(lq) + r')' \
  456. + r'(?P<content>\S|\S.*?\S)(?:'+re.escape(rq)+r')(?=\W|$)')
  457. pos = 0
  458. while True:
  459. mo = reo.search(text,pos)
  460. if not mo: break
  461. if text[mo.start()] == '\\':
  462. # Delete leading backslash.
  463. text = text[:mo.start()] + text[mo.start()+1:]
  464. # Skip past start of match.
  465. pos = mo.start() + 1
  466. else:
  467. attrlist = {}
  468. parse_attributes(mo.group('attrlist'), attrlist)
  469. stag,etag = config.tag(tag, attrlist)
  470. s = mo.group(1) + stag + mo.group('content') + etag
  471. text = text[:mo.start()] + s + text[mo.end():]
  472. pos = mo.start() + len(s)
  473. return text
  474. def subs_tag(tag,dict={}):
  475. """Perform attribute substitution and split tag string returning start, end
  476. tag tuple (c.f. Config.tag())."""
  477. if not tag:
  478. return [None,None]
  479. s = subs_attrs(tag,dict)
  480. if not s:
  481. message.warning('tag \'%s\' dropped: contains undefined attribute' % tag)
  482. return [None,None]
  483. result = s.split('|')
  484. if len(result) == 1:
  485. return result+[None]
  486. elif len(result) == 2:
  487. return result
  488. else:
  489. raise EAsciiDoc,'malformed tag: %s' % tag
  490. def parse_entry(entry, dict=None, unquote=False, unique_values=False,
  491. allow_name_only=False, escape_delimiter=True):
  492. """Parse name=value entry to dictionary 'dict'. Return tuple (name,value)
  493. or None if illegal entry.
  494. If name= then value is set to ''.
  495. If name and allow_name_only=True then value is set to ''.
  496. If name! and allow_name_only=True then value is set to None.
  497. Leading and trailing white space is striped from 'name' and 'value'.
  498. 'name' can contain any printable characters.
  499. If the '=' delimiter character is allowed in the 'name' then
  500. it must be escaped with a backslash and escape_delimiter must be True.
  501. If 'unquote' is True leading and trailing double-quotes are stripped from
  502. 'name' and 'value'.
  503. If unique_values' is True then dictionary entries with the same value are
  504. removed before the parsed entry is added."""
  505. if escape_delimiter:
  506. mo = re.search(r'(?:[^\\](=))',entry)
  507. else:
  508. mo = re.search(r'(=)',entry)
  509. if mo: # name=value entry.
  510. if mo.group(1):
  511. name = entry[:mo.start(1)]
  512. if escape_delimiter:
  513. name = name.replace(r'\=','=') # Unescape \= in name.
  514. value = entry[mo.end(1):]
  515. elif allow_name_only and entry: # name or name! entry.
  516. name = entry
  517. if name[-1] == '!':
  518. name = name[:-1]
  519. value = None
  520. else:
  521. value = ''
  522. else:
  523. return None
  524. if unquote:
  525. name = strip_quotes(name)
  526. if value is not None:
  527. value = strip_quotes(value)
  528. else:
  529. name = name.strip()
  530. if value is not None:
  531. value = value.strip()
  532. if not name:
  533. return None
  534. if dict is not None:
  535. if unique_values:
  536. for k,v in dict.items():
  537. if v == value: del dict[k]
  538. dict[name] = value
  539. return name,value
  540. def parse_entries(entries, dict, unquote=False, unique_values=False,
  541. allow_name_only=False,escape_delimiter=True):
  542. """Parse name=value entries from from lines of text in 'entries' into
  543. dictionary 'dict'. Blank lines are skipped."""
  544. entries = config.expand_templates(entries)
  545. for entry in entries:
  546. if entry and not parse_entry(entry, dict, unquote, unique_values,
  547. allow_name_only, escape_delimiter):
  548. raise EAsciiDoc,'malformed section entry: %s' % entry
  549. def dump_section(name,dict,f=sys.stdout):
  550. """Write parameters in 'dict' as in configuration file section format with
  551. section 'name'."""
  552. f.write('[%s]%s' % (name,writer.newline))
  553. for k,v in dict.items():
  554. k = str(k)
  555. k = k.replace('=',r'\=') # Escape = in name.
  556. # Quote if necessary.
  557. if len(k) != len(k.strip()):
  558. k = '"'+k+'"'
  559. if v and len(v) != len(v.strip()):
  560. v = '"'+v+'"'
  561. if v is None:
  562. # Don't dump undefined attributes.
  563. continue
  564. else:
  565. s = k+'='+v
  566. if s[0] == '#':
  567. s = '\\' + s # Escape so not treated as comment lines.
  568. f.write('%s%s' % (s,writer.newline))
  569. f.write(writer.newline)
  570. def update_attrs(attrs,dict):
  571. """Update 'attrs' dictionary with parsed attributes in dictionary 'dict'."""
  572. for k,v in dict.items():
  573. if not is_name(k):
  574. raise EAsciiDoc,'illegal attribute name: %s' % k
  575. attrs[k] = v
  576. def is_attr_defined(attrs,dic):
  577. """
  578. Check if the sequence of attributes is defined in dictionary 'dic'.
  579. Valid 'attrs' sequence syntax:
  580. <attr> Return True if single attrbiute is defined.
  581. <attr1>,<attr2>,... Return True if one or more attributes are defined.
  582. <attr1>+<attr2>+... Return True if all the attributes are defined.
  583. """
  584. if OR in attrs:
  585. for a in attrs.split(OR):
  586. if dic.get(a.strip()) is not None:
  587. return True
  588. else: return False
  589. elif AND in attrs:
  590. for a in attrs.split(AND):
  591. if dic.get(a.strip()) is None:
  592. return False
  593. else: return True
  594. else:
  595. return dic.get(attrs.strip()) is not None
  596. def filter_lines(filter_cmd, lines, attrs={}):
  597. """
  598. Run 'lines' through the 'filter_cmd' shell command and return the result.
  599. The 'attrs' dictionary contains additional filter attributes.
  600. """
  601. def findfilter(name,dir,filter):
  602. """Find filter file 'fname' with style name 'name' in directory
  603. 'dir'. Return found file path or None if not found."""
  604. if name:
  605. result = os.path.join(dir,'filters',name,filter)
  606. if os.path.isfile(result):
  607. return result
  608. result = os.path.join(dir,'filters',filter)
  609. if os.path.isfile(result):
  610. return result
  611. return None
  612. # Return input lines if there's not filter.
  613. if not filter_cmd or not filter_cmd.strip():
  614. return lines
  615. # Perform attributes substitution on the filter command.
  616. s = subs_attrs(filter_cmd, attrs)
  617. if not s:
  618. message.error('undefined filter attribute in command: %s' % filter_cmd)
  619. return []
  620. filter_cmd = s.strip()
  621. # Parse for quoted and unquoted command and command tail.
  622. # Double quoted.
  623. mo = re.match(r'^"(?P<cmd>[^"]+)"(?P<tail>.*)$', filter_cmd)
  624. if not mo:
  625. # Single quoted.
  626. mo = re.match(r"^'(?P<cmd>[^']+)'(?P<tail>.*)$", filter_cmd)
  627. if not mo:
  628. # Unquoted catch all.
  629. mo = re.match(r'^(?P<cmd>\S+)(?P<tail>.*)$', filter_cmd)
  630. cmd = mo.group('cmd').strip()
  631. found = None
  632. if not os.path.dirname(cmd):
  633. # Filter command has no directory path so search filter directories.
  634. filtername = attrs.get('style')
  635. d = document.attributes.get('docdir')
  636. if d:
  637. found = findfilter(filtername, d, cmd)
  638. if not found:
  639. if USER_DIR:
  640. found = findfilter(filtername, USER_DIR, cmd)
  641. if not found:
  642. if localapp():
  643. found = findfilter(filtername, APP_DIR, cmd)
  644. else:
  645. found = findfilter(filtername, CONF_DIR, cmd)
  646. else:
  647. if os.path.isfile(cmd):
  648. found = cmd
  649. else:
  650. message.warning('filter not found: %s' % cmd)
  651. if found:
  652. filter_cmd = '"' + found + '"' + mo.group('tail')
  653. if sys.platform == 'win32':
  654. # Windows doesn't like running scripts directly so explicitly
  655. # specify interpreter.
  656. if found:
  657. if cmd.endswith('.py'):
  658. filter_cmd = 'python ' + filter_cmd
  659. elif cmd.endswith('.rb'):
  660. filter_cmd = 'ruby ' + filter_cmd
  661. message.verbose('filtering: ' + filter_cmd)
  662. try:
  663. p = subprocess.Popen(filter_cmd, shell=True,
  664. stdin=subprocess.PIPE, stdout=subprocess.PIPE)
  665. output = p.communicate(os.linesep.join(lines))[0]
  666. except Exception:
  667. raise EAsciiDoc,'filter error: %s: %s' % (filter_cmd, sys.exc_info()[1])
  668. if output:
  669. result = [s.rstrip() for s in output.split(os.linesep)]
  670. else:
  671. result = []
  672. filter_status = p.wait()
  673. if filter_status:
  674. message.warning('filter non-zero exit code: %s: returned %d' %
  675. (filter_cmd, filter_status))
  676. if lines and not result:
  677. message.warning('no output from filter: %s' % filter_cmd)
  678. return result
  679. def system(name, args, is_macro=False, attrs=None):
  680. """
  681. Evaluate a system attribute ({name:args}) or system block macro
  682. (name::[args]).
  683. If is_macro is True then we are processing a system block macro otherwise
  684. it's a system attribute.
  685. The attrs dictionary is updated by the counter and set system attributes.
  686. NOTE: The include1 attribute is used internally by the include1::[] macro
  687. and is not for public use.
  688. """
  689. if is_macro:
  690. syntax = '%s::[%s]' % (name,args)
  691. separator = '\n'
  692. else:
  693. syntax = '{%s:%s}' % (name,args)
  694. separator = writer.newline
  695. if name not in ('eval','eval3','sys','sys2','sys3','include','include1','counter','counter2','set','set2','template'):
  696. if is_macro:
  697. msg = 'illegal system macro name: %s' % name
  698. else:
  699. msg = 'illegal system attribute name: %s' % name
  700. message.warning(msg)
  701. return None
  702. if is_macro:
  703. s = subs_attrs(args)
  704. if s is None:
  705. message.warning('skipped %s: undefined attribute in: %s' % (name,args))
  706. return None
  707. args = s
  708. if name != 'include1':
  709. message.verbose('evaluating: %s' % syntax)
  710. if safe() and name not in ('include','include1'):
  711. message.unsafe(syntax)
  712. return None
  713. result = None
  714. if name in ('eval','eval3'):
  715. try:
  716. result = eval(args)
  717. if result is True:
  718. result = ''
  719. elif result is False:
  720. result = None
  721. elif result is not None:
  722. result = str(result)
  723. except Exception:
  724. message.warning('%s: evaluation error' % syntax)
  725. elif name in ('sys','sys2','sys3'):
  726. result = ''
  727. fd,tmp = tempfile.mkstemp()
  728. os.close(fd)
  729. try:
  730. cmd = args
  731. cmd = cmd + (' > %s' % tmp)
  732. if name == 'sys2':
  733. cmd = cmd + ' 2>&1'
  734. if os.system(cmd):
  735. message.warning('%s: non-zero exit status' % syntax)
  736. try:
  737. if os.path.isfile(tmp):
  738. lines = [s.rstrip() for s in open(tmp)]
  739. else:
  740. lines = []
  741. except Exception:
  742. raise EAsciiDoc,'%s: temp file read error' % syntax
  743. result = separator.join(lines)
  744. finally:
  745. if os.path.isfile(tmp):
  746. os.remove(tmp)
  747. elif name in ('counter','counter2'):
  748. mo = re.match(r'^(?P<attr>[^:]*?)(:(?P<seed>.*))?$', args)
  749. attr = mo.group('attr')
  750. seed = mo.group('seed')
  751. if seed and (not re.match(r'^\d+$', seed) and len(seed) > 1):
  752. message.warning('%s: illegal counter seed: %s' % (syntax,seed))
  753. return None
  754. if not is_name(attr):
  755. message.warning('%s: illegal attribute name' % syntax)
  756. return None
  757. value = document.attributes.get(attr)
  758. if value:
  759. if not re.match(r'^\d+$', value) and len(value) > 1:
  760. message.warning('%s: illegal counter value: %s'
  761. % (syntax,value))
  762. return None
  763. if re.match(r'^\d+$', value):
  764. expr = value + '+1'
  765. else:
  766. expr = 'chr(ord("%s")+1)' % value
  767. try:
  768. result = str(eval(expr))
  769. except Exception:
  770. message.warning('%s: evaluation error: %s' % (syntax, expr))
  771. else:
  772. if seed:
  773. result = seed
  774. else:
  775. result = '1'
  776. document.attributes[attr] = result
  777. if attrs is not None:
  778. attrs[attr] = result
  779. if name == 'counter2':
  780. result = ''
  781. elif name in ('set','set2'):
  782. mo = re.match(r'^(?P<attr>[^:]*?)(:(?P<value>.*))?$', args)
  783. attr = mo.group('attr')
  784. value = mo.group('value')
  785. if value is None:
  786. value = ''
  787. if attr.endswith('!'):
  788. attr = attr[:-1]
  789. value = None
  790. if not is_name(attr):
  791. message.warning('%s: illegal attribute name' % syntax)
  792. else:
  793. if attrs is not None:
  794. attrs[attr] = value
  795. if name != 'set2': # set2 only updates local attributes.
  796. document.attributes[attr] = value
  797. if value is None:
  798. result = None
  799. else:
  800. result = ''
  801. elif name == 'include':
  802. if not os.path.exists(args):
  803. message.warning('%s: file does not exist' % syntax)
  804. elif not is_safe_file(args):
  805. message.unsafe(syntax)
  806. else:
  807. result = [s.rstrip() for s in open(args)]
  808. if result:
  809. result = subs_attrs(result)
  810. result = separator.join(result)
  811. result = result.expandtabs(reader.tabsize)
  812. else:
  813. result = ''
  814. elif name == 'include1':
  815. result = separator.join(config.include1[args])
  816. elif name == 'template':
  817. if not args in config.sections:
  818. message.warning('%s: template does not exist' % syntax)
  819. else:
  820. result = []
  821. for line in config.sections[args]:
  822. line = subs_attrs(line)
  823. if line is not None:
  824. result.append(line)
  825. result = '\n'.join(result)
  826. else:
  827. assert False
  828. if result and name in ('eval3','sys3'):
  829. macros.passthroughs.append(result)
  830. result = '\x07' + str(len(macros.passthroughs)-1) + '\x07'
  831. return result
  832. def subs_attrs(lines, dictionary=None):
  833. """Substitute 'lines' of text with attributes from the global
  834. document.attributes dictionary and from 'dictionary' ('dictionary'
  835. entries take precedence). Return a tuple of the substituted lines. 'lines'
  836. containing undefined attributes are deleted. If 'lines' is a string then
  837. return a string.
  838. - Attribute references are substituted in the following order: simple,
  839. conditional, system.
  840. - Attribute references inside 'dictionary' entry values are substituted.
  841. """
  842. def end_brace(text,start):
  843. """Return index following end brace that matches brace at start in
  844. text."""
  845. assert text[start] == '{'
  846. n = 0
  847. result = start
  848. for c in text[start:]:
  849. # Skip braces that are followed by a backslash.
  850. if result == len(text)-1 or text[result+1] != '\\':
  851. if c == '{': n = n + 1
  852. elif c == '}': n = n - 1
  853. result = result + 1
  854. if n == 0: break
  855. return result
  856. if type(lines) == str:
  857. string_result = True
  858. lines = [lines]
  859. else:
  860. string_result = False
  861. if dictionary is None:
  862. attrs = document.attributes
  863. else:
  864. # Remove numbered document attributes so they don't clash with
  865. # attribute list positional attributes.
  866. attrs = {}
  867. for k,v in document.attributes.items():
  868. if not re.match(r'^\d+$', k):
  869. attrs[k] = v
  870. # Substitute attribute references inside dictionary values.
  871. for k,v in dictionary.items():
  872. if v is None:
  873. del dictionary[k]
  874. else:
  875. v = subs_attrs(str(v))
  876. if v is None:
  877. del dictionary[k]
  878. else:
  879. dictionary[k] = v
  880. attrs.update(dictionary)
  881. # Substitute all attributes in all lines.
  882. result = []
  883. for line in lines:
  884. # Make it easier for regular expressions.
  885. line = line.replace('\\{','{\\')
  886. line = line.replace('\\}','}\\')
  887. # Expand simple attributes ({name}).
  888. # Nested attributes not allowed.
  889. reo = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w]*?)\}(?!\\)')
  890. pos = 0
  891. while True:
  892. mo = reo.search(line,pos)
  893. if not mo: break
  894. s = attrs.get(mo.group('name'))
  895. if s is None:
  896. pos = mo.end()
  897. else:
  898. s = str(s)
  899. line = line[:mo.start()] + s + line[mo.end():]
  900. pos = mo.start() + len(s)
  901. # Expand conditional attributes.
  902. # Single name -- higher precedence.
  903. reo1 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w]*?)' \
  904. r'(?P<op>\=|\?|!|#|%|@|\$)' \
  905. r'(?P<value>.*?)\}(?!\\)')
  906. # Multiple names (n1,n2,... or n1+n2+...) -- lower precedence.
  907. reo2 = re.compile(r'(?su)\{(?P<name>[^\\\W][-\w'+OR+AND+r']*?)' \
  908. r'(?P<op>\=|\?|!|#|%|@|\$)' \
  909. r'(?P<value>.*?)\}(?!\\)')
  910. for reo in [reo1,reo2]:
  911. pos = 0
  912. while True:
  913. mo = reo.search(line,pos)
  914. if not mo: break
  915. attr = mo.group()
  916. name = mo.group('name')
  917. if reo == reo2:
  918. if OR in name:
  919. sep = OR
  920. else:
  921. sep = AND
  922. names = [s.strip() for s in name.split(sep) if s.strip() ]
  923. for n in names:
  924. if not re.match(r'^[^\\\W][-\w]*$',n):
  925. message.error('illegal attribute syntax: %s' % attr)
  926. if sep == OR:
  927. # Process OR name expression: n1,n2,...
  928. for n in names:
  929. if attrs.get(n) is not None:
  930. lval = ''
  931. break
  932. else:
  933. lval = None
  934. else:
  935. # Process AND name expression: n1+n2+...
  936. for n in names:
  937. if attrs.get(n) is None:
  938. lval = None
  939. break
  940. else:
  941. lval = ''
  942. else:
  943. lval = attrs.get(name)
  944. op = mo.group('op')
  945. # mo.end() not good enough because '{x={y}}' matches '{x={y}'.
  946. end = end_brace(line,mo.start())
  947. rval = line[mo.start('value'):end-1]
  948. UNDEFINED = '{zzzzz}'
  949. if lval is None:
  950. if op == '=': s = rval
  951. elif op == '?': s = ''
  952. elif op == '!': s = rval
  953. elif op == '#': s = UNDEFINED # So the line is dropped.
  954. elif op == '%': s = rval
  955. elif op in ('@','$'):
  956. s = UNDEFINED # So the line is dropped.
  957. else:
  958. assert False, 'illegal attribute: %s' % attr
  959. else:
  960. if op == '=': s = lval
  961. elif op == '?': s = rval
  962. elif op == '!': s = ''
  963. elif op == '#': s = rval
  964. elif op == '%': s = UNDEFINED # So the line is dropped.
  965. elif op in ('@','$'):
  966. v = re.split(r'(?<!\\):',rval)
  967. if len(v) not in (2,3):
  968. message.error('illegal attribute syntax: %s' % attr)
  969. s = ''
  970. elif not is_re('^'+v[0]+'$'):
  971. message.error('illegal attribute regexp: %s' % attr)
  972. s = ''
  973. else:
  974. v = [s.replace('\\:',':') for s in v]
  975. re_mo = re.match('^'+v[0]+'$',lval)
  976. if op == '@':
  977. if re_mo:
  978. s = v[1] # {<name>@<re>:<v1>[:<v2>]}
  979. else:
  980. if len(v) == 3: # {<name>@<re>:<v1>:<v2>}
  981. s = v[2]
  982. else: # {<name>@<re>:<v1>}
  983. s = ''
  984. else:
  985. if re_mo:
  986. if len(v) == 2: # {<name>$<re>:<v1>}
  987. s = v[1]
  988. elif v[1] == '': # {<name>$<re>::<v2>}
  989. s = UNDEFINED # So the line is dropped.
  990. else: # {<name>$<re>:<v1>:<v2>}
  991. s = v[1]
  992. else:
  993. if len(v) == 2: # {<name>$<re>:<v1>}
  994. s = UNDEFINED # So the line is dropped.
  995. else: # {<name>$<re>:<v1>:<v2>}
  996. s = v[2]
  997. else:
  998. assert False, 'illegal attribute: %s' % attr
  999. s = str(s)
  1000. line = line[:mo.start()] + s + line[end:]
  1001. pos = mo.start() + len(s)
  1002. # Drop line if it contains unsubstituted {name} references.
  1003. skipped = re.search(r'(?su)\{[^\\\W][-\w]*?\}(?!\\)', line)
  1004. if skipped:
  1005. trace('dropped line', line)
  1006. continue;
  1007. # Expand system attributes (eval has precedence).
  1008. reos = [
  1009. re.compile(r'(?su)\{(?P<action>eval):(?P<expr>.*?)\}(?!\\)'),
  1010. re.compile(r'(?su)\{(?P<action>[^\\\W][-\w]*?):(?P<expr>.*?)\}(?!\\)'),
  1011. ]
  1012. skipped = False
  1013. for reo in reos:
  1014. pos = 0
  1015. while True:
  1016. mo = reo.search(line,pos)
  1017. if not mo: break
  1018. expr = mo.group('expr')
  1019. action = mo.group('action')
  1020. expr = expr.replace('{\\','{')
  1021. expr = expr.replace('}\\','}')
  1022. s = system(action, expr, attrs=dictionary)
  1023. if dictionary is not None and action in ('counter','counter2','set','set2'):
  1024. # These actions create and update attributes.
  1025. attrs.update(dictionary)
  1026. if s is None:
  1027. # Drop line if the action returns None.
  1028. skipped = True
  1029. break
  1030. line = line[:mo.start()] + s + line[mo.end():]
  1031. pos = mo.start() + len(s)
  1032. if skipped:
  1033. break
  1034. if not skipped:
  1035. # Remove backslash from escaped entries.
  1036. line = line.replace('{\\','{')
  1037. line = line.replace('}\\','}')
  1038. result.append(line)
  1039. if string_result:
  1040. if result:
  1041. return '\n'.join(result)
  1042. else:
  1043. return None
  1044. else:
  1045. return tuple(result)
  1046. def char_encoding():
  1047. encoding = document.attributes.get('encoding')
  1048. if encoding:
  1049. try:
  1050. codecs.lookup(encoding)
  1051. except LookupError,e:
  1052. raise EAsciiDoc,str(e)
  1053. return encoding
  1054. def char_len(s):
  1055. return len(char_decode(s))
  1056. east_asian_widths = {'W': 2, # Wide
  1057. 'F': 2, # Full-width (wide)
  1058. 'Na': 1, # Narrow
  1059. 'H': 1, # Half-width (narrow)
  1060. 'N': 1, # Neutral (not East Asian, treated as narrow)
  1061. 'A': 1} # Ambiguous (s/b wide in East Asian context,
  1062. # narrow otherwise, but that doesn't work)
  1063. """Mapping of result codes from `unicodedata.east_asian_width()` to character
  1064. column widths."""
  1065. def column_width(s):
  1066. text = char_decode(s)
  1067. if isinstance(text, unicode):
  1068. width = 0
  1069. for c in text:
  1070. width += east_asian_widths[unicodedata.east_asian_width(c)]
  1071. return width
  1072. else:
  1073. return len(text)
  1074. def char_decode(s):
  1075. if char_encoding():
  1076. try:
  1077. return s.decode(char_encoding())
  1078. except Exception:
  1079. raise EAsciiDoc, \
  1080. "'%s' codec can't decode \"%s\"" % (char_encoding(), s)
  1081. else:
  1082. return s
  1083. def char_encode(s):
  1084. if char_encoding():
  1085. return s.encode(char_encoding())
  1086. else:
  1087. return s
  1088. def time_str(t):
  1089. """Convert seconds since the Epoch to formatted local time string."""
  1090. t = time.localtime(t)
  1091. s = time.strftime('%H:%M:%S',t)
  1092. if time.daylight and t.tm_isdst == 1:
  1093. result = s + ' ' + time.tzname[1]
  1094. else:
  1095. result = s + ' ' + time.tzname[0]
  1096. # Attempt to convert the localtime to the output encoding.
  1097. try:
  1098. result = char_encode(result.decode(locale.getdefaultlocale()[1]))
  1099. except Exception:
  1100. pass
  1101. return result
  1102. def date_str(t):
  1103. """Convert seconds since the Epoch to formatted local date string."""
  1104. t = time.localtime(t)
  1105. return time.strftime('%Y-%m-%d',t)
  1106. class Lex:
  1107. """Lexical analysis routines. Static methods and attributes only."""
  1108. prev_element = None
  1109. prev_cursor = None
  1110. def __init__(self):
  1111. raise AssertionError,'no class instances allowed'
  1112. @staticmethod
  1113. def next():
  1114. """Returns class of next element on the input (None if EOF). The
  1115. reader is assumed to be at the first line following a previous element,
  1116. end of file or line one. Exits with the reader pointing to the first
  1117. line of the next element or EOF (leading blank lines are skipped)."""
  1118. reader.skip_blank_lines()
  1119. if reader.eof(): return None
  1120. # Optimization: If we've already checked for an element at this
  1121. # position return the element.
  1122. if Lex.prev_element and Lex.prev_cursor == reader.cursor:
  1123. return Lex.prev_element
  1124. if AttributeEntry.isnext():
  1125. result = AttributeEntry
  1126. elif AttributeList.isnext():
  1127. result = AttributeList
  1128. elif BlockTitle.isnext() and not tables_OLD.isnext():
  1129. result = BlockTitle
  1130. elif Title.isnext():
  1131. if AttributeList.style() == 'float':
  1132. result = FloatingTitle
  1133. else:
  1134. result = Title
  1135. elif macros.isnext():
  1136. result = macros.current
  1137. elif lists.isnext():
  1138. result = lists.current
  1139. elif blocks.isnext():
  1140. result = blocks.current
  1141. elif tables_OLD.isnext():
  1142. result = tables_OLD.current
  1143. elif tables.isnext():
  1144. result = tables.current
  1145. else:
  1146. if not paragraphs.isnext():
  1147. raise EAsciiDoc,'paragraph expected'
  1148. result = paragraphs.current
  1149. # Optimization: Cache answer.
  1150. Lex.prev_cursor = reader.cursor
  1151. Lex.prev_element = result
  1152. return result
  1153. @staticmethod
  1154. def canonical_subs(options):
  1155. """Translate composite subs values."""
  1156. if len(options) == 1:
  1157. if options[0] == 'none':
  1158. options = ()
  1159. elif options[0] == 'normal':
  1160. options = config.subsnormal
  1161. elif options[0] == 'verbatim':
  1162. options = config.subsverbatim
  1163. return options
  1164. @staticmethod
  1165. def subs_1(s,options):
  1166. """Perform substitution specified in 'options' (in 'options' order)."""
  1167. if not s:
  1168. return s
  1169. if document.attributes.get('plaintext') is not None:
  1170. options = ('specialcharacters',)
  1171. result = s
  1172. options = Lex.canonical_subs(options)
  1173. for o in options:
  1174. if o == 'specialcharacters':
  1175. result = config.subs_specialchars(result)
  1176. elif o == 'attributes':
  1177. result = subs_attrs(result)
  1178. elif o == 'quotes':
  1179. result = subs_quotes(result)
  1180. elif o == 'specialwords':
  1181. result = config.subs_specialwords(result)
  1182. elif o in ('replacements','replacements2'):
  1183. result = config.subs_replacements(result,o)
  1184. elif o == 'macros':
  1185. result = macros.subs(result)
  1186. elif o == 'callouts':
  1187. result = macros.subs(result,callouts=True)
  1188. else:
  1189. raise EAsciiDoc,'illegal substitution option: %s' % o
  1190. trace(o, s, result)
  1191. if not result:
  1192. break
  1193. return result
  1194. @staticmethod
  1195. def subs(lines,options):
  1196. """Perform inline processing specified by 'options' (in 'options'
  1197. order) on sequence of 'lines'."""
  1198. if not lines or not options:
  1199. return lines
  1200. options = Lex.canonical_subs(options)
  1201. # Join lines so quoting can span multiple lines.
  1202. para = '\n'.join(lines)
  1203. if 'macros' in options:
  1204. para = macros.extract_passthroughs(para)
  1205. for o in options:
  1206. if o == 'attributes':
  1207. # If we don't substitute attributes line-by-line then a single
  1208. # undefined attribute will drop the entire paragraph.
  1209. lines = subs_attrs(para.split('\n'))
  1210. para = '\n'.join(lines)
  1211. else:
  1212. para = Lex.subs_1(para,(o,))
  1213. if 'macros' in options:
  1214. para = macros.restore_passthroughs(para)
  1215. return para.splitlines()
  1216. @staticmethod
  1217. def set_margin(lines, margin=0):
  1218. """Utility routine that sets the left margin to 'margin' space in a
  1219. block of non-blank lines."""
  1220. # Calculate width of block margin.
  1221. lines = list(lines)
  1222. width = len(lines[0])
  1223. for s in lines:
  1224. i = re.search(r'\S',s).start()
  1225. if i < width: width = i
  1226. # Strip margin width from all lines.
  1227. for i in range(len(lines)):
  1228. lines[i] = ' '*margin + lines[i][width:]
  1229. return lines
  1230. #---------------------------------------------------------------------------
  1231. # Document element classes parse AsciiDoc reader input and write DocBook writer
  1232. # output.
  1233. #---------------------------------------------------------------------------
  1234. class Document(object):
  1235. # doctype property.
  1236. def getdoctype(self):
  1237. return self.attributes.get('doctype')
  1238. def setdoctype(self,doctype):
  1239. self.attributes['doctype'] = doctype
  1240. doctype = property(getdoctype,setdoctype)
  1241. # backend property.
  1242. def getbackend(self):
  1243. return self.attributes.get('backend')
  1244. def setbackend(self,backend):
  1245. if backend:
  1246. backend = self.attributes.get('backend-alias-' + backend, backend)
  1247. self.attributes['backend'] = backend
  1248. backend = property(getbackend,setbackend)
  1249. def __init__(self):
  1250. self.infile = None # Source file name.
  1251. self.outfile = None # Output file name.
  1252. self.attributes = InsensitiveDict()
  1253. self.level = 0 # 0 => front matter. 1,2,3 => sect1,2,3.
  1254. self.has_errors = False # Set true if processing errors were flagged.
  1255. self.has_warnings = False # Set true if warnings were flagged.
  1256. self.safe = False # Default safe mode.
  1257. def update_attributes(self,attrs=None):
  1258. """
  1259. Set implicit attributes and attributes in 'attrs'.
  1260. """
  1261. t = time.time()
  1262. self.attributes['localtime'] = time_str(t)
  1263. self.attributes['localdate'] = date_str(t)
  1264. self.attributes['asciidoc-version'] = VERSION
  1265. self.attributes['asciidoc-file'] = APP_FILE
  1266. self.attributes['asciidoc-dir'] = APP_DIR
  1267. self.attributes['asciidoc-confdir'] = CONF_DIR
  1268. self.attributes['user-dir'] = USER_DIR
  1269. if config.verbose:
  1270. self.attributes['verbose'] = ''
  1271. # Update with configuration file attributes.
  1272. if attrs:
  1273. self.attributes.update(attrs)
  1274. # Update with command-line attributes.
  1275. self.

Large files files are truncated, but you can click here to view the full file