summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-05-23 11:58:05 +0000
committerGerrit Code Review <review@openstack.org>2017-05-23 11:58:05 +0000
commitc8cf1056d75d4be09bd355942f344818809facce (patch)
treeae83289855300e541b9c4429e4a83c6aef7e7f7a
parent6da8fe15be93a30e74a2c235f26d23eab1f5fce2 (diff)
parent2320ee61a3ac369985ae22dbe035a9d451ae680a (diff)
downloadoslo-policy-c8cf1056d75d4be09bd355942f344818809facce.tar.gz
Merge "Add Sphinx extension to pretty-print modules"
-rw-r--r--oslo_policy/generator.py22
-rw-r--r--oslo_policy/sphinxext.py157
-rw-r--r--oslo_policy/tests/test_sphinxext.py82
3 files changed, 250 insertions, 11 deletions
diff --git a/oslo_policy/generator.py b/oslo_policy/generator.py
index 45bb4ae..1a09ec0 100644
--- a/oslo_policy/generator.py
+++ b/oslo_policy/generator.py
@@ -21,19 +21,19 @@ from oslo_policy import policy
LOG = logging.getLogger(__name__)
-_generator_opts = [
+GENERATOR_OPTS = [
cfg.StrOpt('output-file',
help='Path of the file to write to. Defaults to stdout.'),
]
-_rule_opts = [
+RULE_OPTS = [
cfg.MultiStrOpt('namespace',
required=True,
help='Option namespace(s) under "oslo.policy.policies" in '
'which to query for options.'),
]
-_enforcer_opts = [
+ENFORCER_OPTS = [
cfg.StrOpt('namespace',
required=True,
help='Option namespace under "oslo.policy.enforcer" in '
@@ -41,7 +41,7 @@ _enforcer_opts = [
]
-def _get_policies_dict(namespaces):
+def get_policies_dict(namespaces):
"""Find the options available via the given namespaces.
:param namespaces: a list of namespaces registered under
@@ -156,7 +156,7 @@ def _generate_sample(namespaces, output_file=None, include_help=True):
along with rules in which everything is commented out.
False, generates a sample-policy file with only rules.
"""
- policies = _get_policies_dict(namespaces)
+ policies = get_policies_dict(namespaces)
output_file = (open(output_file, 'w') if output_file
else sys.stdout)
@@ -218,8 +218,8 @@ def on_load_failure_callback(*args, **kwargs):
def generate_sample(args=None):
logging.basicConfig(level=logging.WARN)
conf = cfg.ConfigOpts()
- conf.register_cli_opts(_generator_opts + _rule_opts)
- conf.register_opts(_generator_opts + _rule_opts)
+ conf.register_cli_opts(GENERATOR_OPTS + RULE_OPTS)
+ conf.register_opts(GENERATOR_OPTS + RULE_OPTS)
conf(args)
_generate_sample(conf.namespace, conf.output_file)
@@ -227,8 +227,8 @@ def generate_sample(args=None):
def generate_policy(args=None):
logging.basicConfig(level=logging.WARN)
conf = cfg.ConfigOpts()
- conf.register_cli_opts(_generator_opts + _enforcer_opts)
- conf.register_opts(_generator_opts + _enforcer_opts)
+ conf.register_cli_opts(GENERATOR_OPTS + ENFORCER_OPTS)
+ conf.register_opts(GENERATOR_OPTS + ENFORCER_OPTS)
conf(args)
_generate_policy(conf.namespace, conf.output_file)
@@ -236,7 +236,7 @@ def generate_policy(args=None):
def list_redundant(args=None):
logging.basicConfig(level=logging.WARN)
conf = cfg.ConfigOpts()
- conf.register_cli_opts(_enforcer_opts)
- conf.register_opts(_enforcer_opts)
+ conf.register_cli_opts(ENFORCER_OPTS)
+ conf.register_opts(ENFORCER_OPTS)
conf(args)
_list_redundant(conf.namespace)
diff --git a/oslo_policy/sphinxext.py b/oslo_policy/sphinxext.py
new file mode 100644
index 0000000..36ae334
--- /dev/null
+++ b/oslo_policy/sphinxext.py
@@ -0,0 +1,157 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# 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.
+
+"""Sphinx extension for pretty-formatting policy docs."""
+
+import os
+
+from docutils import nodes
+from docutils.parsers import rst
+from docutils.parsers.rst import directives
+from docutils import statemachine
+from oslo_config import cfg
+from sphinx.util.nodes import nested_parse_with_titles
+
+from oslo_policy import generator
+
+
+def _indent(text):
+ """Indent by four spaces."""
+ prefix = ' ' * 4
+
+ def prefixed_lines():
+ for line in text.splitlines(True):
+ yield (prefix + line if line.strip() else line)
+
+ return ''.join(prefixed_lines())
+
+
+def _format_policy_rule(rule):
+ """Output a definition list-style rule.
+
+ For example:
+
+ ``os_compute_api:servers:create``
+
+ Create a server
+
+ Default::
+
+ rule:admin_or_owner
+
+ Operations:
+
+ - **POST** ``/servers``
+ """
+ yield '``{}``'.format(rule.name)
+ yield ''
+
+ if rule.description:
+ for line in statemachine.string2lines(
+ rule.description, tab_width=4, convert_whitespace=True):
+ yield _indent(line)
+
+ yield ''
+
+ yield _indent('Default::')
+ yield ''
+ yield _indent(_indent(rule.check_str))
+
+ if hasattr(rule, 'operations'):
+ yield ''
+ yield _indent('Operations:')
+ yield ''
+ for operation in rule.operations:
+ yield _indent('- **{}** ``{}``'.format(operation['method'],
+ operation['path']))
+
+ yield ''
+
+
+def _format_policy_section(section, rules):
+ # The nested_parse_with_titles will ensure the correct header leve is used.
+ yield section
+ yield '=' * len(section)
+ yield ''
+
+ for rule in rules:
+ for line in _format_policy_rule(rule):
+ yield line
+
+
+def _format_policy(namespaces):
+ policies = generator.get_policies_dict(namespaces)
+
+ for section in sorted(policies.keys()):
+ for line in _format_policy_section(section, policies[section]):
+ yield line
+
+
+class ShowPolicyDirective(rst.Directive):
+
+ has_content = False
+ option_spec = {
+ 'config-file': directives.unchanged,
+ }
+
+ def run(self):
+ env = self.state.document.settings.env
+ app = env.app
+
+ config_file = self.options.get('config-file')
+
+ # if the config_file option was not defined, attempt to reuse the
+ # 'oslo_policy.sphinxpolicygen' extension's setting
+ if not config_file and hasattr(env.config,
+ 'policy_generator_config_file'):
+ config_file = env.config.policy_generator_config_file
+
+ # If we are given a file that isn't an absolute path, look for it
+ # in the source directory if it doesn't exist.
+ candidates = [
+ config_file,
+ os.path.join(app.srcdir, config_file,),
+ ]
+ for c in candidates:
+ if os.path.isfile(c):
+ config_path = c
+ break
+ else:
+ self.error('could not find config file in: %s' % str(candidates))
+
+ self.info('loading config file %s' % config_path)
+
+ conf = cfg.ConfigOpts()
+ opts = generator.GENERATOR_OPTS + generator.RULE_OPTS
+ conf.register_cli_opts(opts)
+ conf.register_opts(opts)
+ conf(
+ args=['--config-file', config_path],
+ )
+ namespaces = conf.namespace[:]
+
+ result = statemachine.ViewList()
+ source_name = '<' + __name__ + '>'
+ for line in _format_policy(namespaces):
+ result.append(line, source_name)
+
+ node = nodes.section()
+ node.document = self.state.document
+ nested_parse_with_titles(self.state, result, node)
+
+ return node.children
+
+
+def setup(app):
+ app.add_directive('show-policy', ShowPolicyDirective)
diff --git a/oslo_policy/tests/test_sphinxext.py b/oslo_policy/tests/test_sphinxext.py
new file mode 100644
index 0000000..4310fba
--- /dev/null
+++ b/oslo_policy/tests/test_sphinxext.py
@@ -0,0 +1,82 @@
+# Copyright 2017 Red Hat, Inc.
+#
+# 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 textwrap
+
+from oslotest import base
+
+from oslo_policy import policy
+from oslo_policy import sphinxext
+
+
+class FormatPolicyTest(base.BaseTestCase):
+
+ def test_minimal(self):
+ results = '\n'.join(list(sphinxext._format_policy_section(
+ 'foo', [policy.RuleDefault('rule_a', '@')])))
+
+ self.assertEqual(textwrap.dedent("""
+ foo
+ ===
+
+ ``rule_a``
+
+ Default::
+
+ @
+ """).lstrip(), results)
+
+ def test_with_description(self):
+ results = '\n'.join(list(sphinxext._format_policy_section(
+ 'foo', [policy.RuleDefault('rule_a', '@', 'My sample rule')]
+ )))
+
+ self.assertEqual(textwrap.dedent("""
+ foo
+ ===
+
+ ``rule_a``
+
+ My sample rule
+
+ Default::
+
+ @
+ """).lstrip(), results)
+
+ def test_with_operations(self):
+ results = '\n'.join(list(sphinxext._format_policy_section(
+ 'foo', [policy.DocumentedRuleDefault(
+ 'rule_a', '@', 'My sample rule', [
+ {'method': 'GET', 'path': '/foo'},
+ {'method': 'POST', 'path': '/some'}])]
+ )))
+
+ self.assertEqual(textwrap.dedent("""
+ foo
+ ===
+
+ ``rule_a``
+
+ My sample rule
+
+ Default::
+
+ @
+
+ Operations:
+
+ - **GET** ``/foo``
+ - **POST** ``/some``
+ """).lstrip(), results)