Skip to content

Commit 98f7d8d

Browse files
committed
Redo sphinx extension
* change module name to everett/sphinxext.py * change auto directive to autocomponent * add an everett domain and a everett:component directive * change options to be done with TypedField This cleans up the sphinx extension significantly and puts us on track to having it indexed and show up more helpfully in documentation.
1 parent 925738b commit 98f7d8d

8 files changed

+293
-191
lines changed

HISTORY.rst

+10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,16 @@
11
History
22
=======
33

4+
0.9 (in development)
5+
--------------------
6+
7+
Feature: Rewrite Sphinx extension. The extension is now in the
8+
``everett.sphinxext`` module and the directive is now ``.. autocomponent::``. It
9+
generates better documentation and it now indexes Everett components.
10+
11+
Documentation fixes and updates.
12+
13+
414
0.8 (January 24th, 2017)
515
------------------------
616

README.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ Configuration with Everett:
2626
* supports key namespaces
2727
* facilitates writing tests that change configuration values
2828
* supports component architectures with auto-documentation of configuration with
29-
a Sphinx ``autoconfig`` directive
29+
a Sphinx ``autocomponent`` directive
3030

3131
Everett is inspired by `python-decouple
3232
<https://github.com/henriquebastos/python-decouple>`_ and `configman
@@ -159,7 +159,7 @@ In your environment, you would provide ``RMQ_HOST``, etc for this component.
159159
You can auto-document the configuration for this component in your Sphinx docs
160160
with::
161161

162-
.. autoconfig:: path.to.RabbitMQComponent
162+
.. autocomponent:: path.to.RabbitMQComponent
163163

164164

165165
Say your app actually needs to connect to two separate queues--one for regular

docs/components.rst

-12
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,6 @@ In our environment, we provide the regular queue configuration with
8181
Same component code--two different instances.
8282

8383

84-
Documenting components
85-
======================
86-
87-
Components can have configuration. It's important to be able to easily document
88-
this configuration.
89-
90-
As such, Everett includes a Sphinx extension that adds a ``autoconfig``
91-
declaration for auto-documenting configuration for components.
92-
93-
.. automodule:: everett.sphinx_autoconfig
94-
95-
9684
Getting configuration information for components
9785
================================================
9886

docs/index.rst

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Contents
1010
history
1111
configuration
1212
components
13+
sphinxext
1314
recipes
1415
library
1516
dev

docs/recipes.rst

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ for the application:
3737

3838

3939
Couple of nice things here. First, is that if you do Sphinx documentation, you
40-
can use ``autoconfig`` to automatically document your configuration based on the
41-
code. Second, you can use
40+
can use ``autocomponent`` to automatically document your configuration based on
41+
the code. Second, you can use
4242
:py:meth:`everett.component.RequiredConfigMixin.get_runtime_config` to print out
4343
the runtime configuratino at startup.
4444

docs/sphinxext.rst

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
==========================
2+
Using the Sphinx extension
3+
==========================
4+
5+
No one likes to spend hours updating configuration documentation.
6+
7+
No one likes to spend hours trying to get something to work only to discover the
8+
configuration documentation is out of date, missing important information, or
9+
just wrong.
10+
11+
Blech.
12+
13+
Everett comes with a `Sphinx <https://http://www.sphinx-doc.org/en/stable/>`_
14+
extension to make it easier to document Everett components and configuration.
15+
This allows you to write configuration code and have it automatically documented
16+
in your Sphinx-generated docs without having to manually update it.
17+
18+
Further, Everett components will show up in the index making it easier for users
19+
to find what they're looking for.
20+
21+
22+
.. automodule:: everett.sphinxext

everett/sphinx_autoconfig.py everett/sphinxext.py

+141-35
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@
22
# License, v. 2.0. If a copy of the MPL was not distributed with this
33
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
44

5-
"""Contains the autoconfig Sphinx extension for auto-documenting components
5+
"""Contains the autocomponent Sphinx extension for auto-documenting components
66
with configuration.
77
8-
The ``autoconfig`` declaration will pull out the class docstring as well as
8+
The ``autocomponent`` declaration will pull out the class docstring as well as
99
configuration requirements, throw it all in a blender and spit it out.
1010
11-
To configure Sphinx, add ``'everett.sphinx_autoconfig'`` to the
12-
``extensions`` in ``conf.py``::
11+
To configure Sphinx, add ``'everett.sphinxext'`` to the ``extensions`` in
12+
``conf.py``::
1313
1414
extensions = [
1515
...
16-
'everett.sphinx_autoconfig'
16+
'everett.sphinxext'
1717
]
1818
1919
@@ -22,30 +22,36 @@
2222
You need to make sure that Everett is installed in the environment
2323
that Sphinx is being run in.
2424
25-
2625
Use it like this in an ``.rst`` file to document a component::
2726
28-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
27+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
28+
29+
30+
.. versionchanged:: 0.9
31+
32+
In Everett 0.8 and prior, the extension was in the
33+
``everett.sphinx_autoconfig`` module and the directive was ``..
34+
autoconfig::``.
2935
3036
3137
**Showing docstring and content**
3238
3339
If you want the docstring for the class, you can specify ``:show-docstring:``::
3440
35-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
41+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
3642
:show-docstring:
3743
3844
3945
If you want to show help, but from a different attribute than the docstring,
4046
you can specify any class attribute::
4147
42-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
48+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
4349
:show-docstring: __everett_help__
4450
4551
4652
You can provide content as well::
4753
48-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
54+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
4955
5056
This is some content!
5157
@@ -56,7 +62,7 @@
5662
5763
You can hide the class name if you want::
5864
59-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
65+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
6066
:hide-classname:
6167
6268
@@ -74,7 +80,7 @@
7480
7581
You can do that like this::
7682
77-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
83+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
7884
:namespace: crashstorage
7985
8086
@@ -92,7 +98,7 @@
9298
9399
You can do that like this::
94100
95-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
101+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
96102
:case: upper
97103
98104
@@ -101,7 +107,7 @@
101107
102108
You can do that like this::
103109
104-
.. autoconfig:: collector.external.boto.crashstorage.BotoS3CrashStorage
110+
.. autocomponent:: collector.external.boto.crashstorage.BotoS3CrashStorage
105111
:case: lower
106112
107113
.. versionadded:: 0.8
@@ -113,9 +119,15 @@
113119
from docutils import nodes
114120
from docutils.parsers.rst import Directive, directives
115121
from docutils.statemachine import ViewList
122+
from sphinx import addnodes
123+
from sphinx.directives import ObjectDescription
124+
from sphinx.domains import Domain, ObjType
125+
from sphinx.roles import XRefRole
126+
from sphinx.util.docfields import TypedField
127+
from sphinx.locale import l_, _
116128
from sphinx.util.docstrings import prepare_docstring
117129

118-
from everett import NO_VALUE
130+
from everett import NO_VALUE, __version__
119131
from everett.manager import qualname
120132

121133

@@ -151,7 +163,98 @@ def upper_lower_none(arg):
151163
raise ValueError('argument must be "upper", "lower" or None')
152164

153165

154-
class AutoConfigDirective(Directive):
166+
class EverettComponent(ObjectDescription):
167+
"""
168+
Description of an Everett component""
169+
"""
170+
171+
doc_field_types = [
172+
TypedField('options', label=l_('Options'),
173+
names=('option', 'opt'),
174+
typerolename='obj', typenames=('parser',),
175+
can_collapse=True),
176+
]
177+
178+
allow_nesting = False
179+
180+
def handle_signature(self, sig, signode):
181+
if sig != 'Configuration':
182+
# Add "component" to the beginning if it's a specific component
183+
signode.clear()
184+
185+
# Add "component" which is the type of this thing
186+
signode += addnodes.desc_annotation('component ', 'component ')
187+
188+
if '.' in sig:
189+
modname, clsname = sig.rsplit('.', 1)
190+
else:
191+
modname, clsname = '', sig
192+
193+
# If there's a module name, then we add the module
194+
if modname:
195+
signode += addnodes.desc_addname(modname + '.', modname + '.')
196+
197+
# Add the class name
198+
signode += addnodes.desc_name(clsname, clsname)
199+
else:
200+
# Add just "Configuration"
201+
signode += addnodes.desc_name(sig, sig)
202+
203+
return sig
204+
205+
def add_target_and_index(self, name, sig, signode):
206+
targetname = '%s-%s' % (self.objtype, name)
207+
208+
if targetname not in self.state.document.ids:
209+
signode['names'].append(targetname)
210+
signode['ids'].append(targetname)
211+
signode['first'] = (not self.names)
212+
self.state.document.note_explicit_target(signode)
213+
214+
objects = self.env.domaindata['everett']['objects']
215+
key = (self.objtype, name)
216+
if key in objects:
217+
self.state_machine.reporter.warning(
218+
'duplicate description of %s %s, ' % (self.objtype, name) +
219+
'other instance in ' + self.env.doc2path(objects[key]),
220+
line=self.lineno
221+
)
222+
objects[key] = self.env.docname
223+
224+
indextext = _('%s (component)') % name
225+
self.indexnode['entries'].append(('single', indextext, targetname, '', None))
226+
227+
228+
class EverettDomain(Domain):
229+
"""Everett domain for component configuration"""
230+
name = 'everett'
231+
label = 'Everett'
232+
233+
object_types = {
234+
'component': ObjType(l_('component'), 'comp'),
235+
}
236+
directives = {
237+
'component': EverettComponent,
238+
}
239+
roles = {
240+
'comp': XRefRole(),
241+
}
242+
initial_data = {
243+
'objects': {},
244+
}
245+
246+
def clear_doc(self, docname):
247+
for (typ, name), doc in list(self.data['objects'].items()):
248+
if doc == docname:
249+
del self.data['objects'][typ, name]
250+
251+
def merge_domaindata(self, docnames, otherdata):
252+
for (typ, name), doc in otherdata['objects'].items():
253+
if doc in docnames:
254+
self.data['objects'][typ, name] = doc
255+
256+
257+
class AutoComponentDirective(Directive):
155258
has_content = True
156259
required_arguments = 1
157260
optional_arguments = 0
@@ -185,11 +288,11 @@ def generate_docs(self, clspath, more_content):
185288
# Add the classname or 'Configuration'
186289
if 'hide-classname' not in self.options:
187290
modname, clsname = split_clspath(clspath)
188-
self.add_line('.. %s:%s:: %s.%s' % ('py', 'class', modname, clsname),
189-
sourcename)
190-
indent = ' '
291+
self.add_line('.. everett:component:: %s.%s' % (modname, clsname), sourcename)
191292
else:
192-
indent = ''
293+
self.add_line('.. everett:component:: Configuration', sourcename)
294+
295+
indent = ' '
193296
self.add_line('', sourcename)
194297

195298
# Add the docstring if there is one and if show-docstring
@@ -212,9 +315,6 @@ def generate_docs(self, clspath, more_content):
212315
config = obj.get_required_config()
213316

214317
if config.options:
215-
self.add_line(indent + 'Configuration:', '')
216-
self.add_line('', '')
217-
218318
sourcename = 'class definition'
219319

220320
for option in config:
@@ -229,17 +329,16 @@ def generate_docs(self, clspath, more_content):
229329
elif self.options['case'] == 'lower':
230330
namespaced_key = namespaced_key.lower()
231331

232-
self.add_line('%s ``%s``' % (indent, namespaced_key), sourcename)
233-
if option.default is NO_VALUE:
234-
self.add_line('%s :default: ' % indent, sourcename)
235-
else:
236-
self.add_line('%s :default: ``%r``' % (indent, option.default),
237-
sourcename)
238-
239-
self.add_line('%s :parser: %s' % (indent, qualname(option.parser)),
240-
sourcename)
241-
self.add_line('', '')
242-
self.add_line('%s %s' % (indent, option.doc), sourcename)
332+
self.add_line('%s:option %s %s:' % (
333+
indent, qualname(option.parser), namespaced_key), sourcename
334+
)
335+
self.add_line('%s %s' % (indent, option.doc), sourcename)
336+
if option.default is not NO_VALUE:
337+
self.add_line('', '')
338+
self.add_line(
339+
'%s Defaults to ``%r``.' % (indent, option.default),
340+
sourcename
341+
)
243342
self.add_line('', '')
244343

245344
def run(self):
@@ -258,4 +357,11 @@ def run(self):
258357

259358

260359
def setup(app):
261-
app.add_directive('autoconfig', AutoConfigDirective)
360+
app.add_domain(EverettDomain)
361+
app.add_directive('autocomponent', AutoComponentDirective)
362+
363+
return {
364+
'version': __version__,
365+
'parallel_read_safe': True,
366+
'parallel_write_safe': True
367+
}

0 commit comments

Comments
 (0)