| .. 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. |
| |
| .. include:: ../../common.defs |
| |
| .. _developer-doc-adding-domains: |
| |
| Creating New Domains |
| ******************** |
| |
| In the event a new type of object or reference needs to be documented, and none |
| of the existing markup options or domains are appropriate, it is possible to |
| extend |RST| and Sphinx by adding custom domains. |
| |
| Each domain may be designed to accept any number of required and optional |
| arguments, as well as any collection of domain options, and each option may be |
| designed to support arbitrary values, restricted (enumerated) values, or to |
| simply act as flags. |
| |
| All custom domain definitions should be located in ``doc/ext/traffic-server.py`` |
| and consist of, at a bare minimum, a domain class definition and a domain |
| reference class definition. Sphinx domains are implemented in Python. |
| |
| For this section, we will use the contrived example of creating a domain which |
| permits us to define and reference a set of variables which are constrained by |
| the following characteristics: |
| |
| #. Each variable in the domain must be one of known list of data types, which |
| we will limit here to the possibilities of :literal:`integer`, |
| :literal:`float`, :literal:`string`. |
| |
| #. Where the data type is not specified, we can assume it is :literal:`string`. |
| |
| #. Variables which are numeric in their type may have a range of permissible |
| values. |
| |
| #. Variables in the domain may still be present and supported in the system, but |
| are planned to be removed in some future release. |
| |
| #. Every variable is associated with a single URI protocol, though there is no |
| validation performed on the value used to represent the protocol name. |
| |
| As stated, this example is fairly contrived and would not match any particularly |
| likely real-world needs, but it will allow us to demonstrate the full extent of |
| custom domain definition without needless complexity, reader's suspension of |
| disbelief permitting. |
| |
| For this chapter's purpose, we will call this domain simply *Variables*, and we |
| will construct classes which allow us to document variables thusly:: |
| |
| .. ts:variable:: http_enabled http integer |
| :deprecated: |
| |
| Enables (any positive, non-zero value) or disables (any zero or negative |
| value) processing of HTTP requests. |
| |
| And referencing of those variables defined with this domain via:: |
| |
| :ts:variable:`http_enabled` |
| |
| Defining the Domain |
| =================== |
| |
| Each domain is defined by a class which inherits from ``std.Target``. Several |
| class attributes are expected, which determine how domain object definitions are |
| processed. |TS| convention is to name each domain's class in camel case, |
| beginning with :literal:`TS` to prevent any class name collisions with builtin |
| Sphinx classes. |
| |
| .. code:: |
| |
| class TSVariable(std.Target): |
| |
| We have named the domain's defining class as *TSVariable* and inherited from the |
| :literal:`std.Target` class. Given the earlier stated requirements, we need a |
| domain which supports at least two required attributes (a name, of course, and |
| a URI protocol with which it is associated) and a commonly defined, though |
| optional, third attribute (a data type). We'll deal with the value ranges and |
| deprecation status later. |
| |
| .. code:: |
| |
| class TSVariable(std.Target): |
| required_arguments = 2 |
| optional_arguments = 1 |
| final_argument_whitespace = False |
| has_content = True |
| |
| We've now specified the appropriate number of required and optional arguments, |
| though not what each one happens to be or in what order the required arguments |
| need be written. Additionally, we've declared that definitions using this |
| domain do not permit whitespace in the final argument, but definitions can have |
| a block of text content which follows them and should be associated with the |
| item being defined. |
| |
| .. note:: |
| |
| Permitting whitespace in the final argument causes the final value of a valid |
| definition to *slurp* the remaining content of the definition. Normally, each |
| argument is separated by whitespace, thus ``foo bar baz`` would only be a |
| valid definition if the domain's required and optional argument counts added |
| up to exactly three. If the domain defined only two arguments as expected, |
| but sets ``final_argument_whitespace`` to *True*, then the definition would |
| be valid and the second argument in this case would be ``bar baz``. |
| |
| Our requirements also state support for optional value ranges, and a flag to |
| indicate whether the variable is being deprecated. These can easily be |
| supported through the ``option_spec``, which allows for options to be tagged |
| on to a domain item, on the lines immediately following its definition. |
| |
| .. code:: |
| |
| class TSVariable(std.Target): |
| ... |
| option_spec = { |
| 'deprecated' : rst.directives.flag, |
| 'range' : rst.directives.unchanged |
| } |
| |
| For our example, ``deprecated`` is simply a boolean flag, and ``range`` will be |
| an arbitrary string on which we will perform no particular transformation or |
| validation (good behavior will be left up to those documenting their variables |
| with this domain). The ``rst.directives`` module may be consulted for a wider |
| range of predefined option types, including the ability to define your own |
| types which can perform any complexity of validation you may desire to |
| implement. |
| |
| It would be good form to also include a docstring for the class explaining the |
| expected arguments in brief. With that included, our class now looks like: |
| |
| .. code:: |
| |
| class TSVariable(std.Target): |
| """ |
| Description of a Traffic Server protocol variable. |
| |
| Required arguments, in order, are: |
| |
| URI Protocol |
| Variable name |
| |
| Optional argument is the data type of the variable, with "string" the |
| the default. Possible values are: "string", "integer", and "float". |
| |
| Options supported are: |
| |
| :deprecated: - A simple flag option indicating whether the variable |
| is slated for removal in future releases. |
| |
| :range: - A string describing the permissible range of values the |
| variable may contain. |
| """ |
| |
| option_spec = { |
| 'deprecated' : rst.directives.flag, |
| 'range' : rst.directives.unchanged |
| } |
| |
| required_arguments = 2 |
| optional_arguments = 1 |
| final_argument_whitespace = False |
| has_content = True |
| |
| Every domain class must also provide a ``run`` method, which is called every |
| time an item definition using the domain is encountered. This method is where |
| all argument and option validations are performed, and where transformation of |
| the definition into the documentation's rendered output occurs. |
| |
| The core responsibilities of the ``run`` method in a domain class are to |
| populate the domain's data dictionary, for use by references, as well as to |
| transform the item's definition into a document structure suitable for |
| rendering. The default title to be used for references will be constructed in |
| this method, and all arguments and options will be processed. |
| |
| Our variables domain might have the following ``run`` method: |
| |
| .. code:: |
| |
| def run(self): |
| var_name, var_proto = self.arguments[0:2] |
| var_type = 'string' |
| |
| if (len(self.arguments) > 2): |
| var_type = self.arguments[2] |
| |
| # Create a documentation node to use as the parent. |
| node = sphinx.addnodes.desc() |
| node.document = self.state.document |
| node['objtype'] = 'variable' |
| |
| # Add the signature child node for permalinks. |
| title = sphinx.addnodes.desc_signature(var_name, '') |
| title['ids'].append(nodes.make_id('variable-'+var_name)) |
| title['names'].append(var_name) |
| title['first'] = False |
| title['objtype'] = node['objtype'] |
| self.add_name(title) |
| title.set_class('ts-variable-title') |
| |
| title += sphinx.addnodes.desc_name(var_name, var_name) |
| node.append(title) |
| |
| env.domaindata['ts']['variable'][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 ('range' in self.options): |
| fl.append(self.make_field('Value range:', self.options['range'])) |
| |
| # 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), '') |
| ) |
| |
| return [ indexnode, node, fl, nn ] |
| |
| Defining the Domain Reference |
| ============================= |
| |
| Domain reference definitions are quite simple in comparison to the full domain |
| definition. As with the domain itself, they are defined by a single class, but |
| inherit from ``XRefRole`` instead. There are no attributes necessary, and only |
| a single method, ``process_link`` need be defined. |
| |
| For our variables domain references, the class definition is a very short one. |
| |TS| convention is to name the reference class the same as the domain class, but |
| with :literal:`Ref` appended to the name. Thus, the domain class ``TSVariable`` |
| is accompanied by a ``TSVariableRef`` reference class. |
| |
| .. code:: |
| |
| class TSVariableRef(XRefRole): |
| def process_link(self, env, ref_node, explicit_title_p, title, target): |
| return title, target |
| |
| The ``process_link`` method will receive several arguments, as described below, |
| and should return two values: a string containing the title of the reference, |
| and a hyperlink target to be used for the rendered documentation. |
| |
| The ``process_link`` method receives the following arguments: |
| |
| ``self`` |
| The reference instance object, as per Python method conventions. |
| |
| ``env`` |
| A dictionary object containing the environment of the documentation |
| processor in its state at the time of the reference encounter. |
| |
| ``ref_node`` |
| The node object of the reference as encountered in the documentation source. |
| |
| ``explicit_title_p`` |
| Contains the text content of the reference's explicit title overriding, if |
| present in the reference markup. |
| |
| ``title`` |
| The processed form of the reference title, which may be the result of domain |
| class transformations or an overriding of the reference title within the |
| reference itself. |
| |
| ``target`` |
| The computed target of the reference, suitable for use by Sphinx to |
| construct hyperlinks to the location of the item's definition, wherever it |
| may reside in the final rendered form of the documentation. |
| |
| In our reference class, we have simply returned the processed title (allowing |
| the documentation to override the variable's name if desired, or defaulting to |
| the domain class's representation of the variable name in all other cases) and |
| the parser's computed target. |
| |
| It is recommended to leave the ``target`` untouched, however you may choose to |
| perform any transformations you wish on the value of the ``title``, bearing in |
| mind that whatever string is returned will appear verbatim in the rendered |
| documentation everywhere references for this domain are used. |
| |
| Exporting the Domain |
| ==================== |
| |
| With both the domain itself and references to it now defined, the final step is |
| to register those classes as domain and reference handlers in a namespace. This |
| is done for |TS| (in its ``:ts:`` namespace) quite easily by modifying the |
| ``TrafficServerDomain`` class, also located in ``doc/ext/traffic-server.py``. |
| |
| The following dictionaries defined by that class should be updated to include |
| the new domain and reference. In each case, the key used when adding to the |
| dictionary should be the string you wish to use in documentation markup for |
| your new domain. In our example's case, we will choose ``variable`` since it |
| aligns with the Python classes we've created above, and their contrived purpose. |
| |
| object_types |
| Used to define the actual markup string |
| |
| directives |
| Defines which class is used to implement a given domain. |
| |
| roles |
| Defines the class used to implement references to a domain. |
| |
| initial_data |
| Used to initialized the dictionary which tracks all encountered instances of |
| each domain. This should always be set to an empty dictionary for each |
| domain. |
| |
| dangling_warnings |
| May be used to provide a default warning if a reference is attempted to a |
| non-existent item for a domain. |
| |