Skip to content

Commit d8ac755

Browse files
authored
✨ New config needscfg_exclude_defaults (#20)
Excludes needs configuration that are set to their default. Useful to unclutter the output TOML file when needscfg_write_all is used.
1 parent 68e2bcb commit d8ac755

File tree

6 files changed

+134
-90
lines changed

6 files changed

+134
-90
lines changed

docs/configuration.rst

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,59 @@ Both formats work - if a ``[needs]`` table exists, only its contents are merged.
320320
Use this feature to separate dynamic configuration (like version numbers or build metadata)
321321
from static Sphinx-Needs configuration in ``conf.py``.
322322

323+
.. _`config_exclude_defaults`:
324+
325+
needscfg_exclude_defaults
326+
-------------------------
327+
328+
**Type:** ``bool``
329+
330+
**Default:** ``False``
331+
332+
Controls whether to exclude configuration options that are set to their default values.
333+
334+
When enabled, the extension compares each Sphinx-Needs configuration value with its default
335+
value. If they match, the option is excluded from the output file.
336+
337+
**Behavior:**
338+
339+
- ``False`` (default): Include all configuration values (subject to :ref:`config_write_all` and :ref:`config_exclude_vars`)
340+
- ``True``: Exclude configuration values that match their default values
341+
342+
**Use cases:**
343+
344+
- Generate cleaner configuration files with only explicitly set values
345+
- Reduce noise in version-controlled configuration files
346+
- Make it easier to see what's been customized vs. defaults
347+
- Minimize file size for generated configuration
348+
349+
**Examples:**
350+
351+
.. code-block:: python
352+
353+
# Include all values, even defaults (default behavior)
354+
needscfg_exclude_defaults = False
355+
356+
# Exclude values that match defaults
357+
needscfg_exclude_defaults = True
358+
needscfg_write_all = True # Usually combined with write_all
359+
360+
.. note::
361+
362+
This option works in combination with :ref:`config_write_all`:
363+
364+
- When ``needscfg_write_all = False``: Only explicitly set values are included (default behavior)
365+
- When ``needscfg_write_all = True`` and ``needscfg_exclude_defaults = False``:
366+
All values including defaults are included
367+
- When ``needscfg_write_all = True`` and ``needscfg_exclude_defaults = True``:
368+
All values are considered but defaults are filtered out
369+
370+
.. tip::
371+
372+
Enable both ``needscfg_write_all`` and ``needscfg_exclude_defaults`` to generate
373+
configuration that includes all customized values while excluding unchanged defaults.
374+
This provides a clean view of what's been explicitly configured.
375+
323376
Examples
324377
--------
325378

needs_config_writer/builder.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ def sort_for_reproducibility(obj: Any, path: str = "") -> Any:
228228
safe_value = get_safe_config(value, f"needs.{config_name}")
229229
# Only include serializable values (None means filtered out)
230230
if safe_value is not None:
231+
# Check if we should exclude default values
232+
if config.needscfg_exclude_defaults and attribute in config._options:
233+
# Get the default value from config options registry
234+
default_value = config._options[attribute].default
235+
# Compare current value with default value
236+
if value == default_value:
237+
continue
238+
231239
if config.needscfg_write_all:
232240
need_attributes[config_name] = safe_value
233241
else:

needs_config_writer/main.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,13 @@ def setup(app: Sphinx):
7979
types=[list],
8080
description="List of TOML file paths to shallow-merge into the output configuration.",
8181
)
82+
app.add_config_value(
83+
"needscfg_exclude_defaults",
84+
False,
85+
"html",
86+
types=[bool],
87+
description="Whether to exclude configuration options that are set to their default values.",
88+
)
8289

8390
# env-before-read-docs is always called (even when no docs changed),
8491
# runs after Sphinx-Needs has injected configuration, and runs before

tests/complex_setups/project/conf.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,6 @@
1717

1818
needscfg_warn_on_diff = True
1919
"""Be sure to update this - running Sphinx with -W will fail the CI, that's wanted."""
20+
21+
needscfg_exclude_defaults = True
22+
"""Do not write default values into the generated config file."""

tests/complex_setups/project/ubproject.toml

Lines changed: 0 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -3,78 +3,10 @@
33
# Do not manually modify it - changes will be overwritten.
44

55
[needs]
6-
allow_unsafe_filters = false
76
build_json = true
8-
build_json_per_id = false
9-
build_json_per_id_path = "needs_id"
10-
build_needumls = ""
11-
builder_filter = "is_external==False"
12-
completion_option = "completion"
13-
constraints_failed_color = ""
14-
css = "modern.css"
15-
debug_filters = false
16-
debug_measurement = false
17-
default_layout = "clean"
18-
diagram_template = "\n{%- if is_need -%}\n<size:12>{{type_name}}</size>\\n**{{title|wordwrap(15, wrapstring='**\\\\n**')}}**\\n<size:10>{{id}}</size>\n{%- else -%}\n<size:12>{{type_name}} (part)</size>\\n**{{content|wordwrap(15, wrapstring='**\\\\n**')}}**\\n<size:10>{{id_parent}}.**{{id}}**</size>\n{%- endif -%}\n"
19-
duration_option = "duration"
20-
external_needs = []
21-
flow_engine = "plantuml"
22-
flow_link_types = [
23-
"links",
24-
]
25-
flow_show_links = false
26-
functions = []
27-
id_from_title = false
28-
id_length = 5
297
id_regex = "^[A-Z0-9_]{3,}"
308
id_required = true
31-
include_needs = true
32-
json_exclude_fields = [
33-
"collapse",
34-
"hide",
35-
"id_complete",
36-
"id_parent",
37-
"is_need",
38-
"is_part",
39-
"lineno_content",
40-
"type_color",
41-
"type_prefix",
42-
"type_style",
43-
]
44-
json_remove_defaults = false
45-
max_title_length = -1
46-
needextend_strict = false
47-
part_prefix = "→ "
48-
permalink_data = "needs.json"
49-
permalink_file = "permalink.html"
50-
report_dead_links = true
51-
report_template = ""
52-
reproducible_json = false
53-
role_need_max_title_length = 30
54-
role_need_template = "{title} ({id})"
559
schema_debug_active = true
56-
schema_debug_ignore = [
57-
"extra_option_success",
58-
"extra_link_success",
59-
"select_success",
60-
"select_fail",
61-
"local_success",
62-
"network_local_success",
63-
]
64-
schema_debug_path = "schema_debug"
65-
schema_severity = "info"
66-
service_all_data = false
67-
show_link_id = true
68-
show_link_title = false
69-
show_link_type = false
70-
statuses = []
71-
table_classes = []
72-
table_columns = "ID;TITLE;STATUS;TYPE;OUTGOING;TAGS"
73-
table_style = "DATATABLES"
74-
tags = []
75-
template_folder = "needs_templates/"
76-
title_from_content = false
77-
title_optional = false
7810
types = [
7911
{ color = "#888888", directive = "commit", prefix = "C_", style = "card", title = "Commit" },
8012
{ directive = "feat", prefix = "FEAT_", title = "Feature" },
@@ -83,14 +15,6 @@ types = [
8315
{ color = "#aaaaaa", directive = "pr", prefix = "PR_", style = "card", title = "PullRequest" },
8416
{ directive = "req", prefix = "REQ_", title = "Requirement" },
8517
]
86-
variant_options = []
87-
warnings_always_warn = false
88-
89-
[needs.constraint_failed_options]
90-
91-
[needs.constraints]
92-
93-
[needs.extensions]
9418

9519
[[needs.extra_links]]
9620
color = "#000000"
@@ -137,8 +61,6 @@ maximum = 5
13761
minimum = 1
13862
type = "integer"
13963

140-
[needs.filter_data]
141-
14264
[needs.flow_configs]
14365
cplant = "\n ' CPLANT by AOKI (https://github.com/aoki/cplant)\n !define BLACK #363D5D\n !define RED #F6363F\n !define PINK #F6216E\n !define MAGENTA #A54FBD\n !define GREEN #37A77C\n !define YELLOW #F97A00\n !define BLUE #1E98F2\n !define CYAN #25AFCA\n !define WHITE #FEF2DC\n\n ' Base Setting\n skinparam Shadowing false\n skinparam BackgroundColor transparent\n skinparam ComponentStyle uml2\n skinparam Default {\n FontName 'Hiragino Sans'\n FontColor BLACK\n FontSize 10\n FontStyle plain\n }\n\n skinparam Sequence {\n ArrowThickness 1\n ArrowColor RED\n ActorBorderThickness 1\n LifeLineBorderColor GREEN\n ParticipantBorderThickness 0\n }\n skinparam Participant {\n BackgroundColor BLACK\n BorderColor BLACK\n FontColor #FFFFFF\n }\n\n skinparam Actor {\n BackgroundColor BLACK\n BorderColor BLACK\n }\n "
14466
handwritten = "\n skinparam handwritten true\n "
@@ -149,8 +71,6 @@ tne = "\n ' Based on \"Tomorrow night eighties\" color theme (see https://git
14971
toptobottom = "\n top to bottom direction\n "
15072
transparent = "\n skinparam backgroundcolor transparent\n "
15173

152-
[needs.global_options]
153-
15474
[needs.graphviz_styles.default.edge]
15575
minlen = "2"
15676

@@ -359,8 +279,6 @@ meta = [
359279
"<<meta_links_all()>>",
360280
]
361281

362-
[needs.render_context]
363-
364282
[needs.schema_definitions."$defs".safe-need]
365283
required = [
366284
"asil",
@@ -405,11 +323,3 @@ enum = [
405323
"D",
406324
]
407325
type = "string"
408-
409-
[needs.services]
410-
411-
[needs.string_links]
412-
413-
[needs.variants]
414-
415-
[needs.warnings]

tests/test_basic.py

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,3 +1207,66 @@ def test_merge_toml_missing_file_warning(
12071207
assert path_ubproject.exists()
12081208

12091209
app.cleanup()
1210+
1211+
1212+
def test_exclude_defaults(tmpdir, make_app, write_fixture_files, snapshot):
1213+
"""
1214+
Test that needscfg_exclude_defaults excludes options set to their default values.
1215+
"""
1216+
conf_py = textwrap.dedent(
1217+
"""
1218+
extensions = ["sphinx_needs", "needs_config_writer"]
1219+
1220+
# Enable writing all options
1221+
needscfg_write_all = True
1222+
1223+
# Enable excluding defaults
1224+
needscfg_exclude_defaults = True
1225+
1226+
# Customize only one option - leave others at defaults
1227+
needs_types = [
1228+
{"directive": "story", "title": "User Story", "prefix": "ST_", "color": "#BFD8D2"},
1229+
{"directive": "spec", "title": "Specification", "prefix": "SP_", "color": "#FEDCD2"},
1230+
]
1231+
# set explicitly to default value
1232+
needs_id_regex = "^[A-Z0-9_]{5,}"
1233+
"""
1234+
)
1235+
index_rst = textwrap.dedent(
1236+
"""
1237+
Test Exclude Defaults
1238+
=====================
1239+
1240+
.. story:: My Story
1241+
:id: STORY_001
1242+
:tags: test
1243+
"""
1244+
)
1245+
file_contents: dict[str, str] = {
1246+
"conf": conf_py,
1247+
"rst": index_rst,
1248+
}
1249+
write_fixture_files(tmpdir, file_contents)
1250+
1251+
app: SphinxTestApp = make_app(srcdir=Path(tmpdir), freshenv=True)
1252+
app.build()
1253+
1254+
assert app.statuscode == 0
1255+
1256+
path_ubproject = Path(app.builder.outdir, "ubproject.toml")
1257+
assert path_ubproject.exists()
1258+
1259+
content = path_ubproject.read_text()
1260+
1261+
# The customized option should be present
1262+
assert 'directive = "story"' in content
1263+
assert 'title = "Specification"' in content
1264+
1265+
# Default values should NOT be present when needscfg_exclude_defaults is True
1266+
# Check for some common Sphinx-Needs options that have defaults
1267+
# These would normally be included with needscfg_write_all=True
1268+
assert "statuses" not in content # Has default value
1269+
assert "tags" not in content # Has default value (empty list)
1270+
assert "id_regex" not in content # Has default value (regex pattern)
1271+
1272+
app.cleanup()

0 commit comments

Comments
 (0)