Sphinx extension to generate resource documentation.

Implements a resourcepages directive which generates resource type
documentation for any type that matches the specified type prefix.

For example:
.. resourcepages:: OS::

Implements blueprint generate-resource-docs

Change-Id: I3b2c94a766686e17e2bdbe8be27657decd65bb3e
This commit is contained in:
Steve Baker 2013-07-26 08:52:13 +12:00
parent e6e0dc5b61
commit ad945070a2
3 changed files with 269 additions and 1 deletions

View File

@ -29,7 +29,8 @@ sys.path.insert(0, os.path.abspath('../../'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = ['sphinx.ext.autodoc',
'sphinx.ext.ifconfig',
'sphinx.ext.viewcode']
'sphinx.ext.viewcode',
'heat.doc.resources']
# Add any paths that contain templates here, relative to this directory.
#templates_path = ['_templates']

0
heat/doc/__init__.py Normal file
View File

267
heat/doc/resources.py Normal file
View File

@ -0,0 +1,267 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# 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.
# -*- coding: utf-8 -*-
from heat.engine import resources
from heat.engine import resource
from heat.openstack.common.gettextutils import _
from docutils import nodes
from sphinx.util.compat import Directive
class resourcepages(nodes.General, nodes.Element): pass
class ResourcePages(Directive):
has_content = False
required_arguments = 0
optional_arguments = 1
final_argument_whitespace = False
option_spec = {}
def run(self):
prefix = self.arguments and self.arguments.pop() or None
content = []
for resource_type, resource_class in _all_resources(prefix):
self.resource_type = resource_type
self.resource_class = resource_class
resourceid = 'resource-%s' % resource_type
section = nodes.section(ids=[resourceid])
content.append(section)
title = nodes.title('', resource_type)
section.append(title)
cls_doc = resource_class.__doc__
if cls_doc:
para = nodes.paragraph('', cls_doc)
section.append(para)
self.contribute_hot_syntax(section)
self.contribute_yaml_syntax(section)
self.contribute_json_syntax(section)
self.contribute_properties(section)
self.contribute_attributes(section)
return content
def _prop_syntax_example(self, prop):
if not prop or not prop.get('Type'):
return 'Value'
prop_type=prop.get('Type')
if prop_type == 'List':
sub_prop = prop.get('Schema')
sub_type = self._prop_syntax_example(sub_prop)
return '[%s, %s, ...]' % (sub_type, sub_type)
elif prop_type == 'Map':
sub_prop = prop.get('Schema', {})
sub_props = []
for sub_key, sub_value in sub_prop.items():
if sub_value.get('Implemented', True):
sub_props.append('"%s": %s' % (
sub_key, self._prop_syntax_example(sub_value)))
return '{%s}' % ', '.join(sub_props or ['...'])
else:
return prop_type
def contribute_hot_syntax(self, section):
section.append(nodes.strong('', _('HOT Syntax')))
schema = self.resource_class.properties_schema
props = []
for prop_key in sorted(schema.keys()):
prop = schema[prop_key]
if prop.get('Implemented', True):
props.append('%s: %s' % (prop_key, self._prop_syntax_example(prop)))
template = '''heat_template_version: 2013-05-23
...
resources:
...
the_resource:
type: %s
properties:
%s''' % (self.resource_type, '\n '.join(props))
block = nodes.literal_block('', template)
section.append(block)
def contribute_yaml_syntax(self, section):
section.append(nodes.strong('', _('YAML Syntax')))
schema = self.resource_class.properties_schema
props = []
for prop_key in sorted(schema.keys()):
prop = schema[prop_key]
if prop.get('Implemented', True):
props.append('%s: %s' % (prop_key, self._prop_syntax_example(prop)))
template = '''HeatTemplateFormatVersion: '2012-12-12'
...
Resources:
...
TheResource:
Type: %s
Properties:
%s''' % (self.resource_type, '\n '.join(props))
block = nodes.literal_block('', template)
section.append(block)
def contribute_json_syntax(self, section):
section.append(nodes.strong('', _('JSON Syntax')))
schema = self.resource_class.properties_schema
props = []
for prop_key in sorted(schema.keys()):
prop = schema[prop_key]
if prop.get('Implemented', True):
props.append('"%s": %s' % (prop_key, self._prop_syntax_example(prop)))
template = '''{
"AWSTemplateFormatVersion" : "2010-09-09",
...
"Resources" : {
"TheResource": {
"Type": "%s",
"Properties": {
%s
}
}
}
}''' % (self.resource_type, ',\n '.join(props))
block = nodes.literal_block('', template)
section.append(block)
def contribute_property(self, prop_list, prop_key, prop):
prop_item = nodes.definition_list_item(
'',nodes.term('', prop_key))
prop_list.append(prop_item)
prop_type = prop.get('Type')
classifier = prop_type
if prop.get('MinValue'):
classifier += _(' from %s') % prop.get('MinValue')
if prop.get('MaxValue'):
classifier += _(' up to %s') % prop.get('MaxValue')
if prop.get('MinLength'):
classifier += _(' from length %s') % prop.get('MinLength')
if prop.get('MaxLength'):
classifier += _(' up to length %s') % prop.get('MaxLength')
prop_item.append(nodes.classifier('', classifier))
definition = nodes.definition()
prop_item.append(definition)
if not prop.get('Implemented', True):
para = nodes.inline('', _('Not implemented.'))
warning = nodes.note('', para)
definition.append(warning)
return
description = prop.get('Description')
if description:
para = nodes.paragraph('', description)
definition.append(para)
if prop.get('Required'):
para = nodes.paragraph('', _('Required property.'))
elif prop.get('Default'):
para = nodes.paragraph(
'',
_('Optional property, defaults to "%s".') % prop.get('Default'))
else:
para = nodes.paragraph('', _('Optional property.'))
definition.append(para)
if prop.get('AllowedPattern'):
para = nodes.paragraph('', _(
'Value must match pattern: %s') % prop.get('AllowedPattern'))
definition.append(para)
if prop.get('AllowedValues'):
allowed = [str(a) for a in prop.get('AllowedValues') if a is not None]
para = nodes.paragraph('', _(
'Allowed values: %s') % ', '.join(allowed))
definition.append(para)
sub_schema = None
if prop.get('Schema') and prop_type == 'Map':
para = nodes.emphasis('', _('Map properties:'))
definition.append(para)
sub_schema = prop.get('Schema')
elif prop_type == 'List' and prop.get('Schema', {}).get('Schema'):
para = nodes.emphasis(
'', _('List contains maps with the properties:'))
definition.append(para)
sub_schema = prop.get('Schema').get('Schema')
if sub_schema:
sub_prop_list = nodes.definition_list()
definition.append(sub_prop_list)
for sub_prop_key in sorted(sub_schema.keys()):
sub_prop = sub_schema[sub_prop_key]
self.contribute_property(sub_prop_list, sub_prop_key, sub_prop)
def contribute_properties(self, section):
schema = self.resource_class.properties_schema
if not schema:
return
section.append(nodes.strong('', _('Properties')))
prop_list = nodes.definition_list()
section.append(prop_list)
for prop_key in sorted(schema.keys()):
prop = schema[prop_key]
self.contribute_property(prop_list, prop_key, prop)
def contribute_attributes(self, section):
schema = self.resource_class.attributes_schema
if not schema:
return
section.append(nodes.strong('', _('Attributes')))
prop_list = nodes.definition_list()
section.append(prop_list)
for prop_key in sorted(schema.keys()):
description = schema[prop_key]
prop_item = nodes.definition_list_item(
'',nodes.term('', prop_key))
prop_list.append(prop_item)
definition = nodes.definition()
prop_item.append(definition)
if description:
def_para = nodes.paragraph('', description)
definition.append(def_para)
def _all_resources(prefix=None):
all_resources = resource._resource_classes
for resource_type in sorted(all_resources.keys()):
resource_class = all_resources[resource_type]
if not prefix or resource_type.startswith(prefix):
yield resource_type, resource_class
def setup(app):
resources.initialise()
app.add_node(resourcepages)
app.add_directive('resourcepages', ResourcePages)