source: trunk/poppler/freetype-2.1.10/src/tools/docmaker/content.py @ 2

Last change on this file since 2 was 2, checked in by Eugene Romanenko, 16 years ago

First import

File size: 16.5 KB
Line 
1#
2#  this file contains routines used to parse the content of documentation
3#  comment block and build a more structured objects out of them
4#
5
6from sources import *
7from utils import *
8import string, re
9
10
11# this regular expresion is used to detect code sequences. these
12# are simply code fragments embedded in '{' and '}' like in:
13#
14#  {
15#    x = y + z;
16#    if ( zookoo == 2 )
17#    {
18#      foobar();
19#    }
20#  }
21#
22# note that identation of the starting and ending accolades must be
23# exactly the same. the code sequence can contain accolades at greater
24# indentation
25#
26re_code_start = re.compile( r"(\s*){\s*$" )
27re_code_end   = re.compile( r"(\s*)}\s*$" )
28
29
30# this regular expression is used to isolate identifiers from
31# other text
32#
33re_identifier = re.compile( r'(\w*)' )
34
35
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#
46class DocCode:
47
48    def __init__( self, margin, lines ):
49        self.lines  = []
50        self.words  = None
51
52        # remove margin spaces
53        for l in lines:
54            if string.strip( l[:margin] ) == "":
55                l = l[margin:]
56            self.lines.append( l )
57
58    def dump( self, prefix = "", width=60 ):
59        lines = self.dump_lines( 0, width )
60        for l in lines:
61            print prefix + l
62   
63    def dump_lines( self, margin=0, width=60 ):
64        result = []
65        for l in self.lines:
66            result.append( " "*margin + l )
67        return result
68   
69
70
71#############################################################################
72#
73# The DocPara class is used to store "normal" text paragraph.
74#
75#   'self.words' contains the list of words that make up the paragraph
76#
77class DocPara:
78
79    def __init__( self, lines ):
80        self.lines = None
81        self.words = []
82        for l in lines:
83            l = string.strip(l)
84            self.words.extend( string.split( l ) )
85
86    def dump( self, prefix = "", width = 60 ):
87        lines = self.dump_lines( 0, width )
88        for l in lines:
89            print prefix + l
90   
91    def dump_lines( self, margin=0, width = 60 ):
92        cur    = ""  # current line
93        col    = 0   # current width
94        result = []
95
96        for word in self.words:
97            ln = len(word)
98            if col > 0:
99                ln = ln+1
100
101            if col + ln > width:
102                result.append( " "*margin + cur )
103                cur = word
104                col = len(word)
105            else:
106                if col > 0:
107                    cur = cur + " "
108                cur = cur + word
109                col = col + ln
110
111        if col > 0:
112            result.append( " "*margin + cur )
113           
114        return result
115
116   
117
118
119#############################################################################
120#
121#  The DocField class is used to store a list containing either DocPara or
122#  DocCode objects. Each DocField also has an optional "name" which is used
123#  when the object corresponds to a field of value definition
124#
125class DocField:
126
127    def __init__( self, name, lines ):
128
129        self.name  = name  # can be None for normal paragraphs/sources
130        self.items = []     # list of items
131
132        mode_none  = 0   # start parsing mode
133        mode_code  = 1   # parsing code sequences
134        mode_para  = 3   # parsing normal paragraph
135
136        margin     = -1  # current code sequence indentation
137        cur_lines  = []
138
139        # now analyze the markup lines to see if they contain paragraphs,
140        # code sequences or fields definitions
141        #
142        start = 0
143        mode  = mode_none
144        for l in lines:
145
146            # are we parsing a code sequence ?
147            if mode == mode_code:
148
149                m = re_code_end.match( l )
150                if m and len(m.group(1)) <= margin:
151                    # that's it, we finised the code sequence
152                    code = DocCode( 0, cur_lines )
153                    self.items.append( code )
154                    margin    = -1
155                    cur_lines = []
156                    mode      = mode_none
157                else:
158                    # nope, continue the code sequence
159                    cur_lines.append( l[margin:] )
160            else:
161                # start of code sequence ?
162                m = re_code_start.match( l )
163                if m:
164                    # save current lines
165                    if cur_lines:
166                        para = DocPara( cur_lines )
167                        self.items.append( para )
168                        cur_lines = []
169
170                    # switch to code extraction mode
171                    margin = len(m.group(1))
172                    mode   = mode_code
173
174                else:
175                    if not string.split( l ) and cur_lines:
176                        # if the line is empty, we end the current paragraph,
177                        # if any
178                        para = DocPara( cur_lines )
179                        self.items.append( para )
180                        cur_lines = []
181                    else:
182                        # otherwise, simply add the line to the current
183                        # paragraph
184                        cur_lines.append( l )
185
186        if mode == mode_code:
187            # unexpected end of code sequence
188            code = DocCode( margin, cur_lines )
189            self.items.append( code )
190
191        elif cur_lines:
192            para = DocPara( cur_lines )
193            self.items.append( para )
194
195    def dump( self, prefix = "" ):
196        if self.field:
197            print prefix + self.field + " ::"
198            prefix = prefix + "----"
199
200        first = 1
201        for p in self.items:
202            if not first:
203                print ""
204            p.dump( prefix )
205            first = 0
206
207    def dump_lines( self, margin=0, width=60 ):
208        result = []
209        nl     = None
210        for p in self.items:
211            if nl:
212                result.append( "" )
213               
214            result.extend( p.dump_lines( margin, width ) )
215            nl = 1
216           
217        return result
218
219# this regular expression is used to detect field definitions
220#
221re_field  = re.compile( r"\s*(\w*)\s*::" )
222
223
224
225class DocMarkup:
226
227    def __init__( self, tag, lines ):
228        self.tag       = string.lower(tag)
229        self.fields    = []
230
231        cur_lines = []
232        field     = None
233        mode      = 0
234
235        for l in lines:
236            m = re_field.match( l )
237            if m:
238                # we detected the start of a new field definition
239
240                # first, save the current one
241                if cur_lines:
242                    f = DocField( field, cur_lines )
243                    self.fields.append( f )
244                    cur_lines = []
245                    field     = None
246
247                field     = m.group(1)   # record field name
248                ln        = len(m.group(0))
249                l         = " "*ln + l[ln:]
250                cur_lines = [ l ]
251            else:
252                cur_lines.append( l )
253
254        if field or cur_lines:
255            f = DocField( field, cur_lines )
256            self.fields.append( f )
257
258    def get_name( self ):
259        try:
260            return self.fields[0].items[0].words[0]
261
262        except:
263            return None
264       
265    def get_start( self ):
266        try:
267            result = ""
268            for word in self.fields[0].items[0].words:
269                result = result + " " + word
270            return result[1:]
271       
272        except:
273            return "ERROR"
274
275    def dump( self, margin ):
276        print " "*margin + "<" + self.tag + ">"
277        for f in self.fields:
278            f.dump( "  " )
279        print " "*margin + "</" + self.tag + ">"
280
281
282
283
284class DocChapter:
285
286    def __init__( self, block ):
287        self.block    = block
288        self.sections = []
289        if block:
290            self.name     = block.name
291            self.title    = block.get_markup_words( "title" )
292            self.order    = block.get_markup_words( "sections" )
293        else:
294            self.name     = "Other"
295            self.title    = string.split( "Miscellaneous" )
296            self.order    = []
297
298
299
300class DocSection:
301
302    def __init__( self, name = "Other" ):
303        self.name        = name
304        self.blocks      = {}
305        self.block_names = []  # ordered block names in section
306        self.defs        = []
307        self.abstract    = ""
308        self.description = ""
309        self.order       = []
310        self.title       = "ERROR"
311        self.chapter     = None
312
313    def add_def( self, block ):
314        self.defs.append( block )
315
316    def add_block( self, block ):
317        self.block_names.append( block.name )
318        self.blocks[ block.name ] = block
319
320    def process( self ):
321        # lookup one block that contains a valid section description
322        for block in self.defs:
323            title = block.get_markup_text( "Title" )
324            if title:
325                self.title       = title
326                self.abstract    = block.get_markup_words( "abstract" )
327                self.description = block.get_markup_items( "description" )
328                self.order       = block.get_markup_words( "order" )
329                return
330
331    def reorder( self ):
332
333        self.block_names = sort_order_list( self.block_names, self.order )
334
335
336class ContentProcessor:
337
338    def __init__( self ):
339        """initialize a block content processor"""
340        self.reset()
341
342        self.sections = {}    # dictionary of documentation sections
343        self.section  = None  # current documentation section
344
345        self.chapters = []        # list of chapters
346
347    def set_section( self, section_name ):
348        """set current section during parsing"""
349        if not self.sections.has_key( section_name ):
350            section = DocSection( section_name )
351            self.sections[ section_name ] = section
352            self.section                  = section
353        else:
354            self.section = self.sections[ section_name ]
355
356    def add_chapter( self, block ):
357        chapter = DocChapter( block )
358        self.chapters.append( chapter )
359
360
361    def reset( self ):
362        """reset the content processor for a new block"""
363        self.markups      = []
364        self.markup       = None
365        self.markup_lines = []
366
367    def add_markup( self ):
368        """add a new markup section"""
369        if self.markup and self.markup_lines:
370
371            # get rid of last line of markup if it's empty
372            marks = self.markup_lines
373            if len(marks) > 0 and not string.strip(marks[-1]):
374                self.markup_lines = marks[:-1]
375
376            m = DocMarkup( self.markup, self.markup_lines )
377
378            self.markups.append( m )
379
380            self.markup       = None
381            self.markup_lines = []
382
383
384    def process_content( self, content ):
385        """process a block content and return a list of DocMarkup objects
386           corresponding to it"""
387        markup       = None
388        markup_lines = []
389        first        = 1
390
391        for line in content:
392            found = None
393            for t in re_markup_tags:
394                m = t.match( line )
395                if m:
396                    found  = string.lower(m.group(1))
397                    prefix = len(m.group(0))
398                    line   = " "*prefix + line[prefix:]   # remove markup from line
399                    break
400
401            # is it the start of a new markup section ?
402            if found:
403                first = 0
404                self.add_markup()  # add current markup content
405                self.markup = found
406                if len(string.strip( line )) > 0:
407                    self.markup_lines.append( line )
408            elif first == 0:
409                self.markup_lines.append( line )
410
411        self.add_markup()
412
413        return self.markups
414
415
416    def  parse_sources( self, source_processor ):
417        blocks = source_processor.blocks
418        count  = len(blocks)
419        for n in range(count):
420
421            source = blocks[n]
422            if source.content:
423                # this is a documentation comment, we need to catch
424                # all following normal blocks in the "follow" list
425                #
426                follow = []
427                m = n+1
428                while m < count and not blocks[m].content:
429                    follow.append( blocks[m] )
430                    m = m+1
431
432                doc_block = DocBlock( source, follow, self )
433
434
435    def  finish( self ):
436
437        # process all sections to extract their abstract, description
438        # and ordered list of items
439        #
440        for sec in self.sections.values():
441            sec.process()
442
443        # process chapters to check that all sections are correctly
444        # listed there
445        for chap in self.chapters:
446            for sec in chap.order:
447                if self.sections.has_key(sec):
448                    section = self.sections[ sec ]
449                    section.chapter = chap
450                    section.reorder()
451                    chap.sections.append( section )
452                else:
453                    sys.stderr.write( "WARNING: chapter '" +
454                        chap.name + "' in " + chap.block.location() + \
455                        " lists unknown section '" + sec + "'\n" )
456
457        # check that all sections are in a chapter
458        #
459        others = []
460        for sec in self.sections.values():
461            if not sec.chapter:
462                others.append(sec)
463
464        # create a new special chapter for all remaining sections
465        # when necessary
466        #
467        if others:
468            chap = DocChapter( None )
469            chap.sections = others
470            self.chapters.append( chap )
471
472
473
474class DocBlock:
475
476    def __init__( self, source, follow, processor ):
477
478        processor.reset()
479
480        self.source    = source
481        self.code      = []
482        self.type      = "ERRTYPE"
483        self.name      = "ERRNAME"
484        self.section   = processor.section
485        self.markups   = processor.process_content( source.content )
486
487        # compute block type from first markup tag
488        try:
489            self.type = self.markups[0].tag
490        except:
491            pass
492
493
494        # compute block name from first markup paragraph
495        try:
496            markup = self.markups[0]
497            para   = markup.fields[0].items[0]
498            name   = para.words[0]
499            m = re_identifier.match( name )
500            if m:
501                name = m.group(1)
502            self.name = name
503        except:
504            pass
505
506        # detect new section starts
507        if self.type == "section":
508            processor.set_section( self.name )
509            processor.section.add_def( self )
510
511        # detect new chapter
512        elif self.type == "chapter":
513            processor.add_chapter( self )
514
515        else:
516            processor.section.add_block( self )
517
518        # now, compute the source lines relevant to this documentation
519        # block. We keep normal comments in for obvious reasons (??)
520        source = []
521        for b in follow:
522            if b.format:
523                break
524            for l in b.lines:
525                # we use "/* */" as a separator
526                if re_source_sep.match( l ):
527                    break
528                source.append( l )
529
530        # now strip the leading and trailing empty lines from the sources
531        start = 0
532        end   = len( source )-1
533
534        while start < end and not string.strip( source[start] ):
535            start = start + 1
536
537        while start < end and not string.strip( source[end] ):
538            end = end - 1
539
540        source = source[start:end+1]
541
542        self.code = source
543
544
545    def location( self ):
546        return self.source.location()
547
548
549
550    def get_markup( self, tag_name ):
551        """return the DocMarkup corresponding to a given tag in a block"""
552        for m in self.markups:
553            if m.tag == string.lower(tag_name):
554                return m
555        return None
556
557
558    def get_markup_name( self, tag_name ):
559        """return the name of a given primary markup in a block"""
560        try:
561            m = self.get_markup( tag_name )
562            return m.get_name()
563        except:
564            return None
565
566
567    def get_markup_words( self, tag_name ):
568        try:
569            m = self.get_markup( tag_name )
570            return m.fields[0].items[0].words
571        except:
572            return []
573
574
575    def get_markup_text( self, tag_name ):
576        result = self.get_markup_words( tag_name )
577        return string.join( result )
578
579
580    def get_markup_items( self, tag_name ):
581        try:
582            m = self.get_markup( tag_name )
583            return m.fields[0].items
584        except:
585            return None
Note: See TracBrowser for help on using the repository browser.