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

Last change on this file since 940 was 940, checked in by jemian, 11 years ago

refs #8, making progress, discard code from previous attempt

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 12.3 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                             #@UnusedImport
22from sphinx.roles import XRefRole                       #@UnusedImport
23from sphinx.locale import l_, _                         #@UnusedImport
24from sphinx.directives import ObjectDescription         #@UnusedImport
25from sphinx.domains import Domain, ObjType, Index       #@UnusedImport
26from sphinx.util.compat import Directive                #@UnusedImport
27from sphinx.util.nodes import make_refnode              #@UnusedImport
28from sphinx.util.docfields import Field, TypedField     #@UnusedImport
29
30
31match_all                   = '.*'
32non_greedy_filler           = match_all+'?'
33double_quote_string_match   = '("'+non_greedy_filler+'")'
34word_match                  = '((?:[a-z_]\w*))'
35cdef_match                  = '(cdef)'
36extended_comment_flag       = '\"\"\"'
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+'\('
44                      + '('+match_all+')' 
45                      + '\)', 
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('^'
55                                                + non_greedy_filler
56                                                + extended_comment_flag, 
57                                                re.IGNORECASE|re.DOTALL)
58spec_extended_comment_block_sig_re = re.compile('^'
59                                                + non_greedy_filler
60                                                + extended_comment_flag
61                                                + '(' + non_greedy_filler + ')'
62                                                + extended_comment_flag
63                                                + non_greedy_filler
64                                                + '$', 
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        results = self.parse_macro_file(sig)
201        indent = ' '*4
202        for item in results:
203            # FIXME:  not desc_annotation but desc_content, but how to get it right?
204            # see <sphinx>/directives/__init__.py for an example
205            # It's a bit more complicated than this.
206            signode += addnodes.desc_annotation('\n', '\n')
207            for line in item.split('\n'):
208                signode += addnodes.desc_annotation(indent+line, indent+line)
209        return sig
210   
211    def parse_macro_file(self, filename):
212        """
213        parse the SPEC macro file and return the ReST blocks
214       
215        :param str filename: name (with optional path) of SPEC macro file
216            (The path is relative to the ``.rst`` document.)
217        :returns [str]: list of ReST-formatted extended comment blocks from SPEC macro file.
218       
219        [future] parse more stuff as planned
220        """
221        results = []
222        if not os.path.exists(filename):
223            raise RuntimeError, "could not find: " + filename
224       
225        buf = open(filename, 'r').read()
226        # TODO: loop until no matches, chopping away the buffer after each match
227        m = spec_extended_comment_block_sig_re.match(buf)
228        if m is not None:
229            rest = m.groups()
230            if len(rest) == 1:
231                results.append(rest[0])
232        return results
233
234
235class SpecXRefRole(XRefRole):
236    """ """
237   
238    def process_link(self, env, refnode, has_explicit_title, title, target):
239        key = ":".join((refnode['refdomain'], refnode['reftype']))
240        refnode[key] = env.temp_data.get(key)        # key was 'spec:def'
241        if not has_explicit_title:
242            title = title.lstrip(':')   # only has a meaning for the target
243            target = target.lstrip('~') # only has a meaning for the title
244            # if the first character is a tilde, don't display the module/class
245            # parts of the contents
246            if title[0:1] == '~':
247                title = title[1:]
248                colon = title.rfind(':')
249                if colon != -1:
250                    title = title[colon+1:]
251        return title, target
252
253    def result_nodes(self, document, env, node, is_ref):
254        # this code adds index entries for each role instance
255        if not is_ref:
256            return [node], []
257        varname = node['reftarget']
258        tgtid = 'index-%s' % env.new_serialno('index')
259        indexnode = addnodes.index()
260        indexnode['entries'] = [
261            ('single', varname, tgtid, ''),
262            #('single', _('environment variable; %s') % varname, tgtid, ''),
263        ]
264        targetnode = nodes.target('', '', ids=[tgtid])
265        document.note_explicit_target(targetnode)
266        return [indexnode, targetnode, node], []
267
268
269class SpecDomain(Domain):
270    """SPEC language domain."""
271   
272    name = 'spec'
273    label = 'SPEC, http://www.certif.com'
274    object_types = {    # type of object that a domain can document
275        'def':    ObjType(l_('def'),    'def'),
276        'rdef':   ObjType(l_('rdef'),   'rdef'),
277        'cdef':   ObjType(l_('cdef'),   'cdef'),
278        'global': ObjType(l_('global'), 'global'),
279        'local':  ObjType(l_('local'),  'local'),
280    }
281    directives = {
282        'def':          SpecMacroObject,
283        'rdef':         SpecMacroObject,
284        'cdef':         SpecMacroObject,
285        'global':       SpecVariableObject,
286        'local':        SpecVariableObject,
287        'macrofile':    SpecMacroSourceObject,
288    }
289    roles = {
290        'def' :     SpecXRefRole(),
291        'rdef':     SpecXRefRole(),
292        'cdef':     SpecXRefRole(),
293        'global':   SpecXRefRole(),
294        'local':    SpecXRefRole(),
295    }
296    initial_data = {
297        'objects': {}, # fullname -> docname, objtype
298    }
299
300    def clear_doc(self, docname):
301        for (typ, name), doc in self.data['objects'].items():
302            if doc == docname:
303                del self.data['objects'][typ, name]
304
305    def resolve_xref(self, env, fromdocname, builder, typ, target, node,
306                     contnode):
307        objects = self.data['objects']
308        objtypes = self.objtypes_for_role(typ)
309        for objtype in objtypes:
310            if (objtype, target) in objects:
311                return make_refnode(builder, fromdocname,
312                                    objects[objtype, target],
313                                    objtype + '-' + target,
314                                    contnode, target + ' ' + objtype)
315
316    def get_objects(self):
317        for (typ, name), docname in self.data['objects'].iteritems():
318            yield name, name, typ, docname, typ + '-' + name, 1
319
320
321# http://sphinx.pocoo.org/ext/tutorial.html#the-setup-function
322
323def setup(app):
324    app.add_domain(SpecDomain)
325    # http://sphinx.pocoo.org/ext/appapi.html#sphinx.domains.Domain
Note: See TracBrowser for help on using the repository browser.