Fix Python support

This commit is contained in:
Eric Holscher 2015-04-20 22:54:32 -07:00
parent 56ceab81e5
commit 09097cf364
9 changed files with 205 additions and 135 deletions

View File

@ -22,6 +22,7 @@ class AutoAPIBase(object):
'{language}/{type}.rst'.format(language=self.language, type=self.type) '{language}/{type}.rst'.format(language=self.language, type=self.type)
) )
ctx.update(**self.get_context_data()) ctx.update(**self.get_context_data())
print ctx['item_map'].keys()
return template.render(**ctx) return template.render(**ctx)
def get_absolute_path(self): def get_absolute_path(self):
@ -88,13 +89,13 @@ class AutoAPIDomain(object):
if self.app.config is not None: if self.app.config is not None:
return getattr(self.app.config, key, None) return getattr(self.app.config, key, None)
def find_files(self): def find_files(self, pattern='*.yaml'):
'''Find YAML/JSON files to parse for namespace information''' '''Find YAML/JSON files to parse for namespace information'''
# TODO do an intelligent glob here, we're picking up too much # TODO do an intelligent glob here, we're picking up too much
files_to_read = [] files_to_read = []
absolute_dir = os.path.normpath(self.get_config('autoapi_dir')) absolute_dir = os.path.normpath(self.get_config('autoapi_dir'))
for root, dirnames, filenames in os.walk(absolute_dir): for root, dirnames, filenames in os.walk(absolute_dir):
for filename in fnmatch.filter(filenames, '*.yaml'): for filename in fnmatch.filter(filenames, pattern):
if os.path.isabs(filename): if os.path.isabs(filename):
files_to_read.append(os.path.join(filename)) files_to_read.append(os.path.join(filename))
else: else:

View File

@ -1,2 +1,2 @@
from .dotnet import DotNetDomain from .dotnet import DotNetDomain
# from .python import PythonDomain from .python import PythonDomain

View File

@ -39,8 +39,6 @@ class DotNetDomain(AutoAPIDomain):
for cls in classes: for cls in classes:
if data.get('type', '').lower() == cls.type.lower(): if data.get('type', '').lower() == cls.type.lower():
obj = cls(data) obj = cls(data)
else:
return None
# Append child objects # Append child objects
# TODO this should recurse in the case we're getting back more complex # TODO this should recurse in the case we're getting back more complex
@ -74,6 +72,8 @@ class DotNetDomain(AutoAPIDomain):
'''Organize objects and namespaces''' '''Organize objects and namespaces'''
def _recurse_ns(obj): def _recurse_ns(obj):
if not obj:
return
namespace = obj.namespace namespace = obj.namespace
if namespace is not None: if namespace is not None:
ns_obj = None ns_obj = None

View File

@ -1,108 +1,188 @@
import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from sphinx.util.osutil import ensuredir
from epyparse import parsed from epyparse import parsed
from ..base import AutoAPIBase from ..base import AutoAPIBase, AutoAPIDomain
from ..settings import env from ..settings import env
# for root, dirnames, filenames in os.walk(app.config.autoapi_dir):
# for filename in fnmatch.filter(filenames, u'*.py'): class PythonDomain(AutoAPIDomain):
# to_open = os.path.join(root, filename)
# if ignore_file(app, to_open): '''Auto API domain handler for Python
# continue
# # print "Parsing Python File from %s" % to_open Parses directly from Python files.
# try:
# parsed_data = parsed(to_open) :param app: Sphinx application passed in as part of the extension
# app.env.autoapi_data.append(parsed_data) '''
# except Exception:
# print "Exception, Keeping going: %s" % to_open def create_class(self, data):
# import traceback '''Return instance of class based on Roslyn type property
# traceback.print_exc()
# app.env.autoapi_enabled = True Data keys handled here:
#
# # Generate RST type
# for obj in app.env.autoapi_data: Set the object class
# # print "Parsing %s" % obj['fullname']
# rst = classify(obj, 'python').render() items
# if rst: Recurse into :py:meth:`create_class` to create child object
# path = os.path.join(app.config.autoapi_root, '%s%s' % (obj['fullname'], app.config.source_suffix[0])) instances
# ensuredir(app.config.autoapi_root)
# with open(path, 'w+') as fp: :param data: dictionary data from Roslyn output artifact
# fp.write(rst.encode('utf8')) '''
# TODO replace this with a global mapping
classes = [PythonClass, PythonFunction, PythonModule]
obj = None
for cls in classes:
if data['type'].lower() == cls.type.lower():
obj = cls(data)
if not obj:
print "Unknown Type: %s" % data['type']
# Append child objects
# TODO this should recurse in the case we're getting back more complex
# argument listings
if 'children' in data:
for item in data['children']:
child_obj = self.create_class(item)
obj.children.append(child_obj)
return obj
def read_file(self, path):
'''Read file input into memory, returning deserialized objects
:param path: Path of file to read
'''
# TODO support JSON here
# TODO sphinx way of reporting errors in logs?
try:
parsed_data = parsed(path)
return parsed_data
except IOError:
print Warning('Error reading file: {0}'.format(path))
except TypeError:
print Warning('Error reading file: {0}'.format(path))
return None
def get_objects(self):
'''Trigger find of serialized sources and build objects'''
for path in self.find_files(pattern='*.py'):
data = self.read_file(os.path.join(self.get_config('autoapi_dir'), path))
if data:
obj = self.create_class(data)
self.add_object(obj)
def add_object(self, obj):
'''Add object to local and app environment storage
:param obj: Instance of a AutoAPI object
'''
self.app.env.autoapi_data.append(obj)
self.objects.append(obj)
def organize_objects(self):
'''Organize objects and namespaces'''
pass
def full(self):
print "Reading"
self.get_objects()
self.organize_objects()
print "Writing"
self.generate_output()
self.write_indexes()
def generate_output(self):
for obj in self.app.env.autoapi_data:
# TODO not here!
for child in obj.children:
obj.item_map[child.type].append(child)
for key in obj.item_map.keys():
obj.item_map[key].sort()
rst = obj.render()
# Detail
detail_dir = os.path.join(self.get_config('autoapi_root'),
*obj.name.split('.'))
ensuredir(detail_dir)
# TODO: Better way to determine suffix?
path = os.path.join(detail_dir, '%s%s' % ('index', self.get_config('source_suffix')[0]))
if rst:
with open(path, 'w+') as detail_file:
detail_file.write(rst)
def write_indexes(self):
# Write Index
top_level_index = os.path.join(self.get_config('autoapi_root'),
'index.rst')
with open(top_level_index, 'w+') as top_level_file:
content = env.get_template('index.rst')
top_level_file.write(content.render())
class PythonBase(AutoAPIBase): class PythonBase(AutoAPIBase):
language = 'python' language = 'python'
def __init__(self, obj): def __init__(self, obj):
obj = super(PythonBase, self).__init__(obj) super(PythonBase, self).__init__(obj)
obj.name = obj['fullname'] # Always exist
self.id = obj['fullname']
def render(self, ctx): # Optional
added_ctx = { self.imports = obj.get('imports', [])
'underline': len(self.name) * self.header self.children = []
} self.parameters = obj.get('params', [])
added_ctx.update(**ctx) self.docstring = obj.get('docstring', '')
super(PythonBase, self).render(ctx=added_ctx)
# For later
self.item_map = defaultdict(list)
def __str__(self):
return '<{cls} {id}>'.format(cls=self.__class__.__name__,
id=self.id)
@property
def name(self):
'''Return short name for member id
'''
try:
return self.obj['fullname']
except KeyError:
return self.id
@property
def short_name(self):
'''Shorten name property'''
return self.name.split('.')[-1]
@property
def namespace(self):
pieces = self.id.split('.')[:-1]
if pieces:
return '.'.join(pieces)
@property
def ref_type(self):
return self.type
@property
def ref_directive(self):
return self.type
class PythonFunction(PythonBase): class PythonFunction(PythonBase):
type = 'function' type = 'function'
class PythonModule(object): class PythonModule(PythonBase):
type = 'module'
def __init__(self, obj):
self.obj = obj
self.item_map = defaultdict(list)
self.sort()
def sort(self):
from .utils import classify
for item in self.obj.get('children', []):
if 'type' not in item:
print "Missing Type: %s" % item
continue
self.item_map[item['type']].append(classify(item, 'python'))
def render(self):
# print "Rendering module %s" % self.obj['fullname']
self.obj['underline'] = len(self.obj['fullname']) * "#"
template = env.get_template('python/module.rst')
ctx = self.obj
ctx.update(dict(
methods=self.item_map['function'],
classes=self.item_map['class'],
imports=self.obj['imports'],
))
return template.render(**ctx)
class PythonClass(object): class PythonClass(PythonBase):
type = 'class'
def __init__(self, obj):
self.obj = obj
self.item_map = defaultdict(list)
self.sort()
def sort(self):
from .utils import classify
for item in self.obj.get('children', []):
if 'type' not in item:
print "Missing Type: %s" % item
continue
self.item_map[item['type']].append(classify(item, 'python'))
def render(self, indent=4):
# print "Rendering class %s" % self.obj['fullname']
template = env.get_template('python/class.rst')
ctx = self.obj
ctx.update(dict(
underline=len(self.obj['fullname']) * "-",
methods=self.item_map['function'],
classes=self.item_map['class'],
indent=indent,
))
return template.render(**ctx)

View File

@ -6,7 +6,7 @@ Sphinx Auto-API
import fnmatch import fnmatch
import shutil import shutil
from .domains import DotNetDomain from .domains import DotNetDomain, PythonDomain
def ignore_file(app, filename): def ignore_file(app, filename):
@ -24,8 +24,8 @@ def load_yaml(app):
if app.config.autoapi_type == 'dotnet': if app.config.autoapi_type == 'dotnet':
domain = DotNetDomain(app) domain = DotNetDomain(app)
#elif app.config.autoapi_type == 'python': elif app.config.autoapi_type == 'python':
# domain = PythonDomain domain = PythonDomain(app)
domain.full() domain.full()
@ -44,3 +44,4 @@ def setup(app):
app.add_config_value('autoapi_ignore', ['*migrations*'], 'html') app.add_config_value('autoapi_ignore', ['*migrations*'], 'html')
app.add_config_value('autoapi_dir', '', 'html') app.add_config_value('autoapi_dir', '', 'html')
app.add_config_value('autoapi_keep_files', True, 'html') app.add_config_value('autoapi_keep_files', True, 'html')
app.add_stylesheet('autoapi.css')

View File

@ -1,24 +1,15 @@
.. class:: {{ fullname }}({{ args|join(',') }}) .. class:: {{ object.name }}({{ object.args|join(',') }})
{% if docstring %} {% if object.docstring %}
.. rubric:: Summary .. rubric:: Summary
{{ docstring|indent(3) }} {{ object.docstring|indent(3) }}
{% endif %} {% endif %}
{% if methods %} {% if methods %}
{% for class in classes %}
{% macro render() %}{{ class.render() }}{% endmacro %}
{{ render()|indent(3) }}
{%- endfor %}
{% endif %}
{% if methods %}
{% for method in methods %} {% for method in methods %}
{% macro render() %}{{ method.render() }}{% endmacro %} {% macro render() %}{{ method.render() }}{% endmacro %}

View File

@ -2,13 +2,13 @@
{% if is_method %} {% if is_method %}
{# Slice self off #} {# Slice self off #}
.. method:: {{ fullname.split('.')[-1] }}({{ args[1:]|join(',') }}) .. method:: {{ object.name.split('.')[-1] }}({{ args[1:]|join(',') }})
{% else %} {% else %}
.. function:: {{ fullname.split('.')[-1] }}({{ args|join(',') }}) .. function:: {{ object.name.split('.')[-1] }}({{ args|join(',') }})
{% endif %} {% endif %}
{% if docstring %} {% if object.docstring %}
{{ docstring|indent(3) }} {{ object.docstring|indent(3) }}
{% endif %} {% endif %}

View File

@ -1,7 +1,7 @@
{# Identention in this file is important #} {# Identention in this file is important #}
.. {{ type }}:: {{ fullname }} .. {{ obj.type }}:: {{ object.name }}
{{ docstring|indent(3) }} {{ object.docstring|indent(3) }}

View File

@ -1,38 +1,35 @@
{{ fullname }} {{ object.name }}
{{ underline }} {{ "-" * object.name|length }}
{% if docstring %} {% if object.docstring %}
.. rubric:: Summary .. rubric:: Summary
{{ docstring }} {{ object.docstring }}
{% endif %} {% endif %}
.. module:: {{ fullname }} .. module:: {{ object.name }}
{% if classes %}
.. rubric:: Classes {% block content %}
{% for class in classes %} {%- macro display_type(item_type) %}
{%- if item_type in item_map %}
{{ class.render() }} {{ item_type.title() }}
{{ "*" * item_type|length }}
{% endfor %} {%- for obj_item in item_map.get(item_type, []) %}
{% macro render() %}{{ obj_item.render() }}{% endmacro %}
{{ render()|indent(4) }}
{%- endfor %}
{%- endif %}
{%- endmacro %}
{% endif %} {%- for item_type in ['function', 'class'] %}
{{ display_type(item_type) }}
{%- endfor %}
{% endblock %}
{% if methods %}
.. rubric:: Functions
{% for method in methods %}
{{ method.render() }}
{% endfor %}
{% endif %}