bpo-37390: Add audit event table to documentations (GH-14406)

Also updates some (unreleased) event names to be consistent with the others.
This commit is contained in:
Steve Dower 2019-06-27 10:47:59 -07:00 committed by GitHub
parent 21cfae107e
commit 44f91c388a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 266 additions and 113 deletions

View file

@ -23,7 +23,7 @@ from docutils import nodes, utils
from sphinx import addnodes
from sphinx.builders import Builder
from sphinx.locale import translators
from sphinx.util import status_iterator
from sphinx.util import status_iterator, logging
from sphinx.util.nodes import split_explicit_title
from sphinx.writers.html import HTMLTranslator
from sphinx.writers.text import TextWriter, TextTranslator
@ -157,7 +157,7 @@ class AuditEvent(Directive):
has_content = True
required_arguments = 1
optional_arguments = 1
optional_arguments = 2
final_argument_whitespace = True
_label = [
@ -166,21 +166,49 @@ class AuditEvent(Directive):
"Raises an :ref:`auditing event <auditing>` {name} with arguments {args}.",
]
@property
def logger(self):
cls = type(self)
return logging.getLogger(cls.__module__ + "." + cls.__name__)
def run(self):
name = self.arguments[0]
if len(self.arguments) >= 2 and self.arguments[1]:
args = [
"``{}``".format(a.strip())
for a in self.arguments[1].strip("'\"").split()
if a.strip()
]
args = (a.strip() for a in self.arguments[1].strip("'\"").split(","))
args = [a for a in args if a]
else:
args = []
label = translators['sphinx'].gettext(self._label[min(2, len(args))])
text = label.format(name="``{}``".format(self.arguments[0]),
args=", ".join(args))
text = label.format(name="``{}``".format(name),
args=", ".join("``{}``".format(a) for a in args if a))
pnode = nodes.paragraph(text, classes=["audit-hook"])
env = self.state.document.settings.env
if not hasattr(env, 'all_audit_events'):
env.all_audit_events = {}
new_info = {
'source': [],
'args': args
}
info = env.all_audit_events.setdefault(name, new_info)
if info is not new_info:
if not self._do_args_match(info['args'], new_info['args']):
self.logger.warn(
"Mismatched arguments for audit-event {}: {!r} != {!r}"
.format(name, info['args'], new_info['args'])
)
if len(self.arguments) >= 3 and self.arguments[2]:
target = self.arguments[2]
ids = []
else:
target = "audit_event_{}_{}".format(name, len(info['source']))
target = re.sub(r'\W', '_', label)
ids = [target]
info['source'].append((env.docname, target))
pnode = nodes.paragraph(text, classes=["audit-hook"], ids=ids)
if self.content:
self.state.nested_parse(self.content, self.content_offset, pnode)
else:
@ -189,6 +217,37 @@ class AuditEvent(Directive):
return [pnode]
# This list of sets are allowable synonyms for event argument names.
# If two names are in the same set, they are treated as equal for the
# purposes of warning. This won't help if number of arguments is
# different!
_SYNONYMS = [
{"file", "path", "fd"},
]
def _do_args_match(self, args1, args2):
if args1 == args2:
return True
if len(args1) != len(args2):
return False
for a1, a2 in zip(args1, args2):
if a1 == a2:
continue
if any(a1 in s and a2 in s for s in self._SYNONYMS):
continue
return False
return True
class audit_event_list(nodes.General, nodes.Element):
pass
class AuditEventListDirective(Directive):
def run(self):
return [audit_event_list('')]
# Support for documenting decorators
@ -394,7 +453,7 @@ class PydocTopicsBuilder(Builder):
'building topics... ',
length=len(pydoc_topic_labels)):
if label not in self.env.domaindata['std']['labels']:
self.warn('label %r not in documentation' % label)
self.env.logger.warn('label %r not in documentation' % label)
continue
docname, labelid, sectname = self.env.domaindata['std']['labels'][label]
doctree = self.env.get_and_resolve_doctree(docname, self)
@ -458,12 +517,72 @@ def parse_pdb_command(env, sig, signode):
return fullname
def process_audit_events(app, doctree, fromdocname):
for node in doctree.traverse(audit_event_list):
break
else:
return
env = app.builder.env
table = nodes.table(cols=3)
group = nodes.tgroup(
'',
nodes.colspec(colwidth=30),
nodes.colspec(colwidth=55),
nodes.colspec(colwidth=15),
)
head = nodes.thead()
body = nodes.tbody()
table += group
group += head
group += body
row = nodes.row()
row += nodes.entry('', nodes.paragraph('', nodes.Text('Audit event')))
row += nodes.entry('', nodes.paragraph('', nodes.Text('Arguments')))
row += nodes.entry('', nodes.paragraph('', nodes.Text('References')))
head += row
for name in sorted(getattr(env, "all_audit_events", ())):
audit_event = env.all_audit_events[name]
row = nodes.row()
node = nodes.paragraph('', nodes.Text(name))
row += nodes.entry('', node)
node = nodes.paragraph()
for i, a in enumerate(audit_event['args']):
if i:
node += nodes.Text(", ")
node += nodes.literal(a, nodes.Text(a))
row += nodes.entry('', node)
node = nodes.paragraph()
for i, (doc, label) in enumerate(audit_event['source'], start=1):
if isinstance(label, str):
ref = nodes.reference("", nodes.Text("[{}]".format(i)), internal=True)
ref['refuri'] = "{}#{}".format(
app.builder.get_relative_uri(fromdocname, doc),
label,
)
node += ref
row += nodes.entry('', node)
body += row
for node in doctree.traverse(audit_event_list):
node.replace_self(table)
def setup(app):
app.add_role('issue', issue_role)
app.add_role('source', source_role)
app.add_directive('impl-detail', ImplementationDetail)
app.add_directive('availability', Availability)
app.add_directive('audit-event', AuditEvent)
app.add_directive('audit-event-table', AuditEventListDirective)
app.add_directive('deprecated-removed', DeprecatedRemoved)
app.add_builder(PydocTopicsBuilder)
app.add_builder(suspicious.CheckSuspiciousMarkupBuilder)
@ -478,4 +597,5 @@ def setup(app):
app.add_directive_to_domain('py', 'awaitablemethod', PyAwaitableMethod)
app.add_directive_to_domain('py', 'abstractmethod', PyAbstractMethod)
app.add_directive('miscnews', MiscNews)
app.connect('doctree-resolved', process_audit_events)
return {'version': '1.0', 'parallel_read_safe': True}