926 lines
27 KiB
Python
926 lines
27 KiB
Python
# Copyright (c) 2015 Russell Sim <russell.sim@gmail.com>
|
|
#
|
|
# All Rights Reserved.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
# implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
import logging
|
|
|
|
import docutils.core
|
|
from docutils import nodes
|
|
from docutils.parsers.rst import Directive
|
|
from docutils.parsers.rst import directives
|
|
import docutils.utils
|
|
from docutils import writers
|
|
import six
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
MIME_MAP = {
|
|
'json': 'application/json',
|
|
'txt': 'text/plain',
|
|
'xml': 'application/xml',
|
|
}
|
|
|
|
STATUS_CODE_MAP = {
|
|
'200': 'Success',
|
|
'201': 'Created',
|
|
'202': 'Accepted',
|
|
'203': 'Non-Authoritative Information',
|
|
'204': 'No Content',
|
|
'205': 'Reset Content',
|
|
'206': 'Partial Content',
|
|
'300': 'Multiple Choices',
|
|
'301': 'Moved Permanently',
|
|
'302': 'Found',
|
|
'303': 'See Other',
|
|
'304': 'Not Modified',
|
|
'400': 'Bad Request',
|
|
'401': 'Unauthorized',
|
|
'403': 'Forbidden',
|
|
'404': 'Not Found',
|
|
'405': 'Method Not Allowed',
|
|
'409': 'Conflict',
|
|
'410': 'Gone',
|
|
'413': 'Request Entity Too Large',
|
|
'415': 'Unsupported Media Type',
|
|
'503': 'Service Unavailable',
|
|
}
|
|
|
|
|
|
def search_node_parents(node, node_name):
|
|
parent = node
|
|
while parent.parent:
|
|
if parent.tagname == node_name:
|
|
return node
|
|
parent = parent.parent
|
|
if parent.tagname == node_name:
|
|
return node
|
|
|
|
|
|
class JSONTranslator(nodes.GenericNodeVisitor):
|
|
def __init__(self, document):
|
|
nodes.NodeVisitor.__init__(self, document)
|
|
self.output = {
|
|
'tags': [],
|
|
'paths': {}
|
|
}
|
|
self.node_stack = []
|
|
self.node_stack.append(self.output)
|
|
self.current_node_name = None
|
|
self.bullet_stack = []
|
|
self.table_stack = []
|
|
self.text = ''
|
|
self.col_num = 0
|
|
self.first_row = 0
|
|
self.hyperlink_name = ''
|
|
self.refuri = ''
|
|
self.listitem = False
|
|
self.lit_block = False
|
|
self.list_indent = 0
|
|
|
|
def search_stack_for(self, tag_name):
|
|
for node in self.node_stack:
|
|
# Skip any list elements, this is a hack, but it' works
|
|
# for now.
|
|
if isinstance(node, (list, ) + six.string_types):
|
|
continue
|
|
if tag_name in node.keys():
|
|
return node
|
|
|
|
def visit_document(self, node):
|
|
# Disable both the document visit and depart
|
|
pass
|
|
|
|
def depart_document(self, node):
|
|
pass
|
|
|
|
def default_visit(self, node):
|
|
"""Default node visit method."""
|
|
self.current_node_name = node.__class__.__name__
|
|
if hasattr(node, 'children') and node.children:
|
|
new_node = {}
|
|
self.node_stack[-1][self.current_node_name] = new_node
|
|
self.node_stack.append(new_node)
|
|
|
|
def default_departure(self, node):
|
|
"""Default node depart method."""
|
|
if hasattr(node, 'children') and node.children:
|
|
self.node_stack.pop()
|
|
|
|
def visit_system_message(self, node):
|
|
pass
|
|
|
|
def depart_system_message(self, node):
|
|
pass
|
|
|
|
def visit_Text(self, node):
|
|
if self.first_row is 0:
|
|
if self.lit_block and len(self.bullet_stack) > 0:
|
|
litblock = node.astext().split('\n')
|
|
litblock = '\n '.join(litblock)
|
|
self.text += litblock
|
|
else:
|
|
self.text += node.astext()
|
|
|
|
def depart_Text(self, node):
|
|
pass
|
|
|
|
def visit_emphasis(self, node):
|
|
if self.first_row > 0:
|
|
inlinetxt = self.table_stack.pop()
|
|
para = inlinetxt.partition(node.astext())
|
|
new_para = ''
|
|
new_para += para[0] + '_' + para[1] + '_' + para[2]
|
|
self.table_stack.append(new_para)
|
|
else:
|
|
self.text += '_'
|
|
|
|
def depart_emphasis(self, node):
|
|
if self.first_row is 0:
|
|
self.text += '_'
|
|
|
|
def visit_literal(self, node):
|
|
if self.first_row > 0:
|
|
inlinetxt = self.table_stack.pop()
|
|
para = inlinetxt.partition(node.astext())
|
|
new_para = ''
|
|
new_para += para[0] + '`' + para[1] + '`' + para[2]
|
|
self.table_stack.append(new_para)
|
|
else:
|
|
self.text += '`'
|
|
|
|
def depart_literal(self, node):
|
|
if self.first_row is 0:
|
|
self.text += '`'
|
|
|
|
def visit_strong(self, node):
|
|
if self.first_row > 0:
|
|
inlinetxt = self.table_stack.pop()
|
|
para = inlinetxt.partition(node.astext())
|
|
new_para = ''
|
|
new_para += para[0] + '**' + para[1] + '**' + para[2]
|
|
self.table_stack.append(new_para)
|
|
else:
|
|
self.text += '**'
|
|
|
|
def depart_strong(self, node):
|
|
if self.first_row is 0:
|
|
self.text += '**'
|
|
|
|
def visit_literal_block(self, node):
|
|
if len(self.bullet_stack) > 0:
|
|
self.text += '\n '
|
|
else:
|
|
self.text += '```\n'
|
|
self.lit_block = True
|
|
|
|
def depart_literal_block(self, node):
|
|
if len(self.bullet_stack) > 0:
|
|
self.text += '\n'
|
|
else:
|
|
self.text += '\n```\n'
|
|
self.lit_block = False
|
|
|
|
def visit_bullet_list(self, node):
|
|
self.bullet_stack.append('*')
|
|
|
|
def depart_bullet_list(self, node):
|
|
self.bullet_stack.pop()
|
|
self.list_indent = len(self.bullet_stack) - 1
|
|
if len(self.bullet_stack) is 0:
|
|
self.text += '\n'
|
|
|
|
def visit_list_item(self, node):
|
|
self.list_indent = len(self.bullet_stack) - 1
|
|
item = '\n%s%s ' % (' ' * self.list_indent,
|
|
self.bullet_stack[-1])
|
|
self.text += item
|
|
self.listitem = True
|
|
|
|
def depart_list_item(self, node):
|
|
self.listitem = False
|
|
self.list_indent = 0
|
|
|
|
def visit_title(self, node):
|
|
self.current_node_name = node.__class__.__name__
|
|
if self.current_node_name not in self.node_stack[-1]:
|
|
new_node = []
|
|
self.node_stack[-1][self.current_node_name] = new_node
|
|
self.node_stack.append(new_node)
|
|
|
|
def depart_title(self, node):
|
|
self.node_stack.pop()
|
|
|
|
def visit_paragraph(self, node):
|
|
if self.first_row > 0:
|
|
self.table_stack.append(node.astext())
|
|
else:
|
|
# listitem text
|
|
if self.listitem is True:
|
|
pass
|
|
else:
|
|
# another para in listitem
|
|
if len(self.bullet_stack) > 0:
|
|
if self.lit_block:
|
|
self.text += '\n' + ' '
|
|
else:
|
|
self.text += '\n' + ' ' * self.list_indent + ' '
|
|
|
|
def depart_paragraph(self, node):
|
|
if self.first_row is 0:
|
|
if self.listitem:
|
|
self.text += '\n'
|
|
self.listitem = False
|
|
else:
|
|
if len(self.bullet_stack) > 0:
|
|
self.text += "\n"
|
|
else:
|
|
# default paragraph
|
|
self.text += "\n\n"
|
|
else:
|
|
if self.first_row > 0:
|
|
para = self.table_stack.pop()
|
|
para = para.strip('\n')
|
|
plist = para.split('\n')
|
|
|
|
# multi-line text in single column
|
|
if len(plist) > 0:
|
|
self.text += """<br>""".join(plist)
|
|
else:
|
|
self.text += para
|
|
|
|
def visit_line_block(self, node):
|
|
if isinstance(self.node_stack[-1], list):
|
|
return
|
|
|
|
self.current_node_name = node.__class__.__name__
|
|
if self.current_node_name not in self.node_stack[-1]:
|
|
new_node = []
|
|
self.node_stack[-1][self.current_node_name] = new_node
|
|
self.node_stack.append(new_node)
|
|
else:
|
|
self.node_stack.append(self.node_stack[-1][self.current_node_name])
|
|
|
|
def depart_line_block(self, node):
|
|
if isinstance(self.node_stack[-1], list):
|
|
self.node_stack.pop()
|
|
|
|
def visit_table(self, node):
|
|
self.col_num = 0
|
|
|
|
def depart_table(self, node):
|
|
self.text += "\n"
|
|
|
|
def visit_tbody(self, node):
|
|
pass
|
|
|
|
def depart_tbody(self, node):
|
|
self.text += "\n"
|
|
self.first_row = 0
|
|
self.col_num = 0
|
|
|
|
def visit_thead(self, node):
|
|
pass
|
|
|
|
def depart_thead(self, node):
|
|
pass
|
|
|
|
def visit_tgroup(self, node):
|
|
pass
|
|
|
|
def depart_tgroup(self, node):
|
|
pass
|
|
|
|
def visit_colspec(self, node):
|
|
pass
|
|
|
|
def depart_colspec(self, node):
|
|
pass
|
|
|
|
def visit_row(self, node):
|
|
if self.first_row is 1 and self.col_num > 0:
|
|
row_separator = [' --- '] * self.col_num
|
|
self.text += "|"
|
|
sep_row = "|".join(row_separator)
|
|
self.text += sep_row
|
|
self.text += "|"
|
|
self.text += "\n"
|
|
|
|
self.text += "|"
|
|
self.first_row += 1
|
|
|
|
def depart_row(self, node):
|
|
self.text += "\n"
|
|
|
|
def visit_entry(self, node):
|
|
self.text += " "
|
|
|
|
def depart_entry(self, node):
|
|
self.text += " |"
|
|
self.col_num += 1
|
|
|
|
def visit_definition(self, node):
|
|
pass
|
|
|
|
def depart_definition(self, node):
|
|
pass
|
|
|
|
def visit_definition_list(self, node):
|
|
pass
|
|
|
|
def depart_definition_list(self, node):
|
|
pass
|
|
|
|
def visit_definition_list_item(self, node):
|
|
pass
|
|
|
|
def depart_definition_list_item(self, node):
|
|
pass
|
|
|
|
def visit_term(self, node):
|
|
self.text += " "
|
|
if self.first_row is 0:
|
|
self.text += node.astext()
|
|
else:
|
|
self.table_stack.append(node.astext())
|
|
|
|
def depart_term(self, node):
|
|
if self.first_row > 0:
|
|
self.text += self.table_stack.pop()
|
|
self.text += """<br>"""
|
|
|
|
def visit_reference(self, node):
|
|
self.hyperlink_name = node.attributes['name']
|
|
self.refuri = node.attributes['refuri']
|
|
self.text += '['
|
|
|
|
def depart_reference(self, node):
|
|
if self.hyperlink_name:
|
|
self.text += ']'
|
|
self.text += '(' + self.refuri + ')'
|
|
else:
|
|
self.text += '[' + self.refuri + ']'
|
|
|
|
self.hyperlink_name = ''
|
|
self.refuri = ''
|
|
|
|
def visit_resource(self, node):
|
|
self.text = ''
|
|
if 'paths' not in self.node_stack[-1]:
|
|
self.node_stack[-1]['paths'] = {}
|
|
self.node_stack.append(self.node_stack[-1]['paths'])
|
|
|
|
def depart_resource(self, node):
|
|
self.node_stack[-1]['description'] = self.text
|
|
# XXX This is a massive hack, this is here because the visit
|
|
# resource url functions don't pop the stack.
|
|
self.node_stack.pop()
|
|
self.node_stack.pop()
|
|
|
|
def visit_resource_url(self, node):
|
|
url_path = node.astext()
|
|
node.clear()
|
|
if url_path not in self.node_stack[-1]:
|
|
self.node_stack[-1][url_path] = []
|
|
new_node = {'responses': {},
|
|
'parameters': [],
|
|
'description': '',
|
|
'produces': [],
|
|
'consumes': [],
|
|
'tags': []}
|
|
self.node_stack[-1][url_path].append(new_node)
|
|
self.node_stack.append(new_node)
|
|
|
|
def depart_resource_url(self, node):
|
|
pass
|
|
|
|
def visit_resource_summary(self, node):
|
|
summary = node.astext()
|
|
self.node_stack[-1]['summary'] = summary
|
|
node.clear()
|
|
|
|
def depart_resource_summary(self, node):
|
|
pass
|
|
|
|
def visit_resource_title(self, node):
|
|
title = node.astext()
|
|
# Should probably be x-title
|
|
self.node_stack[-1]['title'] = title
|
|
node.clear()
|
|
|
|
def depart_resource_title(self, node):
|
|
pass
|
|
|
|
def visit_resource_method(self, node):
|
|
method = node.astext()
|
|
self.node_stack[-1]['method'] = method
|
|
node.clear()
|
|
|
|
def depart_resource_method(self, node):
|
|
pass
|
|
|
|
def visit_field_list(self, node):
|
|
pass
|
|
|
|
def depart_field_list(self, node):
|
|
pass
|
|
|
|
def visit_field(self, node):
|
|
name = node.attributes['names'][0]
|
|
resource = self.node_stack[-1]
|
|
new_response = {'description': ''}
|
|
# TODO(arrsim) this name matching ignores all the other
|
|
# possible names that the fields could have.
|
|
if name == 'statuscode':
|
|
responses = resource['responses']
|
|
status_code = node[0].astext()
|
|
description = node[1].astext()
|
|
if status_code not in responses:
|
|
responses[status_code] = new_response
|
|
if not description and status_code in STATUS_CODE_MAP:
|
|
description = STATUS_CODE_MAP[status_code]
|
|
responses[status_code]['description'] = description
|
|
node.clear()
|
|
elif name == 'responseexample':
|
|
responses = resource['responses']
|
|
status_code = node[0].astext()
|
|
filepath = node[1].astext()
|
|
if status_code not in responses:
|
|
responses[status_code] = new_response
|
|
ext = filepath.rsplit('.', 1)[1]
|
|
mimetype = MIME_MAP[ext]
|
|
if 'examples' not in responses[status_code]:
|
|
responses[status_code]['examples'] = {}
|
|
responses[status_code]['examples'][mimetype] = {'$ref': filepath}
|
|
node.clear()
|
|
elif name == 'requestexample':
|
|
status_code = node[0].astext()
|
|
filepath = node[1].astext()
|
|
ext = filepath.rsplit('.', 1)[1]
|
|
mimetype = MIME_MAP[ext]
|
|
if 'examples' not in resource:
|
|
resource['examples'] = {}
|
|
resource['examples'][mimetype] = {'$ref': filepath}
|
|
node.clear()
|
|
elif name == 'requestschema':
|
|
filepath = node[1].astext()
|
|
resource['parameters'].append(
|
|
{'name': 'body',
|
|
'in': 'body',
|
|
'required': True,
|
|
'schema': {'$ref': filepath}})
|
|
node.clear()
|
|
elif name == 'responseschema':
|
|
responses = resource['responses']
|
|
status_code = node[0].astext()
|
|
filepath = node[1].astext()
|
|
if status_code not in responses:
|
|
responses[status_code] = new_response
|
|
if 'schema' not in responses[status_code]:
|
|
responses[status_code]['schema'] = {}
|
|
responses[status_code]['schema'] = {'$ref': filepath}
|
|
node.clear()
|
|
elif name == 'parameter':
|
|
param_name = node[0].astext()
|
|
description = node[1].astext()
|
|
resource['parameters'].append(
|
|
{'name': param_name,
|
|
'description': description,
|
|
'in': 'path',
|
|
'type': 'string',
|
|
'required': True})
|
|
node.clear()
|
|
elif name == 'query':
|
|
param_name = node[0].astext()
|
|
self.text = ''
|
|
description = ''
|
|
resource['parameters'].append(
|
|
{'name': param_name,
|
|
'description': description,
|
|
'in': 'query',
|
|
'type': 'string',
|
|
'required': False})
|
|
elif name == 'reqheader':
|
|
param_name = node[0].astext()
|
|
description = ''
|
|
self.text = ''
|
|
resource['parameters'].append(
|
|
{'name': param_name,
|
|
'description': description,
|
|
'in': 'header',
|
|
'type': 'string',
|
|
'required': False})
|
|
elif name == 'tag':
|
|
tag = node[1].astext()
|
|
resource['tags'].append(tag)
|
|
node.clear()
|
|
elif name == 'accepts':
|
|
mimetype = node[1].astext()
|
|
resource['consumes'].append(mimetype)
|
|
node.clear()
|
|
elif name == 'produces':
|
|
mimetype = node[1].astext()
|
|
resource['produces'].append(mimetype)
|
|
node.clear()
|
|
else:
|
|
node.clear()
|
|
|
|
def depart_field(self, node):
|
|
name = node.attributes['names'][0]
|
|
resource = self.node_stack[-1]
|
|
if name == 'query' or name == 'reqheader':
|
|
param_name = node[0].astext()
|
|
if self.text.startswith(param_name):
|
|
resource['parameters'][-1]['description'] \
|
|
= self.text[len(param_name):]
|
|
else:
|
|
resource['parameters'][-1]['description'] = self.text
|
|
self.text = ''
|
|
|
|
def visit_field_name(self, node):
|
|
self.node_stack[-1]['name'] = node.astext()
|
|
|
|
def depart_field_name(self, node):
|
|
pass
|
|
|
|
def visit_field_body(self, node):
|
|
self.node_stack[-1]['type'] = node.astext()
|
|
|
|
def depart_field_body(self, node):
|
|
pass
|
|
|
|
def visit_field_type(self, node):
|
|
self.node_stack[-1]['type'] = node.astext()
|
|
|
|
def depart_field_type(self, node):
|
|
pass
|
|
|
|
def visit_swagger_tag(self, node):
|
|
self.text = ''
|
|
self.node_stack.append(self.node_stack[-1]['tags'])
|
|
new_node = {'name': '',
|
|
'description': ''}
|
|
self.node_stack[-1].append(new_node)
|
|
self.node_stack.append(new_node)
|
|
|
|
def depart_swagger_tag(self, node):
|
|
self.node_stack[-1]['description'] = self.text
|
|
self.node_stack.pop()
|
|
self.node_stack.pop()
|
|
|
|
def visit_swagger_tag_name(self, node):
|
|
name = node.astext()
|
|
node.clear()
|
|
self.node_stack[-1]['name'] = name
|
|
|
|
def depart_swagger_tag_name(self, node):
|
|
pass
|
|
|
|
def visit_swagger_tag_summary(self, node):
|
|
summary = node.astext()
|
|
node.clear()
|
|
self.node_stack[-1]['summary'] = summary
|
|
|
|
def depart_swagger_tag_summary(self, node):
|
|
pass
|
|
|
|
|
|
class JSONWriter(writers.Writer):
|
|
|
|
supported = ('json',)
|
|
"""Formats this writer supports."""
|
|
|
|
settings_spec = (
|
|
'"Docutils JSON" Writer Options',
|
|
None,
|
|
[])
|
|
|
|
config_section = 'docutils_json writer'
|
|
config_section_dependencies = ('writers',)
|
|
|
|
output = None
|
|
|
|
def __init__(self):
|
|
writers.Writer.__init__(self)
|
|
self.translator_class = JSONTranslator
|
|
|
|
def translate(self):
|
|
self.visitor = visitor = self.translator_class(self.document)
|
|
self.document.walkabout(visitor)
|
|
self.output = visitor.output
|
|
|
|
|
|
class field_type(nodes.Part, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class resource(nodes.Inline, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class resource_url(nodes.Admonition, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class resource_title(nodes.Admonition, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class resource_summary(nodes.Admonition, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class resource_method(nodes.Admonition, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class Field(object):
|
|
def __init__(self, name, names=(), label=None,
|
|
has_arg=True, rolename=None):
|
|
self.name = name
|
|
self.names = names
|
|
self.label = label
|
|
self.has_arg = has_arg
|
|
self.rolename = rolename
|
|
|
|
@classmethod
|
|
def transform(cls, node):
|
|
node.attributes['names'].append(node[0].astext())
|
|
|
|
|
|
class TypedField(Field):
|
|
def __init__(self, name, names=(), label=None,
|
|
has_arg=True, rolename=None,
|
|
typerolename='', typenames=()):
|
|
super(TypedField, self).__init__(
|
|
name=name,
|
|
names=names,
|
|
label=label,
|
|
has_arg=has_arg,
|
|
rolename=rolename)
|
|
self.typerolename = typerolename
|
|
self.typenames = typenames
|
|
|
|
@classmethod
|
|
def transform(cls, node):
|
|
split = node[0].rawsource.split(None, 2)
|
|
type = None
|
|
if len(split) == 3:
|
|
name, type, value = split
|
|
elif len(split) == 2:
|
|
name, value = split
|
|
else:
|
|
raise Exception('Too Few arguments.')
|
|
node.attributes['names'].append(name)
|
|
if type:
|
|
node.insert(1, field_type(type))
|
|
node[0].replace_self(nodes.field_name(value, value))
|
|
|
|
|
|
class GroupedField(Field):
|
|
|
|
@classmethod
|
|
def transform(cls, node):
|
|
name, value = node[0].rawsource.split(None, 1)
|
|
node.attributes['names'].append(name)
|
|
node[0].replace_self(nodes.field_name(value, value))
|
|
|
|
|
|
class Resource(Directive):
|
|
|
|
method = None
|
|
|
|
required_arguments = 1
|
|
optional_arguments = 0
|
|
has_content = True
|
|
final_argument_whitespace = True
|
|
|
|
doc_field_types = [
|
|
TypedField('parameter', label='Parameters',
|
|
names=('param', 'parameter', 'arg', 'argument'),
|
|
typerolename='obj', typenames=('paramtype', 'type')),
|
|
TypedField('jsonparameter', label='JSON Parameters',
|
|
names=('jsonparameter', 'jsonparam', 'json'),
|
|
typerolename='obj',
|
|
typenames=('jsonparamtype', 'jsontype')),
|
|
TypedField('requestjsonobject', label='Request JSON Object',
|
|
names=('reqjsonobj', 'reqjson', '<jsonobj', '<json'),
|
|
typerolename='obj', typenames=('reqjsonobj', '<jsonobj')),
|
|
TypedField('requestjsonarray', label='Request JSON Array of Objects',
|
|
names=('reqjsonarr', '<jsonarr'),
|
|
typerolename='obj',
|
|
typenames=('reqjsonarrtype', '<jsonarrtype')),
|
|
TypedField('responsejsonobject', label='Response JSON Object',
|
|
names=('resjsonobj', 'resjson', '>jsonobj', '>json'),
|
|
typerolename='obj', typenames=('resjsonobj', '>jsonobj')),
|
|
TypedField('responsejsonarray', label='Response JSON Array of Objects',
|
|
names=('resjsonarr', '>jsonarr'),
|
|
typerolename='obj',
|
|
typenames=('resjsonarrtype', '>jsonarrtype')),
|
|
TypedField('queryparameter', label='Query Parameters',
|
|
names=('queryparameter', 'queryparam', 'qparam', 'query'),
|
|
typerolename='obj',
|
|
typenames=('queryparamtype', 'querytype', 'qtype')),
|
|
GroupedField('formparameter', label='Form Parameters',
|
|
names=('formparameter', 'formparam', 'fparam', 'form')),
|
|
GroupedField('requestheader', label='Request Headers',
|
|
rolename='header',
|
|
names=('<header', 'reqheader', 'requestheader')),
|
|
GroupedField('responseheader', label='Response Headers',
|
|
rolename='header',
|
|
names=('>header', 'resheader', 'responseheader')),
|
|
GroupedField('statuscode', label='Status Codes',
|
|
rolename='statuscode',
|
|
names=('statuscode', 'status', 'code')),
|
|
GroupedField('responseschema', label='Response Schema',
|
|
rolename='responseschema',
|
|
names=('reponse-schema', 'responseschema')),
|
|
|
|
# Swagger Extensions
|
|
GroupedField('responseexample', label='Response Example',
|
|
rolename='responseexample',
|
|
names=('swagger-response', 'responseexample')),
|
|
Field('requestexample', label='Request Example',
|
|
rolename='requestexample',
|
|
names=('swagger-request', 'requestexample')),
|
|
Field('requestschema', label='Request Schema',
|
|
rolename='requestschema',
|
|
names=('swagger-schema', 'requestschema')),
|
|
Field('tag',
|
|
label='Swagger Tag',
|
|
rolename='tag',
|
|
names=('swagger-tag', 'tag')),
|
|
Field('accepts',
|
|
label='Swagger Consumes',
|
|
rolename='accepts',
|
|
names=('swagger-accepts', 'accepts')),
|
|
Field('produces',
|
|
label='Swagger Consumes',
|
|
rolename='produces',
|
|
names=('swagger-produces', 'produces'))
|
|
]
|
|
|
|
option_spec = {
|
|
'title': lambda x: x,
|
|
'synopsis': lambda x: x,
|
|
}
|
|
|
|
def transform_fields(self):
|
|
return {name: f
|
|
for f in self.doc_field_types
|
|
for name in f.names}
|
|
|
|
def run(self):
|
|
node = resource()
|
|
self.state.nested_parse(self.content, self.content_offset, node)
|
|
fields = self.transform_fields()
|
|
|
|
# This is the first line of the definition.
|
|
url = self.arguments[0]
|
|
node.insert(0, resource_url(url, url))
|
|
|
|
if not node.children:
|
|
return [node]
|
|
|
|
if node[0].tagname == 'system_message':
|
|
logger.error(node[0].astext())
|
|
node.remove(node[0])
|
|
|
|
# Method
|
|
node.insert(1, resource_method(self.method, self.method))
|
|
|
|
# Summary
|
|
summary = self.options.get('synopsis', '')
|
|
node.insert(1, resource_summary(summary, summary))
|
|
title = self.options.get('title', '')
|
|
node.insert(1, resource_title(title, title))
|
|
|
|
# Generate field lists
|
|
for child in node:
|
|
if isinstance(child, nodes.field_list):
|
|
for field in child:
|
|
name = field[0].rawsource.split(None, 1)[0]
|
|
fields[name].transform(field)
|
|
return [node]
|
|
|
|
|
|
class HTTPGet(Resource):
|
|
|
|
method = 'get'
|
|
|
|
|
|
class HTTPPost(Resource):
|
|
|
|
method = 'post'
|
|
|
|
|
|
class HTTPPut(Resource):
|
|
|
|
method = 'put'
|
|
|
|
|
|
class HTTPPatch(Resource):
|
|
|
|
method = 'patch'
|
|
|
|
|
|
class HTTPOptions(Resource):
|
|
|
|
method = 'options'
|
|
|
|
|
|
class HTTPHead(Resource):
|
|
|
|
method = 'head'
|
|
|
|
|
|
class HTTPDelete(Resource):
|
|
|
|
method = 'delete'
|
|
|
|
|
|
class HTTPCopy(Resource):
|
|
|
|
method = 'copy'
|
|
|
|
|
|
directives.register_directive('http:get', HTTPGet)
|
|
directives.register_directive('http:post', HTTPPost)
|
|
directives.register_directive('http:put', HTTPPut)
|
|
directives.register_directive('http:patch', HTTPPatch)
|
|
directives.register_directive('http:options', HTTPOptions)
|
|
directives.register_directive('http:head', HTTPHead)
|
|
directives.register_directive('http:delete', HTTPDelete)
|
|
directives.register_directive('http:copy', HTTPCopy)
|
|
|
|
|
|
class swagger_tag(nodes.Inline, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class swagger_tag_name(nodes.Inline, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class swagger_tag_summary(nodes.Inline, nodes.TextElement):
|
|
pass
|
|
|
|
|
|
class SwaggerTag(Directive):
|
|
|
|
method = None
|
|
|
|
required_arguments = 0
|
|
optional_arguments = 0
|
|
has_content = True
|
|
final_argument_whitespace = True
|
|
|
|
option_spec = {
|
|
'synopsis': lambda x: x,
|
|
}
|
|
|
|
def run(self):
|
|
node = swagger_tag()
|
|
self.state.nested_parse(self.content, self.content_offset, node)
|
|
|
|
# This is the first line of the definition.
|
|
name = node[0].astext()
|
|
node[0].replace_self(swagger_tag_name(name, name))
|
|
|
|
# Summary
|
|
summary = self.options.get('synopsis', '')
|
|
node.insert(1, swagger_tag_summary(summary, summary))
|
|
|
|
return [node]
|
|
|
|
|
|
directives.register_directive('swagger:tag', SwaggerTag)
|
|
|
|
|
|
class error_writer(object):
|
|
|
|
def write(self, line):
|
|
logger.warning(line.strip())
|
|
|
|
|
|
def publish_string(string):
|
|
settings_overrides = {'warning_stream': error_writer()}
|
|
return docutils.core.publish_string(
|
|
string, writer=JSONWriter(),
|
|
settings_overrides=settings_overrides)
|