Skip to content

Commit

Permalink
🔧 Store description for each extra need option (#1225)
Browse files Browse the repository at this point in the history
This allows their "origin" to be tracked, and thus to be output as part of a `needs.json` in a later PR
  • Loading branch information
chrisjsewell authored Aug 21, 2024
1 parent 87d51d5 commit 8ede7ca
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 18 deletions.
11 changes: 5 additions & 6 deletions sphinx_needs/api/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@

from typing import Callable

from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.util.logging import SphinxLoggerAdapter

from sphinx_needs.api.exceptions import NeedsApiConfigException, NeedsApiConfigWarning
from sphinx_needs.api.exceptions import NeedsApiConfigException
from sphinx_needs.config import NEEDS_CONFIG, NeedsSphinxConfig
from sphinx_needs.data import NeedsInfoType
from sphinx_needs.functions import register_func
Expand Down Expand Up @@ -85,7 +84,9 @@ def add_need_type(
app.add_directive(directive, sphinx_needs.directives.need.NeedDirective)


def add_extra_option(app: Sphinx, name: str) -> None:
def add_extra_option(
app: Sphinx, name: str, *, description: str = "Added by add_extra_option API"
) -> None:
"""
Adds an extra option to the configuration. This option can then later be used inside needs or ``add_need``.
Expand All @@ -101,9 +102,7 @@ def add_extra_option(app: Sphinx, name: str) -> None:
:param name: Name as string of the extra option
:return: None
"""
if name in NEEDS_CONFIG.extra_options:
raise NeedsApiConfigWarning(f"Option {name} already registered.")
NEEDS_CONFIG.extra_options[name] = directives.unchanged
NEEDS_CONFIG.add_extra_option(name, description)


def add_dynamic_function(
Expand Down
36 changes: 33 additions & 3 deletions sphinx_needs/config.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from __future__ import annotations

from dataclasses import MISSING, dataclass, field, fields
from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, TypedDict
from typing import TYPE_CHECKING, Any, Callable, Dict, Literal, Mapping, TypedDict

from docutils.parsers.rst import directives
from sphinx.application import Sphinx
from sphinx.config import Config as _SphinxConfig

Expand All @@ -15,6 +16,16 @@
from sphinx_needs.data import NeedsInfoType


@dataclass
class ExtraOptionParams:
"""Defines a single extra option for needs"""

description: str
"""A description of the option."""
validator: Callable[[str | None], str]
"""A function to validate the directive option value."""


class Config:
"""
Stores sphinx-needs specific configuration values.
Expand All @@ -26,7 +37,7 @@ class Config:
"""

def __init__(self) -> None:
self._extra_options: dict[str, Callable[[str], Any]] = {}
self._extra_options: dict[str, ExtraOptionParams] = {}
self._warnings: dict[
str, str | Callable[[NeedsInfoType, SphinxLoggerAdapter], bool]
] = {}
Expand All @@ -36,7 +47,7 @@ def clear(self) -> None:
self._warnings = {}

@property
def extra_options(self) -> dict[str, Callable[[str], Any]]:
def extra_options(self) -> Mapping[str, ExtraOptionParams]:
"""Options that are dynamically added to `NeedDirective` & `NeedserviceDirective`,
after the config is initialized.
Expand All @@ -46,6 +57,25 @@ def extra_options(self) -> dict[str, Callable[[str], Any]]:
"""
return self._extra_options

def add_extra_option(
self,
name: str,
description: str,
*,
validator: Callable[[str | None], str] | None = None,
override: bool = False,
) -> None:
"""Adds an extra option to the configuration."""
if not override and name in self._extra_options:
from sphinx_needs.api.exceptions import (
NeedsApiConfigWarning, # avoid circular import
)

raise NeedsApiConfigWarning(f"Option {name} already registered.")
self._extra_options[name] = ExtraOptionParams(
description, directives.unchanged if validator is None else validator
)

@property
def warnings(
self,
Expand Down
22 changes: 16 additions & 6 deletions sphinx_needs/needs.py
Original file line number Diff line number Diff line change
Expand Up @@ -371,12 +371,18 @@ def load_config(app: Sphinx, *_args: Any) -> None:
type="needs",
subtype="config",
)
NEEDS_CONFIG.extra_options[option] = directives.unchanged
NEEDS_CONFIG.add_extra_option(
option, "Added by needs_extra_options config", override=True
)

# ensure options for ``needgantt`` functionality are added to the extra options
for option in (needs_config.duration_option, needs_config.completion_option):
if option not in NEEDS_CONFIG.extra_options:
NEEDS_CONFIG.extra_options[option] = directives.unchanged_required
NEEDS_CONFIG.add_extra_option(
option,
"Added for needgantt functionality",
validator=directives.unchanged_required,
)

# Get extra links and create a dictionary of needed options.
extra_links_raw = needs_config.extra_links
Expand All @@ -388,8 +394,12 @@ def load_config(app: Sphinx, *_args: Any) -> None:
title_from_content = needs_config.title_from_content

# Update NeedDirective to use customized options
NeedDirective.option_spec.update(NEEDS_CONFIG.extra_options)
NeedserviceDirective.option_spec.update(NEEDS_CONFIG.extra_options)
NeedDirective.option_spec.update(
{k: v.validator for k, v in NEEDS_CONFIG.extra_options.items()}
)
NeedserviceDirective.option_spec.update(
{k: v.validator for k, v in NEEDS_CONFIG.extra_options.items()}
)

# Update NeedDirective to use customized links
NeedDirective.option_spec.update(extra_links)
Expand Down Expand Up @@ -433,8 +443,8 @@ def load_config(app: Sphinx, *_args: Any) -> None:
for key, value in NEEDS_CONFIG.extra_options.items():
NeedextendDirective.option_spec.update(
{
key: value,
f"+{key}": value,
key: value.validator,
f"+{key}": value.validator,
f"-{key}": directives.flag,
}
)
Expand Down
7 changes: 4 additions & 3 deletions sphinx_needs/services/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

from typing import Any

from docutils.parsers.rst import directives
from sphinx.application import Sphinx

from sphinx_needs.api.configuration import NEEDS_CONFIG
Expand Down Expand Up @@ -32,10 +31,12 @@ def register(self, name: str, klass: type[BaseService], **kwargs: Any) -> None:
for option in klass.options:
if option not in NEEDS_CONFIG.extra_options:
self.log.debug(f'Register option "{option}" for service "{name}"')
NEEDS_CONFIG.extra_options[option] = directives.unchanged
NEEDS_CONFIG.add_extra_option(option, f"Added by service {name}")
# Register new option directly in Service directive, as its class options got already
# calculated
NeedserviceDirective.option_spec[option] = directives.unchanged
NeedserviceDirective.option_spec[option] = NEEDS_CONFIG.extra_options[
option
].validator

# Init service with custom config
self.services[name] = klass(self.app, name, config, **kwargs)
Expand Down

0 comments on commit 8ede7ca

Please sign in to comment.