Changeset 1004


Ignore:
Timestamp:
Jul 11, 2012 4:20:16 PM (10 years ago)
Author:
jemian
Message:

refs #8, now shows docstrings, need to improve the directive and role handling now, especially with regard to the signature

File:
1 edited

Legend:

Unmodified
Added
Removed
  • specdomain/trunk/src/specdomain/sphinxcontrib/specmacrofileparser.py

    r1002 r1004  
    4343variable_name_match         = r'(@?' + macro_name + r'\[?\]?)'
    4444
     45
     46       
     47extended_comment_block_sig_re = re.compile(
     48                        string_start
     49                        + non_greedy_whitespace
     50                        + extended_comment_marker
     51                        + r'(' + non_greedy_filler + r')'
     52                        + extended_comment_marker
     53                        + non_greedy_filler
     54                        + string_end,
     55                        re.IGNORECASE|re.DOTALL|re.MULTILINE)
     56
     57variable_description_re = re.compile(
     58                        string_start
     59                        + non_greedy_filler
     60                        + r'#:'
     61                        + non_greedy_whitespace
     62                        + r'(' + non_greedy_filler + r')'
     63                        + non_greedy_whitespace
     64                        + string_end,
     65                        re.IGNORECASE|re.DOTALL|re.MULTILINE)
     66
     67   
     68lgc_variable_sig_re = re.compile(
     69                        r''
     70                        + string_start
     71                        + non_greedy_whitespace
     72                        + r'(local|global|constant)'        # 1: object type
     73                        + non_greedy_whitespace
     74                        + r'(' + non_greedy_filler + r')'   # 2: too complicated to parse all at once
     75                        + string_end
     76                        ,
     77                        re.DOTALL
     78                        |re.MULTILINE
     79                    )
     80
     81variable_name_re = re.compile(
     82                        variable_name_match,
     83                        re.IGNORECASE|re.DOTALL|re.MULTILINE
     84                        )
     85
     86spec_macro_declaration_match_re = re.compile(
     87                        string_start
     88                        + r'\s*?'                           # optional blank space
     89                        + r'(r?def)'                        # 1: def_type (rdef | def)
     90                        + non_greedy_whitespace
     91                        + macro_name_match                  # 2: macro_name
     92                        + non_greedy_filler_match           # 3: optional arguments
     93                        + r'\'\{?'                          # start body section
     94                        + non_greedy_filler_match           # 4: body
     95                        + r'\}?\''                          # end body section
     96                        + r'(#.*?)?'                        # 5: optional comment
     97                        + string_end,
     98                        re.IGNORECASE|re.DOTALL|re.MULTILINE)
     99   
     100args_match = re.compile(
     101                          r'\('
     102                        + arglist_match                     # 1:  argument list
     103                        + r'\)',
     104                        re.DOTALL)
     105
    45106class SpecMacrofileParser:
    46107    '''
     
    77138    def read(self, macrofile):
    78139        """
    79         load the SPEC macro source code file into an internal buffer.
    80         Also remember the start and end position of each line.
     140        load the SPEC macro source code file into an internal buffer (self.buf).
     141        Also remember the start and end position of each line (self.line_positions).
    81142       
    82143        :param str filename: name (with optional path) of SPEC macro file
     
    109170
    110171    def parse_macro_file(self):
    111         ext_com = self.find_extended_comments()
    112         desc_com = self.find_descriptive_comments()
    113         def_macro = self.find_def_macro()
    114         cdef_macro = self.find_cdef_macro()
    115         vars = self.find_variables()
    116        
    117         for linenumber in range(len(self.line_positions)):
    118             # TODO: decide the parent for each item, expect all def are at global scope
    119             # TODO: decide which macros and variables should not be documented
    120             # walk through the line numbers in the file
    121             #  if a def_macro starts, note its name and set the parent field
    122             #     of all comments, variables, var_desc, rdef, and cdef within
    123             #     its start_line and end_line range
    124             #  How to handle descriptive comments?
    125             pass
    126        
     172        """
     173        Figure out what can be documented in the file's contents (in self.buf)
     174       
     175            each of the list_...() methods returns a
     176            list of dictionaries where each dictionary
     177            has the keys: objtype, start_line, end_line, and others
     178        """
     179        db = {}
     180        # first, the file parsing
     181        for func in (self.list_def_macros,
     182                     self.list_cdef_macros,
     183                     self.list_variables,
     184                     self.list_extended_comments,
     185                     self.list_descriptive_comments,
     186                     ):
     187            for item in func():
     188                s = item['start_line']
     189                if s not in db.keys():
     190                    db[s] = []
     191                db[s].append(item)
     192       
     193        # then, the analysis of what was found
    127194        self.findings = []
    128         for item in (ext_com, desc_com, def_macro, cdef_macro, vars,):
    129             if len(item)>0:
    130                 self.findings.extend(item)
    131        
    132     extended_comment_block_sig_re = re.compile(
    133                             string_start
    134                             + non_greedy_whitespace
    135                             + extended_comment_marker
    136                             + r'(' + non_greedy_filler + r')'
    137                             + extended_comment_marker
    138                             + non_greedy_filler
    139                             + string_end,
    140                             re.IGNORECASE|re.DOTALL|re.MULTILINE)
    141 
    142     def find_extended_comments(self):
     195        description = ''
     196        clear_description = False
     197        found_first_global_extended_comment = False
     198        for linenumber in sorted(db.keys()):
     199            #print linenumber, ':', ', '.join([d['objtype'] for d in db[linenumber]])
     200           
     201            line = db[linenumber]
     202            item = line[-1]
     203            if item['objtype'] in ('def', 'function def'):
     204                # identify all the children of this item
     205                parent = item['name']
     206                found_first_local_extended_comment = False
     207                for row in xrange(item['start_line']+1, item['end_line']-1):
     208                    if row in db.keys():
     209                        for thing in db[row]:
     210                            thing['parent'] = parent
     211                            if thing['objtype'] == 'extended comment':
     212                                if not found_first_local_extended_comment:
     213                                    # TODO: could override this rule with an option
     214                                    item['description'] = thing['text']
     215                                    found_first_local_extended_comment = False
     216                if not item['name'].startswith('_'):
     217                    # TODO: could override this rule with an option
     218                    self.findings.append(item)
     219           
     220            if item['objtype'] == 'extended comment':
     221                start = item['start_line']
     222                if item['parent'] == None:
     223                    if not found_first_global_extended_comment:
     224                        # TODO: could override this rule with an option
     225                        self.findings.append(item)
     226                        found_first_global_extended_comment = True
     227
     228            if item['objtype'] == 'descriptive comment':
     229                description = item['text']
     230
     231            for item in line:
     232                if item['objtype'] in ('local', 'global', 'constant', 'rdef', 'cdef'):
     233                    if len(description)>0:
     234                        item['description'] = description
     235                        clear_description = True
     236                    if not item['name'].startswith('_'):
     237                        # TODO: could override this rule with an option
     238                        self.findings.append(item)
     239           
     240            if clear_description:
     241                description, clear_description = '', False
     242
     243    def list_extended_comments(self):
    143244        """
    144245        parse the internal buffer for triple-quoted strings, possibly multiline
     246       
     247        Usually, an extended comment is used at the top of a macro file
     248        to describe the file's contents.  It is also used at the top
     249        of a macro definition to describe the macro.  The first line
     250        of an extended comment for a macro should be a short summary,
     251        followed by a blank line, then either a parameter list or
     252        more extensive documentation, as needed.
    145253        """
    146254        items = []
    147         for mo in self.extended_comment_block_sig_re.finditer(self.buf):
     255        for mo in extended_comment_block_sig_re.finditer(self.buf):
    148256            start = self.find_pos_in_line_number(mo.start(1))
    149257            end = self.find_pos_in_line_number(mo.end(1))
     
    157265                          })
    158266        return items
    159        
    160     variable_description_re = re.compile(
    161                             string_start
    162                             + non_greedy_filler
    163                             + r'#:'
    164                             + non_greedy_whitespace
    165                             + r'(' + non_greedy_filler + r')'
    166                             + non_greedy_whitespace
    167                             + string_end,
    168                             re.IGNORECASE|re.DOTALL|re.MULTILINE)
    169 
    170     def find_descriptive_comments(self):
     267
     268    def list_descriptive_comments(self):
    171269        """
    172270        Descriptive comments are used to document items that cannot contain
     
    185283        """
    186284        items = []
    187         for mo in self.variable_description_re.finditer(self.buf):
     285        for mo in variable_description_re.finditer(self.buf):
    188286            start = self.find_pos_in_line_number(mo.start(1))
    189287            end = self.find_pos_in_line_number(mo.end(1))
     
    191289                            'start_line': start,
    192290                            'end_line':   end,
    193                             'objtype':    'variable description',
     291                            'objtype':    'descriptive comment',
    194292                            'text':       mo.group(1),
    195293                            'parent':     None,
    196294                          })
    197295        return items
    198    
    199     lgc_variable_sig_re = re.compile(
    200                             r''
    201                             + string_start
    202                             + non_greedy_whitespace
    203                             + r'(local|global|constant)'        # 1: object type
    204                             + non_greedy_whitespace
    205                             + r'(' + non_greedy_filler + r')'   # 2: too complicated to parse all at once
    206                             + string_end
    207                             ,
    208                             re.DOTALL
    209                             |re.MULTILINE
    210                         )
    211    
    212     variable_name_re = re.compile(
    213                             variable_name_match,
    214                             re.IGNORECASE|re.DOTALL|re.MULTILINE
    215                             )
    216 
    217     def find_variables(self):
     296
     297    def list_variables(self):
    218298        """
    219299        parse the internal buffer for local, global, and constant variable declarations
    220300        """
    221301        items = []
    222         for mo in self.lgc_variable_sig_re.finditer(self.buf):
     302        for mo in lgc_variable_sig_re.finditer(self.buf):
    223303            start = self.find_pos_in_line_number(mo.start(1))
    224304            end = self.find_pos_in_line_number(mo.end(1))
     
    226306            content = mo.group(2)
    227307            p = content.find('#')
    228             if p >= 0:                              # strip off any comment
     308            if p >= 0:                                      # strip off any comment
    229309                content = content[:p]
    230             content = re.sub('[,;]', ' ', content)  # replace , or ; with blank space
     310            content = re.sub('[,;]', ' ', content)          # replace , or ; with blank space
    231311            if content.find('[') >= 0:
    232                 content = re.sub('\s*?\[', '[', content)  # remove blank space before [
    233             for var in self.variable_name_re.finditer(content):
     312                content = re.sub('\s*?\[', '[', content)    # remove blank space before [
     313            for var in variable_name_re.finditer(content):
    234314                name = var.group(1)
    235315                if len(name) > 0:
     
    240320                                    'name':       name,
    241321                                    'parent':     None,
    242                                     'text':     'FIX in find_variables(self):',
    243322                                  })
    244323        return items
    245324
    246     spec_macro_declaration_match_re = re.compile(
    247                             string_start
    248                             + r'\s*?'                           # optional blank space
    249                             + r'(r?def)'                        # 1: def_type (rdef | def)
    250                             + non_greedy_whitespace
    251                             + macro_name_match                  # 2: macro_name
    252                             + non_greedy_filler_match           # 3: optional arguments
    253                             + r'\'\{?'                          # start body section
    254                             + non_greedy_filler_match           # 4: body
    255                             + r'\}?\''                          # end body section
    256                             + r'(#.*?)?'                        # 5: optional comment
    257                             + string_end,
    258                             re.IGNORECASE|re.DOTALL|re.MULTILINE)
    259        
    260     args_match = re.compile(
    261                               r'\('
    262                             + arglist_match                     # 1:  argument list
    263                             + r'\)',
    264                             re.DOTALL)
    265 
    266     def find_def_macro(self):
     325    def list_def_macros(self):
    267326        """
    268327        parse the internal buffer for def and rdef macro declarations
    269328        """
    270329        items = []
    271         for mo in self.spec_macro_declaration_match_re.finditer(self.buf):
     330        for mo in spec_macro_declaration_match_re.finditer(self.buf):
    272331            objtype = mo.group(1)
    273332            start = self.find_pos_in_line_number(mo.start(1))
     
    275334            args = mo.group(3)
    276335            if len(args)>2:
    277                 m = self.args_match.search(args)
     336                m = args_match.search(args)
    278337                if m is not None:
    279338                    objtype = 'function ' + objtype
     
    292351        return items
    293352
    294     def find_cdef_macro(self):
     353    def list_cdef_macros(self):
    295354        """
    296355        parse the internal buffer for def and rdef macro declarations
    297356        """
    298        
    299         # note:  It is not possible to find properly all variations
    300         # of the argument list in a cdef declaration using a regular expression,
    301         # especially across multiple lines.
    302        
     357        # too complicated for a regular expression, just look for the initial part
    303358        items = []
    304         for mo in re.finditer('cdef\(', self.buf):
     359        for mo in re.finditer('cdef\s*?\(', self.buf):
    305360            # look at each potential cdef declaration
    306361            objtype = 'cdef'
    307             s = mo.start()
    308             start = self.find_pos_in_line_number(s)
    309             p = mo.end()
     362            start = self.find_pos_in_line_number(mo.start())
     363            s = p = mo.end()                # offset s for start of args
    310364            nesting = 1                     # number of nested parentheses
    311365            sign = {'(': 1, ')': -1}        # increment or decrement
     
    315369                p += 1
    316370            e = p
    317             text = self.buf[s+5:e-1]    # carve it out, and remove cdef( ... ) wrapping
     371            text = self.buf[s:e-1]    # carve it out, and remove cdef( ... ) wrapping
    318372            end = self.find_pos_in_line_number(e)
    319373            p = text.find(',')
     
    344398        :param int pos: position in the file
    345399        """
    346         # straight search
    347         # TODO: optimize using search by bisection
     400        # TODO: optimize this straight search using a search by bisection
    348401        linenumber = None
    349402        for linenumber, start, end in self.line_positions:
     
    356409    def ReST(self):
    357410        """create the ReStructured Text from what has been found"""
    358 #        if not self.state == 'parsed':
    359 #            raise RuntimeWarning, "state = %s, should be 'parsed'" % self.filename
    360411        return self._simple_ReST_renderer()
    361412
    362413    def _simple_ReST_renderer(self):
    363414        """create a simple ReStructured Text rendition of the findings"""
    364 #        if not self.state == 'parsed':
    365 #            raise RuntimeWarning, "state = %s, should be 'parsed'" % self.filename
    366            
    367415        declarations = []       # variables and constants
    368416        macros = []             # def, cdef, and rdef macros
     
    388436                                              r['start_line'],
    389437                                              r['end_line']) )
    390                 s.append( '' )
    391                 # TODO: make this next be part of the signature display (in specdomain)
    392                 #s.append( '.. rubric:: %s macro declaration' % r['objtype']  )
    393                 s.append( '' )
    394438                s.append( '.. spec:%s:: %s' % ( r['objtype'], r['name'],) )
     439                desc = r.get('description', '')
     440                if len(desc) > 0:
     441                    s.append('')
     442                    for line in desc.splitlines():
     443                        s.append(' '*4 + line)
    395444            elif r['objtype'] in ('function def', 'function rdef',):
    396445                # TODO: apply rules to suppress reporting under certain circumstances
    397446                functions.append(r)
     447                objtype = r['objtype'].split()[1]
    398448                s.append( '' )
    399449                s.append( '.. %s %s %s %d %d' % (self.filename,
    400                                               r['objtype'],
     450                                              objtype,
    401451                                              r['name'],
    402452                                              r['start_line'],
    403453                                              r['end_line']) )
    404                 s.append( '' )
    405                 #s.append( '.. rubric:: %s macro function declaration' % r['objtype']  )
    406                 s.append( '' )
    407                 s.append( '.. spec:%s:: %s(%s)' % ( r['objtype'], r['name'], r['args']) )
     454                s.append( '.. spec:%s:: %s(%s)' % ( objtype, r['name'], r['args']) )
     455                desc = r.get('description', '')
     456                if len(desc) > 0:
     457                    s.append('')
     458                    for line in desc.splitlines():
     459                        s.append(' '*4 + line)
    408460            elif r['objtype'] in ('local', 'global', 'constant'):
    409461                # TODO: apply rules to suppress reporting under certain circumstances
    410                 del r['text']
    411462                declarations.append(r)
    412 
    413         s += report_table('Variable Declarations (%s)' % self.filename, declarations, ('objtype', 'name', 'start_line', ))
    414         s += report_table('Macro Declarations (%s)' % self.filename, macros, ('objtype', 'name', 'start_line', 'end_line'))
    415         s += report_table('Function Macro Declarations (%s)' % self.filename, functions, ('objtype', 'name', 'start_line', 'end_line', 'args'))
    416         #s += report_table('Findings from .mac File', self.findings, ('start_line', 'objtype', 'line',))
     463                s.append( '.. spec:%s:: %s' % ( r['objtype'], r['name']) )
     464                desc = r.get('description', '')
     465                if len(desc) > 0:
     466                    s.append('')
     467                    for line in desc.splitlines():
     468                        s.append(' '*4 + line)
     469
     470        s += _report_table('Variable Declarations (%s)' % self.filename, declarations,
     471                          ('objtype', 'name', 'start_line',))
     472        s += _report_table('Macro Declarations (%s)' % self.filename, macros,
     473                          ('objtype', 'name', 'start_line', 'end_line'))
     474        s += _report_table('Function Macro Declarations (%s)' % self.filename, functions,
     475                          ('objtype', 'name', 'start_line', 'end_line', 'args'))
     476        #s += _report_table('Findings from .mac File', self.findings, ('start_line', 'objtype', 'line',))
    417477
    418478        return '\n'.join(s)
    419479
    420480
    421 def report_table(title, itemlist, col_keys = ('objtype', 'start_line', 'end_line', )):
     481def _report_table(title, itemlist, col_keys = ('objtype', 'start_line', 'end_line', )):
    422482    """
    423483    return the itemlist as a reST table
     
    436496            rows.append( tuple([str(d[key]).strip() for key in col_keys]) )
    437497        last_line = d['start_line']
    438     return make_table(title, col_keys, rows, '=')
    439 
    440 
    441 def make_table(title, labels, rows, titlechar = '='):
     498    return make_rest_table(title, col_keys, rows, '=')
     499
     500
     501def make_rest_table(title, labels, rows, titlechar = '='):
    442502    """
    443     build a reST table (internal routine)
     503    build a reST table
    444504   
    445505    :param str title: placed in a section heading above the table
     
    448508    :param str titlechar: character to use when underlining title as reST section heading
    449509    :returns [str]: each list item is reST
     510   
     511    Example::
     512
     513        title = 'This is a reST table'
     514        labels = ('name', 'phone', 'email')
     515        rows = [
     516                ['Snoopy',           '12345', 'dog@house'],
     517                ['Red Baron',        '65432', 'fokker@triplane'],
     518                ['Charlie Brown',    '12345', 'main@house'],
     519                ]
     520        print '\n'.join(make_rest_table(title, labels, rows))
     521   
     522    This results in this reST::
     523   
     524        This is a reST table
     525        ====================
     526       
     527        ============= ===== ===============
     528        name          phone email         
     529        ============= ===== ===============
     530        Snoopy        12345 dog@house     
     531        Red Baron     65432 fokker@triplane
     532        Charlie Brown 12345 main@house     
     533        ============= ===== ===============
    450534    """
    451535    s = []
     
    467551        s.append( fmt % labels )
    468552        s.append( separator )
    469     s.extend( fmt % row for row in rows )
     553    s.extend( [fmt % tuple(row) for row in rows] )
    470554    s.append( separator )
    471555    return s
     
    482566        p = SpecMacrofileParser(filename)
    483567        print p.ReST()
    484         pprint (p.findings)
     568        #pprint (p.findings)
Note: See TracChangeset for help on using the changeset viewer.