4 # Parse comment blocks to build content blocks (library file).
6 # Copyright 2002-2016 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. Two syntax
50 # forms are supported:
55 # where both `<name>' and `<id>' consist of alphanumeric characters, `_',
56 # and `-'. Use `<id>' if there are multiple, valid `<name>' entries; in the
57 # index, `<id>' will be appended in parentheses.
61 # stem_darkening[autofit]
63 # becomes `stem_darkening (autofit)' in the index.
65 re_identifier = re.compile( r"""
72 # We collect macro names ending in `_H' (group 1), as defined in
73 # `freetype/config/ftheader.h'. While outputting the object data, we use
74 # this info together with the object's file location (group 2) to emit the
75 # appropriate header file macro and its associated file name before the
80 # #define FT_FREETYPE_H <freetype.h>
82 re_header_macro = re.compile( r'^#define\s{1,}(\w{1,}_H)\s{1,}<(.*)>' )
85 ################################################################
89 ## The `DocCode' class is used to store source code lines.
91 ## `self.lines' contains a set of source code lines that will be dumped as
92 ## HTML in a <PRE> tag.
94 ## The object is filled line by line by the parser; it strips the leading
95 ## `margin' space from each input line before storing it in `self.lines'.
99 def __init__( self, margin, lines ):
103 # remove margin spaces
105 if string.strip( l[:margin] ) == "":
107 self.lines.append( l )
109 def dump( self, prefix = "", width = 60 ):
110 lines = self.dump_lines( 0, width )
114 def dump_lines( self, margin = 0, width = 60 ):
117 result.append( " " * margin + l )
122 ################################################################
126 ## `Normal' text paragraphs are stored in the `DocPara' class.
128 ## `self.words' contains the list of words that make up the paragraph.
132 def __init__( self, lines ):
136 l = string.strip( l )
137 self.words.extend( string.split( l ) )
139 def dump( self, prefix = "", width = 60 ):
140 lines = self.dump_lines( 0, width )
144 def dump_lines( self, margin = 0, width = 60 ):
145 cur = "" # current line
146 col = 0 # current width
149 for word in self.words:
155 result.append( " " * margin + cur )
165 result.append( " " * margin + cur )
170 ################################################################
174 ## The `DocField' class stores a list containing either `DocPara' or
175 ## `DocCode' objects. Each DocField object also has an optional `name'
176 ## that is used when the object corresponds to a field or value definition.
180 def __init__( self, name, lines ):
181 self.name = name # can be `None' for normal paragraphs/sources
182 self.items = [] # list of items
184 mode_none = 0 # start parsing mode
185 mode_code = 1 # parsing code sequences
186 mode_para = 3 # parsing normal paragraph
188 margin = -1 # current code sequence indentation
191 # analyze the markup lines to check whether they contain paragraphs,
192 # code sequences, or fields definitions
198 # are we parsing a code sequence?
199 if mode == mode_code:
200 m = re_code_end.match( l )
201 if m and len( m.group( 1 ) ) <= margin:
202 # that's it, we finished the code sequence
203 code = DocCode( 0, cur_lines )
204 self.items.append( code )
209 # otherwise continue the code sequence
210 cur_lines.append( l[margin:] )
212 # start of code sequence?
213 m = re_code_start.match( l )
217 para = DocPara( cur_lines )
218 self.items.append( para )
221 # switch to code extraction mode
222 margin = len( m.group( 1 ) )
225 if not string.split( l ) and cur_lines:
226 # if the line is empty, we end the current paragraph,
228 para = DocPara( cur_lines )
229 self.items.append( para )
232 # otherwise, simply add the line to the current
234 cur_lines.append( l )
236 if mode == mode_code:
237 # unexpected end of code sequence
238 code = DocCode( margin, cur_lines )
239 self.items.append( code )
241 para = DocPara( cur_lines )
242 self.items.append( para )
244 def dump( self, prefix = "" ):
246 print prefix + self.field + " ::"
247 prefix = prefix + "----"
256 def dump_lines( self, margin = 0, width = 60 ):
264 result.extend( p.dump_lines( margin, width ) )
271 # A regular expression to detect field definitions.
278 re_field = re.compile( r"""
289 ################################################################
295 def __init__( self, tag, lines ):
296 self.tag = string.lower( tag )
304 m = re_field.match( l )
306 # We detected the start of a new field definition.
308 # first, save the current one
310 f = DocField( field, cur_lines )
311 self.fields.append( f )
315 field = m.group( 1 ) # record field name
316 ln = len( m.group( 0 ) )
317 l = " " * ln + l[ln:]
320 cur_lines.append( l )
322 if field or cur_lines:
323 f = DocField( field, cur_lines )
324 self.fields.append( f )
326 def get_name( self ):
328 return self.fields[0].items[0].words[0]
332 def dump( self, margin ):
333 print " " * margin + "<" + self.tag + ">"
334 for f in self.fields:
336 print " " * margin + "</" + self.tag + ">"
339 ################################################################
345 def __init__( self, block ):
349 self.name = block.name
350 self.title = block.get_markup_words( "title" )
351 self.order = block.get_markup_words( "sections" )
354 self.title = string.split( "Miscellaneous" )
358 ################################################################
364 def __init__( self, name = "Other" ):
367 self.block_names = [] # ordered block names in section
370 self.description = ""
375 def add_def( self, block ):
376 self.defs.append( block )
378 def add_block( self, block ):
379 self.block_names.append( block.name )
380 self.blocks[block.name] = block
383 # look up one block that contains a valid section description
384 for block in self.defs:
385 title = block.get_markup_text( "title" )
388 self.abstract = block.get_markup_words( "abstract" )
389 self.description = block.get_markup_items( "description" )
390 self.order = block.get_markup_words_all( "order" )
394 self.block_names = sort_order_list( self.block_names, self.order )
397 ################################################################
399 ## CONTENT PROCESSOR CLASS
401 class ContentProcessor:
403 def __init__( self ):
404 """Initialize a block content processor."""
407 self.sections = {} # dictionary of documentation sections
408 self.section = None # current documentation section
410 self.chapters = [] # list of chapters
412 self.headers = {} # dictionary of header macros
414 def set_section( self, section_name ):
415 """Set current section during parsing."""
416 if not section_name in self.sections:
417 section = DocSection( section_name )
418 self.sections[section_name] = section
419 self.section = section
421 self.section = self.sections[section_name]
423 def add_chapter( self, block ):
424 chapter = DocChapter( block )
425 self.chapters.append( chapter )
428 """Reset the content processor for a new block."""
431 self.markup_lines = []
433 def add_markup( self ):
434 """Add a new markup section."""
435 if self.markup and self.markup_lines:
437 # get rid of last line of markup if it's empty
438 marks = self.markup_lines
439 if len( marks ) > 0 and not string.strip( marks[-1] ):
440 self.markup_lines = marks[:-1]
442 m = DocMarkup( self.markup, self.markup_lines )
444 self.markups.append( m )
447 self.markup_lines = []
449 def process_content( self, content ):
450 """Process a block content and return a list of DocMarkup objects
451 corresponding to it."""
458 for t in re_markup_tags:
461 found = string.lower( m.group( 1 ) )
462 prefix = len( m.group( 0 ) )
463 line = " " * prefix + line[prefix:] # remove markup from line
466 # is it the start of a new markup section ?
469 self.add_markup() # add current markup content
471 if len( string.strip( line ) ) > 0:
472 self.markup_lines.append( line )
474 self.markup_lines.append( line )
480 def parse_sources( self, source_processor ):
481 blocks = source_processor.blocks
482 count = len( blocks )
484 for n in range( count ):
487 # this is a documentation comment, we need to catch
488 # all following normal blocks in the "follow" list
492 while m < count and not blocks[m].content:
493 follow.append( blocks[m] )
496 doc_block = DocBlock( source, follow, self )
499 # process all sections to extract their abstract, description
500 # and ordered list of items
502 for sec in self.sections.values():
505 # process chapters to check that all sections are correctly
507 for chap in self.chapters:
508 for sec in chap.order:
509 if sec in self.sections:
510 section = self.sections[sec]
511 section.chapter = chap
513 chap.sections.append( section )
515 sys.stderr.write( "WARNING: chapter '" + \
516 chap.name + "' in " + chap.block.location() + \
517 " lists unknown section '" + sec + "'\n" )
519 # check that all sections are in a chapter
522 for sec in self.sections.values():
527 # create a new special chapter for all remaining sections
531 chap = DocChapter( None )
532 chap.sections = others
533 self.chapters.append( chap )
536 ################################################################
542 def __init__( self, source, follow, processor ):
547 self.type = "ERRTYPE"
548 self.name = "ERRNAME"
549 self.section = processor.section
550 self.markups = processor.process_content( source.content )
552 # compute block type from first markup tag
554 self.type = self.markups[0].tag
558 # compute block name from first markup paragraph
560 markup = self.markups[0]
561 para = markup.fields[0].items[0]
563 m = re_identifier.match( name )
570 if self.type == "section":
571 # detect new section starts
572 processor.set_section( self.name )
573 processor.section.add_def( self )
574 elif self.type == "chapter":
576 processor.add_chapter( self )
578 processor.section.add_block( self )
580 # now, compute the source lines relevant to this documentation
581 # block. We keep normal comments in for obvious reasons (??)
587 # collect header macro definitions
588 m = re_header_macro.match( l )
590 processor.headers[m.group( 2 )] = m.group( 1 );
592 # we use "/* */" as a separator
593 if re_source_sep.match( l ):
597 # now strip the leading and trailing empty lines from the sources
599 end = len( source ) - 1
601 while start < end and not string.strip( source[start] ):
604 while start < end and not string.strip( source[end] ):
607 if start == end and not string.strip( source[start] ):
610 self.code = source[start:end + 1]
612 def location( self ):
613 return self.source.location()
615 def get_markup( self, tag_name ):
616 """Return the DocMarkup corresponding to a given tag in a block."""
617 for m in self.markups:
618 if m.tag == string.lower( tag_name ):
622 def get_markup_words( self, tag_name ):
624 m = self.get_markup( tag_name )
625 return m.fields[0].items[0].words
629 def get_markup_words_all( self, tag_name ):
631 m = self.get_markup( tag_name )
633 for item in m.fields[0].items:
634 # We honour empty lines in an `<Order>' section element by
635 # adding the sentinel `/empty/'. The formatter should then
636 # convert it to an appropriate representation in the
637 # `section_enter' function.
639 words.append( "/empty/" )
644 def get_markup_text( self, tag_name ):
645 result = self.get_markup_words( tag_name )
646 return string.join( result )
648 def get_markup_items( self, tag_name ):
650 m = self.get_markup( tag_name )
651 return m.fields[0].items