Stephen Sugden avatar Stephen Sugden committed b5434df Draft

Enhanced support for inheritance and static methods

Also incorporates bugfix from Mark Smith re: windows paths and not waiting for
coffeedoc process to exit.

Comments (0)

Files changed (3)

coffeedomain/sphinxcontrib/coffeedomain/__init__.py

     app.add_autodocumenter(doc.ClassDocumenter)
     app.add_autodocumenter(doc.FunctionDocumenter)
     app.add_autodocumenter(doc.MethodDocumenter)
+    app.add_autodocumenter(doc.StaticMethodDocumenter)

coffeedomain/sphinxcontrib/coffeedomain/documenters.py

 from sphinx.util.docstrings import prepare_docstring
 from sphinx.ext.autodoc import Documenter, members_option, bool_option, ModuleDocumenter as PyModuleDocumenter
 from subprocess import Popen, PIPE
+import os.path
 import json
+import re
  
+from .domain import MOD_SEP
+
 class StubObject(object):
     """
     A python object that takes the place of the coffeescript object being
         *self.args* and *self.retann* if parsing and resolving was successful.
         """
         self.fullname = self.name
-        self.modname, path = self.name.split('::')
+        self.modname, path = self.name.split(MOD_SEP)
         self.real_modname = self.modname
         self.objpath = path.split('.')
         self.args = None
         return True
             
     def format_name(self):
-        return self.modname + '::' + '.'.join(self.objpath)
+        return self.modname + MOD_SEP + '.'.join(self.objpath)
 
     def get_object_members(self, want_all=False):
         members = []
     @property
     def coffeedoc_module(self):
         filename= self.modname + '.coffee'
-        modules = self.env.temp_data.setdefault('coffee:docgen', {})
+        return self._load_module(filename)
+
+    def _load_module(self, filename):
+        modules = self.env.temp_data.setdefault('coffee:coffeedoc-output', {})
+        if filename in modules:
+            return modules[filename]
         basedir = self.env.config.coffee_src_dir
         parser  = self.env.config.coffee_src_parser or 'commonjs'
-        if filename not in modules:
-            gencmd = ['coffeedoc', '--stdout', '--renderer', 'json', '--parser',
-                      parser, filename]
-            docgen = Popen(gencmd,
-                           cwd=basedir, stdout=PIPE)
-            module_data = json.load(docgen.stdout)[0]['module']
-            print "ran %s" % ' '.join(gencmd)
-            modules[filename] = StubObject('module', module_data)
+
+        gencmd = ['coffeedoc', '--stdout', '--renderer', 'json', '--parser',
+                  parser, filename]
+        docgen = Popen(gencmd, cwd=basedir, stdout=PIPE, shell=True)
+        (stdout, stderr) = docgen.communicate()
+        data = json.loads(stdout)[0]
+        data['path'] = data['path'].replace(basedir+'/', '')
+        data['name'] = data['path'].replace('.coffee', MOD_SEP)
+        modules[filename] = StubObject('module', data)
         return modules[filename]
 
     def import_object(self):
     documents_type = 'module'
     sub_member_keys = ('classes', 'functions')
 
+    option_spec =  {
+        'show-dependencies': bool_option
+    }
+
     def import_object(self):
         self.object = self.coffeedoc_module
         return True
 
     def add_content(self, more_content, no_docstring=False):
         super(ModuleDocumenter, self).add_content(more_content, no_docstring=False)
-        if not self.object['deps']:
-            return
-        self.add_line('*Dependencies:*', '<autodoc>')
-        for localname, module in self.object['deps'].items():
-            self.add_line('  * %s = require "%s"' % (localname, module), '<autodoc>')
+        if self.options.get('show-dependencies') and self.object['deps']:
+            self.add_line('*Dependencies:*', '<autodoc>')
+            for localname, module in self.object['deps'].items():
+                self.add_line('  * ``%s = require "%s"``' % (localname, module),
+                              '<autodoc>')
 
 class SubMember(object):
     option_spec = {
     objtype = 'class'
     documents_type = 'classes'
     sub_member_keys = ('staticmethods', 'instancemethods')
+    option_spec = {'inherited-members': bool_option}
 
-    def get_object_members(self, want_all=False):
-        members = []
-        for type in self.sub_member_keys:
-            for obj in self.object[type]:
-                members.append((obj['name'], StubObject(type, obj)))
-        return False, members
+    def add_directive_header(self, sig):
+        super(ClassDocumenter, self).add_directive_header(sig)
+        parent = self.object['parent']
+        if parent:
+            base = self.find_class_fqn(parent)
+            self.add_line(u'   :parent: ' + base, '<autodoc>')
+
+    def find_class_fqn(self, parent):
+        module = self.coffeedoc_module
+        # Module where the parent class came from
+        modname = None
+        if parent in [c['name'] for c in module['classes']]:
+            modname = module['name']
+
+        else:
+            for (local_name, dep) in module['deps'].iteritems():
+                if local_name.find(parent) >= 0:
+                    if dep[0] == '.':
+                        modname = self._relpath_modname(dep)
+                    else:
+                        modname = dep + MOD_SEP
+                    break
+
+        if modname:
+            return (modname + parent)
+        else:
+            return parent
+
+    def _relpath_modname(self, path):
+        here = os.path.dirname(self.coffeedoc_module['path'])
+        path = re.sub('(\\.js|\\.coffee)$', '', path)
+        return os.path.normpath(os.path.join(here, path)) + MOD_SEP
+
 
 class CodeDocumenter(CoffeedocDocumenter):
     sub_member_keys = ()
 
 class MethodDocumenter(ClassMember, CodeDocumenter):
     objtype = 'method'
-    documents_type = 'methods'
-
-    @property
-    def __doc__(self):
-        return self['docstring']
-
-    def _import_candidates(self, parent):
-        methods = []
-        for meth in parent['instancemethods']:
-            meth['static'] = False
-            methods.append(meth)
-        for meth in parent['staticmethods']:
-            meth['static'] = True
-            methods.append(meth)
-        return methods
+    documents_type = 'instancemethods'
 
     @classmethod
     def can_document_member(cls, member, membername, isattr, parent):
-        return isinstance(member, StubObject) and member.type.endswith('methods')
+        if isinstance(member, StubObject) and member.type == cls.documents_type:
+            return True
+
+class StaticMethodDocumenter(ClassMember, CodeDocumenter):
+    objtype = 'staticmethod'
+    documents_type = 'staticmethods'

coffeedomain/sphinxcontrib/coffeedomain/domain.py

 from sphinx.domains import Domain, ObjType
 from sphinx.domains.python import _pseudo_parse_arglist
 from sphinx.locale import l_, _
-from sphinx.util.docfields import DocFieldTransformer, TypedField, GroupedField
+from sphinx.util.docfields import TypedField, GroupedField
 from sphinx.util.nodes import make_refnode
 
+MOD_SEP = '::'
+
 class CoffeeObj(ObjectDescription):
     doc_field_types = [
-        TypedField('parameter', label='Parameters',
-                     names=('param', 'parameter', 'arg', 'argument'),
-                     typerolename='obj', typenames=('paramtype', 'type')),
+        GroupedField('parameter', label='Parameters',
+                     names=('param', 'parameter', 'arg', 'argument')),
     ]
 
     def handle_signature(self, sig, signode):
             signode += addnodes.desc_annotation(self.display_prefix, self.display_prefix)
 
         fullname, _, args = sig.partition('(')
-        modname, name = fullname.split('::')
+        modname, name = fullname.split(MOD_SEP)
+        classname = self.env.temp_data.get('autodoc:class')
+        if classname and name.startswith(classname):
+            name = name[len(classname):]
         args = args[:-1]
         signode += addnodes.desc_name(name, name)
         if isinstance(self, CoffeeFunction):
         indextext = "%s (%s)" % (fqn, self.display_prefix.strip())
         self.indexnode['entries'].append(('single', _(indextext), fqn, ''))
 
+
 # CoffeeModule inherits from Directive to allow it to output titles etc.
 class CoffeeModule(Directive):
     domain = 'coffee'
         targetnode = nodes.target('', '', ids=['module-' + modname],
                                   ismod=True)
         self.state.document.note_explicit_target(targetnode)
-        # the platform and synopsis aren't printed; in fact, they are only
-        # used in the modindex currently
         indextext = _('%s (module)') % modname[:-2]
         inode = addnodes.index(entries=[('single', indextext,
                                          'module-' + modname, '')])
     option_spec = {
         'module': directives.unchanged,
         'export_name': directives.unchanged,
+        'parent': directives.unchanged,
     }
+
     display_prefix = 'class '
 
+    def handle_signature(self, sig, signode):
+        fullname = super(CoffeeClass, self).handle_signature(sig, signode)
+
+        parent = self.options.get('parent')
+        if parent:
+            signode += addnodes.desc_annotation('extends', ' extends ')
+            modname, classname = parent.split(MOD_SEP)
+            pnode = addnodes.pending_xref(classname,
+                                          refdomain='coffee',
+                                          reftype='class',
+                                          reftarget=parent,
+                                          )
+            pnode += nodes.literal(classname,
+                                   classname,
+                                   classes=['xref',
+                                            'coffee',
+                                            'coffee-class'])
+            signode += pnode
+
+        return fullname
+
 class CoffeeFunction(CoffeeObj):
     option_spec = {
         'module': directives.unchanged,
         'export_name': directives.unchanged,
     }
+
     display_prefix = 'function '
 
+
 class CoffeeMethod(CoffeeFunction):
     option_spec = {
         'module': directives.unchanged,
-        'class': directives.unchanged,
-        'static': directives.unchanged,
     }
-    @property
-    def display_prefix(self):
-        return 'method '
 
+    display_prefix = 'method '
+
+
+class CoffeeStaticMethod(CoffeeFunction):
+    option_spec = {
+        'module': directives.unchanged,
+    }
+
+    display_prefix = 'static method '
 
 class CoffeeXRefRole(XRefRole):
     def process_link(self, env, refnode, has_explicit_title, title, target):
         """ Called after CoffeeDomain.resolve_xref """
         if not has_explicit_title:
-            title = title.split('::').pop()
+            title = title.split(MOD_SEP).pop()
         return title, target
 
+
 class CoffeeDomain(Domain):
     label = 'CoffeeScript'
     name = 'coffee'
     object_types = {
-        'module':      ObjType(l_('module'), 'module'),
-        'function':  ObjType(l_('function'),  'func'),
-        'class':     ObjType(l_('class'),     'class'),
-        'method':    ObjType(l_('method'), 'method')
+        'module':   ObjType(l_('module'),   'module'),
+        'function': ObjType(l_('function'), 'func'),
+        'class':    ObjType(l_('class'),    'class'),
+        'method':   ObjType(l_('method'),   'method'),
+        'staticmethod':   ObjType(l_('staticmethod'),   'staticmethod'),
     }
+
     directives = {
-        'module': CoffeeModule,
+        'module':   CoffeeModule,
         'function': CoffeeFunction,
-        'class': CoffeeClass,
-        'method': CoffeeMethod,
+        'class':    CoffeeClass,
+        'method':   CoffeeMethod,
+        'staticmethod':   CoffeeStaticMethod,
     }
 
     roles = {
+       'mod': CoffeeXRefRole(),
        'meth': CoffeeXRefRole(),
        'class': CoffeeXRefRole(),
        'func': CoffeeXRefRole(),
     }
+
     data_version = 1
     initial_data = {"modules": {}, "objects": {}}
 
     def get_objects(self):
-        for fqn, obj in self.data['objects'].iteritems():
-            (docname, objtype) = obj
+        for fqn, (docname, objtype) in self.data['objects'].iteritems():
             yield (fqn, fqn, objtype, docname, fqn, 1)
-            
+
     def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
         if target[0] == '~':
             target = target[1:]
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.