source: specdomain/src/specdomain/sphinxcontrib/specdomain.py @ 941

Last change on this file since 941 was 941, checked in by jemian, 10 years ago

working on the SPEC parser (to also grab file line numbers), still can't get extended comments into regular rst document body, going into the signature node instead, refs #8

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.2 KB
Line 
1# -*- coding: utf-8 -*-
2"""
3    sphinxcontrib.specdomain
4    ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
6    SPEC domain.
7
8    :copyright: Copyright 2012 by Pete Jemian
9    :license: BSD, see LICENSE for details.
10"""
11
12# http://sphinx.pocoo.org/ext/appapi.html
13
14import os
15import re
16import string                                           #@UnusedImport
17
18from docutils import nodes                              #@UnusedImport
19from docutils.parsers.rst import directives             #@UnusedImport
20
21from sphinx import addnodes
22from sphinx.roles import XRefRole
23from sphinx.locale import l_, _                         #@UnusedImport
24from sphinx.directives import ObjectDescription
25from sphinx.domains import Domain, ObjType, Index       #@UnusedImport
26from sphinx.util.compat import Directive                #@UnusedImport
27from sphinx.util.nodes import make_refnode
28from sphinx.util.docfields import Field, TypedField
29from sphinx.util.docstrings import prepare_docstring    #@UnusedImport
30
31match_all                   = r'.*'
32non_greedy_filler           = match_all + r'?'
33double_quote_string_match   = r'("' + non_greedy_filler + r'")'
34word_match                  = r'((?:[a-z_]\w*))'
35cdef_match                  = r'(cdef)'
36extended_comment_flag       = r'\"\"\"'
37
38
39spec_macro_sig_re = re.compile(
40                               r'''^ ([a-zA-Z_]\w*)         # macro name
41                               ''', re.VERBOSE)
42
43spec_func_sig_re = re.compile(word_match + r'\('
44                      + r'(' + match_all + r')' 
45                      + r'\)', 
46                      re.IGNORECASE|re.DOTALL)
47
48spec_cdef_name_sig_re = re.compile(double_quote_string_match, 
49                                   re.IGNORECASE|re.DOTALL)
50
51
52spec_extended_comment_flag_sig_re = re.compile(extended_comment_flag, 
53                                               re.IGNORECASE|re.DOTALL)
54spec_extended_comment_start_sig_re = re.compile(r'^'
55                                                + non_greedy_filler
56                                                + extended_comment_flag, 
57                                                re.IGNORECASE|re.DOTALL)
58spec_extended_comment_block_sig_re = re.compile(r'^'
59                                                + non_greedy_filler
60                                                + extended_comment_flag
61                                                + r'(' + non_greedy_filler + r')'
62                                                + extended_comment_flag
63                                                + non_greedy_filler
64                                                + r'$', 
65                                                re.IGNORECASE|re.DOTALL|re.MULTILINE)
66
67
68class SpecMacroObject(ObjectDescription):
69    """
70    Description of a SPEC macro definition
71    """
72
73    doc_field_types = [
74        TypedField('parameter', label=l_('Parameters'),
75                   names=('param', 'parameter', 'arg', 'argument',
76                          'keyword', 'kwarg', 'kwparam'),
77                   typerolename='def', typenames=('paramtype', 'type'),
78                   can_collapse=True),
79        Field('returnvalue', label=l_('Returns'), has_arg=False,
80              names=('returns', 'return')),
81        Field('returntype', label=l_('Return type'), has_arg=False,
82              names=('rtype',)),
83    ]
84
85    def add_target_and_index(self, name, sig, signode):
86        targetname = '%s-%s' % (self.objtype, name)
87        signode['ids'].append(targetname)
88        self.state.document.note_explicit_target(signode)
89        indextext = self._get_index_text(name)
90        if indextext:
91            self.indexnode['entries'].append(('single', indextext,
92                                              targetname, ''))
93
94    def _get_index_text(self, name):
95        macro_types = {
96            'def':  'SPEC macro definition; %s',
97            'rdef': 'SPEC run-time macro definition; %s',
98            'cdef': 'SPEC chained macro definition; %s',
99        }
100        if self.objtype in macro_types:
101            return _(macro_types[self.objtype]) % name
102        else:
103            return ''
104
105    def handle_signature(self, sig, signode):
106        # Must be able to match these (without preceding def or rdef)
107        #     def macro_name
108        #     def macro_name()
109        #     def macro_name(arg1, arg2)
110        #     rdef macro_name
111        #     cdef("macro_name", "content", "groupname", flags)
112        m = spec_func_sig_re.match(sig) or spec_macro_sig_re.match(sig)
113        if m is None:
114            raise ValueError
115        arglist = m.groups()
116        name = arglist[0]
117        args = []
118        if len(arglist) > 1:
119            args = arglist[1:]
120            if name == 'cdef':
121                # TODO: need to match complete arg list
122                # several different signatures are possible (see cdef-examples.mac)
123                # for now, just get the macro name and ignore the arg list
124                m = spec_cdef_name_sig_re.match(args[0])
125                arglist = m.groups()
126                name = arglist[0].strip('"')
127                args = ['<<< cdef argument list not handled yet >>>']       # FIXME:
128        signode += addnodes.desc_name(name, name)
129        if len(args) > 0:
130            signode += addnodes.desc_addname(args, args)
131        return name
132
133
134class SpecVariableObject(ObjectDescription):
135    """
136    Description of a SPEC variable
137    """
138
139
140class SpecMacroSourceObject(ObjectDescription):
141    """
142    Document a SPEC macro source code file
143   
144    This code responds to the ReST file directive::
145   
146        .. spec:macrofile:: partial/path/name/somefile.mac
147            :displayorder: fileorder
148   
149    The ``:displayorder`` parameter indicates how the
150    contents will be sorted for appearance in the ReST document.
151   
152        **fileorder**, **file**
153            Items will be documented in the order in
154            which they appear in the ``.mac`` file.
155       
156        **alphabetical**, **alpha**
157            Items will be documented in alphabetical order.
158   
159    A (near) future enhancement would be to provide for
160    documenting all macro files in a directory, with optional
161    recursion into subdirectories.  By default, the code would
162    only document files that match the glob pattern ``*.mac``.
163    Such as::
164   
165       .. spec:directory:: partial/path/name
166          :recursion:
167          :displayorder: alphabetical
168    """
169   
170    # TODO: work-in-progress
171   
172    doc_field_types = [
173        Field('displayorder', label=l_('Display order'), has_arg=False,
174              names=('displayorder', 'synonym')),
175    ]
176
177    def add_target_and_index(self, name, sig, signode):
178        targetname = '%s-%s' % (self.objtype, name)
179        signode['ids'].append(targetname)
180        self.state.document.note_explicit_target(signode)
181        indextext = sig
182        if indextext:
183            self.indexnode['entries'].append(('single', indextext,
184                                              targetname, ''))
185
186    def handle_signature(self, sig, signode):
187        signode += addnodes.desc_name(sig, sig)
188        # TODO: this is the place to parse the SPEC macro source code file named in "sig"
189        '''
190        Since 2002, SPEC has allowed for triple-quoted strings as extended comments.
191        Few, if any, have used them.
192        Assume that they will contain ReST formatted comments.
193        The first, simplest thing to do is to read the .mac file and only extract
194        all the extended comments and add them as nodes to the current document.
195       
196        An additional step would be to parse for def, cdef, rdef, global, local, const, ...
197        Another step would be to attach source code and provide links from each to
198        highlighted source code blocks.
199        '''
200        extended_comments_list = self.parse_macro_file(sig)
201        for extended_comment in extended_comments_list:
202            linenumber = -1                 # FIXME:
203            #for line in prepare_docstring(extended_comment, ignore=1):
204            #    self.result.append(self.indent + line, sig, linenumber)     # FIXME:
205        return sig
206   
207    def parse_macro_file(self, filename):
208        """
209        parse the SPEC macro file and return the ReST blocks
210       
211        :param str filename: name (with optional path) of SPEC macro file
212            (The path is relative to the ``.rst`` document.)
213        :returns [str]: list of ReST-formatted extended comment blocks (docstrings) from SPEC macro file.
214       
215        [future] parse more stuff as planned, this is very simplistic for now
216        """
217        results = []
218        if not os.path.exists(filename):
219            raise RuntimeError, "could not find: " + filename
220       
221        buf = open(filename, 'r').read()
222        #n = len(buf)
223        for node in spec_extended_comment_block_sig_re.finditer(buf):
224            #g = node.group()
225            #gs = node.groups()
226            #s = node.start()
227            #e = node.end()
228            #t = buf[s:e]
229            results.append(node.groups())            # TODO: can we get line number also?
230        return results
231
232
233class SpecXRefRole(XRefRole):
234    """ """
235   
236    def process_link(self, env, refnode, has_explicit_title, title, target):
237        key = ":".join((refnode['refdomain'], refnode['reftype']))
238        refnode[key] = env.temp_data.get(key)        # key was 'spec:def'
239        if not has_explicit_title:
240            title = title.lstrip(':')   # only has a meaning for the target
241            target = target.lstrip('~') # only has a meaning for the title
242            # if the first character is a tilde, don't display the module/class
243            # parts of the contents
244            if title[0:1] == '~':
245                title = title[1:]
246                colon = title.rfind(':')
247                if colon != -1:
248                    title = title[colon+1:]
249        return title, target
250
251    def result_nodes(self, document, env, node, is_ref):
252        # this code adds index entries for each role instance
253        if not is_ref:
254            return [node], []
255        varname = node['reftarget']
256        tgtid = 'index-%s' % env.new_serialno('index')
257        indexnode = addnodes.index()
258        indexnode['entries'] = [
259            ('single', varname, tgtid, ''),
260            #('single', _('environment variable; %s') % varname, tgtid, ''),
261        ]
262        targetnode = nodes.target('', '', ids=[tgtid])
263        document.note_explicit_target(targetnode)
264        return [indexnode, targetnode, node], []
265
266
267class SpecDomain(Domain):
268    """SPEC language domain."""
269   
270    name = 'spec'
271    label = 'SPEC, http://www.certif.com'
272    object_types = {    # type of object that a domain can document
273        'def':    ObjType(l_('def'),    'def'),
274        'rdef':   ObjType(l_('rdef'),   'rdef'),
275        'cdef':   ObjType(l_('cdef'),   'cdef'),
276        'global': ObjType(l_('global'), 'global'),
277        'local':  ObjType(l_('local'),  'local'),
278    }
279    directives = {
280        'def':          SpecMacroObject,
281        'rdef':         SpecMacroObject,
282        'cdef':         SpecMacroObject,
283        'global':       SpecVariableObject,
284        'local':        SpecVariableObject,
285        'macrofile':    SpecMacroSourceObject,
286    }
287    roles = {
288        'def' :     SpecXRefRole(),
289        'rdef':     SpecXRefRole(),
290        'cdef':     SpecXRefRole(),
291        'global':   SpecXRefRole(),
292        'local':    SpecXRefRole(),
293    }
294    initial_data = {
295        'objects': {}, # fullname -> docname, objtype
296    }
297
298    def clear_doc(self, docname):
299        for (typ, name), doc in self.data['objects'].items():
300            if doc == docname:
301                del self.data['objects'][typ, name]
302
303    def resolve_xref(self, env, fromdocname, builder, typ, target, node,
304                     contnode):
305        objects = self.data['objects']
306        objtypes = self.objtypes_for_role(typ)
307        for objtype in objtypes:
308            if (objtype, target) in objects:
309                return make_refnode(builder, fromdocname,
310                                    objects[objtype, target],
311                                    objtype + '-' + target,
312                                    contnode, target + ' ' + objtype)
313
314    def get_objects(self):
315        for (typ, name), docname in self.data['objects'].iteritems():
316            yield name, name, typ, docname, typ + '-' + name, 1
317
318
319# http://sphinx.pocoo.org/ext/tutorial.html#the-setup-function
320
321def setup(app):
322    app.add_domain(SpecDomain)
323    # http://sphinx.pocoo.org/ext/appapi.html#sphinx.domains.Domain
Note: See TracBrowser for help on using the repository browser.