summaryrefslogtreecommitdiffstats
path: root/Doc/tools
diff options
context:
space:
mode:
Diffstat (limited to 'Doc/tools')
-rw-r--r--Doc/tools/extensions/pyspecific.py142
1 files changed, 131 insertions, 11 deletions
diff --git a/Doc/tools/extensions/pyspecific.py b/Doc/tools/extensions/pyspecific.py
index f79b250..a6f39b0 100644
--- a/Doc/tools/extensions/pyspecific.py
+++ b/Doc/tools/extensions/pyspecific.py
@@ -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))
+
+ 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"])
+ 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}