Skip to content

Commit d03b2c7

Browse files
authored
Merge pull request #45 from regisb/regisb/settings-annotations
[BD-21] Sphinx extension and configuration file for setting annotations
2 parents e82dbf0 + e10a9c6 commit d03b2c7

File tree

12 files changed

+233
-48
lines changed

12 files changed

+233
-48
lines changed

CHANGELOG.rst

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ Change Log
1111

1212
.. There should always be an "Unreleased" section for changes pending release.
1313
14+
[0.8.0] - 2020-09-10
15+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16+
17+
* Add ``settings`` Sphinx extension with setting annotation configuration file
18+
1419
[0.7.0] - 2020-09-07
1520
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1621

@@ -30,7 +35,7 @@ Change Log
3035
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3136

3237
* Add ``featuretoggles`` Sphinx extension
33-
* Include ``config_and_tools`` folder in pip-installable package
38+
* Include ``contrib`` folder in pip-installable package
3439
* Add ADR 0001-config-and-tools.rst for adding a place in this repository for shared annotation configs and supporting tools.
3540

3641
[0.4.0] - 2020-07-22

README.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ annotating code which stores personally identifiable information (PII), these
1515
tools are optimized for that use case but can be generalized for other types of
1616
annotations.
1717

18-
Additionally, a logically separate part of this repository will contain specific annotation configurations and supporting tools, such as Sphinx extensions for documenting specific annotation types. See ``config_and_tools``.
18+
Additionally, a logically separate part of this repository will contain specific annotation configurations and supporting tools, such as Sphinx extensions for documenting specific annotation types. See the ``contrib`` folder.
1919

2020
Documentation
2121
-------------

code_annotations/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22
Extensible tools for parsing annotations in codebases.
33
"""
44

5-
__version__ = '0.7.0'
5+
__version__ = '0.8.0'
File renamed without changes.

code_annotations/config_and_tools/config/feature_toggle_annotations.yaml renamed to code_annotations/contrib/config/feature_toggle_annotations.yaml

File renamed without changes.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
source_path: ./
2+
report_path: reports
3+
safelist_path: .annotation_safe_list.yml
4+
annotations:
5+
setting:
6+
- ".. setting_name:":
7+
- ".. setting_default:":
8+
- ".. setting_description:":
9+
- ".. setting_warning:":
10+
extensions:
11+
python:
12+
- py
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""
2+
Sphinx extensions for generating human-readable documentation from code annotations.
3+
"""
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
"""
2+
Base utilities for building annotation-based Sphinx extensions.
3+
"""
4+
5+
6+
from code_annotations.base import AnnotationConfig
7+
from code_annotations.find_static import StaticSearch
8+
9+
10+
def find_annotations(source_path, config_path, group_by_key):
11+
"""
12+
Find the feature toggles as defined in the configuration file.
13+
14+
Return:
15+
toggles (dict): feature toggles indexed by name.
16+
"""
17+
config = AnnotationConfig(
18+
config_path, verbosity=0, source_path_override=source_path
19+
)
20+
results = StaticSearch(config).search()
21+
22+
toggles = {}
23+
current_entry = {}
24+
for filename, entries in results.items():
25+
for entry in entries:
26+
key = entry["annotation_token"]
27+
value = entry["annotation_data"]
28+
if key == group_by_key:
29+
toggle_name = value
30+
toggles[toggle_name] = {
31+
"filename": filename,
32+
"line_number": entry["line_number"],
33+
}
34+
current_entry = toggles[toggle_name]
35+
else:
36+
current_entry[key] = value
37+
38+
return toggles
39+
40+
41+
def quote_value(value):
42+
"""
43+
Quote a Python object if it is string-like.
44+
"""
45+
if value in ("True", "False", "None"):
46+
return str(value)
47+
try:
48+
float(value)
49+
return str(value)
50+
except ValueError:
51+
pass
52+
if isinstance(value, str):
53+
return '"{}"'.format(value)
54+
return str(value)

code_annotations/config_and_tools/sphinx/extensions/featuretoggles.py renamed to code_annotations/contrib/sphinx/extensions/featuretoggles.py

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55

66
import pkg_resources
77

8-
from code_annotations.base import AnnotationConfig
9-
from code_annotations.find_static import StaticSearch
108
from docutils import nodes
119
from sphinx.util.docutils import SphinxDirective
1210

11+
from .base import find_annotations, quote_value
12+
1313

1414
def find_feature_toggles(source_path):
1515
"""
@@ -20,30 +20,9 @@ def find_feature_toggles(source_path):
2020
"""
2121
config_path = pkg_resources.resource_filename(
2222
"code_annotations",
23-
os.path.join("config_and_tools", "config", "feature_toggle_annotations.yaml"),
24-
)
25-
config = AnnotationConfig(
26-
config_path, verbosity=0, source_path_override=source_path
23+
os.path.join("contrib", "config", "feature_toggle_annotations.yaml"),
2724
)
28-
results = StaticSearch(config).search()
29-
30-
toggles = {}
31-
current_entry = {}
32-
for filename, entries in results.items():
33-
for entry in entries:
34-
key = entry["annotation_token"]
35-
value = entry["annotation_data"]
36-
if key == ".. toggle_name:":
37-
toggle_name = value
38-
toggles[toggle_name] = {
39-
"filename": filename,
40-
"line_number": entry["line_number"],
41-
}
42-
current_entry = toggles[toggle_name]
43-
else:
44-
current_entry[key] = value
45-
46-
return toggles
25+
return find_annotations(source_path, config_path, ".. toggle_name:")
4726

4827

4928
class FeatureToggles(SphinxDirective):
@@ -121,22 +100,6 @@ def iter_nodes(self):
121100
)
122101

123102

124-
def quote_value(value):
125-
"""
126-
Quote a Python object if it is string-like.
127-
"""
128-
if value in ("True", "False", "None"):
129-
return str(value)
130-
try:
131-
float(value)
132-
return str(value)
133-
except ValueError:
134-
pass
135-
if isinstance(value, str):
136-
return '"{}"'.format(value)
137-
return str(value)
138-
139-
140103
def setup(app):
141104
"""
142105
Declare the Sphinx extension.
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""
2+
Sphinx extension for viewing (non-toggle) setting annotations.
3+
"""
4+
import os
5+
6+
import pkg_resources
7+
8+
from docutils import nodes
9+
from docutils.parsers.rst import directives
10+
from sphinx.util.docutils import SphinxDirective
11+
12+
from .base import find_annotations, quote_value
13+
14+
15+
def find_settings(source_path):
16+
"""
17+
Find the Django settings as defined in the configuration file.
18+
19+
Return:
20+
settings (dict): Django settings indexed by name.
21+
"""
22+
config_path = pkg_resources.resource_filename(
23+
"code_annotations",
24+
os.path.join("contrib", "config", "setting_annotations.yaml"),
25+
)
26+
return find_annotations(source_path, config_path, ".. setting_name:")
27+
28+
29+
class Settings(SphinxDirective):
30+
"""
31+
Sphinx directive to document Django settings in a single documentation page.
32+
33+
Use this directive as follows::
34+
35+
.. settings::
36+
:folder_path: lms/envs/common.py
37+
38+
This directive supports the following configuration parameters:
39+
40+
- ``settings_source_path``: absolute path to the repository file tree. E.g:
41+
42+
settings_source_path = os.path.join(os.path.dirname(__file__), "..", "..")
43+
44+
- ``settings_repo_url``: Github repository where the code is hosted. E.g:
45+
46+
settings_repo_url = "https://github.com/edx/myrepo"
47+
48+
- ``settings_repo_version``: current version of the git repository. E.g:
49+
50+
import git
51+
try:
52+
repo = git.Repo(search_parent_directories=True)
53+
settings_repo_version = repo.head.object.hexsha
54+
except git.InvalidGitRepositoryError:
55+
settings_repo_version = "master"
56+
"""
57+
58+
required_arguments = 0
59+
optional_arguments = 1
60+
option_spec = {"folder_path": directives.unchanged}
61+
62+
def run(self):
63+
"""
64+
Public interface of the Directive class.
65+
66+
Return:
67+
nodes (list): nodes to be appended to the resulting document.
68+
"""
69+
toggle_nodes = list(self.iter_nodes())
70+
return [nodes.section("", *toggle_nodes, ids=["settings"])]
71+
72+
def iter_nodes(self):
73+
"""
74+
Iterate on the docutils nodes generated by this directive.
75+
"""
76+
source_path = os.path.join(
77+
self.env.config.settings_source_path, self.options.get("folder_path", "")
78+
)
79+
settings = find_settings(source_path)
80+
for setting_name in sorted(settings):
81+
setting = settings[setting_name]
82+
yield nodes.title(text=setting_name)
83+
setting_default_value = setting.get(".. setting_default:", "Not defined")
84+
setting_default_node = nodes.literal(
85+
text=quote_value(setting_default_value)
86+
)
87+
yield nodes.paragraph("", "Default: ", setting_default_node)
88+
yield nodes.paragraph(
89+
"",
90+
"Source: ",
91+
nodes.reference(
92+
text="{} (line {})".format(
93+
setting["filename"], setting["line_number"]
94+
),
95+
refuri="{}/blob/{}/{}#L{}".format(
96+
self.env.config.settings_repo_url,
97+
self.env.config.settings_repo_version,
98+
setting["filename"],
99+
setting["line_number"],
100+
),
101+
),
102+
)
103+
yield nodes.paragraph(text=setting.get(".. setting_description:", ""))
104+
if setting.get(".. setting_warning:") not in (None, "None", "n/a", "N/A"):
105+
yield nodes.warning(
106+
"", nodes.paragraph("", setting[".. setting_warning:"])
107+
)
108+
109+
110+
def setup(app):
111+
"""
112+
Declare the Sphinx extension.
113+
"""
114+
app.add_config_value(
115+
"settings_source_path", os.path.abspath(".."), "env",
116+
)
117+
app.add_config_value("settings_repo_url", "", "env")
118+
app.add_config_value("settings_repo_version", "master", "env")
119+
app.add_directive("settings", Settings)
120+
121+
return {
122+
"version": "0.1",
123+
"parallel_read_safe": True,
124+
"parallel_write_safe": True,
125+
}

0 commit comments

Comments
 (0)