from docutils import nodes, statemachine
from docutils.parsers.rst import Directive
from sphinx.util.compat import make_admonition
from sphinx.locale import _
[docs]def setup(app):
global BASE_TYPES
global LIST_TYPES
app.add_config_value('rfc2119_include', True, True)
for t in LIST_TYPES:
app.add_node(t)
for node_type in BASE_TYPES + (rfc2119interpretation,):
app.add_node(
node_type,
html=(visit_rfc2119_node, depart_rfc2119_node),
latex=(visit_rfc2119_node, depart_rfc2119_node),
text=(visit_rfc2119_node, depart_rfc2119_node))
app.add_directive('must', MustDirective)
app.add_directive('must_not', MustNotDirective)
app.add_directive('shall', ShallDirective)
app.add_directive('shall_not', ShallNotDirective)
app.add_directive('required', RequiredDirective)
app.add_directive('should', ShouldDirective)
app.add_directive('should_not', ShouldNotDirective)
app.add_directive('recommended', RecommendedDirective)
app.add_directive('not_recommended', NotRecommendedDirective)
app.add_directive('optional', OptionalDirective)
app.add_directive('may', MayDirective)
app.add_directive('mandatorylist', MandatoryListDirective)
app.add_directive('recommendationlist', RecommendationListDirective)
app.add_directive('optionallist', OptionalListDirective)
app.add_directive('rfc2119interpretation', rfc2119InterpretationDirective)
app.connect('doctree-resolved', process_rfc2119_nodes)
app.connect('env-purge-doc', purge_rfc2119_mandatory)
app.connect('env-purge-doc', purge_rfc2119_recommendation)
app.connect('env-purge-doc', purge_rfc2119_optional)
return {"version": "0.2"}
[docs]class rfc2119interpretation(nodes.Admonition, nodes.Element): pass
[docs]class mandatory(nodes.Admonition, nodes.Element): pass
[docs]class recommended(nodes.Admonition, nodes.Element): pass
[docs]class optional(nodes.Admonition, nodes.Element): pass
[docs]class mandatorylist(nodes.General, nodes.Element): pass
[docs]class recommendationlist(nodes.General, nodes.Element): pass
[docs]class optionallist(nodes.General, nodes.Element): pass
BASE_TYPES = (mandatory, recommended, optional)
LIST_TYPES = (mandatorylist, recommendationlist, optionallist)
[docs]def visit_rfc2119_node(self, node):
self.visit_admonition(node)
[docs]def depart_rfc2119_node(self, node):
self.depart_admonition(node)
[docs]class rfc2119InterpretationDirective(Directive):
has_content = False # True
[docs] def run(self):
lines = ("""The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.""",)
boilerplate = statemachine.ViewList(initlist=lines)
self.content.append(boilerplate)
return make_admonition(
rfc2119interpretation, self.name, [_('RFC 2119 keywords')],
self.options, self.content,
self.lineno, self.content_offset,
self.block_text, self.state, self.state_machine)
[docs]class MandatoryListDirective(Directive):
has_content = True
[docs] def run(self):
return [mandatorylist('')]
[docs]class RecommendationListDirective(Directive):
has_content = True
[docs] def run(self):
return [recommendationlist('')]
[docs]class OptionalListDirective(Directive):
has_content = True
[docs] def run(self):
return [optionallist('')]
[docs]class rfc2119Directive(Directive):
""" An abstract base for rfc2199 requirements."""
# this label not used because this class is treated as abstract
# we expcet subclasses to overwrite them
label = "rfc2119"
requirement_class = "rfc2119"
has_content = True
[docs] def run(self):
env = self.state.document.settings.env
targetid = "%s-%d" % (
self.requirement_class,
env.new_serialno(self.requirement_class))
targetnode = nodes.target('', '', ids=[targetid])
# BUG? why is it always mandatory?
# maybe select node type per self.requirement_class
ad = make_admonition(
mandatory, self.name, [_(self.label)], self.options,
self.content, self.lineno, self.content_offset,
self.block_text, self.state, self.state_machine)
env_data_name = "rfc2119_all_%s" % self.requirement_class
if not hasattr(env, env_data_name):
exec("env.%s = []" % env_data_name)
env_data = eval("env.%s" % env_data_name)
env_data.append({
'docname': env.docname,
'lineno': self.lineno,
'rfc2119': ad[0].deepcopy(),
'target': targetnode})
return [targetnode] + ad
[docs]class OptionalDirective(rfc2119Directive):
label = "Optional"
requirement_class = "optional"
[docs]class MayDirective(rfc2119Directive):
label = "May"
requirement_class = "optional"
[docs]class ShouldDirective(rfc2119Directive):
label = "Should"
requirement_class = "recommendation"
[docs]class ShouldNotDirective(rfc2119Directive):
label = "Should Not"
requirement_class = "recommendation"
[docs]class RecommendedDirective(rfc2119Directive):
label = "Recommended"
requirement_class = "recommendation"
[docs]class NotRecommendedDirective(rfc2119Directive):
label = "Not Recommended"
requirement_class = "recommendation"
[docs]class MustDirective(rfc2119Directive):
label = "Must"
requirement_class = "mandatory"
[docs]class MustNotDirective(rfc2119Directive):
label = "Must Not"
requirement_class = "mandatory"
[docs]class ShallDirective(rfc2119Directive):
label = "Shall"
requirement_class = "mandatory"
[docs]class ShallNotDirective(rfc2119Directive):
label = "Shall Not"
requirement_class = "mandatory"
[docs]class RequiredDirective(rfc2119Directive):
label = "Required"
requirement_class = "mandatory"
[docs]def purge_rfc2119_mandatory(app, env, docname):
if not hasattr(env, 'rfc2119_all_mandatorys'):
return
env.rfc2119_all_mandatory = [mandatory for mandatory in env.rfc2119_all_mandatory if mandatory['docname'] != docname]
[docs]def purge_rfc2119_recommendation(app, env, docname):
if not hasattr(env, 'rfc2119_all_recommendation'):
return
env.rfc2119_all_recommendation = [rec for rec in env.rfc2119_all_recommendation if rec['docname'] != docname]
[docs]def purge_rfc2119_optional(app, env, docname):
if not hasattr(env, 'rfc2119_all_optional'):
return
env.rfc2119_all_optional = [rec for rec in env.rfc2119_all_optional if rec['docname'] != docname]
# TODO - for the other node types, not just mandatory
[docs]def process_rfc2119_nodes(app, doctree, fromdocname):
global BASE_TYPES
global LIST_TYPES
if not app.config.rfc2119_include:
for node_type in BASE_TYPES:
for node in doctree.traverse(node_type):
node.parent.remove(node)
# replace list_type nodes with an actual list
# of the nodes they list
env = app.builder.env
for node_type in LIST_TYPES:
for node in doctree.traverse(node_type):
if not app.config.rfc2119_include:
node.replace_self([])
continue
content = []
if node_type == mandatorylist:
env_data = env.rfc2119_all_mandatory
elif node_type == recommendationlist:
env_data = env.rfc2119_all_recommendation
elif node_type == optionallist:
env_data = env.rfc2119_all_optional
else:
raise Exception('invalid node_type: %s' % node_type)
for info in env_data:
para = nodes.paragraph()
filename = env.doc2path(info['docname'], base=None)
description = (
_('(The original entry is located in %s, line %d and can be found ') %
(filename, info['lineno']))
para += nodes.Text(description, description)
newnode = nodes.reference('', '')
innernode = nodes.emphasis(_('here'), _('here'))
newnode['refdocname'] = info['docname']
newnode['refuri'] = app.builder.get_relative_uri(
fromdocname, info['docname'])
newnode['refuri'] += '#' + info['target']['refid']
newnode.append(innernode)
para += newnode
para+= nodes.Text('.)', '.)')
content.append(info['rfc2119'])
content.append(para)
node.replace_self(content)