Permalink
Browse files

Initial commit, lot's of grossness

  • Loading branch information...
0 parents commit 0036bb27213bbcc185d559aaf5991f817eddfd25 @grncdr committed Jan 1, 2012
@@ -0,0 +1 @@
+*.pyc
@@ -0,0 +1,12 @@
+"""
+ sphinxcontrib
+ ~~~~~~~~~~~~~
+
+ This package is a namespace package that contains all extensions
+ distributed in the ``sphinx-contrib`` distribution.
+
+ :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
+ :license: BSD, see LICENSE for details.
+"""
+
+__import__('pkg_resources').declare_namespace(__name__)
@@ -0,0 +1,9 @@
+def setup(app):
+ app.add_config_value('coffee_src_dir', None, 'env')
+ from .domain import CoffeeDomain
+ app.add_domain(CoffeeDomain)
+ from .documenters import *
+ app.add_autodocumenter(ModuleDocumenter)
+ app.add_autodocumenter(ClassDocumenter)
+ app.add_autodocumenter(FunctionDocumenter)
+ app.add_autodocumenter(MethodDocumenter)
@@ -0,0 +1,202 @@
+from .parser import CoffeeParser, CoffeeNode
+from sphinx.util.docstrings import prepare_docstring
+from sphinx.ext.autodoc import Documenter, members_option, bool_option
+from pdb import set_trace
+
+def get_docstring(node):
+ """
+ Extract the doc-comment preceding the node if it exists
+ """
+ # Special case for file comments
+ if node.siblings and node.siblings.index(node) == 1 and not node.parent:
+ return None
+ prev = node.prev_sibling
+ if prev and prev.tag == 'Comment':
+ return prev['comment']
+
+def variable_name(node):
+ if node.tag == 'Assign':
+ if node['value'].tag == 'Class':
+ return variable_name(node['value'])
+ return expand_variable_name(node['variable'])
+ elif node.tag == 'Class':
+ return node['variable']['base']['value']
+
+def full_name(node):
+ if node.tag == 'Assign':
+ name = expand_variable_name(node['variable'])
+ maybe_class = node.find_parent('Class')
+ if maybe_class:
+ name = "%s.%s" % (maybe_class['variable']['base']['value'], name)
+ return name
+ else:
+ return None
+
+def expand_variable_name(var):
+ parts = []
+ for p in var['properties']:
+ # Handle indexed properties, they will come out as quoted strings
+ if p.tag == 'Index':
+ p = p['index']
+ if 'name' not in p:
+ break
+ else:
+ parts.append(p['name']['value'])
+ if 'value' in var['base']:
+ parts.insert(0, var['base']['value'])
+ return '.'.join(parts)
+
+class CoffeeASTDocumenter(Documenter):
+ priority = 11 # Need to outrank AttributeDocumenter
+ option_spec = {
+ 'members': members_option
+ }
+
+ @property
+ def filename(self):
+ return self.modname + '.coffee'
+
+ @classmethod
+ def can_document_member(cls, ast_node, ast_node_name, isattr, p_documenter):
+ if not isinstance(ast_node, CoffeeNode):
+ return False
+ # Always walk down through assignments to find the actual thing
+ while ast_node.tag == 'Assign':
+ ast_node = ast_node['value']
+ return ast_node.tag == cls.documents_ast_node_tag
+
+ def get_object_members(self, want_all=False):
+ members = []
+ for node in self.get_child_nodes():
+ if node.tag == 'Comment': continue
+ node.__doc__ = get_docstring(node)
+ members.append((variable_name(node), node))
+ return False, members
+
+ def import_object(self):
+ obj = self.ast_node
+ while obj.tag == 'Assign':
+ obj = obj['value']
+ self.object = obj
+ self.object.__doc__ = self.ast_node.__doc__
+ return True
+
+ def filter_members(self, members, want_all):
+ for (name, node) in members:
+ if not node.__doc__:
+ continue
+ # XXX - Cache the node with a fully-qualified name
+ # This is pretty gross, but this name is the only thing passed
+ # to the constructor of the Documenter, and trying to look up
+ # the node again via the parser is worse.
+ #
+ # https://bitbucket.org/birkenfeld/sphinx/src/65e4c29a24e4/sphinx/ext/autodoc.py#cl-636
+ fqn = '::'.join([self.modname, '.'.join(self.objpath + [name])])
+ self.env.domaindata['coffee']['node_cache'][fqn] = node
+
+ yield (name, node, False)
+
+ @property
+ def ast_node(self):
+ return self.env.domaindata['coffee']['node_cache'][self.name]
+
+ def parse_name(self):
+ parts = self.name.split('::')
+ self.modname = parts[0]
+ self.fullname = self.name
+ self.objpath = len(parts) == 2 and parts[1].split('.') or []
+ self.args = None
+ self.retann = None
+ return True
+
+ def format_name(self):
+ return self.name
+
+class ModuleDocumenter(CoffeeASTDocumenter):
+ objtype = 'module'
+ domain = 'coffee'
+ content_indent = u' '
+ documents_ast_node_tag = "-----" # Never match nodes
+ titles_allowed = True
+
+ @property
+ def ast_node(self):
+ return self.parser.get_nodes(self.filename)
+
+ @property
+ def parser(self):
+ domain_data = self.env.domaindata['coffee']
+ if 'parser' not in domain_data:
+ domain_data['node_cache'] = {}
+ domain_data['parser'] = CoffeeParser(self.env.config.coffee_src_dir)
+ return domain_data['parser']
+
+ def get_child_nodes(self):
+ return self.ast_node
+
+ def generate(self, **kwargs):
+ self.parse_name()
+ self.real_modname = self.modname
+ self.add_directive_header(u'')
+
+ first_node = self.ast_node[0]
+ if first_node.tag == 'Comment':
+ for line in prepare_docstring(first_node['comment']):
+ self.add_line(line, self.name)
+ self.document_members(True)
+
+class ClassDocumenter(CoffeeASTDocumenter):
+ content_indent = u' '
+ domain = 'coffee'
+ objtype = 'class'
+ documents_ast_node_tag = 'Class'
+
+ def import_object(self):
+ """
+ Over-ride import object to add an 'exported as' note to
+ module.exports assignments
+ """
+ super(ClassDocumenter, self).import_object()
+ if self.ast_node.tag == 'Assign':
+ var_name = expand_variable_name(self.ast_node['variable'])
+ if var_name.find('exports') > -1:
+ export_name = var_name.partition('exports')[2]
+ if export_name:
+ export_name = export_name.strip('.')
+ else:
+ export_name = self.modname
+ self.object.__doc__ += '\n\nExported as "%s"' % export_name
+ return True
+
+ def get_child_nodes(self):
+ exprs = self.object['body']['expressions']
+ children = []
+ for expr in exprs:
+ if expr.tag == 'Value' and expr['base'].tag == 'Obj':
+ children += expr['base']['properties']
+ # TODO - Add support for documenting non properties?
+ return children
+
+class FunctionDocumenter(CoffeeASTDocumenter):
+ content_indent = u' '
+ domain = 'coffee'
+ objtype = 'function'
+ documents_ast_node_tag = 'Code'
+
+ def get_child_nodes(self):
+ return []
+
+ def format_signature(self):
+ param_names = (p['name']['value'] for p in self.object['params'])
+ return '(%s)' % ', '.join(param_names)
+
+class MethodDocumenter(FunctionDocumenter):
+ objtype = 'method'
+ priority = FunctionDocumenter.priority + 1
+
+ @classmethod
+ def can_document_member(cls, ast_node, ast_node_name, isattr, p_documenter):
+ if not isinstance(p_documenter, ClassDocumenter):
+ return False
+ return FunctionDocumenter.can_document_member(ast_node, ast_node_name,
+ isattr, p_documenter)
@@ -0,0 +1,118 @@
+from docutils import nodes
+from docutils.parsers.rst import directives
+from sphinx import addnodes
+from sphinx.directives import ObjectDescription, Directive
+from sphinx.roles import XRefRole
+from sphinx.domains import Domain, ObjType
+from sphinx.domains.python import _pseudo_parse_arglist
+from sphinx.locale import l_, _
+from sphinx.util.nodes import make_refnode
+
+class CoffeeObj(ObjectDescription):
+ def handle_signature(self, sig, signode):
+ if self.display_prefix:
+ signode += addnodes.desc_annotation(self.display_prefix, self.display_prefix)
+
+ fullname, _, args = sig.partition('(')
+ modname, name = fullname.split('::')
+ args = args[:-1]
+ signode += addnodes.desc_name(name, name)
+ if isinstance(self, CoffeeFunction):
+ _pseudo_parse_arglist(signode, args)
+ return fullname
+
+ def add_target_and_index(self, fqn, sig, signode):
+ doc = self.state.document
+ if fqn not in self.state.document.ids:
+ signode['names'].append(fqn)
+ signode['ids'].append(fqn)
+ self.state.document.note_explicit_target(signode)
+ objects = self.env.domaindata['coffee']['objects']
+ objects[fqn] = (self.env.docname, self.objtype)
+
+ indextext = "%s (%s)" % (fqn.replace('::','/'), 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):
+ required_arguments = 1
+ has_content = False
+
+ def run(self):
+ env = self.state.document.settings.env
+ modname = self.arguments[0].strip()
+ env.temp_data['coffee:module'] = modname
+ targetnode = nodes.target('', '', ids=['module-' + modname], ismod=True)
+ self.state.document.note_explicit_target(targetnode)
+ indextext = _('%s (module)') % modname
+ inode = addnodes.index(entries=[('single', indextext,
+ 'module-' + modname, '')])
+ #section = nodes.section()
+ return [nodes.title('', modname), targetnode, inode]
+
+
+class CoffeeClass(CoffeeObj):
+ option_spec = {
+ 'module': directives.unchanged,
+ 'export_name': directives.unchanged,
+ }
+ display_prefix = 'class '
+
+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,
+ }
+ display_prefix = '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()
+ 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')
+ }
+ directives = {
+ 'module': CoffeeModule,
+ 'function': CoffeeFunction,
+ 'class': CoffeeClass,
+ 'method': CoffeeMethod,
+ }
+
+ roles = {
+ 'meth': CoffeeXRefRole(),
+ 'class': CoffeeXRefRole(),
+ 'func': CoffeeXRefRole(),
+ }
+ initial_data = {"node_cache": {}, "objects": {}}
+
+ def get_objects(self):
+ for fqn, node in self.data['node_cache'].items():
+ (docname, objtype) = self.data['objects'][fqn]
+ disp_name = fqn.split('::').pop()
+ yield (fqn, disp_name, objtype, docname, fqn, 1)
+
+ def resolve_xref(self, env, fromdocname, builder, type, target, node, contnode):
+ if target[0] == '~':
+ target = target[1:]
+ doc, _ = self.data['objects'].get(target, (None, None))
+ if doc:
+ return make_refnode(builder, fromdocname, doc, target, contnode,
+ target)
Oops, something went wrong.

0 comments on commit 0036bb2

Please sign in to comment.