This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/master by this push: new 44fb76f39d cmake out of source doc build support (#10252) 44fb76f39d is described below commit 44fb76f39d3043adb95806fada8b04b0aa97273b Author: Chris McFarlen <ch...@mcfarlen.us> AuthorDate: Tue Aug 22 11:30:01 2023 -0500 cmake out of source doc build support (#10252) Co-authored-by: Chris McFarlen <cmcfar...@apple.com> --- CMakeLists.txt | 12 + doc/CMakeLists.txt | 60 +++++ doc/conf.cmake.in.py | 495 ++++++++++++++++++++++++++++++++++ doc/ext/local-config.cmake.in.py | 18 ++ doc/ext/traffic-server.cmake.in.py | 535 +++++++++++++++++++++++++++++++++++++ doc/manpages.cmake.in.py | 98 +++++++ 6 files changed, 1218 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a3016c9df..9603fbf086 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option(ENABLE_FAST_SDK "Use fast SDK APIs (default OFF)") option(ENABLE_JEMALLOC "Use jemalloc (default OFF)") option(ENABLE_LUAJIT "Use LuaJIT (default OFF)") option(ENABLE_MIMALLOC "Use mimalloc (default OFF)") +option(ENABLE_DOCS "Build docs (default OFF)") if(CMAKE_SYSTEM_NAME STREQUAL Linux) set(DEFAULT_POSIX_CAP ON) @@ -296,6 +297,13 @@ check_symbol_exists( ) check_symbol_exists(TLS1_3_VERSION "openssl/ssl.h" TS_USE_TLS13) +# document tools +if(ENABLE_DOCS) + find_package(Python3 REQUIRED) + find_package(Java COMPONENTS Runtime REQUIRED) + find_program(PipEnv pipenv REQUIRED) +endif() + # Catch2 for tests set(CATCH_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/lib/catch2) @@ -392,6 +400,10 @@ if(ENABLE_EXAMPLE) add_subdirectory(example) endif() +if(ENABLE_DOCS) + add_subdirectory(doc) +endif() + add_custom_target(clang-format-install COMMAND ${CMAKE_SOURCE_DIR}/tools/clang-format.sh --install WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt new file mode 100644 index 0000000000..cabf6fa921 --- /dev/null +++ b/doc/CMakeLists.txt @@ -0,0 +1,60 @@ +####################### +# +# 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. +# +####################### + + +set(PLANTUML_DATE "1.2018.1") +set(PLANTUML_ARCHIVE ${CMAKE_CURRENT_BINARY_DIR}/plantuml-${PLANTUML_DATE}.tar.bz2 ) +set(PLANTUML_JAR ${CMAKE_CURRENT_BINARY_DIR}/plantuml-${PLANTUML_DATE}/plantuml.jar) + +file(DOWNLOAD + https://ci.trafficserver.apache.org/bintray/plantuml-${PLANTUML_DATE}.tar.bz2 + ${PLANTUML_ARCHIVE} + EXPECTED_HASH SHA1=4dbf218641a777007f9bc72ca8017a41a23e1081 + ) +file(ARCHIVE_EXTRACT + INPUT ${PLANTUML_ARCHIVE} + PATTERNS *.jar) + +configure_file(ext/local-config.cmake.in.py ext/local-config.py) +configure_file(ext/traffic-server.cmake.in.py ext/traffic-server.py) +configure_file(conf.cmake.in.py conf.py) +configure_file(manpages.cmake.in.py manpages.py) + +# Docs are built with python so we need a target to setup pipenv +set(RUNPIPENV PIPENV_PIPFILE=${CMAKE_CURRENT_SOURCE_DIR}/Pipfile ${PipEnv}) +add_custom_command( + OUTPUT Pipfile.lock + COMMAND ${RUNPIPENV} install + COMMENT "Setup pipenv" + DEPENDS Pipfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) + +add_custom_target( + generate_docs + COMMAND ${RUNPIPENV} run python ${CMAKE_CURRENT_SOURCE_DIR}/checkvers.py --check-version + COMMAND ${RUNPIPENV} run python -m sphinx -c ${CMAKE_CURRENT_BINARY_DIR} -b html ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/docbuild/html + DEPENDS + Pipfile.lock + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} +) +#add_custom_command( +# TARGET generate_docs +# POST_BUILD +# COMMAND ${RUNPIPENV} --rm +# COMMENT "Cleaning up pipenv" +#) diff --git a/doc/conf.cmake.in.py b/doc/conf.cmake.in.py new file mode 100644 index 0000000000..e5f534bbff --- /dev/null +++ b/doc/conf.cmake.in.py @@ -0,0 +1,495 @@ +# -*- coding: utf-8 -*- +# +# Apache Traffic Server documentation build configuration file, created by +# sphinx-quickstart on Mon Mar 4 06:23:15 2013. +# +# 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. + +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +from sphinx.writers import manpage +from docutils.transforms import frontmatter +from docutils.utils import unescape +from docutils.utils import punctuation_chars +from docutils.parsers.rst import states +from docutils import nodes +import re +import sys +import os +from datetime import date +from sphinx import version_info +# Import man_pages from manpages.py to get the list of manpages to generate in +# separate files. Default is to put everything in apachetrafficserver.1 +# For these reasons, despite what linting tools might say, this import is required. +from manpages import man_pages + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('ext')) +sys.path.insert(0, os.path.abspath('.')) + + +# Allow for us to add our override CSS file (new with Sphinx 1.x) +def setup(app): + app.add_css_file('override.css') + +# -- General configuration ----------------------------------------------------- + + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = [ + 'sphinx.ext.graphviz', + 'sphinx.ext.intersphinx', + 'sphinx.ext.autodoc', + 'sphinx.ext.todo', + 'sphinx.ext.coverage', + 'sphinx.ext.viewcode', + 'sphinxcontrib.plantuml', + 'sphinxcontrib.jquery', + 'traffic-server', +] + +# Contains values that are dependent on configure.ac. +LOCAL_CONFIG = os.path.join(os.environ['PWD'], "ext", "local-config.py") +with open(LOCAL_CONFIG) as f: + exec(compile(f.read(), LOCAL_CONFIG, 'exec')) + +if version_info < (3, 0): + print("Documentation requires Sphinx 3.0 or later.") + exit(1) + +# XXX Disabling docxygen for now, since it make RTD documentation builds time +# out, eg. https://readthedocs.org/projects/trafficserver/builds/3525976/ +# extensions += [ +# 'doxygen', +# ] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'Apache Traffic Server' +copyright = f'{date.today().year}, d...@trafficserver.apache.org' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. + +version = '@TS_VERSION_STRING@' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None +locale_dirs = ['locale/'] +gettext_compact = False + +# HACK for Read-the-Docs +# Generate .mo files just in time +if os.environ.get('READTHEDOCS') == 'True': + import polib + print("Generating .mo files"), + for locale_dir in locale_dirs: + for path, dummy, filenames in os.walk(locale_dir): + for filename in filenames: + po_file = os.path.join(path, filename) + base, ext = os.path.splitext(po_file) + if ext == ".po": + mo_file = base + ".mo" + po = polib.pofile(po_file) + po.save_as_mofile(fpath=mo_file) + print("done") +else: + # On RedHat-based distributions, install the python-sphinx_rtd_theme package + # to get an end result that looks more like readthedoc.org. + try: + import sphinx_rtd_theme + html_theme = 'sphinx_rtd_theme' + html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] + except Exception: + pass +# End of HACK + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = [] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'default' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +nitpicky = True +nitpick_ignore = [('c:identifier', 'int64_t'), + ('c:identifier', 'uint64_t'), + ('c:identifier', 'uint8_t'), + ('c:identifier', 'int32_t'), + ('c:identifier', 'size_t'), + ('c:identifier', 'ssize_t'), + ('c:identifier', 'sockaddr'), + ('c:identifier', 'time_t'), + ('cpp:identifier', 'T'), # template arg + ('cpp:identifier', 'F'), # template arg + ('cpp:identifier', 'Args'), # variadic template arg + ('cpp:identifier', 'Rest'), # variadic template arg + ] + +# Autolink issue references. +# See Customizing the Parser in the docutils.parsers.rst module. + + +# Customize parser.inliner in the only way that Sphinx supports. +# docutils.parsers.rst.Parser takes an instance of states.Inliner or a +# subclass, but Sphinx initializes the parser without any arguments, +# in SphinxStandaloneReader.set_parser('restructuredtext'), +# which is called from Publisher.set_components(). + +# states.Inliner isn't a new-style class, so super() isn't an option. +BaseInliner = states.Inliner + + +class Inliner(states.Inliner): + def init_customizations(self, settings): + self.__class__ = BaseInliner + BaseInliner.init_customizations(self, settings) + self.__class__ = Inliner + + # Copied from states.Inliner.init_customizations(). + # In Docutils 0.13 these are locals. + if not hasattr(self, 'start_string_prefix'): + self.start_string_prefix = (u'(^|(?<=\\s|[%s%s]))' % + (punctuation_chars.openers, + punctuation_chars.delimiters)) + if not hasattr(self, 'end_string_suffix'): + self.end_string_suffix = (u'($|(?=\\s|[\x00%s%s%s]))' % + (punctuation_chars.closing_delimiters, + punctuation_chars.delimiters, + punctuation_chars.closers)) + + issue = re.compile( + r''' + {start_string_prefix} + TS-\d+ + {end_string_suffix}'''.format( + start_string_prefix=self.start_string_prefix, + end_string_suffix=self.end_string_suffix), + re.VERBOSE | re.UNICODE) + + self.implicit_dispatch.append((issue, self.issue_reference)) + + def issue_reference(self, match, lineno): + text = match.group(0) + + rawsource = unescape(text, True) + text = unescape(text, False) + + refuri = 'https://issues.apache.org/jira/browse/' + text + + return [nodes.reference(rawsource, text, refuri=refuri)] + + +states.Inliner = Inliner + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +#html_theme = 'agogo' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = [] + +# The name for this set of Sphinx documents. If None, it defaults to +# "<project> v<release> documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +html_logo = 'static/images/trans_logo_tm_380x69.png' + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +html_favicon = 'static/images/favicon.ico' + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a <link> tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'ApacheTrafficServerdoc' + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # 'preamble': '', +} + +if 'latex_a4' in tags: + latex_elements['papersize'] = 'a4paper' +elif 'latex_paper' in tags: + latex_elements['papersiize'] = 'letterpaper' + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'ApacheTrafficServer.tex', u'Apache Traffic Server Documentation', + u'd...@trafficserver.apache.org', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + +# -- Options for manual page output -------------------------------------------- + +# The global "man_pages" is imported from ts/manpages.py + +# If true, show URL addresses after external links. +#man_show_urls = False + +# Get the manual page description from the reStructuredText document. +# This keeps the list of manual pages consistent with the source +# documents and includes the same brief description in both the HTML +# and manual page outputs. + + +# Override ManualPageWriter and ManualPageTranslator in the only way +# that Sphinx supports + +BaseWriter = manpage.ManualPageWriter + + +class ManualPageWriter(BaseWriter): + def translate(self): + transform = frontmatter.DocTitle(self.document) + + section, index = transform.candidate_index(self.document) + if index: + + # A sentence after the title is the manual page description + if len(section) > 1 and isinstance(section[1], nodes.paragraph): + + description = section.pop(1).astext() + description = description[:1].lower() + description[1:] + description = description.rstrip('.') + + self.document.settings.subtitle = description + + # Instead of section_level = -1, use the standard Docutils + # DocTitle transform to hide the top level title + transform.promote_title(self.document) + + # The title is the manual page name + transform.set_metadata() + + BaseWriter.translate(self) + + +manpage.ManualPageWriter = ManualPageWriter + +BaseTranslator = manpage.ManualPageTranslator + + +class ManualPageTranslator(BaseTranslator): + def __init__(self, builder, *args, **kwds): + BaseTranslator.__init__(self, builder, *args, **kwds) + + # Instead of section_level = -1, use the standard Docutils + # DocTitle transform to hide the top level title + self.section_level = 0 + + +manpage.ManualPageTranslator = ManualPageTranslator + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'ApacheTrafficServer', u'Apache Traffic Server Documentation', + u'd...@trafficserver.apache.org', 'ApacheTrafficServer', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'Apache Traffic Server' +epub_author = u'd...@trafficserver.apache.org' +epub_publisher = u'd...@trafficserver.apache.org' +epub_copyright = u'2013, d...@trafficserver.apache.org' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True +#mathjax_path = 'https://docs.trafficserver.apache.org/__RTD/MathJax.js' + +# Enabling marking bit fields as 'bitfield_N`. +# Currently parameterized fields don't work. When they do, we should change to +# 'bitfield(N)'. +cpp_id_attributes = ['bitfield_1', 'bitfield_3', 'bitfield_24'] diff --git a/doc/ext/local-config.cmake.in.py b/doc/ext/local-config.cmake.in.py new file mode 100644 index 0000000000..6f582f732c --- /dev/null +++ b/doc/ext/local-config.cmake.in.py @@ -0,0 +1,18 @@ +# 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. + +plantuml = '@Java_JAVA_EXECUTABLE@ -jar @PLANTUML_JAR@' +plantuml_output_format = 'svg' diff --git a/doc/ext/traffic-server.cmake.in.py b/doc/ext/traffic-server.cmake.in.py new file mode 100644 index 0000000000..dc159cc738 --- /dev/null +++ b/doc/ext/traffic-server.cmake.in.py @@ -0,0 +1,535 @@ +# -*- coding: utf-8 -*- +# +# 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 2013 by the Apache Software Foundation + :license: Apache +""" + +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 + +import os +import subprocess +import re +import yaml + +# 2/3 compat logic +try: + basestring + + def is_string_type(s): + return isinstance(s, basestring) +except NameError: + def is_string_type(s): + return isinstance(s, str) + + +def get_code(data): + + # if needed we can add a type representer, + # def float_representer(dumper, value): + # return dumper.represent_scalar(u'tag:yaml.org,2002:float', str(value), style="'") + + # yaml.add_representer(float, float_representer) + + return yaml.dump(data) + + +class TSConfVar(std.Target): + """ + Description of a traffic server configuration variable. + + Argument is the variable as defined in records.yaml. + + Descriptive text should follow, indented. + + Then the bulk description (if any) undented. This should be considered equivalent to the Doxygen + short and long description. + """ + + option_spec = { + 'class': rst.directives.class_option, + 'reloadable': rst.directives.flag, + 'deprecated': rst.directives.flag, + 'overridable': rst.directives.flag, + 'units': rst.directives.unchanged, + 'legacy': rst.directives.flag, + } + required_arguments = 3 + optional_arguments = 1 # default is optional, special case if omitted + final_argument_whitespace = True + has_content = True + + def __generate_code(self, cv_name, cv_default, cv_type): + def add_object(config, var, value, type=None): + def get_value(type, value): + + def have_multipliers(value, mps): + for m in mps: + if value.endswith(m): + return True + + return False + + if value is None or value == 'nullptr' or value == 'NULL': + return None + + # We want to make the type right when inserting the element into the dict. + if type == 'FLOAT': + return float(value) + elif type == 'INT': + # We need to make it YAML compliant as this will be an int, so if contains + # any special character like hex or a multiplier, then we make a string. ATS will + # parse it as string anyway. + if value.startswith('0x') or have_multipliers(value, ['K', 'M', 'G', 'T']): + return str(value) + else: + return int(value) + elif type == 'STRING': + return str(value) + + return None + + obj = {} + key = '' + index = var.find('.') + if index < 0: # last part + config[var] = get_value(type, value) + else: + key = var[:index] + if key not in config: + config[key] = {} + + add_object(config[key], var[index + 1:], value, type=type) + + name = cv_name + if name.startswith("proxy.config."): + name = name[len("proxy.config."):] + elif name.startswith("local.config."): + name = name[len("local.config."):] + + ts = {} + config = {} + + # Build the object + add_object(config, name, cv_default, cv_type) + ts['ts'] = config + code = get_code(ts) + literal = nodes.literal_block(code, code) + literal['linenos'] = True + literal['force'] = True + literal['language'] = 'yaml' + literal['caption'] = 'records.yaml' + + return literal + + def make_field(self, tag, value): + field = nodes.field() + field.append(nodes.field_name(text=tag)) + body = nodes.field_body() + if is_string_type(value): + body.append(sphinx.addnodes.compact_paragraph(text=value)) + else: + body.append(value) + field.append(body) + return field + + # External entry point + def run(self): + env = self.state.document.settings.env + cv_default = None + cv_scope, cv_name, cv_type = self.arguments[0:3] + if (len(self.arguments) > 3): + cv_default = self.arguments[3] + + # First, make a generic desc() node to be the parent. + node = sphinx.addnodes.desc() + node.document = self.state.document + node['objtype'] = 'cv' + + # Next, make a signature node. This creates a permalink and a + # highlighted background when the link is selected. + title = sphinx.addnodes.desc_signature(cv_name, '') + title['ids'].append(nodes.make_id(cv_name)) + title['ids'].append(cv_name) + title['names'].append(cv_name) + title['first'] = False + title['objtype'] = 'cv' + self.add_name(title) + title['classes'].append('ts-cv-title') + + # Finally, add a desc_name() node to display the name of the + # configuration variable. + title += sphinx.addnodes.desc_name(cv_name, cv_name) + + node.append(title) + + if ('class' in self.options): + title['classes'].append(self.options.get('class')) + # This has to be a distinct node before the title. if nested then + # the browser will scroll forward to just past the title. + nodes.target('', '', names=[cv_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['ts']['cv'][cv_name] = env.docname + + fl = nodes.field_list() + fl.append(self.make_field('Scope', cv_scope)) + fl.append(self.make_field('Type', cv_type)) + if (cv_default): + fl.append(self.make_field('Default', cv_default)) + else: + fl.append(self.make_field('Default', sphinx.addnodes.literal_emphasis(text='*NONE*'))) + if ('units' in self.options): + fl.append(self.make_field('Units', self.options['units'])) + if ('reloadable' in self.options): + fl.append(self.make_field('Reloadable', 'Yes')) + if ('overridable' in self.options): + fl.append(self.make_field('Overridable', 'Yes')) + if ('deprecated' in self.options): + fl.append(self.make_field('Deprecated', 'Yes')) + + # add yaml rep if record is not legacy. + code_block = None + code_block_title = None + if 'legacy' not in self.options: + code_block = self.__generate_code(cv_name, cv_default, cv_type) + code_block_title = sphinx.addnodes.compact_paragraph(text="yaml-rep:") + self.add_name(code_block_title) + self.add_name(code_block) + + # Get any contained content + nn = nodes.compound() + self.state.nested_parse(self.content, self.content_offset, nn) + + # Create an index node so that Sphinx adds this config variable to the + # index. nodes.make_id() specifies the link anchor name that is + # implicitly generated by the anchor node above. + indexnode = sphinx.addnodes.index(entries=[]) + if sphinx.version_info >= (1, 4): + indexnode['entries'].append( + ('single', _('%s') % cv_name, nodes.make_id(cv_name), '', '') + ) + else: + indexnode['entries'].append( + ('single', _('%s') % cv_name, nodes.make_id(cv_name), '') + ) + if code_block is None: + return [indexnode, node, fl, nn] + else: + return [indexnode, node, fl, code_block_title, code_block, nn] + + +class TSConfVarRef(XRefRole): + def process_link(self, env, ref_node, explicit_title_p, title, target): + return title, target + + +def metrictypes(typename): + return directives.choice(typename.lower(), ('counter', 'gauge', 'derivative', 'flag', 'text')) + + +def metricunits(unitname): + return directives.choice( + unitname.lower(), + ('ratio', + 'percent', + 'kbits', + 'mbits', + 'bytes', + 'kbytes', + 'mbytes', + 'nanoseconds', + 'microseconds', + 'milliseconds', + 'seconds')) + + +class TSStat(std.Target): + """ + Description of a traffic server statistic. + + Argument is the JSON stat group ("global", etc.) in which the statistic is + returned, then the statistic name as used by traffic_ctl/stats_over_http, + followed by the value type of the statistic ('string', 'integer'), and + finally an example value. + + Descriptive text should follow, indented. + + Then the bulk description (if any) undented. This should be considered + equivalent to the Doxygen short and long description. + """ + + option_spec = { + 'type': metrictypes, + 'units': metricunits, + 'introduced': rst.directives.unchanged, + 'deprecated': rst.directives.unchanged, + 'ungathered': rst.directives.flag + } + required_arguments = 3 + optional_arguments = 1 # example value is optional + final_argument_whitespace = True + has_content = True + + def make_field(self, tag, value): + field = nodes.field() + field.append(nodes.field_name(text=tag)) + body = nodes.field_body() + if is_string_type(value): + body.append(sphinx.addnodes.compact_paragraph(text=value)) + else: + body.append(value) + field.append(body) + return field + + # External entry point + def run(self): + env = self.state.document.settings.env + stat_example = None + stat_group, stat_name, stat_type = self.arguments[0:3] + if (len(self.arguments) > 3): + stat_example = self.arguments[3] + + # First, make a generic desc() node to be the parent. + node = sphinx.addnodes.desc() + node.document = self.state.document + node['objtype'] = 'stat' + + # Next, make a signature node. This creates a permalink and a + # highlighted background when the link is selected. + title = sphinx.addnodes.desc_signature(stat_name, '') + title['ids'].append(nodes.make_id('stat-' + stat_name)) + title['names'].append(stat_name) + title['first'] = False + title['objtype'] = 'stat' + self.add_name(title) + title['classes'].append('ts-stat-title') + + # Finally, add a desc_name() node to display the name of the + # configuration variable. + title += sphinx.addnodes.desc_name(stat_name, stat_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. + nodes.target('', '', names=[stat_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['ts']['stat'][stat_name] = env.docname + + fl = nodes.field_list() + fl.append(self.make_field('Collection', stat_group)) + if ('type' in self.options): + fl.append(self.make_field('Type', self.options['type'])) + if ('units' in self.options): + fl.append(self.make_field('Units', self.options['units'])) + fl.append(self.make_field('Datatype', stat_type)) + if ('introduced' in self.options and len(self.options['introduced']) > 0): + fl.append(self.make_field('Introduced', self.options['introduced'])) + if ('deprecated' in self.options): + if (len(self.options['deprecated']) > 0): + fl.append(self.make_field('Deprecated', self.options['deprecated'])) + else: + fl.append(self.make_field('Deprecated', 'Yes')) + if ('ungathered' in self.options): + fl.append(self.make_field('Gathered', 'No')) + if (stat_example): + fl.append(self.make_field('Example', stat_example)) + + # Get any contained content + nn = nodes.compound() + self.state.nested_parse(self.content, self.content_offset, nn) + + # Create an index node so that Sphinx adds this statistic to the + # index. nodes.make_id() specifies the link anchor name that is + # implicitly generated by the anchor node above. + indexnode = sphinx.addnodes.index(entries=[]) + + if sphinx.version_info >= (1, 4): + indexnode['entries'].append( + ('single', _('%s') % stat_name, nodes.make_id(stat_name), '', '') + ) + else: + indexnode['entries'].append( + ('single', _('%s') % stat_name, nodes.make_id(stat_name), '') + ) + + return [indexnode, node, fl, nn] + + +class TSStatRef(XRefRole): + def process_link(self, env, ref_node, explicit_title_p, title, target): + return title, target + + +class TrafficServerDomain(Domain): + """ + Apache Traffic Server Documentation. + """ + + name = 'ts' + label = 'Traffic Server' + data_version = 2 + + object_types = { + 'cv': ObjType(_('configuration variable'), 'cv'), + 'stat': ObjType(_('statistic'), 'stat') + } + + directives = { + 'cv': TSConfVar, + 'stat': TSStat + } + + roles = { + 'cv': TSConfVarRef(), + 'stat': TSStatRef() + } + + initial_data = { + 'cv': {}, # full name -> docname + 'stat': {} + } + + dangling_warnings = { + 'cv': "No definition found for configuration variable '%(target)s'", + 'stat': "No definition found for statistic '%(target)s'" + } + + def clear_doc(self, docname): + cv_list = self.data['cv'] + for var, doc in list(cv_list.items()): + if doc == docname: + del cv_list[var] + stat_list = self.data['stat'] + for var, doc in list(stat_list.items()): + if doc == docname: + del stat_list[var] + + def find_doc(self, key, obj_type): + zret = None + + if obj_type == 'cv': + obj_list = self.data['cv'] + elif obj_type == 'stat': + obj_list = self.data['stat'] + 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, 'records.yaml') + + # Python 2/3 compat - iteritems is 2, items is 3 + # Although perhaps the lists are small enough items could be used in Python 2. + try: + {}.iteritems() + + def get_objects(self): + for var, doc in self.data['cv'].iteritems(): + yield var, var, 'cv', doc, var, 1 + for var, doc in self.data['stat'].iteritems(): + yield var, var, 'stat', doc, var, 1 + except AttributeError: + def get_objects(self): + for var, doc in self.data['cv'].items(): + yield var, var, 'cv', doc, var, 1 + for var, doc in self.data['stat'].items(): + yield var, var, 'stat', doc, var, 1 + + +# get the branch this documentation is building for in X.X.x form +REPO_ROOT = '@PROJECT_SOURCE_DIR@' +ts_version = '@TS_VERSION_STRING@' + +# get the current branch the local repository is on +REPO_GIT_DIR = os.path.join(REPO_ROOT, ".git") +git_branch = subprocess.check_output(['git', '--git-dir', REPO_GIT_DIR, + 'rev-parse', '--abbrev-ref', 'HEAD']) + + +def make_github_link(name, rawtext, text, lineno, inliner, options=None, content=None): + """ + This docutils role lets us link to source code via the handy :ts:git: markup. + Link references are rooted at the top level source directory. All links resolve + to GitHub. + + Examples: + + To link to proxy/Main.cc: + + Hi, here is a link to the proxy entry point: :ts:git:`proxy/Main.cc`. + + To link to CONTRIBUTING.md: + + If you want to contribute, take a look at :ts:git:`CONTRIBUTING.md`. + """ + if options is None: + options = {} + if content is None: + content = [] + url = 'https://github.com/apache/trafficserver/blob/{}/{}' + ref = ts_version if ts_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') + + # Very ugly, but as of Sphinx 1.8 it must be done. There is an `override` option to add_crossref_type + # but it only applies to the directive, not the role (`file` in this case). If this isn't cleared + # explicitly the build will fail out due to the conflict. In this case, since the role action is the + # same in all cases, the output is correct. This does assume the config file names and log files + # names are disjoint sets. + del app.registry.domain_roles['std']['file'] + + app.add_crossref_type('logfile', 'file', + objname='Log file', + indextemplate='pair: %s; Log files') + + rst.roles.register_generic_role('arg', nodes.emphasis) + rst.roles.register_generic_role('const', nodes.literal) + + app.add_domain(TrafficServerDomain) + + # this lets us do :ts:git:`<file_path>` and link to the file on github + app.add_role_to_domain('ts', 'git', make_github_link) diff --git a/doc/manpages.cmake.in.py b/doc/manpages.cmake.in.py new file mode 100644 index 0000000000..dcbfae71df --- /dev/null +++ b/doc/manpages.cmake.in.py @@ -0,0 +1,98 @@ +# 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. + +import os + +srcdir = '@CMAKE_CURRENT_SOURCE_DIR@' + +man_pages = [ + # Add all files in the reference/api directory to the list of manual + # pages + ('developer-guide/api/functions/' + filename[:-4], filename.split('.', 1)[0], filename.split('.', 1)[0] + ' API function', None, '3ts') for filename in os.listdir('{}/developer-guide/api/functions/'.format(srcdir)) if filename != 'index.en.rst' and filename.endswith('.rst')] + [ + + # Add all files in the appendices/command-line directory to the list + # of manual pages + ('appendices/command-line/traffic_cache_tool.en', 'traffic_cache_tool', + u'Traffic Server cache management tool', None, '1'), + ('appendices/command-line/traffic_crashlog.en', 'traffic_crashlog', + u'Traffic Server crash log helper', None, '8'), + ('appendices/command-line/traffic_ctl.en', 'traffic_ctl', + u'Traffic Server command line tool', None, '8'), + ('appendices/command-line/traffic_layout.en', 'traffic_layout', + u'Traffic Server sandbox management tool', None, '1'), + ('appendices/command-line/traffic_logcat.en', 'traffic_logcat', + u'Traffic Server log spooler', None, '8'), + ('appendices/command-line/traffic_logstats.en', 'traffic_logstats', + u'Traffic Server analyzer', None, '8'), + ('appendices/command-line/traffic_server.en', 'traffic_server', + u'Traffic Server', None, '8'), + ('appendices/command-line/traffic_top.en', 'traffic_top', + u'Display Traffic Server statistics', None, '1'), + ('appendices/command-line/traffic_via.en', 'traffic_via', + u'Traffic Server Via header decoder', None, '1'), + ('appendices/command-line/traffic_wccp.en', 'traffic_wccp', + u'Traffic Server WCCP client', None, '1'), + ('appendices/command-line/tspush.en', 'tspush', + u'Push objects into the Traffic Server cache', None, '1'), + ('appendices/command-line/tsxs.en', 'tsxs', + u'Traffic Server plugin tool', None, '1'), + + # Add all files in the admin-guide/files directory to the list + # of manual pages + ('admin-guide/files/cache.config.en', 'cache.config', + u'Traffic Server cache configuration file', None, '5'), + ('admin-guide/files/hosting.config.en', 'hosting.config', + u'Traffic Server domain hosting configuration file', None, '5'), + ('admin-guide/files/ip_allow.yaml.en', 'ip_allow.yaml', + u'Traffic Server IP access control configuration file', None, '5'), + ('admin-guide/files/logging.yaml.en', 'logging.yaml', + u'Traffic Server logging configuration file', None, '5'), + ('admin-guide/files/parent.config.en', 'parent.config', + u'Traffic Server parent cache configuration file', None, '5'), + ('admin-guide/files/plugin.config.en', 'plugin.config', + u'Traffic Server global plugin configuration file', None, '5'), + ('admin-guide/files/records.yaml.en', 'records.yaml', + u'Traffic Server configuration file', None, '5'), + ('admin-guide/files/remap.config.en', 'remap.config', + u'Traffic Server remap rules configuration file', None, '5'), + ('admin-guide/files/sni.yaml.en', 'sni.yaml', + u'Traffic Server sni rules configuration file', None, '5'), + ('admin-guide/files/splitdns.config.en', 'splitdns.config', + u'Traffic Server split DNS configuration file', None, '5'), + ('admin-guide/files/ssl_multicert.config.en', 'ssl_multicert.config', + u'Traffic Server SSL certificate configuration file', None, '5'), + ('admin-guide/files/storage.config.en', 'storage.config', + u'Traffic Server cache storage configuration file', None, '5'), + ('admin-guide/files/strategies.yaml.en', 'strategies.yaml', + u'Traffic Server cache hierarchy configuration file', None, '5'), + ('admin-guide/files/volume.config.en', 'volume.config', + u'Traffic Server cache volume configuration file', None, '5'), + +] + +if __name__ == '__main__': + # Use optparse instead of argparse because this needs to work on old Python versions. + import optparse + + parser = optparse.OptionParser(description='Traffic Server Sphinx docs configuration') + parser.add_option('--section', type=int, default=0, dest='section') + + (options, args) = parser.parse_args() + + # Print the names of the man pages for the requested manual section. + for page in man_pages: + if options.section == 0 or options.section == int(page[4][0]): + print(page[1] + '.' + page[4])