Path:
strictdoc/core/feature.py
Lines:
106
Non-empty lines:
90
Non-empty lines covered with requirements:
90 / 90 (100.0%)
Functions:
8
Functions covered by requirements:
8 / 8 (100.0%)
1
"""2
The Feature abstraction: facade for cross-cutting export/server3
capabilities that are not document-content conversion (see Format for4
that).5
6
@relation(SDOC-SRS-119, scope=file)7
"""8
9
from abc import ABC, abstractmethod
10
from dataclasses import dataclass
11
from typing import TYPE_CHECKING
12
13
if TYPE_CHECKING:
14
from strictdoc.core.project_config import ProjectConfig
15
from strictdoc.core.traceability_index import TraceabilityIndex
16
from strictdoc.export.html.html_templates import HTMLTemplates
17
18
19
@dataclass20
class FeatureContext:
21
"""
22
Shared, per-call state that Feature.export()/render_screen()23
implementations pull from. Deliberately separate from Format's24
ExportContext: it carries only what a Feature actually needs25
(project_config/traceability_index/html_templates), not26
Format-specific fields like parallelizer or the HTML2PDF-only27
bundle_* fields.28
"""29
30
project_config: "ProjectConfig"
31
traceability_index: "TraceabilityIndex"
32
html_templates: "HTMLTemplates"
33
34
35
class Feature(ABC):
36
HANDLE: str
37
"""
38
Reuses the existing ProjectFeature enum value string verbatim (e.g.39
"PROJECT_STATISTICS_SCREEN"), so string-based project_features=[...]40
entries keep resolving to this Feature unchanged.41
"""42
43
@staticmethod44
@abstractmethod45
def supports_export() -> bool:
46
raise NotImplementedError
47
48
def export(self, context: FeatureContext) -> None:
49
"""Only called if supports_export() is True."""
50
raise NotImplementedError(
51
f"{self.__class__.__name__} does not support export."
52
)53
54
@staticmethod55
@abstractmethod56
def supports_server() -> bool:
57
raise NotImplementedError
58
59
def screen_filename(self) -> str:
60
"""
61
Only called if supports_server() is True. The62
document_relative_path.relative_path value (e.g.63
"project_statistics.html") this Feature's screen owns, used as the64
dispatch key inside main_router.py's generate_document(). Not a65
FastAPI route: every HTML screen is served through one shared,66
cached, lock-protected route, and this filename is only the67
dispatch key into that shared machinery.68
"""69
raise NotImplementedError(
70
f"{self.__class__.__name__} does not support a server screen."
71
)72
73
def render_screen(self, context: FeatureContext) -> None:
74
"""
75
Only called if supports_server() is True. Must write the screen's76
HTML file to context.project_config.export_output_html_root, the77
same contract every generate_document() branch already follows.78
"""79
raise NotImplementedError(
80
f"{self.__class__.__name__} does not support a server screen."
81
)82
83
def screen_icon(self) -> str:
84
"""
85
Jinja template path for the left-sidebar nav icon, rendered via86
`{% include feature.screen_icon() %}`, e.g.87
"features/project_statistics/ico16_stat.svg".88
89
Resolved against the Jinja template search path, which is the90
static HTML_TEMPLATE_DIRS list in strictdoc/core/environment.py --91
the same mechanism every other feature's own .jinja templates92
already rely on. A built-in Feature's icon lives under its own93
feature's existing "features/<feature>/*.jinja" namespace inside94
its "<feature>/templates" entry (e.g.95
strictdoc/features/project_statistics/templates/features/96
project_statistics/ico16_stat.svg, alongside that feature's own97
main.jinja/index.jinja) -- no separate registration needed.98
99
TODO: a second method returning raw markup directly (for Features100
that can't register an entry in HTML_TEMPLATE_DIRS, e.g. a custom101
Feature defined outside strictdoc's own package) is a likely102
future addition, not implemented yet.103
"""104
raise NotImplementedError(
105
f"{self.__class__.__name__} does not provide a nav icon."
106
)