diff --git a/docs/conf.py b/docs/conf.py index 82d81816c..7af6a7b5a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -265,6 +265,7 @@ r"http://localhost:\d+", r"http://127.0.0.1:\d+", r"../.*", + r"http://sourceforge.net/projects/plantuml.*", r"https?://useblocks.com/sphinx-needs/bench/index.html", ] @@ -288,339 +289,11 @@ # -- Options for Needs extension --------------------------------------- +needs_from_toml = "ubproject.toml" + needs_debug_measurement = "READTHEDOCS" in os.environ # run on CI needs_debug_filters = True -needs_types = [ - # Architecture types - { - "directive": "int", - "title": "Interface", - "content": "plantuml", - "prefix": "I_", - "color": "#BFD8D2", - "style": "card", - }, - { - "directive": "comp", - "title": "Component", - "content": "plantuml", - "prefix": "C_", - "color": "#BFD8D2", - "style": "card", - }, - { - "directive": "sys", - "title": "System", - "content": "plantuml", - "prefix": "S_", - "color": "#BFD8D2", - "style": "card", - }, - # Normal types - { - "directive": "req", - "title": "Requirement", - "prefix": "R_", - "color": "#BFD8D2", - "style": "node", - }, - { - "directive": "spec", - "title": "Specification", - "prefix": "S_", - "color": "#FEDCD2", - "style": "node", - }, - { - "directive": "impl", - "title": "Implementation", - "prefix": "I_", - "color": "#DF744A", - "style": "node", - }, - { - "directive": "test", - "title": "Test Case", - "prefix": "T_", - "color": "#DCB239", - "style": "node", - }, - { - "directive": "feature", - "title": "Feature", - "prefix": "F_", - "color": "#FFCC00", - "style": "node", - }, - { - "directive": "user", - "title": "User", - "prefix": "U_", - "color": "#777777", - "style": "node", - }, - { - "directive": "action", - "title": "Action", - "prefix": "A_", - "color": "#FFCC00", - "style": "node", - }, - { - "directive": "milestone", - "title": "Milestone", - "prefix": "M_", - "color": "#FF3333", - "style": "node", - }, - # for tutorial - { - "directive": "tutorial-project", - "title": "Project", - "prefix": "P_", - "color": "#BFD8D2", - "style": "rectangle", - }, - { - "directive": "tutorial-req", - "title": "Requirement", - "prefix": "R_", - "color": "#BFD8D2", - "style": "rectangle", - }, - { - "directive": "tutorial-spec", - "title": "Specification", - "prefix": "S_", - "color": "#FEDCD2", - "style": "rectangle", - }, - { - "directive": "tutorial-test", - "title": "Test Case", - "prefix": "T_", - "color": "#f9e79f", - "style": "rectangle", - }, -] - -needs_extra_links = [ - { - "option": "blocks", - "incoming": "is blocked by", - "outgoing": "blocks", - "copy": True, - "style": "#AA0000", - "style_part": "dotted,#AA0000", - "style_start": "-", - "style_end": "-o", - "allow_dead_links": True, - }, - { - "option": "tests", - "incoming": "is tested by", - "outgoing": "tests", - "copy": True, - "style": "#00AA00", - "style_part": "dotted,#00AA00", - }, - { - "option": "checks", - "incoming": "is checked by", - "outgoing": "checks", - "copy": False, - "style": "#00AA00", - "style_part": "dotted,#00AA00", - }, - { - "option": "triggers", - "incoming": "triggered by", - "outgoing": "triggers", - "copy": False, - "style": "#00AA00", - "style_part": "solid,#777777", - "allow_dead_links": True, - }, - { - "option": "starts_with", - "incoming": "triggers directly", - "outgoing": "starts with", - "copy": False, - "style": "#00AA00", - "style_part": "solid,#777777", - }, - { - "option": "starts_after", - "incoming": "triggers at end", - "outgoing": "starts after", - "copy": False, - "style": "#00AA00", - "style_part": "solid,#777777", - }, - { - "option": "ends_with", - "incoming": "triggers to end with", - "outgoing": "ends with", - "copy": False, - "style": "#00AA00", - "style_part": "solid,#777777", - }, - # for tutorial - { - "option": "tutorial_required_by", - "incoming": "requires", - "outgoing": "required by", - "style": "#00AA00", - }, - { - "option": "tutorial_specifies", - "incoming": "specified by", - "outgoing": "specifies", - }, - { - "option": "tutorial_tests", - "incoming": "tested by", - "outgoing": "tests", - }, -] - -needs_variant_options = ["status"] - -needs_flow_configs = { - "my_config": """ - skinparam monochrome true - skinparam componentStyle uml2 - """, - "another_config": """ - skinparam class { - BackgroundColor PaleGreen - ArrowColor SeaGreen - BorderColor SpringGreen - } - """, - "tutorial": """ - left to right direction - skinparam backgroundcolor transparent - skinparam Arrow { - Color #57ACDC - FontColor #808080 - FontStyle Bold - } - skinparam rectangleBorderThickness 2 - """, -} - -needs_graphviz_styles = { - "tutorial": { - "graph": { - "rankdir": "LR", - "bgcolor": "transparent", - }, - "node": { - "fontname": "sans-serif", - "fontsize": 12, - "penwidth": 2, - "margin": "0.11,0.11", - "style": "rounded", - }, - "edge": { - "color": "#57ACDC", - "fontsize": 10, - "fontcolor": "#808080", - }, - } -} - -needs_show_link_type = False -needs_show_link_title = False -needs_title_optional = True -needs_max_title_length = 75 - -needs_id_regex = "^[A-Za-z0-9_]*" -needs_id_required = False -# needs_css = "dark.css" - -needs_table_style = "datatables" -needs_table_columns = "ID;TITLE;STATUS;OUTGOING" - -needs_extra_options = [ - "my_extra_option", - "another_option", - "author", - "comment", - "amount", - "hours", - "image", - "config", - "github", - "value", - "unit", -] - -_names = [t["directive"] for t in needs_types] + ["issue", "pr", "commit"] -needs_warnings = { - "type_check": f"type not in {_names}", - # 'valid_status': 'status not in ["open", "in progress", "closed", "done", "implemented"] and status is not None' -} - -needs_default_layout = "clean" -needs_default_style = None - -needs_layouts = { - "example": { - "grid": "simple_side_right_partial", - "layout": { - "head": ['**<>** for *<>*'], - "meta": [ - '**status**: <>', - '**author**: <>', - ], - "side": ['<>'], - }, - }, - "permalink_example": { - "grid": "simple", - "layout": { - "head": [ - '<>: **<>** <> <> <> ' - ], - "meta": ["<>", "<>"], - }, - }, - "detail_view": { - "grid": "simple", - "layout": { - "head": [ - '<>: **<>** <> <> <> ' - '<>' - ], - "meta": ["<>", "<>"], - }, - }, -} - -needs_service_all_data = True - -needs_services = {} - -needs_string_links = { - "config_link": { - "regex": r"^(?P\w+)$", - "link_url": 'https://sphinxcontrib-needs.readthedocs.io/en/latest/configuration.html#{{value | replace("_", "-")}}', - "link_name": 'Sphinx-Needs docs for {{value | replace("_", "-") }}', - "options": ["config"], - }, - "github_link": { - "regex": r"^(?P\w+)$", - "link_url": "https://github.com/useblocks/sphinxcontrib-needs/issues/{{value}}", - "link_name": "GitHub #{{value}}", - "options": ["github"], - }, -} - def custom_defined_func(): return "List of contributors:" @@ -642,59 +315,8 @@ def custom_defined_func(): # }, # ] -# build needs.json to make permalinks work -needs_build_json = True -needs_reproducible_json = True -needs_json_remove_defaults = True - -# build needs_json for every needs-id to make detail panel -needs_build_json_per_id = False - -# contains different constraints -needs_constraints = { - "critical": { - "check_0": "'critical' in tags", - "check_1": "'SECURITY_REQ' in links", - "severity": "CRITICAL", - }, - "security": {"check_0": "'security' in tags", "severity": "HIGH"}, - "team": {"check_0": 'author == "Bob"', "severity": "LOW"}, -} - -# defines what to do if a constraint is not met -needs_constraint_failed_options = { - "CRITICAL": {"on_fail": ["warn"], "style": ["red_bar"], "force_style": True}, - "HIGH": {"on_fail": ["warn"], "style": ["orange_bar"], "force_style": True}, - "MEDIUM": {"on_fail": ["warn"], "style": ["yellow_bar"], "force_style": False}, - "LOW": {"on_fail": [], "style": ["yellow_bar"], "force_style": False}, -} - -NOTE_TEMPLATE = """ -.. _{{id}}: - -.. note:: {{title}} ({{id}}) - - {{content|indent(4) }} - - {% if status -%} - **status**: {{status}} - {% endif %} - - {% if tags -%} - **tags**: {{"; ".join(tags)}} - {% endif %} - - {% if links -%} - **links**: - {% for link in links -%} - :ref:`{{link}} <{{link}}>` {%if loop.index < links|length -%}; {% endif -%} - {% endfor -%} - {% endif %} -""" - -DEFAULT_DIAGRAM_TEMPLATE = "{{type_name}}\\n**{{title|wordwrap(15, wrapstring='**\\\\n**')}}**\\n{{id}}" - # You can uncomment some of the following lines to override the default configuration for Sphinx-Needs. +# DEFAULT_DIAGRAM_TEMPLATE = "{{type_name}}\\n**{{title|wordwrap(15, wrapstring='**\\\\n**')}}**\\n{{id}}" # needs_diagram_template = DEFAULT_DIAGRAM_TEMPLATE # Absolute path to the needs_report_template_file based on the conf.py directory diff --git a/docs/configuration.rst b/docs/configuration.rst index 251c164db..d699d1079 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -3,7 +3,7 @@ Configuration ============= -All configurations take place in your project's **conf.py** file. +All root configurations take place in your project's :external+sphinx:ref:`conf.py file `. Activation ---------- @@ -14,6 +14,13 @@ Add **sphinx_needs** to your extensions. extensions = ["sphinx_needs",] +Available sphinx-needs options are then listed below, that can be added to your **conf.py** file. + +.. versionadded:: 4.1.0 + + Configuration can also be specified via a `toml file `__. + See :ref:`needs_from_toml` for more details. + .. _config-warnings: Build Warnings @@ -79,6 +86,42 @@ Options All configuration options starts with the prefix ``needs_`` for **Sphinx-Needs**. +.. _needs_from_toml: + +needs_from_toml +~~~~~~~~~~~~~~~ + +.. versionadded:: 4.1.0 + +This configuration takes the (relative) path to a `toml file `__ which contains some or all of the needs configuration (configuration in the toml will override that in the :file:`conf.py`). + +.. code-block:: python + + needs_from_toml = "ubproject.toml" + +Configuration in the toml can contain any of the options below, under a ``[needs]`` section, +but with the ``needs_`` prefix removed. +For example: + +.. code-block:: toml + + [needs] + id_required = true + id_length = 3 + types = [ + {directive="req", title="Requirement", prefix="R_", color="#BFD8D2", style="node"}, + {directive="spec", title="Specification", prefix="S_", color="#FEDCD2", style="node"}, + ] + +To specify a different `table path `__ to read from in the toml file, use the ``needs_from_toml_table`` option. +For example to read from a ``[tool.needs]`` table: + +.. code-block:: python + + needs_from_toml_table = ["tool", "needs"] + +.. caution:: Any configuration specifying relative paths in the toml file will be resolved relative to the directory containing the :file:`conf.py` file. + needs_include_needs ~~~~~~~~~~~~~~~~~~~ Set this option to False, if no needs should be documented inside the generated documentation. diff --git a/docs/contributing.rst b/docs/contributing.rst index e1decc0f2..033b7a610 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -117,7 +117,7 @@ Running JS Testcases with PyTest **Setup Cypress Locally** * Install Node JS on your computer and ensure it can be accessed through the CMD. -* Install Cypress using the npm package manager by running ``npm install cypress``. Visit this link for more information on `how to install Cypress `_. +* Install Cypress using the npm package manager by running ``npm install cypress``. Visit this link for more information on `how to install Cypress `_. * Verify if Cypress is installed correctly and is executable by running: ``npx cypress verify``. Get out this page for more information about `Cypress commandline `_. * If everything is successful then we can use Cypress. diff --git a/docs/ubproject.toml b/docs/ubproject.toml new file mode 100644 index 000000000..d9e06dcbe --- /dev/null +++ b/docs/ubproject.toml @@ -0,0 +1,331 @@ +[needs] +extra_options = [ + "my_extra_option", + "another_option", + "author", + "comment", + "amount", + "hours", + "image", + "config", + "github", + "value", + "unit", +] +variant_options = ["status"] +show_link_type = false +show_link_title = false +title_optional = true +max_title_length = 75 +id_regex = "^[A-Za-z0-9_]*" +id_required = false +table_style = "datatables" +table_columns = "ID;TITLE;STATUS;OUTGOING" +default_layout = "clean" +service_all_data = true +# needs.json build options +build_json = true +reproducible_json = true +json_remove_defaults = true +build_json_per_id = false + +[[needs.types]] +directive = "int" +title = "Interface" +content = "plantuml" +prefix = "I_" +color = "#BFD8D2" +style = "card" + +[[needs.types]] +directive = "comp" +title = "Component" +content = "plantuml" +prefix = "C_" +color = "#BFD8D2" +style = "card" + +[[needs.types]] +directive = "sys" +title = "System" +content = "plantuml" +prefix = "S_" +color = "#BFD8D2" +style = "card" + +[[needs.types]] +directive = "req" +title = "Requirement" +prefix = "R_" +color = "#BFD8D2" +style = "node" + +[[needs.types]] +directive = "spec" +title = "Specification" +prefix = "S_" +color = "#FEDCD2" +style = "node" + +[[needs.types]] +directive = "impl" +title = "Implementation" +prefix = "I_" +color = "#DF744A" +style = "node" + +[[needs.types]] +directive = "test" +title = "Test Case" +prefix = "T_" +color = "#DCB239" +style = "node" + +[[needs.types]] +directive = "feature" +title = "Feature" +prefix = "F_" +color = "#FFCC00" +style = "node" + +[[needs.types]] +directive = "user" +title = "User" +prefix = "U_" +color = "#777777" +style = "node" + +[[needs.types]] +directive = "action" +title = "Action" +prefix = "A_" +color = "#FFCC00" +style = "node" + +[[needs.types]] +directive = "milestone" +title = "Milestone" +prefix = "M_" +color = "#FF3333" +style = "node" + +[[needs.types]] +directive = "tutorial-project" +title = "Project" +prefix = "P_" +color = "#BFD8D2" +style = "rectangle" + +[[needs.types]] +directive = "tutorial-req" +title = "Requirement" +prefix = "R_" +color = "#BFD8D2" +style = "rectangle" + +[[needs.types]] +directive = "tutorial-spec" +title = "Specification" +prefix = "S_" +color = "#FEDCD2" +style = "rectangle" + +[[needs.types]] +directive = "tutorial-test" +title = "Test Case" +prefix = "T_" +color = "#f9e79f" +style = "rectangle" + +[[needs.extra_links]] +option = "blocks" +incoming = "is blocked by" +outgoing = "blocks" +copy = true +style = "#AA0000" +style_part = "dotted,#AA0000" +style_start = "-" +style_end = "-o" +allow_dead_links = true + +[[needs.extra_links]] +option = "tests" +incoming = "is tested by" +outgoing = "tests" +copy = true +style = "#00AA00" +style_part = "dotted,#00AA00" + +[[needs.extra_links]] +option = "checks" +incoming = "is checked by" +outgoing = "checks" +copy = false +style = "#00AA00" +style_part = "dotted,#00AA00" + +[[needs.extra_links]] +option = "triggers" +incoming = "triggered by" +outgoing = "triggers" +copy = false +style = "#00AA00" +style_part = "solid,#777777" +allow_dead_links = true + +[[needs.extra_links]] +option = "starts_with" +incoming = "triggers directly" +outgoing = "starts with" +copy = false +style = "#00AA00" +style_part = "solid,#777777" + +[[needs.extra_links]] +option = "starts_after" +incoming = "triggers at end" +outgoing = "starts after" +copy = false +style = "#00AA00" +style_part = "solid,#777777" + +[[needs.extra_links]] +option = "ends_with" +incoming = "triggers to end with" +outgoing = "ends with" +copy = false +style = "#00AA00" +style_part = "solid,#777777" + +[[needs.extra_links]] +option = "tutorial_required_by" +incoming = "requires" +outgoing = "required by" +style = "#00AA00" + +[[needs.extra_links]] +option = "tutorial_specifies" +incoming = "specified by" +outgoing = "specifies" + +[[needs.extra_links]] +option = "tutorial_tests" +incoming = "tested by" +outgoing = "tests" + +[needs.warnings] +type_check = "type not in ['int', 'comp', 'sys', 'req', 'spec', 'impl', 'test', 'feature', 'user', 'action', 'milestone', 'tutorial-project', 'tutorial-req', 'tutorial-spec', 'tutorial-test', 'issue', 'pr', 'commit']" + +[needs.constraints.critical] +check_0 = "'critical' in tags" +check_1 = "'SECURITY_REQ' in links" +severity = "CRITICAL" + +[needs.constraints.security] +check_0 = "'security' in tags" +severity = "HIGH" + +[needs.constraints.team] +check_0 = "author == \"Bob\"" +severity = "LOW" + +[needs.constraint_failed_options.CRITICAL] +on_fail = ["warn"] +style = ["red_bar"] +force_style = true + +[needs.constraint_failed_options.HIGH] +on_fail = ["warn"] +style = ["orange_bar"] +force_style = true + +[needs.constraint_failed_options.MEDIUM] +on_fail = ["warn"] +style = ["yellow_bar"] +force_style = false + +[needs.constraint_failed_options.LOW] +on_fail = [] +style = ["yellow_bar"] +force_style = false + +[needs.flow_configs] +my_config = """ +skinparam monochrome true +skinparam componentStyle uml2 +""" +another_config = """ +skinparam class { + BackgroundColor PaleGreen + ArrowColor SeaGreen + BorderColor SpringGreen +} +""" +tutorial = """ +left to right direction +skinparam backgroundcolor transparent +skinparam Arrow { + Color #57ACDC + FontColor #808080 + FontStyle Bold +} +skinparam rectangleBorderThickness 2 +""" + +[needs.graphviz_styles.tutorial.graph] +rankdir = "LR" +bgcolor = "transparent" + +[needs.graphviz_styles.tutorial.node] +fontname = "sans-serif" +fontsize = 12 +penwidth = 2 +margin = "0.11,0.11" +style = "rounded" + +[needs.graphviz_styles.tutorial.edge] +color = "#57ACDC" +fontsize = 10 +fontcolor = "#808080" + +[needs.layouts.example] +grid = "simple_side_right_partial" + +[needs.layouts.example.layout] +head = ["**<>** for *<>*"] +meta = ["**status**: <>", "**author**: <>"] +side = ["<>"] + +[needs.layouts.permalink_example] +grid = "simple" + +[needs.layouts.permalink_example.layout] +head = [ + "<>: **<>** <> <> <> ", +] +meta = ["<>", "<>"] + +[needs.layouts.detail_view] +grid = "simple" + +[needs.layouts.detail_view.layout] +head = [ + "<>: **<>** <> <> <> <>", +] +meta = ["<>", "<>"] + +[needs.string_links.config_link] +regex = "^(?P\\w+)$" +link_url = "https://sphinxcontrib-needs.readthedocs.io/en/latest/configuration.html#{{value | replace(\"_\", \"-\")}}" +link_name = "Sphinx-Needs docs for {{value | replace(\"_\", \"-\") }}" +options = ["config"] + +[needs.string_links.github_link] +regex = "^(?P\\w+)$" +link_url = "https://github.com/useblocks/sphinxcontrib-needs/issues/{{value}}" +link_name = "GitHub #{{value}}" +options = ["github"] + +[needs_json] +path = "_build/html/furo/needs.json" +src = "." diff --git a/pyproject.toml b/pyproject.toml index 9adf4a675..5a31ee75a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,6 +39,7 @@ requests = "^2.32" # external_links jsonschema = ">=3.2.0" # needsimport schema validation sphinx-data-viewer = "^0.1.5" # needservice debug output sphinxcontrib-jquery = "^4" # needed for datatables in sphinx>=6 +tomli = { version = "*", python = "<3.11" } # [project.optional-dependencies.plotting] # for needpie / needbar @@ -149,6 +150,8 @@ module = [ "requests_file", "sphinx_data_viewer.*", "sphinxcontrib.plantuml.*", + "tomllib.*", + "tomli.*", ] ignore_missing_imports = true @@ -167,6 +170,11 @@ module = [ ] disable_error_code = ["attr-defined", "no-any-return"] +[[tool.mypy.overrides]] +module = ["sphinx_needs.needs"] +# issue with importing tomllib/tomli based on python version +disable_error_code = ["no-redef"] + [build-system] requires = ["setuptools", "poetry_core>=1.0.8"] # setuptools for deps like plantuml build-backend = "poetry.core.masonry.api" diff --git a/sphinx_needs/config.py b/sphinx_needs/config.py index 5d4f055b9..1a8524076 100644 --- a/sphinx_needs/config.py +++ b/sphinx_needs/config.py @@ -291,6 +291,12 @@ def add_config_values(cls, app: Sphinx) -> None: types=item.metadata["types"], ) + @classmethod + def field_names(cls) -> set[str]: + """Get all config fields (without ``needs_`` prefix)""" + names = [field.name for field in fields(cls)] + return {name[1:] if name.startswith("_") else name for name in names} + @classmethod def get_default(cls, name: str) -> Any: """Get the default value for a config item.""" @@ -301,6 +307,16 @@ def get_default(cls, name: str) -> Any: return _field.default_factory() return _field.default + from_toml: str | None = field( + default=None, metadata={"rebuild": "env", "types": (str, type(None))} + ) + """Path to a TOML file to load configuration from.""" + + from_toml_table: list[str] = field( + default_factory=lambda: ["needs"], metadata={"rebuild": "env", "types": (list,)} + ) + """Path to the table in the toml file to load configuration from.""" + types: list[NeedType] = field( default_factory=lambda: [ { diff --git a/sphinx_needs/needs.py b/sphinx_needs/needs.py index d56659649..e54fcd2ee 100644 --- a/sphinx_needs/needs.py +++ b/sphinx_needs/needs.py @@ -112,6 +112,11 @@ from sphinx_needs.utils import node_match from sphinx_needs.warnings import process_warnings +try: + import tomllib # added in python 3.11 +except ImportError: + import tomli as tomllib + __version__ = VERSION = "4.0.0" _NODE_TYPES_T = Dict[ @@ -256,6 +261,7 @@ def setup(app: Sphinx) -> dict[str, Any]: # EVENTS ######################################################################## # Make connections to events + app.connect("config-inited", load_config_from_toml, priority=10) # runs early app.connect("config-inited", load_config) app.connect("config-inited", check_configuration) @@ -356,6 +362,48 @@ def process_caller(app: Sphinx, doctree: nodes.document, fromdocname: str) -> No return process_caller +def load_config_from_toml(app: Sphinx, config: Config) -> None: + """ + Load config from toml file, if defined in conf.py + """ + needs_config = NeedsSphinxConfig(config) + if needs_config.from_toml is None: + return + + # resolve relative to confdir + toml_file = Path(app.confdir, needs_config.from_toml).resolve() + toml_path = needs_config.from_toml_table + + if not toml_file.exists(): + log_warning( + LOGGER, + f"'needs_from_toml' file does not exist: {toml_file}", + "config", + None, + ) + return + try: + with toml_file.open("rb") as f: + toml_data = tomllib.load(f) + for key in toml_path: + toml_data = toml_data[key] + assert isinstance(toml_data, dict), "Data must be a dict" + except Exception as e: + log_warning( + LOGGER, + f"Error loading 'needs_from_toml' file: {e}", + "config", + None, + ) + return + + allowed_keys = NeedsSphinxConfig.field_names() + for key, value in toml_data.items(): + if key not in allowed_keys: + continue + config["needs_" + key] = value + + def load_config(app: Sphinx, *_args: Any) -> None: """ Register extra options and directive based on config from conf.py diff --git a/tests/__snapshots__/test_needs_from_toml.ambr b/tests/__snapshots__/test_needs_from_toml.ambr new file mode 100644 index 000000000..dd3933cfc --- /dev/null +++ b/tests/__snapshots__/test_needs_from_toml.ambr @@ -0,0 +1,29 @@ +# name: test_needs_from_toml[test_app0] + dict({ + 'current_version': '0.1.0', + 'versions': dict({ + '0.1.0': dict({ + 'needs': dict({ + 'O_001': dict({ + 'custom': 'custom value', + 'docname': 'index', + 'external_css': 'external_link', + 'full_title': 'Test need', + 'id': 'O_001', + 'layout': '', + 'section_name': 'TEST DOCUMENT', + 'sections': list([ + 'TEST DOCUMENT', + ]), + 'status': 'open', + 'title': 'Test need', + 'type': 'other', + 'type_name': 'Another need type', + }), + }), + 'needs_amount': 1, + 'needs_defaults_removed': True, + }), + }), + }) +# --- diff --git a/tests/doc_test/needs_from_toml/a.toml b/tests/doc_test/needs_from_toml/a.toml new file mode 100644 index 000000000..764d855b2 --- /dev/null +++ b/tests/doc_test/needs_from_toml/a.toml @@ -0,0 +1,9 @@ +[needs] +id_regex = "^[A-Za-z0-9_]" +types = [ + { directive = "other", title = "Another need type", prefix = "O_", color = "#DCB239" }, +] +extra_options = ["custom"] +build_json = true +reproducible_json = true +json_remove_defaults = true diff --git a/tests/doc_test/needs_from_toml/conf.py b/tests/doc_test/needs_from_toml/conf.py new file mode 100644 index 000000000..b109dec93 --- /dev/null +++ b/tests/doc_test/needs_from_toml/conf.py @@ -0,0 +1,7 @@ +project = "needs" +version = "0.1.0" +copyright = "2024" + +extensions = ["sphinx_needs"] + +needs_from_toml = "a.toml" diff --git a/tests/doc_test/needs_from_toml/index.rst b/tests/doc_test/needs_from_toml/index.rst new file mode 100644 index 000000000..830ad9932 --- /dev/null +++ b/tests/doc_test/needs_from_toml/index.rst @@ -0,0 +1,7 @@ +TEST DOCUMENT +============= + +.. other:: Test need + :id: O_001 + :status: open + :custom: custom value diff --git a/tests/test_needs_from_toml.py b/tests/test_needs_from_toml.py new file mode 100644 index 000000000..a45a4880b --- /dev/null +++ b/tests/test_needs_from_toml.py @@ -0,0 +1,26 @@ +import json +from pathlib import Path + +import pytest +from syrupy.filters import props + + +@pytest.mark.parametrize( + "test_app", + [ + { + "buildername": "html", + "srcdir": "doc_test/needs_from_toml", + "no_plantuml": True, + } + ], + indirect=True, +) +def test_needs_from_toml(test_app, snapshot): + app = test_app + app.build() + assert not app._warning.getvalue() + data = json.loads(Path(app.outdir, "needs.json").read_text("utf8")) + assert data == snapshot( + exclude=props("created", "project", "creator", "needs_schema") + )