diff options
Diffstat (limited to 'wsmeext/sphinxext.py')
-rw-r--r-- | wsmeext/sphinxext.py | 600 |
1 files changed, 0 insertions, 600 deletions
diff --git a/wsmeext/sphinxext.py b/wsmeext/sphinxext.py deleted file mode 100644 index 7c45a0f..0000000 --- a/wsmeext/sphinxext.py +++ /dev/null @@ -1,600 +0,0 @@ -import inspect -import re -import sys - -import six - -from sphinx import addnodes -from sphinx.ext import autodoc -from sphinx.domains.python import PyClasslike, PyClassmember -from sphinx.domains import Domain, ObjType -from sphinx.directives import ObjectDescription -from sphinx.util.docfields import Field -from sphinx.util.nodes import make_refnode - -from sphinx.roles import XRefRole -from sphinx.locale import l_, _ - -from docutils.parsers.rst import Directive -from docutils.parsers.rst import directives - -import wsme -import wsme.types -import wsme.rest.json -import wsme.rest.xml - -field_re = re.compile(r':(?P<field>\w+)(\s+(?P<name>\w+))?:') - - -def datatypename(datatype): - if isinstance(datatype, wsme.types.UserType): - return datatype.name - if isinstance(datatype, wsme.types.DictType): - return 'dict(%s: %s)' % (datatypename(datatype.key_type), - datatypename(datatype.value_type)) - if isinstance(datatype, wsme.types.ArrayType): - return 'list(%s)' % datatypename(datatype.item_type) - return datatype.__name__ - - -def make_sample_object(datatype): - if datatype is wsme.types.bytes: - return six.b('samplestring') - if datatype is wsme.types.text: - return u'sample unicode' - if datatype is int: - return 5 - sample_obj = getattr(datatype, 'sample', datatype)() - return sample_obj - - -def get_protocols(names): - names = list(names) - protocols = [] - if 'rest' in names: - names.remove('rest') - protocols.extend('restjson', 'restxml') - if 'restjson' in names: - names.remove('restjson') - protocols.append(('Json', wsme.rest.json)) - if 'restxml' in names: - names.remove('restxml') - protocols.append(('XML', wsme.rest.xml)) - for name in names: - p = wsme.protocol.getprotocol(name) - protocols.append((p.displayname or p.name, p)) - return protocols - - -class SampleType(object): - """A Sample Type""" - - #: A Int - aint = int - - def __init__(self, aint=None): - if aint: - self.aint = aint - - @classmethod - def sample(cls): - return cls(10) - - -class SampleService(wsme.WSRoot): - @wsme.expose(SampleType) - @wsme.validate(SampleType, int, str) - def change_aint(data, aint, dummy='useless'): - """ - :param aint: The new value - - :return: The data object with its aint field value changed. - """ - data.aint = aint - return data - - -def getroot(env, force=False): - root = env.temp_data.get('wsme:root') - if not force and root: - return root - rootpath = env.temp_data.get('wsme:rootpath', env.app.config.wsme_root) - - if rootpath is None: - return None - - modname, classname = rootpath.rsplit('.', 1) - __import__(modname) - module = sys.modules[modname] - root = getattr(module, classname) - env.temp_data['wsme:root'] = root - return root - - -def scan_services(service, path=[]): - has_functions = False - for name in dir(service): - if name.startswith('_'): - continue - a = getattr(service, name) - if inspect.ismethod(a): - if hasattr(a, '_wsme_definition'): - has_functions = True - if inspect.isclass(a): - continue - if len(path) > wsme.rest.APIPATH_MAXLEN: - raise ValueError("Path is too long: " + str(path)) - for value in scan_services(a, path + [name]): - yield value - if has_functions: - yield service, path - - -def find_service_path(env, service): - root = getroot(env) - if service == root: - return [] - for s, path in scan_services(root): - if s == service: - return path - return None - - -class TypeDirective(PyClasslike): - def get_index_text(self, modname, name_cls): - return _('%s (webservice type)') % name_cls[0] - - def add_target_and_index(self, name_cls, sig, signode): - ret = super(TypeDirective, self).add_target_and_index( - name_cls, sig, signode - ) - name = name_cls[0] - types = self.env.domaindata['wsme']['types'] - if name in types: - self.state_machine.reporter.warning( - 'duplicate type description of %s ' % name) - types[name] = self.env.docname - return ret - - -class AttributeDirective(PyClassmember): - doc_field_types = [ - Field('datatype', label=l_('Type'), has_arg=False, - names=('type', 'datatype')) - ] - - -def check_samples_slot(value): - """Validate the samples_slot option to the TypeDocumenter. - - Valid positions are 'before-docstring' and - 'after-docstring'. Using the explicit 'none' disables sample - output. The default is after-docstring. - """ - if not value: - return 'after-docstring' - val = directives.choice( - value, - ('none', # do not include - 'before-docstring', # show samples then docstring - 'after-docstring', # show docstring then samples - )) - return val - - -class TypeDocumenter(autodoc.ClassDocumenter): - objtype = 'type' - directivetype = 'type' - domain = 'wsme' - - required_arguments = 1 - default_samples_slot = 'after-docstring' - - option_spec = dict( - autodoc.ClassDocumenter.option_spec, - **{'protocols': lambda l: [v.strip() for v in l.split(',')], - 'samples-slot': check_samples_slot, - }) - - @staticmethod - def can_document_member(member, membername, isattr, parent): - # we don't want to be automaticaly used - # TODO check if the member is registered an an exposed type - return False - - def format_name(self): - return self.object.__name__ - - def format_signature(self): - return u'' - - def add_directive_header(self, sig): - super(TypeDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - def import_object(self): - if super(TypeDocumenter, self).import_object(): - wsme.types.register_type(self.object) - return True - else: - return False - - def add_content(self, more_content, no_docstring=False): - # Check where to include the samples - samples_slot = self.options.samples_slot or self.default_samples_slot - - def add_docstring(): - super(TypeDocumenter, self).add_content( - more_content, no_docstring) - - def add_samples(): - protocols = get_protocols( - self.options.protocols or self.env.app.config.wsme_protocols - ) - content = [] - if protocols: - sample_obj = make_sample_object(self.object) - content.extend([ - l_(u'Data samples:'), - u'', - u'.. cssclass:: toggle', - u'' - ]) - for name, protocol in protocols: - language, sample = protocol.encode_sample_value( - self.object, sample_obj, format=True) - content.extend([ - name, - u' .. code-block:: ' + language, - u'', - ]) - content.extend( - u' ' * 8 + line - for line in six.text_type(sample).split('\n')) - for line in content: - self.add_line(line, u'<wsmeext.sphinxext') - - self.add_line(u'', '<wsmeext.sphinxext>') - - if samples_slot == 'after-docstring': - add_docstring() - add_samples() - elif samples_slot == 'before-docstring': - add_samples() - add_docstring() - else: - add_docstring() - - -class AttributeDocumenter(autodoc.AttributeDocumenter): - datatype = None - domain = 'wsme' - - @staticmethod - def can_document_member(member, membername, isattr, parent): - return isinstance(parent, TypeDocumenter) - - def import_object(self): - success = super(AttributeDocumenter, self).import_object() - if success: - self.datatype = self.object.datatype - return success - - def add_content(self, more_content, no_docstring=False): - self.add_line( - u':type: %s' % datatypename(self.datatype), - '<wsmeext.sphinxext>' - ) - self.add_line(u'', '<wsmeext.sphinxext>') - super(AttributeDocumenter, self).add_content( - more_content, no_docstring) - - def add_directive_header(self, sig): - super(AttributeDocumenter, self).add_directive_header(sig) - - -class RootDirective(Directive): - """ - This directive is to tell what class is the Webservice root - """ - has_content = False - required_arguments = 1 - optional_arguments = 0 - final_argument_whitespace = False - option_spec = { - 'webpath': directives.unchanged - } - - def run(self): - env = self.state.document.settings.env - rootpath = self.arguments[0].strip() - env.temp_data['wsme:rootpath'] = rootpath - if 'wsme:root' in env.temp_data: - del env.temp_data['wsme:root'] - if 'webpath' in self.options: - env.temp_data['wsme:webpath'] = self.options['webpath'] - return [] - - -class ServiceDirective(ObjectDescription): - name = 'service' - - optional_arguments = 1 - - def handle_signature(self, sig, signode): - path = sig.split('/') - - namespace = '/'.join(path[:-1]) - if namespace and not namespace.endswith('/'): - namespace += '/' - - servicename = path[-1] - - if not namespace and not servicename: - servicename = '/' - - signode += addnodes.desc_annotation('service ', 'service ') - - if namespace: - signode += addnodes.desc_addname(namespace, namespace) - - signode += addnodes.desc_name(servicename, servicename) - - return sig - - -class ServiceDocumenter(autodoc.ClassDocumenter): - domain = 'wsme' - objtype = 'service' - directivetype = 'service' - - def add_directive_header(self, sig): - super(ServiceDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - def format_signature(self): - return u'' - - def format_name(self): - path = find_service_path(self.env, self.object) - if path is None: - return - return '/' + '/'.join(path) - - -class FunctionDirective(PyClassmember): - name = 'function' - objtype = 'function' - - def get_signature_prefix(self, sig): - return 'function ' - - -def document_function(funcdef, docstrings=None, protocols=['restjson']): - """A helper function to complete a function documentation with return and - parameter types""" - # If the function doesn't have a docstring, add an empty list - # so the default behaviors below work correctly. - if not docstrings: - docstrings = [[]] - found_params = set() - - for si, docstring in enumerate(docstrings): - for i, line in enumerate(docstring): - m = field_re.match(line) - if m and m.group('field') == 'param': - found_params.add(m.group('name')) - - next_param_pos = (0, 0) - - for arg in funcdef.arguments: - content = [ - u':type %s: :wsme:type:`%s`' % ( - arg.name, datatypename(arg.datatype)) - ] - if arg.name not in found_params: - content.insert(0, u':param %s: ' % (arg.name)) - pos = next_param_pos - else: - for si, docstring in enumerate(docstrings): - for i, line in enumerate(docstring): - m = field_re.match(line) - if m and m.group('field') == 'param' \ - and m.group('name') == arg.name: - pos = (si, i + 1) - break - docstring = docstrings[pos[0]] - docstring[pos[1]:pos[1]] = content - next_param_pos = (pos[0], pos[1] + len(content)) - - if funcdef.return_type: - content = [ - u':rtype: %s' % datatypename(funcdef.return_type) - ] - pos = None - for si, docstring in enumerate(docstrings): - for i, line in enumerate(docstring): - m = field_re.match(line) - if m and m.group('field') == 'return': - pos = (si, i + 1) - break - else: - pos = next_param_pos - docstring = docstrings[pos[0]] - docstring[pos[1]:pos[1]] = content - - codesamples = [] - - if protocols: - params = [] - for arg in funcdef.arguments: - params.append(( - arg.name, - arg.datatype, - make_sample_object(arg.datatype) - )) - codesamples.extend([ - u':%s:' % l_(u'Parameters samples'), - u' .. cssclass:: toggle', - u'' - ]) - for name, protocol in protocols: - language, sample = protocol.encode_sample_params( - params, format=True) - codesamples.extend([ - u' ' * 4 + name, - u' .. code-block:: ' + language, - u'', - ]) - codesamples.extend(( - u' ' * 12 + line - for line in six.text_type(sample).split('\n') - )) - - if funcdef.return_type: - codesamples.extend([ - u':%s:' % l_(u'Return samples'), - u' .. cssclass:: toggle', - u'' - ]) - sample_obj = make_sample_object(funcdef.return_type) - for name, protocol in protocols: - language, sample = protocol.encode_sample_result( - funcdef.return_type, sample_obj, format=True) - codesamples.extend([ - u' ' * 4 + name, - u' .. code-block:: ' + language, - u'', - ]) - codesamples.extend(( - u' ' * 12 + line - for line in six.text_type(sample).split('\n') - )) - - docstrings[0:0] = [codesamples] - return docstrings - - -class FunctionDocumenter(autodoc.MethodDocumenter): - domain = 'wsme' - directivetype = 'function' - objtype = 'function' - priority = 1 - - option_spec = { - 'path': directives.unchanged, - 'method': directives.unchanged - } - - @staticmethod - def can_document_member(member, membername, isattr, parent): - return (isinstance(parent, ServiceDocumenter) and - wsme.api.iswsmefunction(member)) - - def import_object(self): - ret = super(FunctionDocumenter, self).import_object() - self.directivetype = 'function' - self.wsme_fd = wsme.api.FunctionDefinition.get(self.object) - self.retann = datatypename(self.wsme_fd.return_type) - return ret - - def format_args(self): - args = [arg.name for arg in self.wsme_fd.arguments] - defaults = [ - arg.default - for arg in self.wsme_fd.arguments if not arg.mandatory - ] - return inspect.formatargspec(args, defaults=defaults) - - def get_doc(self, encoding=None): - """Inject the type and param fields into the docstrings so that the - user can add its own param fields to document the parameters""" - docstrings = super(FunctionDocumenter, self).get_doc(encoding) - - protocols = get_protocols( - self.options.protocols or self.env.app.config.wsme_protocols - ) - - return document_function( - self.wsme_fd, docstrings, protocols - ) - - def add_content(self, more_content, no_docstring=False): - super(FunctionDocumenter, self).add_content(more_content, no_docstring) - - def format_name(self): - return self.wsme_fd.name - - def add_directive_header(self, sig): - super(FunctionDocumenter, self).add_directive_header(sig) - # remove the :module: option that was added by ClassDocumenter - result_len = len(self.directive.result) - for index, item in zip(reversed(range(result_len)), - reversed(self.directive.result)): - if ':module:' in item: - self.directive.result.pop(index) - - -class WSMEDomain(Domain): - name = 'wsme' - label = 'WSME' - - object_types = { - 'type': ObjType(l_('type'), 'type', 'obj'), - 'service': ObjType(l_('service'), 'service', 'obj') - } - - directives = { - 'type': TypeDirective, - 'attribute': AttributeDirective, - 'service': ServiceDirective, - 'root': RootDirective, - 'function': FunctionDirective, - } - - roles = { - 'type': XRefRole() - } - - initial_data = { - 'types': {}, # fullname -> docname - } - - def clear_doc(self, docname): - keys = list(self.data['types'].keys()) - for key in keys: - value = self.data['types'][key] - if value == docname: - del self.data['types'][key] - - def resolve_xref(self, env, fromdocname, builder, - type, target, node, contnode): - if target not in self.data['types']: - return None - todocname = self.data['types'][target] - return make_refnode( - builder, fromdocname, todocname, target, contnode, target) - - -def setup(app): - app.add_domain(WSMEDomain) - app.add_autodocumenter(TypeDocumenter) - app.add_autodocumenter(AttributeDocumenter) - app.add_autodocumenter(ServiceDocumenter) - app.add_autodocumenter(FunctionDocumenter) - - app.add_config_value('wsme_root', None, 'env') - app.add_config_value('wsme_webpath', '/', 'env') - app.add_config_value('wsme_protocols', ['restjson', 'restxml'], 'env') - app.add_javascript('toggle.js') - app.add_stylesheet('toggle.css') |