be5c4b7d63
Adds a pretty straightforward Sphinx plugin that reads the JSON profile file and renders it nicely in a document that is then included from the Redfish page. Change-Id: Ic2da61cb510897eac8a2e162816cfd05cc22994c
188 lines
5.8 KiB
Python
188 lines
5.8 KiB
Python
# 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.
|
|
|
|
import json
|
|
import os
|
|
|
|
from sphinx.application import Sphinx
|
|
|
|
__version__ = "1.0.0"
|
|
|
|
|
|
# Data model #
|
|
|
|
|
|
class Entity:
|
|
"""Represents an entity in the profile."""
|
|
|
|
def __init__(self, name, src):
|
|
self.name = name
|
|
self.src = src
|
|
self.purpose = src.get('Purpose', '')
|
|
self.writable = src.get('WriteRequirement') == 'Mandatory'
|
|
self.required = (src.get('ReadRequirement') in ('Mandatory', None)
|
|
or self.writable)
|
|
|
|
|
|
class ActionParameter(Entity):
|
|
"""Represents a parameter in an Action."""
|
|
|
|
def __init__(self, name, src):
|
|
super().__init__(name, src)
|
|
self.required_values = src.get('ParameterValues') or []
|
|
self.recommended_values = src.get('RecommendedValues') or []
|
|
|
|
|
|
class Action(Entity):
|
|
"""Represents an action on a resource."""
|
|
|
|
def __init__(self, name, src):
|
|
super().__init__(name, src)
|
|
self.parameters = {
|
|
name: ActionParameter(name, value)
|
|
for name, value in src.get('Parameters', {}).items()
|
|
}
|
|
|
|
|
|
class Resource(Entity):
|
|
"""Represents any resource in the profile.
|
|
|
|
Both top-level resources and nested fields are represented by this class
|
|
(but actions are not).
|
|
"""
|
|
|
|
def __init__(self, name, src):
|
|
super().__init__(name, src)
|
|
self.min_support_values = src.get('MinSupportValues')
|
|
self.properties = {
|
|
name: Resource(name, value)
|
|
for name, value in src.get('PropertyRequirements', {}).items()
|
|
}
|
|
self.actions = {
|
|
name: Action(name, value)
|
|
for name, value in src.get('ActionRequirements', {}).items()
|
|
}
|
|
self.link_to = (src['Values'][0]
|
|
if src.get('Comparison') == 'LinkToResource'
|
|
else None)
|
|
|
|
|
|
# Rendering #
|
|
|
|
LEVELS = {0: '=', 1: '-', 2: '~', 3: '^'}
|
|
INDENT = ' ' * 4
|
|
|
|
|
|
class NestedWriter:
|
|
"""A writer that is nested with indentations."""
|
|
|
|
def __init__(self, dest, level=0):
|
|
self.dest = dest
|
|
self.level = level
|
|
|
|
def text(self, text):
|
|
print(INDENT * self.level + text, file=self.dest)
|
|
|
|
def para(self, text):
|
|
self.text(text)
|
|
print(file=self.dest)
|
|
|
|
def _nested_common(self, res):
|
|
required = " **[required]**" if res.required else ""
|
|
writable = " **[writable]**" if res.writable else ""
|
|
self.text(f"``{res.name}``{required}{writable}")
|
|
nested = NestedWriter(self.dest, self.level + 1)
|
|
if res.purpose:
|
|
nested.para(res.purpose)
|
|
return nested
|
|
|
|
def action(self, res):
|
|
nested = self._nested_common(res)
|
|
for prop in res.parameters.values():
|
|
nested.action_parameter(prop)
|
|
print(file=self.dest)
|
|
|
|
def action_parameter(self, res):
|
|
self._nested_common(res)
|
|
print(file=self.dest)
|
|
|
|
def resource(self, res):
|
|
nested = self._nested_common(res)
|
|
for prop in res.properties.values():
|
|
nested.resource(prop)
|
|
if res.link_to:
|
|
# NOTE(dtantsur): this is a bit hacky, but we don't have
|
|
# definitions for all possible collections.
|
|
split = res.link_to.split('Collection')
|
|
if len(split) > 1:
|
|
nested.text("Link to a collection of "
|
|
f":ref:`Redfish-{split[0]}` resources.")
|
|
else:
|
|
nested.text(f"Link to a :ref:`Redfish-{res.link_to}` "
|
|
"resource.")
|
|
|
|
print(file=self.dest)
|
|
|
|
|
|
class Writer(NestedWriter):
|
|
|
|
def __init__(self, dest):
|
|
super().__init__(dest)
|
|
|
|
def title(self, text, level=1):
|
|
print(text, file=self.dest)
|
|
print(LEVELS[level] * len(text), file=self.dest)
|
|
|
|
def top_level(self, res):
|
|
required = " **[required]**" if res.required else ""
|
|
self.para(f".. _Redfish-{res.name}:")
|
|
self.title(f"{res.name}")
|
|
self.para(f"{res.purpose}{required}")
|
|
if res.properties:
|
|
self.title("Properties", level=2)
|
|
for name, prop in res.properties.items():
|
|
self.resource(prop)
|
|
if res.actions:
|
|
self.title("Actions", level=2)
|
|
for name, act in res.actions.items():
|
|
self.action(act)
|
|
|
|
|
|
def builder_inited(app: Sphinx):
|
|
source = os.path.join(app.srcdir, app.config.redfish_interop_source)
|
|
with open(source) as fp:
|
|
profile = json.load(fp)
|
|
fname = os.path.basename(source).replace('json', 'rst')
|
|
dstdir = os.path.join(app.srcdir, app.config.redfish_interop_output_dir)
|
|
with open(os.path.join(dstdir, fname), 'wt') as dest:
|
|
w = Writer(dest)
|
|
w.title(f"{profile['ProfileName']} {profile['ProfileVersion']}", 0)
|
|
w.para(profile['Purpose'])
|
|
|
|
try:
|
|
for name, value in sorted(
|
|
(name, value)
|
|
for name, value in profile['Resources'].items()
|
|
):
|
|
w.top_level(Resource(name, value))
|
|
except Exception:
|
|
import traceback
|
|
traceback.print_exc()
|
|
raise
|
|
|
|
|
|
def setup(app: Sphinx):
|
|
app.connect('builder-inited', builder_inited)
|
|
app.add_config_value('redfish_interop_source', None, 'env', [str])
|
|
app.add_config_value('redfish_interop_output_dir', None, 'env', [str])
|
|
return {'version': __version__}
|