Changeset 929


Ignore:
Timestamp:
Jun 14, 2012 12:17:34 AM (10 years ago)
Author:
jemian
Message:

restart with simpler domain, start to add index entries, refs #8

Location:
specdomain/src/specdomain
Files:
2 edited

Legend:

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

    r926 r929  
    1212# http://sphinx.pocoo.org/ext/appapi.html
    1313
    14 import re
    15 import string                                       #@UnusedImport
     14import re                                               #@UnusedImport
     15import string                                           #@UnusedImport
    1616
    17 from docutils import nodes
    18 from docutils.parsers.rst import directives
     17from docutils import nodes                              #@UnusedImport
     18from docutils.parsers.rst import directives             #@UnusedImport
    1919
    20 from sphinx import addnodes
    21 from sphinx.roles import XRefRole
    22 from sphinx.locale import l_, _
    23 from sphinx.directives import ObjectDescription
    24 from sphinx.domains import Domain, ObjType, Index
    25 from sphinx.util.compat import Directive
    26 from sphinx.util.nodes import make_refnode
    27 from sphinx.util.docfields import Field, TypedField
    28 
    29 
    30 # TODO: tailor these for SPEC
    31 # RE to split at word boundaries
    32 wsplit_re = re.compile(r'(\W+)')
    33 
    34 # http://www.greenend.org.uk/rjk/tech/regexp.html
    35 
    36 # REs for SPEC signatures
    37 #spec_func_sig_re = re.compile(
    38 #    r'''^ ([\w.]*:)?             # module name
    39 #          (\w+)  \s*             # thing name
    40 #          (?: \((.*)\)           # optional: arguments
    41 #           (?:\s* -> \s* (.*))?  #           return annotation
    42 #          )? $                   # and nothing more
    43 #          ''', re.VERBOSE)
    44 
    45 spec_func_sig_re = re.compile(
    46     r'''^ ([a-zA-Z_]\w*)         # macro name
    47           ((\s+\S+)*)            # optional: arguments
    48           $                      # and nothing more
    49           ''', re.VERBOSE)
    50 
    51 
    52 spec_macro_sig_re = re.compile(
    53     r'''^ ([\w.]*:)?             # module name
    54           ([A-Z]\w+) $           # thing name
    55           ''', re.VERBOSE)
    56 
    57 spec_global_sig_re = re.compile(
    58     r'''^ ([\w.]*:)?             # module name
    59           ([A-Z]\w+) $           # thing name
    60           ''', re.VERBOSE)
    61 
    62 
    63 spec_var_sig_re = re.compile(
    64     r'''^ ([\w.]*:)?             # module name
    65           ([A-Z]\w+) $           # thing name
    66           ''', re.VERBOSE)
    67 
    68 
    69 spec_record_sig_re = re.compile(
    70     r'''^ ([\w.]*:)?             # module name
    71           (\#\w+) $              # thing name
    72           ''', re.VERBOSE)
    73 
    74 
    75 spec_macroparamlist_re = re.compile(r'((\s+\S+)*)')  # split at whitespace
    76 
    77 spec_paramlist_re = re.compile(r'([\[\],])')  # split at '[', ']' and ','
     20from sphinx import addnodes                             #@UnusedImport
     21from sphinx.roles import XRefRole                       #@UnusedImport
     22from sphinx.locale import l_, _                         #@UnusedImport
     23from sphinx.directives import ObjectDescription         #@UnusedImport
     24from sphinx.domains import Domain, ObjType, Index       #@UnusedImport
     25from sphinx.util.compat import Directive                #@UnusedImport
     26from sphinx.util.nodes import make_refnode              #@UnusedImport
     27from sphinx.util.docfields import Field, TypedField     #@UnusedImport
    7828
    7929
    8030class SpecObject(ObjectDescription):
    8131    """
    82     Description of a SPEC language object.
     32    Description of a SPEC object (macro definition or variable).
    8333    """
    84     doc_field_types = [
    85         TypedField('parameter', label=l_('Parameters'),
    86                    names=('param', 'parameter', 'arg', 'argument'),
    87                    typerolename='type', typenames=('type',)),
    88         Field('returnvalue', label=l_('Returns'), has_arg=False,
    89               names=('returns', 'return')),
    90         Field('returntype', label=l_('Return type'), has_arg=False,
    91               names=('rtype',)),
    92     ]
    9334
    94     def _add_signature_prefix(self, signode):
    95         if self.objtype != 'function':
    96             sig_prefix = self.objtype + ' '
    97             signode += addnodes.desc_annotation(sig_prefix, sig_prefix)
     35    def handle_signature(self, sig, signode):
     36        # TODO: must be able to match these
     37        # def macro_name
     38        # def macro_name()
     39        # def macro_name(arg1, arg2)
     40        name, args = sig.strip().split()   
     41        signode += addnodes.desc_name(name, name)
     42        if len(args) > 0:
     43            signode += addnodes.desc_addname(args, args)
     44        return name
    9845
    99     def needs_arglist(self):
    100         return self.objtype == 'function'
    101 
    102     def _resolve_module_name(self, signode, modname, name):
    103         # determine module name, as well as full name
    104         env_modname = self.options.get(
    105             'module', self.env.temp_data.get('spec:module', 'spec'))
    106         if modname:
    107             fullname = modname + name
    108             signode['module'] = modname[:-1]
    109         else:
    110             fullname = env_modname + ':' + name
    111             signode['module'] = env_modname
    112         signode['fullname'] = fullname
    113         self._add_signature_prefix(signode)
    114         name_prefix = signode['module'] + ':'
    115         signode += addnodes.desc_addname(name_prefix, name_prefix)
    116         signode += addnodes.desc_name(name, name)
    117         return fullname
    118 
    119     def handle_signature(self, sig, signode):   # TODO: this logic will have to be changed
    120         if sig.startswith('#'):
    121             return self._handle_record_signature(sig, signode)
    122         elif sig[0].isupper():
    123             return self._handle_macro_signature(sig, signode)
    124         return self._handle_function_signature(sig, signode)
    125 
    126     def _handle_record_signature(self, sig, signode):
    127         m = spec_record_sig_re.match(sig)
    128         if m is None:
    129             raise ValueError
    130         modname, name = m.groups()
    131         return self._resolve_module_name(signode, modname, name)
    132 
    133     def _handle_macro_signature(self, sig, signode):
    134         m = spec_macro_sig_re.match(sig)
    135         if m is None:
    136             raise ValueError
    137         modname, name = m.groups()
    138         return self._resolve_module_name(signode, modname, name)
    139 
    140     def _handle_function_signature(self, sig, signode):
    141         m = spec_func_sig_re.match(sig)
    142         if m is None:
    143             raise ValueError
    144         #modname, name, arglist, retann = m.groups()
    145         name, arglist, retann = m.groups()
    146         modname = signode.parent['desctype'] + ":"
    147 
    148         fullname = self._resolve_module_name(signode, modname, name)
    149 
    150         if not arglist:
    151             if self.needs_arglist():
    152                 # for callables, add an empty parameter list
    153                 signode += addnodes.desc_parameterlist()
    154             if retann:
    155                 signode += addnodes.desc_returns(retann, retann)
    156             if self.objtype == 'function':
    157                 return fullname + '/0'
    158             return fullname
    159         signode += addnodes.desc_parameterlist()
    160 
    161         stack = [signode[-1]]
    162         counters = [0, 0]
    163         for token in spec_paramlist_re.split(arglist):
    164             if token == '[':
    165                 opt = addnodes.desc_optional()
    166                 stack[-1] += opt
    167                 stack.append(opt)
    168             elif token == ']':
    169                 try:
    170                     stack.pop()
    171                 except IndexError:
    172                     raise ValueError
    173             elif not token or token == ',' or token.isspace():
    174                 pass
    175             else:
    176                 token = token.strip()
    177                 stack[-1] += addnodes.desc_parameter(token, token)
    178                 if len(stack) == 1:
    179                     counters[0] += 1
    180                 else:
    181                     counters[1] += 1
    182         if len(stack) != 1:
    183             raise ValueError
    184         if not counters[1]:
    185             fullname = '%s/%d' % (fullname, counters[0])
    186         else:
    187             fullname = '%s/%d..%d' % (fullname, counters[0], sum(counters))
    188         if retann:
    189             signode += addnodes.desc_returns(retann, retann)
    190         return fullname
    191 
    192     def _get_index_text(self, name):
    193         if self.objtype == 'function':
    194             return _('%s (SPEC function)') % name
    195         elif self.objtype == 'macro':
     46    def _get_index_text(self, name):    # TODO: needs to be checked
     47        if self.objtype == 'def':
    19648            return _('%s (SPEC macro)') % name
    197         elif self.objtype == 'global':
     49        elif self.objtype == 'rdef':
     50            return _('%s (SPEC macro)') % name
     51        elif self.objtype == 'cdef':
    19852            return _('%s (SPEC global)') % name
    199         elif self.objtype == 'record':
    200             return _('%s (SPEC record)') % name
    20153        else:
    20254            return ''
    20355
    204     def add_target_and_index(self, name, sig, signode):
    205         if name not in self.state.document.ids:
    206             signode['names'].append(name)
    207             signode['ids'].append(name)
    208             signode['first'] = (not self.names)
    209             self.state.document.note_explicit_target(signode)
    210             if self.objtype =='function':
    211                 finv = self.env.domaindata['spec']['functions']
    212                 fname, arity = name.split('/')
    213                 if '..' in arity:
    214                     first, last = map(int, arity.split('..'))
    215                 else:
    216                     first = last = int(arity)
    217                 for arity_index in range(first, last+1):
    218                     if fname in finv and arity_index in finv[fname]:
    219                         self.env.warn(
    220                             self.env.docname,
    221                             ('duplicate SPEC function description'
    222                              'of %s, ') % name +
    223                             'other instance in ' +
    224                             self.env.doc2path(finv[fname][arity_index][0]),
    225                             self.lineno)
    226                     arities = finv.setdefault(fname, {})
    227                     arities[arity_index] = (self.env.docname, name)
    228             else:
    229                 oinv = self.env.domaindata['spec']['objects']
    230                 if name in oinv:
    231                     self.env.warn(
    232                         self.env.docname,
    233                         'duplicate SPEC object description of %s, ' % name +
    234                         'other instance in ' + self.env.doc2path(oinv[name][0]),
    235                         self.lineno)
    236                 oinv[name] = (self.env.docname, self.objtype)
    23756
    238         indextext = self._get_index_text(name)
    239         if indextext:
    240             self.indexnode['entries'].append(('single', indextext, name, name))
     57class SpecXRefRole(XRefRole):    # TODO: needs to be checked
    24158
    242 
    243 class SpecCurrentModule(Directive):
    244     """
    245     This directive is just to tell Sphinx that we're documenting
    246     stuff in module foo, but links to module foo won't lead here.
    247     """
    248 
    249     has_content = False
    250     required_arguments = 1
    251     optional_arguments = 0
    252     final_argument_whitespace = False
    253     option_spec = {}
    254 
    255     def run(self):
    256         env = self.state.document.settings.env
    257         modname = self.arguments[0].strip()
    258         if modname == 'None':
    259             env.temp_data['spec:module'] = None
    260         else:
    261             env.temp_data['spec:module'] = modname
    262         return []
    263 
    264 
    265 class SpecModule(Directive):
    266     """
    267     Directive to mark description of a new module.
    268    
    269     :returns: list of nodes
    270     """
    271     has_content = False
    272     required_arguments = 1
    273     optional_arguments = 0
    274     final_argument_whitespace = False
    275     option_spec = {
    276         'platform': lambda x: x,
    277         'synopsis': lambda x: x,
    278         'noindex': directives.flag,
    279         'deprecated': directives.flag,
    280     }
    281 
    282     def run(self):
    283         env = self.state.document.settings.env
    284         modname = self.arguments[0].strip()
    285         noindex = 'noindex' in self.options
    286         env.temp_data['spec:module'] = modname
    287 
    288         env.domaindata['spec']['modules'][modname] = \
    289             (env.docname, self.options.get('synopsis', ''),
    290              self.options.get('platform', ''), 'deprecated' in self.options)
    291         targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
    292         self.state.document.note_explicit_target(targetnode)
    293         ret = [targetnode]
    294         # XXX this behavior of the module directive is a mess...
    295         if 'platform' in self.options:
    296             platform = self.options['platform']
    297             node = nodes.paragraph()
    298             node += nodes.emphasis('', _('Platforms: '))
    299             node += nodes.Text(platform, platform)
    300             ret.append(node)
    301         # the synopsis isn't printed; in fact, it is only used in the
    302         # modindex currently
    303         if not noindex:
    304             indextext = _('%s (module)') % modname
    305             inode = addnodes.index(entries=[('single', indextext,
    306                                              'module-' + modname, modname)])
    307             ret.append(inode)
    308         return ret
    309 
    310 
    311 class SpecVariable(Directive):
    312     pass
    313 
    314 class SpecModuleIndex(Index):
    315     """
    316     Index subclass to provide the SPEC module index.
    317     """
    318 
    319     name = 'modindex'
    320     localname = l_('SPEC Module Index')
    321     shortname = l_('modules')
    322 
    323     def generate(self, docnames=None):
    324         content = {}
    325         # list of prefixes to ignore
    326         ignores = self.domain.env.config['modindex_common_prefix']
    327         ignores = sorted(ignores, key=len, reverse=True)
    328         # list of all modules, sorted by module name
    329         modules = sorted(self.domain.data['modules'].iteritems(),
    330                          key=lambda x: x[0].lower())
    331         # sort out collapsable modules
    332         prev_modname = ''
    333         num_toplevels = 0
    334         for modname, (docname, synopsis, platforms, deprecated) in modules:
    335             if docnames and docname not in docnames:
    336                 continue
    337 
    338             for ignore in ignores:
    339                 if modname.startswith(ignore):
    340                     modname = modname[len(ignore):]
    341                     stripped = ignore
    342                     break
    343             else:
    344                 stripped = ''
    345 
    346             # we stripped the whole module name?
    347             if not modname:
    348                 modname, stripped = stripped, ''
    349 
    350             entries = content.setdefault(modname[0].lower(), [])
    351 
    352             package = modname.split(':')[0]
    353             if package != modname:
    354                 # it's a submodule
    355                 if prev_modname == package:
    356                     # first submodule - make parent a group head
    357                     entries[-1][1] = 1
    358                 elif not prev_modname.startswith(package):
    359                     # submodule without parent in list, add dummy entry
    360                     entries.append([stripped + package, 1, '', '', '', '', ''])
    361                 subtype = 2
    362             else:
    363                 num_toplevels += 1
    364                 subtype = 0
    365 
    366             qualifier = deprecated and _('Deprecated') or ''
    367             entries.append([stripped + modname, subtype, docname,
    368                             'module-' + stripped + modname, platforms,
    369                             qualifier, synopsis])
    370             prev_modname = modname
    371 
    372         # apply heuristics when to collapse modindex at page load:
    373         # only collapse if number of toplevel modules is larger than
    374         # number of submodules
    375         collapse = len(modules) - num_toplevels < num_toplevels
    376 
    377         # sort by first letter
    378         content = sorted(content.iteritems())
    379 
    380         return content, collapse
    381 
    382 
    383 class SpecXRefRole(XRefRole):
    38459    def process_link(self, env, refnode, has_explicit_title, title, target):
    385         refnode['spec:module'] = env.temp_data.get('spec:module')
     60        refnode['spec:def'] = env.temp_data.get('spec:def')
    38661        if not has_explicit_title:
    38762            title = title.lstrip(':')   # only has a meaning for the target
     
    39671        return title, target
    39772
     73    def result_nodes(self, document, env, node, is_ref):
     74        if not is_ref:
     75            return [node], []
     76        varname = node['reftarget']
     77        tgtid = 'index-%s' % env.new_serialno('index')
     78        indexnode = addnodes.index()
     79        indexnode['entries'] = [
     80            ('single', varname, tgtid, ''),
     81            #('single', _('environment variable; %s') % varname, tgtid, ''),
     82        ]
     83        targetnode = nodes.target('', '', ids=[tgtid])
     84        document.note_explicit_target(targetnode)
     85        return [indexnode, targetnode, node], []
     86
    39887
    39988class SpecDomain(Domain):
    40089    """SPEC language domain."""
    40190    name = 'spec'
    402     label = 'SPEC'
    403     object_types = {
    404         'def':      ObjType(l_('def'),      'def'),
    405         'rdef':     ObjType(l_('rdef'),     'rdef'),
    406         'cdef':     ObjType(l_('cdef'),     'cdef'),
    407         #'record':   ObjType(l_('record'),   'record'),
    408         #'module':   ObjType(l_('module'),   'mod'),
    409         #'variable': ObjType(l_('variable'), 'var'),
     91    label = 'SPEC, http://www.certif.com'
     92    object_types = {    # type of object that a domain can document
     93        'def':  ObjType(l_('def'),  'def'),
     94        'rdef': ObjType(l_('rdef'), 'rdef'),
     95        'cdef': ObjType(l_('cdef'), 'cdef'),
    41096    }
    41197    directives = {
    412         'def':      SpecObject,
    413         'rdef':     SpecObject,
    414         'cdef':     SpecObject,
    415         #'module':        SpecModule,
    416         #'variable':      SpecVariable,
    417         #'global':        SpecVariable,
    418         #'currentmodule': SpecCurrentModule,
     98        'def':          SpecObject,
     99        'rdef':         SpecObject,
     100        'cdef':         SpecObject,
    419101    }
    420102    roles = {
    421         'def' :     SpecXRefRole(),
    422         'rdef':     SpecXRefRole(),
    423         'cdef':     SpecXRefRole(),
    424         #'record': SpecXRefRole(),
    425         #'mod':    SpecXRefRole(),
     103        'def' :  SpecXRefRole(),
     104        'rdef':  SpecXRefRole(),
     105        'cdef':  SpecXRefRole(),
    426106    }
    427107    initial_data = {
    428         'objects': {},    # fullname -> docname, objtype
    429         'functions' : {}, # fullname -> arity -> (targetname, docname)
    430         'modules': {},    # modname -> docname, synopsis, platform, deprecated
    431         'globals': {},    # ??????????
     108        'objects': {}, # fullname -> docname, objtype
    432109    }
    433     indices = [
    434         SpecModuleIndex,
    435     ]
    436110
    437111    def clear_doc(self, docname):
    438         for fullname, (fn, _) in self.data['objects'].items():
    439             if fn == docname:
    440                 del self.data['objects'][fullname]
    441         for modname, (fn, _, _, _) in self.data['modules'].items():
    442             if fn == docname:
    443                 del self.data['modules'][modname]
    444         for fullname, funcs in self.data['functions'].items():
    445             for arity, (fn, _) in funcs.items():
    446                 if fn == docname:
    447                     del self.data['functions'][fullname][arity]
    448             if not self.data['functions'][fullname]:
    449                 del self.data['functions'][fullname]
     112        for (typ, name), doc in self.data['objects'].items():
     113            if doc == docname:
     114                del self.data['objects'][typ, name]
    450115
    451     def _find_obj(self, env, modname, name, objtype, searchorder=0):
    452         """
    453         Find a Python object for "name", perhaps using the given module and/or
    454         classname.
    455         """
    456         if not name:
    457             return None, None
    458         if ":" not in name:
    459             name = "%s:%s" % (modname, name)
    460 
    461         if name in self.data['objects']:
    462             return name, self.data['objects'][name][0]
    463 
    464         if '/' in name:
    465             fname, arity = name.split('/')
    466             arity = int(arity)
    467         else:
    468             fname = name
    469             arity = -1
    470         if fname not in self.data['functions']:
    471             return None, None
    472 
    473         arities = self.data['functions'][fname]
    474         if arity == -1:
    475             arity = min(arities)
    476         if arity in arities:
    477             docname, targetname = arities[arity]
    478             return targetname, docname
    479         return None, None
    480 
    481     def resolve_xref(self, env, fromdocname, builder,
    482                      typ, target, node, contnode):
    483         if typ == 'mod' and target in self.data['modules']:
    484             docname, synopsis, platform, deprecated = \
    485                 self.data['modules'].get(target, ('','','', ''))
    486             if not docname:
    487                 return None
    488             else:
    489                 title = '%s%s%s' % ((platform and '(%s) ' % platform),
    490                                     synopsis,
    491                                     (deprecated and ' (deprecated)' or ''))
    492                 return make_refnode(builder, fromdocname, docname,
    493                                     'module-' + target, contnode, title)
    494         else:
    495             modname = node.get('spec:module')
    496             searchorder = node.hasattr('refspecific') and 1 or 0
    497             name, obj = self._find_obj(env, modname, target, typ, searchorder)
    498             if not obj:
    499                 return None
    500             else:
    501                 return make_refnode(builder, fromdocname, obj, name,
    502                                     contnode, name)
     116    def resolve_xref(self, env, fromdocname, builder, typ, target, node,
     117                     contnode):
     118        objects = self.data['objects']
     119        objtypes = self.objtypes_for_role(typ)
     120        for objtype in objtypes:
     121            if (objtype, target) in objects:
     122                return make_refnode(builder, fromdocname,
     123                                    objects[objtype, target],
     124                                    objtype + '-' + target,
     125                                    contnode, target + ' ' + objtype)
    503126
    504127    def get_objects(self):
    505         for refname, (docname, doctype) in self.data['objects'].iteritems():
    506             yield (refname, refname, doctype, docname, refname, 1)
     128        for (typ, name), docname in self.data['objects'].iteritems():
     129            yield name, name, typ, docname, typ + '-' + name, 1
    507130
     131
     132# http://sphinx.pocoo.org/ext/tutorial.html#the-setup-function
    508133
    509134def setup(app):
  • specdomain/src/specdomain/test/test_doc.rst

    r927 r929  
    2020^^^^^^^^^^^
    2121
    22 .. spec:def:: example_defined_macro arg1 arg2 arg3
     22.. spec:def:: no_args_macro
     23
     24.. spec:def:: def_macro arg1 arg2 arg3
    2325
    2426   :param arg1: anything
     
    2628   :param arg2: another thing
    2729
    28 .. spec:rdef:: example_runtime_defined_macro content
     30.. spec:rdef:: rdef_macro content
    2931
    3032   :param content: SPEC code (single or multi-line but typically a single macro)
    3133   :type  content: str
    3234
    33 .. spec:cdef:: example_chained_macro(identifier, content, placement)
     35.. spec:cdef:: cdef_macro(identifier, content, placement)
    3436
    3537   :param identifier: one-word name for this macro chain
     
    5052^^^^^^^^^^^^^^
    5153
    52 .. py:function:: the_time(t = None)
     54.. py:function:: python_function(t = None)
    5355
    5456   :param t: time_t object or None (defaults to ``now()``)
    5557   :type  t: str
     58
     59ReST example
     60^^^^^^^^^^^^^^
     61
     62.. rst:directive:: rst_directive
    5663
    5764
     
    6572^^^^^^^^^^^
    6673
    67 * macro definition: :spec:def:`example_defined_macro`
    68 * runtime-defined macro definition: :spec:rdef:`example_runtime_defined_macro`
    69 * chained macro definition: :spec:cdef:`example_chained_macro`
     74* macro definition: :spec:def:`def_macro`
     75* runtime-defined macro definition: :spec:rdef:`rdef_macro`
     76* chained macro definition: :spec:cdef:`cdef_macro`
    7077
    7178SPEC Variables
     
    7986^^^^^^^^^^^^^^
    8087
    81 See the python method :py:func:`the_time()` (defined above)
     88See the python method :py:func:`python_function()` (defined above)
Note: See TracChangeset for help on using the changeset viewer.