4 # Parse comment blocks to build content blocks (library file).
6 # Copyright 2002, 2004, 2006-2009, 2012-2014 by
9 # This file is part of the FreeType project, and may only be used,
10 # modified, and distributed under the terms of the FreeType project
11 # license, LICENSE.TXT. By continuing to use, modify, or distribute
12 # this file you indicate that you have read the license and
13 # understand and accept it fully.
16 # This file contains routines to parse documentation comment blocks,
17 # building more structured objects out of them.
28 # Regular expressions to detect code sequences. `Code sequences' are simply
29 # code fragments embedded in '{' and '}', as demonstrated in the following
40 # Note that the indentation of the first opening brace and the last closing
41 # brace must be exactly the same. The code sequence itself should have a
42 # larger indentation than the surrounding braces.
44 re_code_start = re.compile( r"(\s*){\s*$" )
45 re_code_end = re.compile( r"(\s*)}\s*$" )
49 # A regular expression to isolate identifiers from other text.
51 re_identifier = re.compile( r'((?:\w|-)*)' )
55 # We collect macro names ending in `_H' (group 1), as defined in
56 # `config/ftheader.h'. While outputting the object data, we use this info
57 # together with the object's file location (group 2) to emit the appropriate
58 # header file macro and its associated file name before the object itself.
62 # #define FT_FREETYPE_H <freetype.h>
64 re_header_macro = re.compile( r'^#define\s{1,}(\w{1,}_H)\s{1,}<(.*)>' )
67 ################################################################
71 ## The `DocCode' class is used to store source code lines.
73 ## `self.lines' contains a set of source code lines that will be dumped as
74 ## HTML in a <PRE> tag.
76 ## The object is filled line by line by the parser; it strips the leading
77 ## `margin' space from each input line before storing it in `self.lines'.
81 def __init__( self, margin, lines ):
85 # remove margin spaces
87 if string.strip( l[:margin] ) == "":
89 self.lines.append( l )
91 def dump( self, prefix = "", width = 60 ):
92 lines = self.dump_lines( 0, width )
96 def dump_lines( self, margin = 0, width = 60 ):
99 result.append( " " * margin + l )
104 ################################################################
108 ## `Normal' text paragraphs are stored in the `DocPara' class.
110 ## `self.words' contains the list of words that make up the paragraph.
114 def __init__( self, lines ):
118 l = string.strip( l )
119 self.words.extend( string.split( l ) )
121 def dump( self, prefix = "", width = 60 ):
122 lines = self.dump_lines( 0, width )
126 def dump_lines( self, margin = 0, width = 60 ):
127 cur = "" # current line
128 col = 0 # current width
131 for word in self.words:
137 result.append( " " * margin + cur )
147 result.append( " " * margin + cur )
152 ################################################################
156 ## The `DocField' class stores a list containing either `DocPara' or
157 ## `DocCode' objects. Each DocField object also has an optional `name'
158 ## that is used when the object corresponds to a field or value definition.
162 def __init__( self, name, lines ):
163 self.name = name # can be `None' for normal paragraphs/sources
164 self.items = [] # list of items
166 mode_none = 0 # start parsing mode
167 mode_code = 1 # parsing code sequences
168 mode_para = 3 # parsing normal paragraph
170 margin = -1 # current code sequence indentation
173 # analyze the markup lines to check whether they contain paragraphs,
174 # code sequences, or fields definitions
180 # are we parsing a code sequence?
181 if mode == mode_code:
182 m = re_code_end.match( l )
183 if m and len( m.group( 1 ) ) <= margin:
184 # that's it, we finished the code sequence
185 code = DocCode( 0, cur_lines )
186 self.items.append( code )
191 # otherwise continue the code sequence
192 cur_lines.append( l[margin:] )
194 # start of code sequence?
195 m = re_code_start.match( l )
199 para = DocPara( cur_lines )
200 self.items.append( para )
203 # switch to code extraction mode
204 margin = len( m.group( 1 ) )
207 if not string.split( l ) and cur_lines:
208 # if the line is empty, we end the current paragraph,
210 para = DocPara( cur_lines )
211 self.items.append( para )
214 # otherwise, simply add the line to the current
216 cur_lines.append( l )
218 if mode == mode_code:
219 # unexpected end of code sequence
220 code = DocCode( margin, cur_lines )
221 self.items.append( code )
223 para = DocPara( cur_lines )
224 self.items.append( para )
226 def dump( self, prefix = "" ):
228 print prefix + self.field + " ::"
229 prefix = prefix + "----"
238 def dump_lines( self, margin = 0, width = 60 ):
246 result.extend( p.dump_lines( margin, width ) )
253 # A regular expression to detect field definitions.
260 re_field = re.compile( r"""
271 ################################################################
277 def __init__( self, tag, lines ):
278 self.tag = string.lower( tag )
286 m = re_field.match( l )
288 # We detected the start of a new field definition.
290 # first, save the current one
292 f = DocField( field, cur_lines )
293 self.fields.append( f )
297 field = m.group( 1 ) # record field name
298 ln = len( m.group( 0 ) )
299 l = " " * ln + l[ln:]
302 cur_lines.append( l )
304 if field or cur_lines:
305 f = DocField( field, cur_lines )
306 self.fields.append( f )
308 def get_name( self ):
310 return self.fields[0].items[0].words[0]
314 def dump( self, margin ):
315 print " " * margin + "<" + self.tag + ">"
316 for f in self.fields:
318 print " " * margin + "</" + self.tag + ">"
321 ################################################################
327 def __init__( self, block ):
331 self.name = block.name
332 self.title = block.get_markup_words( "title" )
333 self.order = block.get_markup_words( "sections" )
336 self.title = string.split( "Miscellaneous" )
340 ################################################################
346 def __init__( self, name = "Other" ):
349 self.block_names = [] # ordered block names in section
352 self.description = ""
357 def add_def( self, block ):
358 self.defs.append( block )
360 def add_block( self, block ):
361 self.block_names.append( block.name )
362 self.blocks[block.name] = block
365 # look up one block that contains a valid section description
366 for block in self.defs:
367 title = block.get_markup_text( "title" )
370 self.abstract = block.get_markup_words( "abstract" )
371 self.description = block.get_markup_items( "description" )
372 self.order = block.get_markup_words_all( "order" )
376 self.block_names = sort_order_list( self.block_names, self.order )
379 ################################################################
381 ## CONTENT PROCESSOR CLASS
383 class ContentProcessor:
385 def __init__( self ):
386 """Initialize a block content processor."""
389 self.sections = {} # dictionary of documentation sections
390 self.section = None # current documentation section
392 self.chapters = [] # list of chapters
394 self.headers = {} # dictionary of header macros
396 def set_section( self, section_name ):
397 """Set current section during parsing."""
398 if not section_name in self.sections:
399 section = DocSection( section_name )
400 self.sections[section_name] = section
401 self.section = section
403 self.section = self.sections[section_name]
405 def add_chapter( self, block ):
406 chapter = DocChapter( block )
407 self.chapters.append( chapter )
410 """Reset the content processor for a new block."""
413 self.markup_lines = []
415 def add_markup( self ):
416 """Add a new markup section."""
417 if self.markup and self.markup_lines:
419 # get rid of last line of markup if it's empty
420 marks = self.markup_lines
421 if len( marks ) > 0 and not string.strip( marks[-1] ):
422 self.markup_lines = marks[:-1]
424 m = DocMarkup( self.markup, self.markup_lines )
426 self.markups.append( m )
429 self.markup_lines = []
431 def process_content( self, content ):
432 """Process a block content and return a list of DocMarkup objects
433 corresponding to it."""
440 for t in re_markup_tags:
443 found = string.lower( m.group( 1 ) )
444 prefix = len( m.group( 0 ) )
445 line = " " * prefix + line[prefix:] # remove markup from line
448 # is it the start of a new markup section ?
451 self.add_markup() # add current markup content
453 if len( string.strip( line ) ) > 0:
454 self.markup_lines.append( line )
456 self.markup_lines.append( line )
462 def parse_sources( self, source_processor ):
463 blocks = source_processor.blocks
464 count = len( blocks )
466 for n in range( count ):
469 # this is a documentation comment, we need to catch
470 # all following normal blocks in the "follow" list
474 while m < count and not blocks[m].content:
475 follow.append( blocks[m] )
478 doc_block = DocBlock( source, follow, self )
481 # process all sections to extract their abstract, description
482 # and ordered list of items
484 for sec in self.sections.values():
487 # process chapters to check that all sections are correctly
489 for chap in self.chapters:
490 for sec in chap.order:
491 if sec in self.sections:
492 section = self.sections[sec]
493 section.chapter = chap
495 chap.sections.append( section )
497 sys.stderr.write( "WARNING: chapter '" + \
498 chap.name + "' in " + chap.block.location() + \
499 " lists unknown section '" + sec + "'\n" )
501 # check that all sections are in a chapter
504 for sec in self.sections.values():
509 # create a new special chapter for all remaining sections
513 chap = DocChapter( None )
514 chap.sections = others
515 self.chapters.append( chap )
518 ################################################################
524 def __init__( self, source, follow, processor ):
529 self.type = "ERRTYPE"
530 self.name = "ERRNAME"
531 self.section = processor.section
532 self.markups = processor.process_content( source.content )
534 # compute block type from first markup tag
536 self.type = self.markups[0].tag
540 # compute block name from first markup paragraph
542 markup = self.markups[0]
543 para = markup.fields[0].items[0]
545 m = re_identifier.match( name )
552 if self.type == "section":
553 # detect new section starts
554 processor.set_section( self.name )
555 processor.section.add_def( self )
556 elif self.type == "chapter":
558 processor.add_chapter( self )
560 processor.section.add_block( self )
562 # now, compute the source lines relevant to this documentation
563 # block. We keep normal comments in for obvious reasons (??)
569 # collect header macro definitions
570 m = re_header_macro.match( l )
572 processor.headers[m.group( 2 )] = m.group( 1 );
574 # we use "/* */" as a separator
575 if re_source_sep.match( l ):
579 # now strip the leading and trailing empty lines from the sources
581 end = len( source ) - 1
583 while start < end and not string.strip( source[start] ):
586 while start < end and not string.strip( source[end] ):
589 if start == end and not string.strip( source[start] ):
592 self.code = source[start:end + 1]
594 def location( self ):
595 return self.source.location()
597 def get_markup( self, tag_name ):
598 """Return the DocMarkup corresponding to a given tag in a block."""
599 for m in self.markups:
600 if m.tag == string.lower( tag_name ):
604 def get_markup_words( self, tag_name ):
606 m = self.get_markup( tag_name )
607 return m.fields[0].items[0].words
611 def get_markup_words_all( self, tag_name ):
613 m = self.get_markup( tag_name )
615 for item in m.fields[0].items:
616 # We honour empty lines in an `<Order>' section element by
617 # adding the sentinel `/empty/'. The formatter should then
618 # convert it to an appropriate representation in the
619 # `section_enter' function.
621 words.append( "/empty/" )
626 def get_markup_text( self, tag_name ):
627 result = self.get_markup_words( tag_name )
628 return string.join( result )
630 def get_markup_items( self, tag_name ):
632 m = self.get_markup( tag_name )
633 return m.fields[0].items