sphinx-autoapi/autoapi/domains/dotnet.py
Anthony Johnson 4b13bebc8e Large refactor. Cleans up, adds tests, intermediate namespaces
Indexes are still broken due to past changes, but intermediate namespaces are
now linkable.
2015-04-14 15:59:09 -07:00

297 lines
9.1 KiB
Python

import os
from collections import defaultdict
from sphinx.util.console import bold, darkgreen
from sphinx.util.osutil import ensuredir
from ..base import AutoAPIBase, AutoAPIDomain
from ..settings import env
class DotNetDomain(AutoAPIDomain):
'''Auto API domain handler for .NET
Searches for YAML files, and soon to be JSON files as well, for auto API
sources
:param app: Sphinx application passed in as part of the extension
'''
def find_files(self):
'''Find YAML/JSON files to parse for namespace information'''
# TODO do an intelligent glob here, we're picking up too much
files_to_read = os.listdir(self.get_config('autoapi_dir'))
for _path in self.app.status_iterator(
files_to_read,
'[AutoAPI] Reading files... ',
darkgreen,
len(files_to_read)):
yield _path
def create_class(self, data):
'''Return instance of class based on Roslyn type property
Data keys handled here:
type
Set the object class
items
Recurse into :py:meth:`create_class` to create child object
instances
:param data: dictionary data from Roslyn output artifact
'''
# TODO replace this with a global mapping
classes = [DotNetNamespace, DotNetClass, DotNetProperty, DotNetMethod,
DotNetEnum, DotNetConstructor, DotNetStruct, DotNetInterface,
DotNetDelegate, DotNetField, DotNetEvent]
obj = None
for cls in classes:
if data['type'].lower() == cls.type.lower():
obj = cls(data)
# Append child objects
# TODO this should recurse in the case we're getting back more complex
# argument listings
if 'items' in data:
obj.children = []
obj.item_map = defaultdict(list)
for item in data['items']:
child_obj = self.create_class(item)
obj.children.append(child_obj)
obj.item_map[item['type']].append(child_obj)
return obj
def get_objects(self):
'''Trigger find of serialized sources and build objects'''
for path in self.find_files():
data = self.read_file(os.path.join(self.get_config('autoapi_dir'), path))
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 .NET object
'''
self.app.env.autoapi_data.append(obj)
self.objects.append(obj)
def sort_objects(self):
'''Not implemented yet'''
pass
# print "Sorting objects"
# Sort objects
# for obj in app.env.autoapi_data:
# rst = parse(obj, 'dotnet')
# if rst:
# path = os.path.join(app.config.autoapi_root, '%s%s' % (obj['name']['CSharp'], app.config.source_suffix[0]))
# ensuredir(app.config.autoapi_root)
# with open(path, 'w+') as fp:
# fp.write(rst)
def organize_objects(self):
'''Organize objects and namespaces'''
def _recurse_ns(obj):
namespace = obj.namespace()
if namespace is not None:
ns_obj = None
for (n, search_obj) in enumerate(self.app.env.autoapi_data):
if (search_obj.id == namespace and
isinstance(search_obj, DotNetNamespace)):
ns_obj = self.app.env.autoapi_data[n]
if ns_obj is None:
ns_obj = self.create_class({'id': namespace,
'type': 'namespace'})
self.app.env.autoapi_data.append(ns_obj)
self.namespaces[ns_obj.id] = ns_obj
if obj not in ns_obj.children:
ns_obj.children.append(obj)
_recurse_ns(ns_obj)
for obj in self.app.env.autoapi_data:
_recurse_ns(obj)
def full(self):
print "Reading"
self.get_objects()
self.organize_objects()
print "Writing"
self.generate_output()
self.write_indexes()
def generate_output(self):
# for namespace, objs in namespaces.items():
for obj in self.app.env.autoapi_data:
# path = os.path.join(app.config.autoapi_root, '%s%s' % (namespace, app.config.source_suffix[0]))
# namespace_obj = DotNetNamespace(namespace, objs)
# ensuredir(app.config.autoapi_root)
# with open(path, 'w+') as index_file:
# namespace_rst = namespace_obj.render()
# if namespace_rst:
# index_file.write(namespace_rst)
# for obj in objs:
# TODO not here!
obj.item_map = defaultdict(list)
for child in obj.children:
obj.item_map[child.type].append(child)
rst = obj.render()
# Detail
detail_dir = os.path.join(self.get_config('autoapi_root'),
*obj.name.split('.'))
ensuredir(detail_dir)
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)
for namespace, obj in self.namespaces.items():
path = os.path.join(self.get_config('autoapi_root'), '%s%s' % (namespace, self.get_config('source_suffix')[0]))
ensuredir(self.get_config('autoapi_root'))
with open(path, 'w+') as index_file:
namespace_rst = obj.render()
if namespace_rst:
index_file.write(namespace_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 DotNetBase(AutoAPIBase):
'''Base .NET object representation'''
language = 'dotnet'
def __init__(self, obj):
super(DotNetBase, self).__init__(obj)
# Always exist
self.id = obj['id']
self.type = obj['type']
# Use name or id
try:
self.name = obj['qualifiedName']['CSharp']
except:
self.name = self.id
self.short_name = self.name.split('.')[-1]
# Optional
self.summary = obj.get('summary', '')
self.parameters = []
self.items = []
self.children = []
# Syntax example and parameter list
syntax = obj.get('syntax', None)
self.example = ''
if syntax is not None:
# Code example
try:
self.example = syntax['content']['CSharp']
except KeyError:
pass
self.parameters = []
for param in syntax.get('parameters', []):
if 'id' in param:
self.parameters.append({
'name': param.get('id'),
'type': param.get('type', {}).get('id', None),
'desc': param.get('description', '')
})
self.items = obj.get('items', [])
def __str__(self):
return '<{cls} {id}>'.format(cls=self.__class__.__name__,
id=self.id)
def namespace(self):
pieces = self.id.split('.')[:-1]
if pieces:
return '.'.join(pieces)
@property
def ref_type(self):
return self.type.lower().replace('class', 'cls').replace('interface', 'iface').replace('delegate', 'del')
def to_ref_type(self, _type):
return _type.lower().replace('class', 'cls').replace('interface', 'iface').replace('delegate', 'del')
class DotNetNamespace(DotNetBase):
type = 'namespace'
reftype = 'ns'
class DotNetMethod(DotNetBase):
type = 'method'
reftype = 'meth'
class DotNetProperty(DotNetBase):
type = 'property'
reftype = 'prop'
class DotNetEnum(DotNetBase):
type = 'enum'
reftype = 'enum'
class DotNetStruct(DotNetBase):
type = 'struct'
reftype = 'struct'
class DotNetConstructor(DotNetBase):
type = 'constructor'
reftype = 'ctor'
class DotNetInterface(DotNetBase):
type = 'interface'
reftype = 'iface'
class DotNetDelegate(DotNetBase):
type = 'delegate'
reftype = 'del'
class DotNetClass(DotNetBase):
type = 'class'
reftype = 'cls'
class DotNetField(DotNetBase):
type = 'field'
reftype = 'field'
class DotNetEvent(DotNetBase):
type = 'event'
reftype = 'event'
class DotNetVirtualNamespace(AutoAPIBase):
language = 'dotnet'
type = 'namespace'
reftype = 'ns'
def __init__(self, name, objs):
self.name = self.short_name = name
self.children = []
self.type = 'namespace'
for obj in objs:
self.children.append(obj.obj)