PageRenderTime 58ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/modules/freetype2/src/tools/docmaker/content.py

https://bitbucket.org/bgirard/tiling
Python | 584 lines | 534 code | 11 blank | 39 comment | 2 complexity | 142e5041e327399f358be9dba149a64d MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause, BSD-2-Clause, LGPL-3.0, AGPL-1.0, MPL-2.0-no-copyleft-exception, GPL-2.0, JSON, Apache-2.0, 0BSD, MIT
  1. # Content (c) 2002, 2004, 2006, 2007, 2008, 2009
  2. # David Turner <david@freetype.org>
  3. #
  4. # This file contains routines used to parse the content of documentation
  5. # comment blocks and build more structured objects out of them.
  6. #
  7. from sources import *
  8. from utils import *
  9. import string, re
  10. # this regular expression is used to detect code sequences. these
  11. # are simply code fragments embedded in '{' and '}' like in:
  12. #
  13. # {
  14. # x = y + z;
  15. # if ( zookoo == 2 )
  16. # {
  17. # foobar();
  18. # }
  19. # }
  20. #
  21. # note that indentation of the starting and ending accolades must be
  22. # exactly the same. the code sequence can contain accolades at greater
  23. # indentation
  24. #
  25. re_code_start = re.compile( r"(\s*){\s*$" )
  26. re_code_end = re.compile( r"(\s*)}\s*$" )
  27. # this regular expression is used to isolate identifiers from
  28. # other text
  29. #
  30. re_identifier = re.compile( r'(\w*)' )
  31. # we collect macros ending in `_H'; while outputting the object data, we use
  32. # this info together with the object's file location to emit the appropriate
  33. # header file macro and name before the object itself
  34. #
  35. re_header_macro = re.compile( r'^#define\s{1,}(\w{1,}_H)\s{1,}<(.*)>' )
  36. #############################################################################
  37. #
  38. # The DocCode class is used to store source code lines.
  39. #
  40. # 'self.lines' contains a set of source code lines that will be dumped as
  41. # HTML in a <PRE> tag.
  42. #
  43. # The object is filled line by line by the parser; it strips the leading
  44. # "margin" space from each input line before storing it in 'self.lines'.
  45. #
  46. class DocCode:
  47. def __init__( self, margin, lines ):
  48. self.lines = []
  49. self.words = None
  50. # remove margin spaces
  51. for l in lines:
  52. if string.strip( l[:margin] ) == "":
  53. l = l[margin:]
  54. self.lines.append( l )
  55. def dump( self, prefix = "", width = 60 ):
  56. lines = self.dump_lines( 0, width )
  57. for l in lines:
  58. print prefix + l
  59. def dump_lines( self, margin = 0, width = 60 ):
  60. result = []
  61. for l in self.lines:
  62. result.append( " " * margin + l )
  63. return result
  64. #############################################################################
  65. #
  66. # The DocPara class is used to store "normal" text paragraph.
  67. #
  68. # 'self.words' contains the list of words that make up the paragraph
  69. #
  70. class DocPara:
  71. def __init__( self, lines ):
  72. self.lines = None
  73. self.words = []
  74. for l in lines:
  75. l = string.strip( l )
  76. self.words.extend( string.split( l ) )
  77. def dump( self, prefix = "", width = 60 ):
  78. lines = self.dump_lines( 0, width )
  79. for l in lines:
  80. print prefix + l
  81. def dump_lines( self, margin = 0, width = 60 ):
  82. cur = "" # current line
  83. col = 0 # current width
  84. result = []
  85. for word in self.words:
  86. ln = len( word )
  87. if col > 0:
  88. ln = ln + 1
  89. if col + ln > width:
  90. result.append( " " * margin + cur )
  91. cur = word
  92. col = len( word )
  93. else:
  94. if col > 0:
  95. cur = cur + " "
  96. cur = cur + word
  97. col = col + ln
  98. if col > 0:
  99. result.append( " " * margin + cur )
  100. return result
  101. #############################################################################
  102. #
  103. # The DocField class is used to store a list containing either DocPara or
  104. # DocCode objects. Each DocField also has an optional "name" which is used
  105. # when the object corresponds to a field or value definition
  106. #
  107. class DocField:
  108. def __init__( self, name, lines ):
  109. self.name = name # can be None for normal paragraphs/sources
  110. self.items = [] # list of items
  111. mode_none = 0 # start parsing mode
  112. mode_code = 1 # parsing code sequences
  113. mode_para = 3 # parsing normal paragraph
  114. margin = -1 # current code sequence indentation
  115. cur_lines = []
  116. # now analyze the markup lines to see if they contain paragraphs,
  117. # code sequences or fields definitions
  118. #
  119. start = 0
  120. mode = mode_none
  121. for l in lines:
  122. # are we parsing a code sequence ?
  123. if mode == mode_code:
  124. m = re_code_end.match( l )
  125. if m and len( m.group( 1 ) ) <= margin:
  126. # that's it, we finished the code sequence
  127. code = DocCode( 0, cur_lines )
  128. self.items.append( code )
  129. margin = -1
  130. cur_lines = []
  131. mode = mode_none
  132. else:
  133. # nope, continue the code sequence
  134. cur_lines.append( l[margin:] )
  135. else:
  136. # start of code sequence ?
  137. m = re_code_start.match( l )
  138. if m:
  139. # save current lines
  140. if cur_lines:
  141. para = DocPara( cur_lines )
  142. self.items.append( para )
  143. cur_lines = []
  144. # switch to code extraction mode
  145. margin = len( m.group( 1 ) )
  146. mode = mode_code
  147. else:
  148. if not string.split( l ) and cur_lines:
  149. # if the line is empty, we end the current paragraph,
  150. # if any
  151. para = DocPara( cur_lines )
  152. self.items.append( para )
  153. cur_lines = []
  154. else:
  155. # otherwise, simply add the line to the current
  156. # paragraph
  157. cur_lines.append( l )
  158. if mode == mode_code:
  159. # unexpected end of code sequence
  160. code = DocCode( margin, cur_lines )
  161. self.items.append( code )
  162. elif cur_lines:
  163. para = DocPara( cur_lines )
  164. self.items.append( para )
  165. def dump( self, prefix = "" ):
  166. if self.field:
  167. print prefix + self.field + " ::"
  168. prefix = prefix + "----"
  169. first = 1
  170. for p in self.items:
  171. if not first:
  172. print ""
  173. p.dump( prefix )
  174. first = 0
  175. def dump_lines( self, margin = 0, width = 60 ):
  176. result = []
  177. nl = None
  178. for p in self.items:
  179. if nl:
  180. result.append( "" )
  181. result.extend( p.dump_lines( margin, width ) )
  182. nl = 1
  183. return result
  184. # this regular expression is used to detect field definitions
  185. #
  186. re_field = re.compile( r"\s*(\w*|\w(\w|\.)*\w)\s*::" )
  187. class DocMarkup:
  188. def __init__( self, tag, lines ):
  189. self.tag = string.lower( tag )
  190. self.fields = []
  191. cur_lines = []
  192. field = None
  193. mode = 0
  194. for l in lines:
  195. m = re_field.match( l )
  196. if m:
  197. # we detected the start of a new field definition
  198. # first, save the current one
  199. if cur_lines:
  200. f = DocField( field, cur_lines )
  201. self.fields.append( f )
  202. cur_lines = []
  203. field = None
  204. field = m.group( 1 ) # record field name
  205. ln = len( m.group( 0 ) )
  206. l = " " * ln + l[ln:]
  207. cur_lines = [l]
  208. else:
  209. cur_lines.append( l )
  210. if field or cur_lines:
  211. f = DocField( field, cur_lines )
  212. self.fields.append( f )
  213. def get_name( self ):
  214. try:
  215. return self.fields[0].items[0].words[0]
  216. except:
  217. return None
  218. def get_start( self ):
  219. try:
  220. result = ""
  221. for word in self.fields[0].items[0].words:
  222. result = result + " " + word
  223. return result[1:]
  224. except:
  225. return "ERROR"
  226. def dump( self, margin ):
  227. print " " * margin + "<" + self.tag + ">"
  228. for f in self.fields:
  229. f.dump( " " )
  230. print " " * margin + "</" + self.tag + ">"
  231. class DocChapter:
  232. def __init__( self, block ):
  233. self.block = block
  234. self.sections = []
  235. if block:
  236. self.name = block.name
  237. self.title = block.get_markup_words( "title" )
  238. self.order = block.get_markup_words( "sections" )
  239. else:
  240. self.name = "Other"
  241. self.title = string.split( "Miscellaneous" )
  242. self.order = []
  243. class DocSection:
  244. def __init__( self, name = "Other" ):
  245. self.name = name
  246. self.blocks = {}
  247. self.block_names = [] # ordered block names in section
  248. self.defs = []
  249. self.abstract = ""
  250. self.description = ""
  251. self.order = []
  252. self.title = "ERROR"
  253. self.chapter = None
  254. def add_def( self, block ):
  255. self.defs.append( block )
  256. def add_block( self, block ):
  257. self.block_names.append( block.name )
  258. self.blocks[block.name] = block
  259. def process( self ):
  260. # look up one block that contains a valid section description
  261. for block in self.defs:
  262. title = block.get_markup_text( "title" )
  263. if title:
  264. self.title = title
  265. self.abstract = block.get_markup_words( "abstract" )
  266. self.description = block.get_markup_items( "description" )
  267. self.order = block.get_markup_words( "order" )
  268. return
  269. def reorder( self ):
  270. self.block_names = sort_order_list( self.block_names, self.order )
  271. class ContentProcessor:
  272. def __init__( self ):
  273. """initialize a block content processor"""
  274. self.reset()
  275. self.sections = {} # dictionary of documentation sections
  276. self.section = None # current documentation section
  277. self.chapters = [] # list of chapters
  278. self.headers = {} # dictionary of header macros
  279. def set_section( self, section_name ):
  280. """set current section during parsing"""
  281. if not self.sections.has_key( section_name ):
  282. section = DocSection( section_name )
  283. self.sections[section_name] = section
  284. self.section = section
  285. else:
  286. self.section = self.sections[section_name]
  287. def add_chapter( self, block ):
  288. chapter = DocChapter( block )
  289. self.chapters.append( chapter )
  290. def reset( self ):
  291. """reset the content processor for a new block"""
  292. self.markups = []
  293. self.markup = None
  294. self.markup_lines = []
  295. def add_markup( self ):
  296. """add a new markup section"""
  297. if self.markup and self.markup_lines:
  298. # get rid of last line of markup if it's empty
  299. marks = self.markup_lines
  300. if len( marks ) > 0 and not string.strip( marks[-1] ):
  301. self.markup_lines = marks[:-1]
  302. m = DocMarkup( self.markup, self.markup_lines )
  303. self.markups.append( m )
  304. self.markup = None
  305. self.markup_lines = []
  306. def process_content( self, content ):
  307. """process a block content and return a list of DocMarkup objects
  308. corresponding to it"""
  309. markup = None
  310. markup_lines = []
  311. first = 1
  312. for line in content:
  313. found = None
  314. for t in re_markup_tags:
  315. m = t.match( line )
  316. if m:
  317. found = string.lower( m.group( 1 ) )
  318. prefix = len( m.group( 0 ) )
  319. line = " " * prefix + line[prefix:] # remove markup from line
  320. break
  321. # is it the start of a new markup section ?
  322. if found:
  323. first = 0
  324. self.add_markup() # add current markup content
  325. self.markup = found
  326. if len( string.strip( line ) ) > 0:
  327. self.markup_lines.append( line )
  328. elif first == 0:
  329. self.markup_lines.append( line )
  330. self.add_markup()
  331. return self.markups
  332. def parse_sources( self, source_processor ):
  333. blocks = source_processor.blocks
  334. count = len( blocks )
  335. for n in range( count ):
  336. source = blocks[n]
  337. if source.content:
  338. # this is a documentation comment, we need to catch
  339. # all following normal blocks in the "follow" list
  340. #
  341. follow = []
  342. m = n + 1
  343. while m < count and not blocks[m].content:
  344. follow.append( blocks[m] )
  345. m = m + 1
  346. doc_block = DocBlock( source, follow, self )
  347. def finish( self ):
  348. # process all sections to extract their abstract, description
  349. # and ordered list of items
  350. #
  351. for sec in self.sections.values():
  352. sec.process()
  353. # process chapters to check that all sections are correctly
  354. # listed there
  355. for chap in self.chapters:
  356. for sec in chap.order:
  357. if self.sections.has_key( sec ):
  358. section = self.sections[sec]
  359. section.chapter = chap
  360. section.reorder()
  361. chap.sections.append( section )
  362. else:
  363. sys.stderr.write( "WARNING: chapter '" + \
  364. chap.name + "' in " + chap.block.location() + \
  365. " lists unknown section '" + sec + "'\n" )
  366. # check that all sections are in a chapter
  367. #
  368. others = []
  369. for sec in self.sections.values():
  370. if not sec.chapter:
  371. others.append( sec )
  372. # create a new special chapter for all remaining sections
  373. # when necessary
  374. #
  375. if others:
  376. chap = DocChapter( None )
  377. chap.sections = others
  378. self.chapters.append( chap )
  379. class DocBlock:
  380. def __init__( self, source, follow, processor ):
  381. processor.reset()
  382. self.source = source
  383. self.code = []
  384. self.type = "ERRTYPE"
  385. self.name = "ERRNAME"
  386. self.section = processor.section
  387. self.markups = processor.process_content( source.content )
  388. # compute block type from first markup tag
  389. try:
  390. self.type = self.markups[0].tag
  391. except:
  392. pass
  393. # compute block name from first markup paragraph
  394. try:
  395. markup = self.markups[0]
  396. para = markup.fields[0].items[0]
  397. name = para.words[0]
  398. m = re_identifier.match( name )
  399. if m:
  400. name = m.group( 1 )
  401. self.name = name
  402. except:
  403. pass
  404. if self.type == "section":
  405. # detect new section starts
  406. processor.set_section( self.name )
  407. processor.section.add_def( self )
  408. elif self.type == "chapter":
  409. # detect new chapter
  410. processor.add_chapter( self )
  411. else:
  412. processor.section.add_block( self )
  413. # now, compute the source lines relevant to this documentation
  414. # block. We keep normal comments in for obvious reasons (??)
  415. source = []
  416. for b in follow:
  417. if b.format:
  418. break
  419. for l in b.lines:
  420. # collect header macro definitions
  421. m = re_header_macro.match( l )
  422. if m:
  423. processor.headers[m.group( 2 )] = m.group( 1 );
  424. # we use "/* */" as a separator
  425. if re_source_sep.match( l ):
  426. break
  427. source.append( l )
  428. # now strip the leading and trailing empty lines from the sources
  429. start = 0
  430. end = len( source ) - 1
  431. while start < end and not string.strip( source[start] ):
  432. start = start + 1
  433. while start < end and not string.strip( source[end] ):
  434. end = end - 1
  435. if start == end and not string.strip( source[start] ):
  436. self.code = []
  437. else:
  438. self.code = source[start:end + 1]
  439. def location( self ):
  440. return self.source.location()
  441. def get_markup( self, tag_name ):
  442. """return the DocMarkup corresponding to a given tag in a block"""
  443. for m in self.markups:
  444. if m.tag == string.lower( tag_name ):
  445. return m
  446. return None
  447. def get_markup_name( self, tag_name ):
  448. """return the name of a given primary markup in a block"""
  449. try:
  450. m = self.get_markup( tag_name )
  451. return m.get_name()
  452. except:
  453. return None
  454. def get_markup_words( self, tag_name ):
  455. try:
  456. m = self.get_markup( tag_name )
  457. return m.fields[0].items[0].words
  458. except:
  459. return []
  460. def get_markup_text( self, tag_name ):
  461. result = self.get_markup_words( tag_name )
  462. return string.join( result )
  463. def get_markup_items( self, tag_name ):
  464. try:
  465. m = self.get_markup( tag_name )
  466. return m.fields[0].items
  467. except:
  468. return None
  469. # eof