--- a +++ b/Docs/exts/ammr_directives.py @@ -0,0 +1,400 @@ +# -*- coding: utf-8 -*- +# +# +# Based on Sphinx Domain specification from the Apache Traffic +# Server project. See: +# https://docs.trafficserver.apache.org/en/latest/developer-guide/documentation/adding-domains.en.html +# +# :copyright: Copyright 2013 by the Apache Software Foundation +# :license: Apache +# +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" + TS Sphinx Directives + ~~~~~~~~~~~~~~~~~~~~~~~~~ + + Sphinx Docs directives for Apache Traffic Server + + :copyright: Copyright 2017 AnyBodyTechnology + :license: Apache +""" +import subprocess +import re +import os + +from docutils import nodes +from docutils.parsers import rst +from docutils.parsers.rst import directives +from sphinx.domains import Domain, ObjType, std +from sphinx.roles import XRefRole +from sphinx.locale import _ +import sphinx + + + + +class AMMR_BMStatement(std.Target): + """ + Description of a AMMR Body Model Statements. + + Required argument is: Variable name + + Options supported are: + + :deprecated: - A simple flag option indicating whether the variable + is slated for removal in future releases. + + :default: - A string representing the default value + :options: - A list of possible values + :type: - A string witht defines which can be set. + """ + + option_spec = { + "deprecated": rst.directives.flag, + "default": rst.directives.unchanged, + "type": rst.directives.unchanged, + "noindex": rst.directives.flag, + } + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + has_content = True + + def make_field(self, tag, value): + field = nodes.field() + field.append(nodes.field_name(text=tag)) + body = nodes.field_body() + if isinstance(value, str): + body.append(sphinx.addnodes.compact_paragraph(text=value)) + else: + body.append(value) + field.append(body) + return field + + def run(self): + env = self.state.document.settings.env + var_name = self.arguments[0] + + # Create a documentation node to use as the parent. + node = sphinx.addnodes.desc() + node["domain"] = "ammr" + node.document = self.state.document + node["objtype"] = "bm_statement" + node['classes'].append("section") + + # Add the signature child node for permalinks. + title = sphinx.addnodes.desc_signature(var_name, "") + title["ids"].append(nodes.make_id(var_name)) + title["ids"].append(var_name) + title["names"].append(var_name) + title["first"] = False + title["objtype"] = node["objtype"] + self.add_name(title) + title["classes"].append("ammr-bm_statement-title") + # title["classes"].append("sd-d-none") + + title += sphinx.addnodes.desc_name(var_name, var_name) + node.append(title) + + # This has to be a distinct node before the title. if nested then + # the browser will scroll forward to just past the title. + anchor = nodes.target("", "", names=[var_name]) + # Second (optional) arg is 'msgNode' - no idea what I should pass for that + # or if it even matters, although I now think it should not be used. + self.state.document.note_explicit_target(title) + + env.domaindata["ammr"]["bm_statement"][var_name] = env.docname + + # Create table detailing all provided domain options + fl = nodes.field_list() + if "deprecated" in self.options: + fl.append(self.make_field("Deprecated", "Yes")) + + # Parse any associated block content for the item's description + nn = nodes.compound() + self.state.nested_parse(self.content, self.content_offset, nn) + + # Create an index node so Sphinx will list this variable and its + # references in the index section. + indexnode = sphinx.addnodes.index(entries=[]) + indexnode["entries"].append( + ("single", _("%s") % var_name, nodes.make_id(var_name), "", None) + ) + + rtn = [] + if "noindex" not in self.options: + rtn.append(indexnode) + rtn.append(node) + if len(fl): + rtn.append(fl) + rtn.append(nn) + + return rtn + + +class AMMR_BMStatementRef(XRefRole): + def process_link(self, env, ref_node, explicit_title_p, title, target): + return title, target + + +class AMMR_BMConstant(std.Target): + """ + Description of a AMMR Body Model Constants. + + Required argument is: Constant name + + Options supported are: + + :deprecated: - A simple flag option indicating whether the variable + is slated for removal in future releases. + + :value: - A string descripting the values of the constant + """ + + option_spec = { + "deprecated": rst.directives.flag, + "value": rst.directives.unchanged, + "noindex": rst.directives.flag, + } + + required_arguments = 1 + optional_arguments = 0 + final_argument_whitespace = False + has_content = True + + def make_field(self, tag, value): + field = nodes.field() + field.append(nodes.field_name(text=tag)) + body = nodes.field_body() + if isinstance(value, str): + body.append(sphinx.addnodes.compact_paragraph(text=value)) + else: + body.append(value) + field.append(body) + return field + + def run(self): + env = self.state.document.settings.env + var_name = self.arguments[0] + + # Create a documentation node to use as the parent. + node = sphinx.addnodes.desc() + node.document = self.state.document + node["objtype"] = "bm_constant" + node['classes'].append("section") + + # Add the signature child node for permalinks. + title = sphinx.addnodes.desc_signature(var_name, "") + title["ids"].append(nodes.make_id(var_name)) + title["ids"].append(var_name) + title["names"].append(var_name) + title["first"] = False + title["objtype"] = node["objtype"] + self.add_name(title) + title["classes"].append("ammr-bm_constant-title") + # title["classes"].append("sd-d-none") + + + title += sphinx.addnodes.desc_name(var_name, var_name) + node.append(title) + + # This has to be a distinct node before the title. if nested then + # the browser will scroll forward to just past the title. + anchor = nodes.target("", "", names=[var_name]) + # Second (optional) arg is 'msgNode' - no idea what I should pass for that + # or if it even matters, although I now think it should not be used. + self.state.document.note_explicit_target(title) + + env.domaindata["ammr"]["bm_constant"][var_name] = env.docname + + # Create table detailing all provided domain options + fl = nodes.field_list() + + if "deprecated" in self.options: + fl.append(self.make_field("Deprecated", "Yes")) + + if "value" in self.options: + fl.append(self.make_field("Value", self.options["value"])) + + # Parse any associated block content for the item's description + nn = nodes.compound() + self.state.nested_parse(self.content, self.content_offset, nn) + + # Create an index node so Sphinx will list this variable and its + # references in the index section. + indexnode = sphinx.addnodes.index(entries=[]) + indexnode["entries"].append( + ("single", _("%s") % var_name, nodes.make_id(var_name), "", None) + ) + + rtn = [] + if "noindex" not in self.options: + rtn.append(indexnode) + rtn.extend([node, fl, nn]) + + return rtn + + +class AMMR_BMConstantRef(XRefRole): + def process_link(self, env, ref_node, explicit_title_p, title, target): + return title, target + + +class AMMRDomain(Domain): + """ + AMMR Documentation. + """ + + name = "ammr" + label = "AMMR" + data_version = 2 + + object_types = { + "bm_statement": ObjType(_("Body Model Statement"), "bm_statement"), + "bm_constant": ObjType(_("Body Model Constant"), "bm_constant"), + } + + directives = {"bm_statement": AMMR_BMStatement, "bm_constant": AMMR_BMConstant} + + roles = {"bm_statement": AMMR_BMStatementRef(), "bm_constant": AMMR_BMConstantRef()} + + initial_data = {"bm_statement": {}, "bm_constant": {}} # full name -> docname + + dangling_warnings = { + "bm_statement": "No definition found for Body Model statement '%(target)s'", + "bm_constant": "No definition found for Body Model constant '%(target)s'", + } + + def clear_doc(self, docname): + bm_statement_list = self.data["bm_statement"] + for var, doc in list(bm_statement_list.items()): + if doc == docname: + del bm_statement_list[var] + bm_constant_list = self.data["bm_constant"] + for var, doc in list(bm_constant_list.items()): + if doc == docname: + del bm_constant_list[var] + + def search_doc(self, key): + zret = [] + roles_to_search = ["bm_statement", "bm_constant"] + for role in roles_to_search: + if key in self.data[role]: + zret.append( (self.data[role][key], role) ) + return zret + + def resolve_any_xref(self, env, src_doc, builder, target, node, cont_node): + role_matches = self.search_doc(target) + condidate_node = [] + for (dst_doc, role) in role_matches: + newnode = sphinx.util.nodes.make_refnode( + builder, + src_doc, + dst_doc, + nodes.make_id(target), + cont_node, + target, + ) + condidate_node.append((f"ammr:{role}", newnode)) + + return condidate_node + + + + def find_doc(self, key, obj_type): + zret = None + + if obj_type == "bm_statement": + obj_list = self.data["bm_statement"] + elif obj_type == "bm_constant": + obj_list = self.data["bm_constant"] + else: + obj_list = None + + if obj_list and key in obj_list: + zret = obj_list[key] + + return zret + + def resolve_xref(self, env, src_doc, builder, obj_type, target, node, cont_node): + dst_doc = self.find_doc(target, obj_type) + if dst_doc: + return sphinx.util.nodes.make_refnode( + builder, + src_doc, + dst_doc, + nodes.make_id(target), + cont_node, + target, + ) + + def get_objects(self): + for var, doc in self.data["bm_statement"].items(): + yield var, var, "bm_statement", doc, var, 1 + for var, doc in self.data["bm_constant"].items(): + yield var, var, "bm_constant", doc, var, 1 + + +# get the branch this documentation is building for in X.X.x form +with open(os.path.normpath(os.path.join(__file__, "../../../AMMR.version.any")), "r") as f: + contents = f.read() + match = re.compile(r'.*AMMR_VERSION\s"(?P<version>.*)"').search(contents) + ammr_version = ".".join(match.groupdict()["version"].split(".", 3)[:3]) + +# get the current branch the local repository is on +git_branch = subprocess.check_output(["git", "rev-parse", "--abbrev-ref", "HEAD"]) + + +def make_git_link(name, rawtext, text, lineno, inliner, options={}, content=[]): + """ + This docutils role lets us link to source code via the handy :ammr:git: markup. + Link references are rooted at the top level source directory. All links resolve + to Github. + + Example: + + To link to CONTRIBUTING.md: + + If you want to contribute, take a look at :ammr:git:`CONTRIBUTING.md`. + """ + url = "https://github.com/AnyBody/ammr/blob/{}/{}" + ref = ammr_version if ammr_version == git_branch else "master" + node = nodes.reference(rawtext, text, refuri=url.format(ref, text), **options) + return [node], [] + + +def setup(app): + # app.add_crossref_type('configfile', 'file', + # objname='Configuration file', + # indextemplate='pair: %s; Configuration files') + # app.add_crossref_type('logfile', 'file', + # objname='Log file', + # indextemplate='pair: %s; Log files') + + app.add_domain(AMMRDomain) + + # this lets us do :ammr:git:`<file_path>` and link to the file on github + app.add_role_to_domain("ammr", "git", make_git_link) + + return { + 'parallel_read_safe': True, + 'parallel_write_safe': True, + } + +