Import directives/roles from zuul

This imports the current directives from Zuul itself, and adds
an example doc page that exercises them all so this repo is more
self-testing.

Also, use python3 by default to ensure we remain py3 compat.

Change-Id: Ie5b3cedd5e8dfaf0763d09a901fc9ba0e5b63683
This commit is contained in:
James E. Blair 2017-08-07 16:45:27 -07:00
parent 553fdce0cf
commit 27322342f2
5 changed files with 408 additions and 41 deletions

View File

@ -30,6 +30,8 @@ extensions = [
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
primary_domain = 'zuul'
# The suffix of source filenames.
source_suffix = '.rst'

132
doc/source/examples.rst Normal file
View File

@ -0,0 +1,132 @@
Examples
========
Jobs
----
.. job:: example-job
This is an example job.
.. var:: foo
This is a variable used by this job.
.. var:: bar
This is a sub key.
.. var:: items
:type: list
This variable is a list.
.. var:: baz
This is an item in a list.
.. job:: example-job
:variant: stable
This is a variant of :job:`example-job` which runs on stable branches.
This is a job role: :job:`example-job`
This is a job variable role: :var:`example-job.foo.bar`
Roles
-----
.. role:: example-role
This is an example role.
**Role Variables**
.. var:: foo
This is a variable used by this role.
.. var:: bar
This is a sub key.
.. var:: items
:type: list
This variable is a list.
.. var:: baz
This is an item in a list.
This is an (Ansible) role (Sphinx) role: :role:`example-role`
This is an (Ansible) role variable (Sphinx) role: :var:`example-role.items.baz`
Configuration Attributes
------------------------
.. attr:: example-attr
:required:
This is an example configuration attribute.
.. attr:: foo
:default: bar
A sub attribute.
.. value:: bar
An attribute value.
.. value:: baz
Another attribute value.
This is an attribute role: :attr:`example-attr.foo`
This is an attribute value role: :value:`example-attr.foo.bar`
Job Variables
-------------
.. var:: example-variable
This is an example variable.
.. var:: foo
This is a variable.
.. var:: bar
This is a sub key.
.. var:: items
:type: list
This variable is a list.
.. var:: baz
This is an item in a list.
This is a variable role: :var:`example-variable.items.baz`
Statistics
----------
.. stat:: example-stat
This is an example statistic.
.. stat:: foo
:type: counter
A sub stat.
This is a statistics role: :stat:`example-stat.foo`

View File

@ -1,9 +1,9 @@
.. include:: ../../README.rst
.. toctree::
:maxdepth: 2
examples
Indices and tables
==================

View File

@ -4,6 +4,7 @@ skipsdist = True
envlist = pep8
[testenv]
basepython = python3
install_command = pip install {opts} {packages}
deps = -r{toxinidir}/requirements.txt
-r{toxinidir}/test-requirements.txt

View File

@ -15,7 +15,10 @@
from sphinx import addnodes
from docutils.parsers.rst import Directive
from sphinx.domains import Domain, ObjType
from sphinx.roles import XRefRole
from sphinx.directives import ObjectDescription
from sphinx.util.nodes import make_refnode
from docutils import nodes
import os
import yaml
@ -26,7 +29,7 @@ class Layout(object):
self.jobs = []
class BaseZuulDirective(Directive):
class ZuulDirective(Directive):
has_content = True
def find_zuul_yaml(self):
@ -117,31 +120,235 @@ class BaseZuulDirective(Directive):
return lines
class ZuulJobDirective(BaseZuulDirective, ObjectDescription):
option_spec = {
'variant': lambda x: x,
class ZuulObjectDescription(ZuulDirective, ObjectDescription):
object_names = {
'attr': 'attribute',
'var': 'variable',
}
def handle_signature(self, sig, signode):
signode += addnodes.desc_name(sig, sig)
return sig
def get_path(self):
return self.env.ref_context.get('zuul:attr_path', [])
def get_display_path(self):
return self.env.ref_context.get('zuul:display_attr_path', [])
@property
def parent_pathname(self):
return '.'.join(self.get_display_path())
@property
def full_pathname(self):
name = self.names[-1].lower()
return '.'.join(self.get_path() + [name])
def add_target_and_index(self, name, sig, signode):
targetname = self.objtype + '-' + name
if 'variant' in self.options:
targetname += '-' + self.options['variant']
targetname = self.objtype + '-' + self.full_pathname
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
objects = self.env.domaindata['zuul']['objects']
if targetname in objects:
self.state_machine.reporter.warning(
'duplicate object description of %s, ' % targetname +
'other instance in ' +
self.env.doc2path(objects[targetname][0]) +
', use :noindex: for one of them',
line=self.lineno)
objects[targetname] = (self.env.docname, self.objtype)
indextext = '%s (%s)' % (name, self.objtype)
objname = self.object_names.get(self.objtype, self.objtype)
if self.parent_pathname:
indextext = '%s (%s of %s)' % (name, objname,
self.parent_pathname)
else:
indextext = '%s (%s)' % (name, objname)
self.indexnode['entries'].append(('single', indextext,
targetname, '', None))
class ZuulAutoJobDirective(BaseZuulDirective):
######################################################################
#
# Object description directives
#
class ZuulJobDirective(ZuulObjectDescription):
option_spec = {
'variant': lambda x: x,
}
def before_content(self):
path = self.env.ref_context.setdefault('zuul:attr_path', [])
element = self.names[-1]
path.append(element)
def after_content(self):
path = self.env.ref_context.get('zuul:attr_path')
if path:
path.pop()
def handle_signature(self, sig, signode):
signode += addnodes.desc_name(sig, sig)
return sig
class ZuulRoleDirective(ZuulObjectDescription):
def before_content(self):
path = self.env.ref_context.setdefault('zuul:attr_path', [])
element = self.names[-1]
path.append(element)
def after_content(self):
path = self.env.ref_context.get('zuul:attr_path')
if path:
path.pop()
def handle_signature(self, sig, signode):
signode += addnodes.desc_name(sig, sig)
return sig
class ZuulAttrDirective(ZuulObjectDescription):
has_content = True
option_spec = {
'required': lambda x: x,
'default': lambda x: x,
'noindex': lambda x: x,
}
def before_content(self):
path = self.env.ref_context.setdefault('zuul:attr_path', [])
path.append(self.names[-1])
path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
path.append(self.names[-1])
def after_content(self):
path = self.env.ref_context.get('zuul:attr_path')
if path:
path.pop()
path = self.env.ref_context.get('zuul:display_attr_path')
if path:
path.pop()
def handle_signature(self, sig, signode):
path = self.get_display_path()
signode['is_multiline'] = True
line = addnodes.desc_signature_line()
line['add_permalink'] = True
for x in path:
line += addnodes.desc_addname(x + '.', x + '.')
line += addnodes.desc_name(sig, sig)
if 'required' in self.options:
line += addnodes.desc_annotation(' (required)', ' (required)')
signode += line
if 'default' in self.options:
line = addnodes.desc_signature_line()
line += addnodes.desc_type('Default: ', 'Default: ')
line += nodes.literal(self.options['default'],
self.options['default'])
signode += line
return sig
class ZuulValueDirective(ZuulObjectDescription):
has_content = True
def handle_signature(self, sig, signode):
signode += addnodes.desc_name(sig, sig)
return sig
class ZuulVarDirective(ZuulObjectDescription):
has_content = True
option_spec = {
'type': lambda x: x,
'hidden': lambda x: x,
'noindex': lambda x: x,
}
type_map = {
'list': '[]',
'dict': '{}',
}
def get_type_str(self):
if 'type' in self.options:
return self.type_map[self.options['type']]
return ''
def before_content(self):
path = self.env.ref_context.setdefault('zuul:attr_path', [])
element = self.names[-1]
path.append(element)
path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
element = self.names[-1] + self.get_type_str()
path.append(element)
def after_content(self):
path = self.env.ref_context.get('zuul:attr_path')
if path:
path.pop()
path = self.env.ref_context.get('zuul:display_attr_path')
if path:
path.pop()
def handle_signature(self, sig, signode):
if 'hidden' in self.options:
return sig
path = self.get_display_path()
for x in path:
signode += addnodes.desc_addname(x + '.', x + '.')
signode += addnodes.desc_name(sig, sig)
return sig
class ZuulStatDirective(ZuulObjectDescription):
has_content = True
option_spec = {
'type': lambda x: x,
'hidden': lambda x: x,
'noindex': lambda x: x,
}
def before_content(self):
path = self.env.ref_context.setdefault('zuul:attr_path', [])
element = self.names[-1]
path.append(element)
path = self.env.ref_context.setdefault('zuul:display_attr_path', [])
element = self.names[-1]
path.append(element)
def after_content(self):
path = self.env.ref_context.get('zuul:attr_path')
if path:
path.pop()
path = self.env.ref_context.get('zuul:display_attr_path')
if path:
path.pop()
def handle_signature(self, sig, signode):
if 'hidden' in self.options:
return sig
path = self.get_display_path()
for x in path:
signode += addnodes.desc_addname(x + '.', x + '.')
signode += addnodes.desc_name(sig, sig)
if 'type' in self.options:
t = ' (%s)' % self.options['type']
signode += addnodes.desc_annotation(t, t)
return sig
######################################################################
#
# Autodoc directives
#
class ZuulAutoJobDirective(ZuulDirective):
def run(self):
name = self.content[0]
lines = self.generate_zuul_job_content(name)
@ -149,7 +356,7 @@ class ZuulAutoJobDirective(BaseZuulDirective):
return []
class ZuulAutoJobsDirective(BaseZuulDirective):
class ZuulAutoJobsDirective(ZuulDirective):
has_content = False
def run(self):
@ -165,25 +372,7 @@ class ZuulAutoJobsDirective(BaseZuulDirective):
return []
class ZuulRoleDirective(BaseZuulDirective, ObjectDescription):
def handle_signature(self, sig, signode):
signode += addnodes.desc_name(sig, sig)
return sig
def add_target_and_index(self, name, sig, signode):
targetname = self.objtype + '-' + name
if targetname not in self.state.document.ids:
signode['names'].append(targetname)
signode['ids'].append(targetname)
signode['first'] = (not self.names)
self.state.document.note_explicit_target(signode)
indextext = '%s (%s)' % (name, self.objtype)
self.indexnode['entries'].append(('single', indextext,
targetname, '', None))
class ZuulAutoRoleDirective(BaseZuulDirective):
class ZuulAutoRoleDirective(ZuulDirective):
def run(self):
name = self.content[0]
lines = self.generate_zuul_role_content(name)
@ -191,7 +380,7 @@ class ZuulAutoRoleDirective(BaseZuulDirective):
return []
class ZuulAutoRolesDirective(BaseZuulDirective):
class ZuulAutoRolesDirective(ZuulDirective):
has_content = False
def run(self):
@ -202,29 +391,72 @@ class ZuulAutoRolesDirective(BaseZuulDirective):
return []
class ZuulAbbreviatedXRefRole(XRefRole):
def process_link(self, env, refnode, has_explicit_title, title,
target):
title, target = super(ZuulAbbreviatedXRefRole, self).process_link(
env, refnode, has_explicit_title, title, target)
if not has_explicit_title:
title = title.split('.')[-1]
return title, target
class ZuulDomain(Domain):
name = 'zuul'
label = 'Zuul'
object_types = {
'job': ObjType('job'),
'role': ObjType('role'),
}
directives = {
# Object description directives
'job': ZuulJobDirective,
'role': ZuulRoleDirective,
'attr': ZuulAttrDirective,
'value': ZuulValueDirective,
'var': ZuulVarDirective,
'stat': ZuulStatDirective,
# Autodoc directives
'autojob': ZuulAutoJobDirective,
'autojobs': ZuulAutoJobsDirective,
'role': ZuulRoleDirective,
'autorole': ZuulAutoRoleDirective,
'autoroles': ZuulAutoRolesDirective,
}
roles = {
'job': XRefRole(innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
'role': XRefRole(innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
'attr': XRefRole(innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
'value': ZuulAbbreviatedXRefRole(
innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
'var': XRefRole(innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
'stat': XRefRole(innernodeclass=nodes.inline, # type: ignore
warn_dangling=True),
}
initial_data = {
'layout': None,
'layout_path': None,
'role_paths': None,
}
'objects': {},
} # type: Dict[str, Dict]
def resolve_xref(self, env, fromdocname, builder, type, target,
node, contnode):
objects = self.data['objects']
name = type + '-' + target
obj = objects.get(name)
if obj:
return make_refnode(builder, fromdocname, obj[0], name,
contnode, name)
def clear_doc(self, docname):
for fullname, (fn, _l) in list(self.data['objects'].items()):
if fn == docname:
del self.data['objects'][fullname]
def setup(app):