from collections import defaultdict from operator import itemgetter import re from sphinx import addnodes from sphinx.directives import ObjectDescription from sphinx.domains import Domain, Index from sphinx.roles import XRefRole from sphinx.util.nodes import make_refnode # Reference: # https://www.sphinx-doc.org/en/master/development/tutorials/todo.html # https://www.sphinx-doc.org/en/master/development/tutorials/recipe.html # cSpell:ignore contnode # cSpell:ignore docname # cSpell:ignore docnames # cSpell:ignore localname # cSpell:ignore refnode # cSpell:ignore signode class OptionDirective(ObjectDescription): has_content = True def handle_signature(self, sig, signode): signode += addnodes.desc_name(text=sig) return sig def add_target_and_index(self, name_cls, sig, signode): m = re.match(r'^--([^\[= ]+)', sig) if not m: raise Exception('option must start with --') option_name = m.group(1) signode['ids'].append(f'option-{option_name}') qpdf = self.env.get_domain('qpdf') qpdf.add_option(sig, option_name) class OptionIndex(Index): name = 'options' localname = 'qpdf Command-line Options' shortname = 'Options' def generate(self, docnames=None): content = defaultdict(list) options = self.domain.get_objects() options = sorted(options, key=itemgetter(0)) # name, subtype, docname, anchor, extra, qualifier, description for name, display_name, typ, docname, anchor, _ in options: m = re.match(r'^(--([^\[= ]+))', display_name) if not m: raise Exception( 'OptionIndex.generate: display name not as expected') content[m.group(2)[0].lower()].append( (m.group(1), 0, docname, anchor, '', '', typ)) content = sorted(content.items()) return content, True class QpdfDomain(Domain): name = 'qpdf' label = 'qpdf documentation domain' roles = { 'ref': XRefRole() } directives = { 'option': OptionDirective, } indices = { OptionIndex, } initial_data = { 'options': [], # object list } def get_full_qualified_name(self, node): return '{}.{}'.format('option', node.arguments[0]) def get_objects(self): for obj in self.data['options']: yield(obj) def resolve_xref(self, env, from_doc_name, builder, typ, target, node, contnode): match = [(docname, anchor) for name, sig, typ, docname, anchor, priority in self.get_objects() if name == f'option.{target[2:]}'] if len(match) > 0: to_doc_name = match[0][0] match_target = match[0][1] return make_refnode(builder, from_doc_name, to_doc_name, match_target, contnode, match_target) else: raise Exception(f'invalid option xref ({target})') def add_option(self, signature, option_name): if self.env.docname != 'cli': raise Exception( 'qpdf:option directives don\'t work outside of cli.rst') name = f'option.{option_name}' anchor = f'option-{option_name}' # name, display_name, type, docname, anchor, priority self.data['options'].append( (name, signature, '', self.env.docname, anchor, 0)) def purge_options(self, docname): self.data['options'] = list([ x for x in self.data['options'] if x[3] != docname ]) def purge_options(app, env, docname): option = env.get_domain('qpdf') option.purge_options(docname) def setup(app): app.add_domain(QpdfDomain) app.connect('env-purge-doc', purge_options) return { 'version': '0.1', 'parallel_read_safe': True, 'parallel_write_safe': True, }