Merge "migrate to os-api-ref"

This commit is contained in:
Jenkins 2016-06-09 13:38:13 +00:00 committed by Gerrit Code Review
commit 6c9c576525
19 changed files with 78 additions and 782 deletions

View File

@ -1,352 +0,0 @@
# 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.
"""This provides a sphinx extension able to create the HTML needed
for the api-ref website.
It contains 2 new stanzas.
.. rest_method:: GET /foo/bar
Which is designed to be used as the first stanza in a new section to
state that section is about that REST method. During processing the
rest stanza will be reparented to be before the section in question,
and used as a show/hide selector for it's details.
.. rest_parameters:: file.yaml
- name1: name_in_file1
- name2: name_in_file2
- name3: name_in_file3
Which is designed to build structured tables for either response or
request parameters. The stanza takes a value which is a file to lookup
details about the parameters in question.
The contents of the stanza are a yaml list of key / value pairs. The
key is the name of the parameter to be shown in the table. The value
is the key in the file.yaml where all other metadata about the
parameter will be extracted. This allows for reusing parameter
definitions widely in API definitions, but still providing for control
in both naming and ordering of parameters at every declaration.
"""
from docutils import nodes
from docutils.parsers.rst.directives.tables import Table
from docutils.statemachine import ViewList
from sphinx.util.compat import Directive
import six
import yaml
def full_name(cls):
return cls.__module__ + '.' + cls.__name__
class rest_method(nodes.Part, nodes.Element):
"""rest_method custom node type
We specify a custom node type for rest_method so that we can
accumulate all the data about the rest method, but not render as
part of the normal rendering process. This means that we need a
renderer for every format we wish to support with this.
"""
pass
class rest_expand_all(nodes.Part, nodes.Element):
pass
class RestExpandAllDirective(Directive):
has_content = True
def run(self):
return [rest_expand_all()]
class RestMethodDirective(Directive):
# this enables content in the directive
has_content = True
def run(self):
lineno = self.state_machine.abs_line_number()
target = nodes.target()
section = nodes.section(classes=["detail-control"])
node = rest_method()
method, sep, url = self.content[0].partition(' ')
node['method'] = method
node['url'] = url
node['target'] = self.state.parent.attributes['ids'][0]
temp_target = "%s-selector" % node['target']
target = nodes.target(ids=[temp_target])
self.state.add_target(temp_target, '', target, lineno)
section += node
return [target, section]
class RestParametersDirective(Table):
headers = ["Name", "In", "Type", "Description"]
def yaml_from_file(self, fpath):
"""Collect Parameter stanzas from inline + file.
This allows use to reference an external file for the actual
parameter definitions.
"""
try:
with open(fpath, 'r') as stream:
lookup = yaml.load(stream)
except IOError:
self.env.warn(
self.env.docname,
"Parameters file %s not found" % fpath)
return
except yaml.YAMLError as exc:
self.app.warn(exc)
raise
content = "\n".join(self.content)
parsed = yaml.load(content)
new_content = list()
for paramlist in parsed:
for name, ref in paramlist.items():
if ref in lookup:
new_content.append((name, lookup[ref]))
else:
self.env.warn(
"%s:%s " % (
self.state_machine.node.source,
self.state_machine.node.line),
("No field definition for ``%s`` found in ``%s``. "
" Skipping." % (ref, fpath)))
self.yaml = new_content
def run(self):
self.env = self.state.document.settings.env
self.app = self.env.app
# Make sure we have some content, which should be yaml that
# defines some parameters.
if not self.content:
error = self.state_machine.reporter.error(
'No parameters defined',
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
return [error]
if not len(self.arguments) >= 1:
self.state_machine.reporter.error(
'No reference file defined',
nodes.literal_block(self.block_text, self.block_text),
line=self.lineno)
return [error]
rel_fpath, fpath = self.env.relfn2path(self.arguments.pop())
self.yaml_file = fpath
self.yaml_from_file(self.yaml_file)
self.max_cols = len(self.headers)
self.options['widths'] = (20, 10, 10, 60)
self.col_widths = self.get_column_widths(self.max_cols)
# Actually convert the yaml
title, messages = self.make_title()
table_node = self.build_table()
self.add_name(table_node)
if title:
table_node.insert(0, title)
return [table_node] + messages
def get_rows(self, table_data):
rows = []
groups = []
trow = nodes.row()
entry = nodes.entry()
para = nodes.paragraph(text=six.text_type(table_data))
entry += para
trow += entry
rows.append(trow)
return rows, groups
# Add a column for a field. In order to have the RST inside
# these fields get rendered, we need to use the
# ViewList. Note, ViewList expects a list of lines, so chunk
# up our content as a list to make it happy.
def add_col(self, value):
entry = nodes.entry()
result = ViewList(value.split('\n'))
self.state.nested_parse(result, 0, entry)
return entry
def show_no_yaml_error(self):
trow = nodes.row(classes=["no_yaml"])
trow += self.add_col("No yaml found %s" % self.yaml_file)
trow += self.add_col("")
trow += self.add_col("")
trow += self.add_col("")
return trow
def collect_rows(self):
rows = []
groups = []
try:
for key, values in self.yaml:
min_version = values.get('min_version', '')
desc = values.get('description', '')
classes = []
if min_version:
desc += ("\n\n**New in version %s**\n" % min_version)
min_ver_css_name = ("rp_min_ver_" +
str(min_version).replace('.', '_'))
classes.append(min_ver_css_name)
trow = nodes.row(classes=classes)
name = key
if values.get('required', False) is False:
name += " (Optional)"
trow += self.add_col(name)
trow += self.add_col(values.get('in'))
trow += self.add_col(values.get('type'))
trow += self.add_col(desc)
rows.append(trow)
except AttributeError as exc:
if 'key' in locals():
self.app.warn("Failure on key: %s, values: %s. %s" %
(key, values, exc))
else:
rows.append(self.show_no_yaml_error())
return rows, groups
def build_table(self):
table = nodes.table()
tgroup = nodes.tgroup(cols=len(self.headers))
table += tgroup
tgroup.extend(
nodes.colspec(colwidth=col_width, colname='c' + str(idx))
for idx, col_width in enumerate(self.col_widths)
)
thead = nodes.thead()
tgroup += thead
row_node = nodes.row()
thead += row_node
row_node.extend(nodes.entry(h, nodes.paragraph(text=h))
for h in self.headers)
tbody = nodes.tbody()
tgroup += tbody
rows, groups = self.collect_rows()
tbody.extend(rows)
table.extend(groups)
return table
def rest_method_html(self, node):
tmpl = """
<div class="row operation-grp">
<div class="col-md-1 operation">
<a name="%(target)s" class="operation-anchor" href="#%(target)s">
<span class="glyphicon glyphicon-link"></span></a>
<span class="label label-success">%(method)s</span>
</div>
<div class="col-md-5">%(url)s</div>
<div class="col-md-5">%(desc)s</div>
<div class="col-md-1">
<button
class="btn btn-info btn-sm btn-detail"
data-target="#%(target)s-detail"
data-toggle="collapse"
id="%(target)s-detail-btn"
>detail</button>
</div>
</div>"""
self.body.append(tmpl % node)
raise nodes.SkipNode
def rest_expand_all_html(self, node):
tmpl = """
<div>
<div class=col-md-11></div>
<div class=col-md-1>
<button id="expand-all"
data-toggle="collapse"
class="btn btn-info btn-sm btn-expand-all"
>Show All</button>
</div>
</div>"""
self.body.append(tmpl % node)
raise nodes.SkipNode
def resolve_rest_references(app, doctree):
for node in doctree.traverse():
if isinstance(node, rest_method):
rest_node = node
rest_method_section = node.parent
rest_section = rest_method_section.parent
gp = rest_section.parent
# Added required classes to the top section
rest_section.attributes['classes'].append('api-detail')
rest_section.attributes['classes'].append('collapse')
# Pop the title off the collapsed section
title = rest_section.children.pop(0)
rest_node['desc'] = title.children[0]
# In order to get the links in the sidebar to be right, we
# have to do some id flipping here late in the game. The
# rest_method_section has basically had a dummy id up
# until this point just to keep it from colliding with
# it's parent.
rest_section.attributes['ids'][0] = (
"%s-detail" % rest_section.attributes['ids'][0])
rest_method_section.attributes['ids'][0] = rest_node['target']
# Pop the overall section into it's grand parent,
# right before where the current parent lives
idx = gp.children.index(rest_section)
rest_section.remove(rest_method_section)
gp.insert(idx, rest_method_section)
def setup(app):
app.add_node(rest_method,
html=(rest_method_html, None))
app.add_node(rest_expand_all,
html=(rest_expand_all_html, None))
app.add_directive('rest_parameters', RestParametersDirective)
app.add_directive('rest_method', RestMethodDirective)
app.add_directive('rest_expand_all', RestExpandAllDirective)
app.add_stylesheet('bootstrap.min.css')
app.add_stylesheet('api-site.css')
app.add_javascript('bootstrap.min.js')
app.add_javascript('api-site.js')
app.connect('doctree-read', resolve_rest_references)
return {'version': '0.1'}

View File

@ -1,81 +0,0 @@
tt.literal {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
white-space: nowrap;
background-color: #f9f2f4;
border-radius: 4px;
}
/* bootstrap users blockquote for pull quotes, so they are much
larger, we need them smaller */
blockquote { font-size: 1em; }
pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.428571429;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
}
tbody>tr:nth-child(odd)>td,
tbody>tr:nth-child(odd)>th {
background-color: #f9f9f9;
}
table>thead>tr>th, table>tbody>tr>th, table>tfoot>tr>th, table>thead>tr>td, table>tbody>tr>td, table>tfoot>tr>td {
padding: 8px;
line-height: 1.428571429;
vertical-align: top;
border-top: 1px solid #ddd;
}
td>p {
margin: 0 0 0.5em;
}
div.document {
width: 80% !important;
}
@media (max-width: 1200px) {
div.document {
width: 960px !important;
}
}
.operation-grp {
padding-top: 0.5em;
padding-bottom: 1em;
}
/* Ensure the method buttons and their links don't split lines when
the page is narrower */
.operation {
/* this moves the link icon into the gutter */
margin-left: -1.25em;
margin-right: 1.25em;
white-space: nowrap;
}
/* These make the links only show up on hover */
a.operation-anchor {
visibility: hidden;
}
.operation-grp:hover a.operation-anchor {
visibility: visible;
}
/* All tables for requests should be full width */
.api-detail table.docutils {
width: 100%;
}

View File

@ -1,110 +0,0 @@
(function() {
var pageCache;
$(document).ready(function() {
pageCache = $('.api-documentation').html();
// Show the proper JSON/XML example when toggled
$('.example-select').on('change', function(e) {
$(e.currentTarget).find(':selected').tab('show')
});
// Change the text on the expando buttons when appropriate
$('.api-detail')
.on('hide.bs.collapse', function(e) {
processButton(this, 'detail');
})
.on('show.bs.collapse', function(e) {
processButton(this, 'close');
});
var expandAllActive = true;
// Expand the world
$('#expand-all').click(function () {
if (expandAllActive) {
expandAllActive = false;
$('.api-detail').collapse('show');
$('#expand-all').attr('data-toggle', '');
$(this).text('Hide All');
} else {
expandAllActive = true;
$('.api-detail').collapse('hide');
$('#expand-all').attr('data-toggle', 'collapse');
$(this).text('Show All');
}});
// Wire up the search button
$('#search-btn').on('click', function(e) {
searchPage();
});
// Wire up the search box enter
$('#search-box').on('keydown', function(e) {
if (e.keyCode === 13) {
searchPage();
return false;
}
});
});
/**
* highlight terms based on the regex in the provided $element
*/
function highlightTextNodes($element, regex) {
var markup = $element.html();
// Do regex replace
// Inject span with class of 'highlighted termX' for google style highlighting
$element.html(markup.replace(regex, '>$1<span class="highlight">$2</span>$3<'));
}
function searchPage() {
$(".api-documentation").html(pageCache);
//make sure that all div's are expanded/hidden accordingly
$('.api-detail.in').each(function (e) {
$(this).collapse('hide');
});
var startTime = new Date().getTime(),
searchTerm = $('#search-box').val();
// The regex is the secret, it prevents text within tag declarations to be affected
var regex = new RegExp(">([^<]*)?(" + searchTerm + ")([^>]*)?<", "ig");
highlightTextNodes($('.api-documentation'), regex);
// Once we've highlighted the node, lets expand any with a search match in them
$('.api-detail').each(function () {
var $elem = $(this);
if ($elem.html().indexOf('<span class="highlight">') !== -1) {
$elem.collapse('show');
processButton($elem, 'close');
}
});
// log the results
if (console.log) {
console.log("search completed in: " + ((new Date().getTime()) - startTime) + "ms");
}
$('.api-detail')
.on('hide.bs.collapse', function (e) {
processButton(this, 'detail');
})
.on('show.bs.collapse', function (e) {
processButton(this, 'close');
});
}
/**
* Helper function for setting the text, styles for expandos
*/
function processButton(button, text) {
$('#' + $(button).attr('id') + '-btn').text(text)
.toggleClass('btn-info')
.toggleClass('btn-default');
}
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -41,7 +41,7 @@ sys.path.insert(0, os.path.abspath('./'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'ext.rest_parameters',
'os_api_ref',
'oslosphinx',
]
@ -138,7 +138,7 @@ pygments_style = 'sphinx'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.

View File

@ -1,81 +0,0 @@
tt.literal {
padding: 2px 4px;
font-size: 90%;
color: #c7254e;
white-space: nowrap;
background-color: #f9f2f4;
border-radius: 4px;
}
/* bootstrap users blockquote for pull quotes, so they are much
larger, we need them smaller */
blockquote { font-size: 1em; }
pre {
display: block;
padding: 9.5px;
margin: 0 0 10px;
font-size: 13px;
line-height: 1.428571429;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
}
tbody>tr:nth-child(odd)>td,
tbody>tr:nth-child(odd)>th {
background-color: #f9f9f9;
}
table>thead>tr>th, table>tbody>tr>th, table>tfoot>tr>th, table>thead>tr>td, table>tbody>tr>td, table>tfoot>tr>td {
padding: 8px;
line-height: 1.428571429;
vertical-align: top;
border-top: 1px solid #ddd;
}
td>p {
margin: 0 0 0.5em;
}
div.document {
width: 80% !important;
}
@media (max-width: 1200px) {
div.document {
width: 960px !important;
}
}
.operation-grp {
padding-top: 0.5em;
padding-bottom: 1em;
}
/* Ensure the method buttons and their links don't split lines when
the page is narrower */
.operation {
/* this moves the link icon into the gutter */
margin-left: -1.25em;
margin-right: 1.25em;
white-space: nowrap;
}
/* These make the links only show up on hover */
a.operation-anchor {
visibility: hidden;
}
.operation-grp:hover a.operation-anchor {
visibility: visible;
}
/* All tables for requests should be full width */
.api-detail table.docutils {
width: 100%;
}

View File

@ -1,110 +0,0 @@
(function() {
var pageCache;
$(document).ready(function() {
pageCache = $('.api-documentation').html();
// Show the proper JSON/XML example when toggled
$('.example-select').on('change', function(e) {
$(e.currentTarget).find(':selected').tab('show')
});
// Change the text on the expando buttons when appropriate
$('.api-detail')
.on('hide.bs.collapse', function(e) {
processButton(this, 'detail');
})
.on('show.bs.collapse', function(e) {
processButton(this, 'close');
});
var expandAllActive = true;
// Expand the world
$('#expand-all').click(function () {
if (expandAllActive) {
expandAllActive = false;
$('.api-detail').collapse('show');
$('#expand-all').attr('data-toggle', '');
$(this).text('Hide All');
} else {
expandAllActive = true;
$('.api-detail').collapse('hide');
$('#expand-all').attr('data-toggle', 'collapse');
$(this).text('Show All');
}});
// Wire up the search button
$('#search-btn').on('click', function(e) {
searchPage();
});
// Wire up the search box enter
$('#search-box').on('keydown', function(e) {
if (e.keyCode === 13) {
searchPage();
return false;
}
});
});
/**
* highlight terms based on the regex in the provided $element
*/
function highlightTextNodes($element, regex) {
var markup = $element.html();
// Do regex replace
// Inject span with class of 'highlighted termX' for google style highlighting
$element.html(markup.replace(regex, '>$1<span class="highlight">$2</span>$3<'));
}
function searchPage() {
$(".api-documentation").html(pageCache);
//make sure that all div's are expanded/hidden accordingly
$('.api-detail.in').each(function (e) {
$(this).collapse('hide');
});
var startTime = new Date().getTime(),
searchTerm = $('#search-box').val();
// The regex is the secret, it prevents text within tag declarations to be affected
var regex = new RegExp(">([^<]*)?(" + searchTerm + ")([^>]*)?<", "ig");
highlightTextNodes($('.api-documentation'), regex);
// Once we've highlighted the node, lets expand any with a search match in them
$('.api-detail').each(function () {
var $elem = $(this);
if ($elem.html().indexOf('<span class="highlight">') !== -1) {
$elem.collapse('show');
processButton($elem, 'close');
}
});
// log the results
if (console.log) {
console.log("search completed in: " + ((new Date().getTime()) - startTime) + "ms");
}
$('.api-detail')
.on('hide.bs.collapse', function (e) {
processButton(this, 'detail');
})
.on('show.bs.collapse', function (e) {
processButton(this, 'close');
});
}
/**
* Helper function for setting the text, styles for expandos
*/
function processButton(button, text) {
$('#' + $(button).attr('id') + '-btn').text(text)
.toggleClass('btn-info')
.toggleClass('btn-default');
}
})();

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -41,7 +41,7 @@ sys.path.insert(0, os.path.abspath('./'))
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'ext.rest_parameters',
'os_api_ref',
'oslosphinx',
]
@ -138,7 +138,7 @@ pygments_style = 'sphinx'
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ['_static']
# html_static_path = ['_static']
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.

View File

@ -96,6 +96,24 @@ volume_type_id:
type: string
# variables in query
action:
description: |
The action. Valid values are "set" or "unset."
in: query
required: true
type: string
all-tenants:
description: |
Shows details for all tenants. Admin only..
in: query
required: false
type: string
bootable_query:
description: |
Filters results by bootable status. Default=None.
in: query
required: false
type: boolean
detail:
description: |
Indicates whether to show pool details or only
@ -104,6 +122,12 @@ detail:
in: query
required: false
type: boolean
image-id:
description: |
Creates volume from image ID. Default=None.
in: query
required: false
type: string
limit:
description: |
Requests a page size of items. Returns a number
@ -123,6 +147,26 @@ marker:
in: query
required: false
type: string
metadata_query:
description: |
Filters results by a metadata key and value pair.
Default=None.
in: query
required: true
type: object
migration_status_query:
description: |
Filters results by a migration status. Default=None.
Admin only.
in: query
required: false
type: string
name_volume:
description: |
Filters results by a name. Default=None.
in: query
required: false
type: string
sort:
description: |
Comma-separated list of sort keys and optional
@ -159,6 +203,12 @@ sort_key_1:
in: query
required: false
type: string
status_query:
description: |
Filters results by a status. Default=None.
in: query
required: false
type: boolean
usage:
description: |
Set to ``usage=true`` to show quota usage.
@ -168,12 +218,6 @@ usage:
type: boolean
# variables in body
QoS_support:
description: |
The quality of service (QoS) support.
in: body
required: true
type: boolean
absolute:
description: |
An ``absolute`` limits object.
@ -446,12 +490,6 @@ extra_specs:
in: body
required: true
type: object
is_public:
description:
Volume type which is accessible to the public.
in: body
required: false
type: boolean
extra_specs_1:
description: |
A key and value pair that contains additional
@ -644,6 +682,12 @@ is_incremental:
in: body
required: false
type: boolean
is_public:
description:
Volume type which is accessible to the public.
in: body
required: false
type: boolean
key:
description: |
The metadata key name for the metadata that you
@ -836,7 +880,7 @@ multiattach_1:
type: boolean
name:
description: |
The name of the volume transfer.
The name of the Volume Transfer.
in: body
required: true
type: string
@ -1115,6 +1159,12 @@ qos_specs:
in: body
required: true
type: object
QoS_support:
description: |
The quality of service (QoS) support.
in: body
required: true
type: boolean
quota_set:
description: |
A ``quota_set`` object.
@ -1355,6 +1405,14 @@ storage_protocol_1:
in: body
required: true
type: string
total_capacity:
description: |
The total capacity for the back-end volume, in
GBs. A valid value is a string, such as ``unknown``, or an
integer.
in: body
required: true
type: string
totalBackupGigabytesUsed:
description: |
The total number of backups gibibytes (GiB) used.
@ -1385,14 +1443,6 @@ totalVolumesUsed:
in: body
required: true
type: integer
total_capacity:
description: |
The total capacity for the back-end volume, in
GBs. A valid value is a string, such as ``unknown``, or an
integer.
in: body
required: true
type: string
updated:
description: |
The date and time stamp when the extension was

View File

@ -11,6 +11,7 @@ ddt>=1.0.1 # MIT
fixtures>=3.0.0 # Apache-2.0/BSD
mock>=2.0 # BSD
mox3>=0.7.0 # Apache-2.0
os-api-ref>=0.1.0 # Apache-2.0
oslotest>=1.10.0 # Apache-2.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2 # BSD
python-subunit>=0.0.18 # Apache-2.0/BSD

View File

@ -29,9 +29,10 @@ passenv = *_proxy *_PROXY
# (sheel)This environment is called from CI scripts to test and publish
# the API Ref to developer.openstack.org.
whitelist_externals = rm
deps = -r{toxinidir}/test-requirements.txt
install_command = pip install -U --force-reinstall {opts} {packages}
commands =
rm -rf cinder/api-ref/build
rm -rf api-ref/build
sphinx-build -W -b html -d api-ref/build/doctrees/v1 api-ref/v1/source api-ref/build/html/v1
sphinx-build -W -b html -d api-ref/build/doctrees/v2 api-ref/v2/source api-ref/build/html/v2