blob: bae9c6bbf23ceb1c8f49f378acd24e5abe50aa88 [file] [log] [blame]
# -*- 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 sphinx.domains import Domain, ObjType, std
from sphinx.roles import XRefRole
from sphinx.locale import l_, _
import sphinx
class TSConfVar(std.Target):
"""
Description of a traffic server configuration variable.
Argument is the variable as defined in records.config.
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,
'metric' : rst.directives.unchanged,
}
required_arguments = 3
optional_arguments = 1 # default is optional, special case if omitted
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 (isinstance(value, basestring)):
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['names'].append(cv_name)
title['first'] = False
title['objtype'] = 'cv'
self.add_name(title)
title.set_class('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.set_class(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.
anchor = 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 ('metric' in self.options):
fl.append(self.make_field('Metric', self.options['metric']))
if ('reloadable' in self.options):
fl.append(self.make_field('Reloadable', 'Yes'))
if ('deprecated' in self.options):
fl.append(self.make_field('Deprecated', 'Yes'))
# 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=[])
indexnode['entries'].append(
('single', _('%s') % cv_name, nodes.make_id(cv_name), '')
)
return [ indexnode, node, fl, nn ]
class TSConfVarRef(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(l_('configuration variable'), 'cv')
}
directives = {
'cv' : TSConfVar
}
roles = {
'cv' : TSConfVarRef()
}
initial_data = {
'cv' : {} # full name -> docname
}
dangling_warnings = {
'cv' : "No definition found for configuration variable '%(target)s'"
}
def clear_doc(self, docname):
cv_list = self.data['cv']
for var, doc in cv_list.items():
if doc == docname:
del cv_list[var]
def find_doc(self, key, obj_type):
zret = None
if obj_type == 'cv' :
obj_list = self.data['cv']
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.config')
def get_objects(self):
for var, doc in self.data['cv'].iteritems():
yield var, var, 'cv', doc, var, 1
# These types are ignored as missing references for the C++ domain.
EXTERNAL_TYPES = set((
'int', 'uint',
'uint8_t', 'uint16_t', 'uint24_t', 'uint32_t', 'uint64_t',
'int8_t', 'int16_t', 'int24_t', 'int32_t', 'int64_t',
'off_t', 'size_t', 'time_t',
'Event', 'INK_MD5', 'DLL<EvacuationBlock>',
))
# Clean up specific references that we know will never be defined but are implicitly used by
# other domain directives. Hand convert them to literals.
def xref_cleanup(app, env, node, contnode):
rdomain = node['refdomain']
rtype = node['reftype']
rtarget = node['reftarget']
if 'cpp' == rdomain:
if 'type' == rtype:
# one of the predefined type, or a pointer or reference to it.
if (rtarget in EXTERNAL_TYPES) or (('*' == rtarget[-1] or '&' == rtarget[-1]) and rtarget[:-1] in EXTERNAL_TYPES):
node = nodes.literal()
node += contnode
return node
return;
def setup(app):
app.add_crossref_type('configfile', 'file',
objname='Configuration file',
indextemplate='pair: %s; Configuration files')
rst.roles.register_generic_role('arg', nodes.emphasis)
rst.roles.register_generic_role('const', nodes.literal)
app.add_domain(TrafficServerDomain)
# Types that we want the C domain to consider built in
for word in EXTERNAL_TYPES:
sphinx.domains.c.CObject.stopwords.add(word)
app.connect('missing-reference', xref_cleanup)