specware 1.2.28__tar.gz → 1.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {specware-1.2.28 → specware-1.3.0}/PKG-INFO +2 -2
- {specware-1.2.28 → specware-1.3.0}/pyproject.toml +2 -2
- {specware-1.2.28 → specware-1.3.0}/src/specware/applconfig.py +55 -34
- {specware-1.2.28 → specware-1.3.0}/src/specware/build.py +19 -5
- specware-1.3.0/src/specware/cliexport.py +399 -0
- {specware-1.2.28 → specware-1.3.0}/src/specware/cliexportheader.py +38 -25
- {specware-1.2.28 → specware-1.3.0}/src/specware/clifind.py +4 -8
- {specware-1.2.28 → specware-1.3.0}/src/specware/cliview.py +20 -21
- {specware-1.2.28 → specware-1.3.0}/src/specware/contentc.py +135 -58
- {specware-1.2.28 → specware-1.3.0}/src/specware/interface.py +92 -72
- {specware-1.2.28 → specware-1.3.0}/src/specware/interfacedoc.py +27 -19
- {specware-1.2.28 → specware-1.3.0}/src/specware/interfacemapper.py +9 -7
- {specware-1.2.28 → specware-1.3.0}/src/specware/spec.pickle +0 -0
- {specware-1.2.28 → specware-1.3.0}/src/specware/transitionmap.py +5 -3
- {specware-1.2.28 → specware-1.3.0}/src/specware/util.py +80 -25
- {specware-1.2.28 → specware-1.3.0}/src/specware/validation.py +51 -34
- specware-1.2.28/src/specware/cliexport.py +0 -300
- {specware-1.2.28 → specware-1.3.0}/README.md +0 -0
- {specware-1.2.28 → specware-1.3.0}/src/specware/__init__.py +0 -0
- {specware-1.2.28 → specware-1.3.0}/src/specware/py.typed +0 -0
- {specware-1.2.28 → specware-1.3.0}/src/specware/rtems.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: specware
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Provides utilities to specify software.
|
|
5
5
|
Keywords: certification,documentation,markdown,qualification,requirements-management,traceability
|
|
6
6
|
Author: The specware Authors
|
|
@@ -20,7 +20,7 @@ Classifier: Topic :: Software Development :: Quality Assurance
|
|
|
20
20
|
Classifier: Topic :: Software Development :: Testing :: Acceptance
|
|
21
21
|
Classifier: Topic :: Text Processing :: Markup
|
|
22
22
|
Classifier: Typing :: Typed
|
|
23
|
-
Requires-Dist: specitems>=1.
|
|
23
|
+
Requires-Dist: specitems>=1.7.0
|
|
24
24
|
Requires-Python: >=3.11
|
|
25
25
|
Project-URL: Source Code, https://github.com/specthings/specware
|
|
26
26
|
Project-URL: Bug Tracker, https://github.com/specthings/specware/issues
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "specware"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "Provides utilities to specify software."
|
|
9
9
|
authors = [
|
|
10
10
|
{name = "The specware Authors", email = "specthings@embedded-brains.de"}
|
|
@@ -13,7 +13,7 @@ license = "BSD-2-Clause"
|
|
|
13
13
|
readme = "README.md"
|
|
14
14
|
requires-python = ">=3.11"
|
|
15
15
|
dependencies = [
|
|
16
|
-
"specitems >=1.
|
|
16
|
+
"specitems >=1.7.0",
|
|
17
17
|
]
|
|
18
18
|
keywords = [
|
|
19
19
|
"certification",
|
|
@@ -29,14 +29,14 @@ Provides methods to generate the application configuration documentation.
|
|
|
29
29
|
import itertools
|
|
30
30
|
from typing import Any, Callable, Optional
|
|
31
31
|
|
|
32
|
-
from specitems import (ClangFormatter,
|
|
33
|
-
get_value_plural, Item, ItemCache,
|
|
34
|
-
ItemMapper, TextContent)
|
|
35
|
-
|
|
36
|
-
from .contentc import (CContent,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
get_value_header_file)
|
|
32
|
+
from specitems import (ClangFormatter, ContentContext, EnabledSet,
|
|
33
|
+
GenericContent, get_value_plural, Item, ItemCache,
|
|
34
|
+
ItemGetValueContext, ItemMapper, TextContent)
|
|
35
|
+
|
|
36
|
+
from .contentc import (CContent, DEFAULT_ITEM_MARKER, add_item_marker,
|
|
37
|
+
get_value_double_colon, get_value_doxygen_function,
|
|
38
|
+
get_value_doxygen_group, get_value_doxygen_ref,
|
|
39
|
+
get_value_hash, get_value_header_file)
|
|
40
40
|
from .rtems import is_export_affected
|
|
41
41
|
|
|
42
42
|
_GROUP_MEMBER_ROLES = ("appl-config-group-member", "interface-ingroup")
|
|
@@ -68,19 +68,22 @@ class _ContentAdaptor:
|
|
|
68
68
|
By default, Sphinx content is generated.
|
|
69
69
|
"""
|
|
70
70
|
|
|
71
|
-
def __init__(self,
|
|
71
|
+
def __init__(self,
|
|
72
|
+
mapper: ItemMapper,
|
|
73
|
+
content: Any,
|
|
74
|
+
item_marker: str = DEFAULT_ITEM_MARKER) -> None:
|
|
72
75
|
self.mapper = mapper
|
|
73
76
|
self.content = content
|
|
77
|
+
self.item_marker = item_marker
|
|
74
78
|
|
|
75
79
|
def substitute(self, text: Optional[str]) -> str:
|
|
76
80
|
""" Substitute the optional text using the item mapper. """
|
|
77
81
|
return self.mapper.substitute(text)
|
|
78
82
|
|
|
79
|
-
def add_group(self,
|
|
83
|
+
def add_group(self, group: Item, name: str, description: str) -> None:
|
|
80
84
|
""" Add the option group. """
|
|
81
85
|
self.content.add_automatically_generated_warning()
|
|
82
|
-
|
|
83
|
-
self.content.add(f"Generated from spec:{uid}")
|
|
86
|
+
add_item_marker(self.content, self.item_marker, group)
|
|
84
87
|
self.content.add_header(name, level=self.content.section_level)
|
|
85
88
|
self.content.add(description)
|
|
86
89
|
|
|
@@ -96,11 +99,10 @@ class _ContentAdaptor:
|
|
|
96
99
|
else:
|
|
97
100
|
self.content.add(text)
|
|
98
101
|
|
|
99
|
-
def add_option(self,
|
|
102
|
+
def add_option(self, item: Item, name: str,
|
|
100
103
|
index_entries: list[str]) -> None:
|
|
101
104
|
""" Add the option. """
|
|
102
|
-
|
|
103
|
-
self.content.add(f"Generated from spec:{uid}")
|
|
105
|
+
add_item_marker(self.content, self.item_marker, item)
|
|
104
106
|
with self.content.directive("raw", "latex"):
|
|
105
107
|
self.content.add("\\clearpage")
|
|
106
108
|
self.content.add_index_entries([name] + index_entries)
|
|
@@ -148,8 +150,11 @@ class _ContentAdaptor:
|
|
|
148
150
|
|
|
149
151
|
class _TextContentAdaptor(_ContentAdaptor):
|
|
150
152
|
|
|
151
|
-
def __init__(self,
|
|
152
|
-
|
|
153
|
+
def __init__(self,
|
|
154
|
+
mapper: ItemMapper,
|
|
155
|
+
content: TextContent,
|
|
156
|
+
item_marker: str = DEFAULT_ITEM_MARKER) -> None:
|
|
157
|
+
super().__init__(mapper, content, item_marker)
|
|
153
158
|
|
|
154
159
|
|
|
155
160
|
class _DoxygenContentAdaptor(_ContentAdaptor):
|
|
@@ -157,8 +162,10 @@ class _DoxygenContentAdaptor(_ContentAdaptor):
|
|
|
157
162
|
|
|
158
163
|
def __init__(self,
|
|
159
164
|
mapper: ItemMapper,
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
context: ContentContext,
|
|
166
|
+
formatter: Optional[ClangFormatter] = None,
|
|
167
|
+
item_marker: str = DEFAULT_ITEM_MARKER) -> None:
|
|
168
|
+
super().__init__(mapper, CContent(context), item_marker)
|
|
162
169
|
self._formatter = formatter
|
|
163
170
|
self._reset()
|
|
164
171
|
|
|
@@ -174,17 +181,17 @@ class _DoxygenContentAdaptor(_ContentAdaptor):
|
|
|
174
181
|
self._notes = ""
|
|
175
182
|
self._description = ""
|
|
176
183
|
|
|
177
|
-
def add_group(self,
|
|
184
|
+
def add_group(self, group: Item, name: str, description: str) -> None:
|
|
178
185
|
identifier = f"RTEMSApplConfig{name.replace(' ', '')}"
|
|
179
|
-
self.content.
|
|
186
|
+
add_item_marker(self.content, self.item_marker, group)
|
|
180
187
|
with self.content.defgroup_block(identifier, name):
|
|
181
188
|
self.content.add("@ingroup RTEMSApplConfig")
|
|
182
189
|
self.content.wrap(description)
|
|
183
190
|
self.content.add("@{")
|
|
184
191
|
|
|
185
|
-
def add_option(self,
|
|
192
|
+
def add_option(self, item: Item, name: str,
|
|
186
193
|
_index_entries: list[str]) -> None:
|
|
187
|
-
self.content.
|
|
194
|
+
add_item_marker(self.content, self.item_marker, item)
|
|
188
195
|
self.content.open_doxygen_block()
|
|
189
196
|
self._name = name
|
|
190
197
|
|
|
@@ -243,7 +250,7 @@ def _generate_constraints(content: _ContentAdaptor, item: Item,
|
|
|
243
250
|
enabled_set: EnabledSet) -> None:
|
|
244
251
|
constraint_list = _get_constraints(content, item, enabled_set)
|
|
245
252
|
if len(constraint_list) > 1:
|
|
246
|
-
constraints =
|
|
253
|
+
constraints = content.content.fragment()
|
|
247
254
|
prologue = ("The following constraints apply "
|
|
248
255
|
"to this configuration option:")
|
|
249
256
|
constraints.add_list(constraint_list, prologue)
|
|
@@ -290,12 +297,12 @@ def document_option(content: TextContent, mapper: ItemMapper, item: Item,
|
|
|
290
297
|
def _generate(group: Item, options: dict[str, Item], enabled_set: EnabledSet,
|
|
291
298
|
content: _ContentAdaptor) -> None:
|
|
292
299
|
content.register_license_and_copyrights_of_item(group)
|
|
293
|
-
content.add_group(group
|
|
300
|
+
content.add_group(group, group["name"],
|
|
294
301
|
content.substitute(group["description"]))
|
|
295
302
|
for item in sorted(options.values(), key=lambda x: x["name"]):
|
|
296
303
|
content.mapper.item = item
|
|
297
304
|
content.register_license_and_copyrights_of_item(item)
|
|
298
|
-
content.add_option(item
|
|
305
|
+
content.add_option(item, item["name"], item["index-entries"])
|
|
299
306
|
_document_option(item, enabled_set, content)
|
|
300
307
|
content.add_licence_and_copyrights()
|
|
301
308
|
|
|
@@ -368,7 +375,7 @@ def is_application_configuration_affected(config: dict, item_cache: ItemCache,
|
|
|
368
375
|
items specified by the UIDs, otherwise false.
|
|
369
376
|
|
|
370
377
|
Args:
|
|
371
|
-
config: The application configuration
|
|
378
|
+
config: The application configuration task.
|
|
372
379
|
item_cache: The item cache containing the application configuration
|
|
373
380
|
groups and options.
|
|
374
381
|
uids: The UIDs of the items which changed.
|
|
@@ -388,16 +395,27 @@ def is_application_configuration_affected(config: dict, item_cache: ItemCache,
|
|
|
388
395
|
def _generate_documentation(config: dict, group_config: dict, group: Item,
|
|
389
396
|
options: dict[str, Item],
|
|
390
397
|
text_content: _TextContentAdaptor) -> None:
|
|
391
|
-
|
|
398
|
+
with text_content.mapper.work(text_content.content.context):
|
|
399
|
+
_generate(group, options, config["enabled-documentation"],
|
|
400
|
+
text_content)
|
|
392
401
|
text_content.write(group_config["target"])
|
|
393
402
|
|
|
394
403
|
|
|
404
|
+
def _get_options(group: Item) -> dict[str, Item]:
|
|
405
|
+
return {
|
|
406
|
+
child.uid: child
|
|
407
|
+
for child in group.children("interface-ingroup")
|
|
408
|
+
if child.type.startswith("interface/appl-config-option")
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
|
|
395
412
|
def generate_application_configuration(
|
|
396
413
|
config: dict,
|
|
397
414
|
group_uids: list[str],
|
|
398
415
|
item_cache: ItemCache,
|
|
399
416
|
create_mapper: Callable[[Item, list[str]], ItemMapper],
|
|
400
417
|
create_content: Callable[[], TextContent],
|
|
418
|
+
doxygen_context: ContentContext,
|
|
401
419
|
formatter: Optional[ClangFormatter] = None,
|
|
402
420
|
write_documentation: bool = True) -> None:
|
|
403
421
|
# pylint: disable=too-many-arguments
|
|
@@ -413,6 +431,7 @@ def generate_application_configuration(
|
|
|
413
431
|
create_mapper: The item mapper constructor to create mappers used for
|
|
414
432
|
content substitutions.
|
|
415
433
|
create_content: The content builder constructor.
|
|
434
|
+
doxygen_context: The content context of the Doxygen source.
|
|
416
435
|
formatter: The optional formatter used to format the Doxygen header
|
|
417
436
|
file.
|
|
418
437
|
write_documentation: Write the documentation target of each group.
|
|
@@ -423,7 +442,9 @@ def generate_application_configuration(
|
|
|
423
442
|
text_mapper = create_mapper(some_item, group_uids)
|
|
424
443
|
doxygen_mapper = ItemMapper(some_item)
|
|
425
444
|
_add_doxygen_get_values(doxygen_mapper)
|
|
426
|
-
doxygen_content = _DoxygenContentAdaptor(
|
|
445
|
+
doxygen_content = _DoxygenContentAdaptor(
|
|
446
|
+
doxygen_mapper, doxygen_context, formatter,
|
|
447
|
+
config.get("doxygen-item-marker", DEFAULT_ITEM_MARKER))
|
|
427
448
|
doxygen_content.content.add_automatically_generated_warning()
|
|
428
449
|
with doxygen_content.content.defgroup_block(
|
|
429
450
|
"RTEMSApplConfig", "Application Configuration Options"):
|
|
@@ -431,14 +452,14 @@ def generate_application_configuration(
|
|
|
431
452
|
for group_config in config["groups"]:
|
|
432
453
|
group = item_cache[group_config["uid"]]
|
|
433
454
|
assert group.type == "interface/appl-config-group"
|
|
434
|
-
options
|
|
435
|
-
for child in group.children("interface-ingroup"):
|
|
436
|
-
if child.type.startswith("interface/appl-config-option"):
|
|
437
|
-
options[child.uid] = child
|
|
455
|
+
options = _get_options(group)
|
|
438
456
|
if write_documentation:
|
|
439
457
|
_generate_documentation(
|
|
440
458
|
config, group_config, group, options,
|
|
441
|
-
_TextContentAdaptor(
|
|
459
|
+
_TextContentAdaptor(
|
|
460
|
+
text_mapper, create_content(),
|
|
461
|
+
config.get("documentation-item-marker",
|
|
462
|
+
DEFAULT_ITEM_MARKER)))
|
|
442
463
|
_generate(group, options, config["enabled-source"], doxygen_content)
|
|
443
464
|
doxygen_content.content.prepend_copyrights_and_licenses()
|
|
444
465
|
doxygen_content.content.prepend([
|
|
@@ -119,13 +119,27 @@ def _gather_source_files(item: Item, enabled_set: list[str],
|
|
|
119
119
|
source_files)
|
|
120
120
|
|
|
121
121
|
|
|
122
|
+
_TEST_TYPES = ["test-case", "requirement/functional/action"]
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def _map_test_target_to_item(item_cache: ItemCache) -> dict[str, Item]:
|
|
126
|
+
return {
|
|
127
|
+
item["test-target"]: item
|
|
128
|
+
for test_type in _TEST_TYPES
|
|
129
|
+
for item in item_cache.items_by_type.get(test_type, [])
|
|
130
|
+
if item["test-header"]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
|
|
122
134
|
def _gather_test_header(item_cache: ItemCache, enabled_set: list[str],
|
|
123
135
|
source_files: list[str]) -> None:
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
136
|
+
item_by_test_target = _map_test_target_to_item(item_cache)
|
|
137
|
+
test_headers: list[str] = []
|
|
138
|
+
for source_file in source_files:
|
|
139
|
+
item = item_by_test_target.pop(source_file, None)
|
|
140
|
+
if item is not None and item.is_enabled(enabled_set):
|
|
141
|
+
test_headers.append(item["test-header"]["target"])
|
|
142
|
+
source_files.extend(test_headers)
|
|
129
143
|
|
|
130
144
|
|
|
131
145
|
def gather_build_files(config: dict,
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
# SPDX-License-Identifier: BSD-2-Clause
|
|
2
|
+
"""
|
|
3
|
+
Provides a command line interface to export the specification to source and
|
|
4
|
+
documentation files.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
# Copyright (C) 2020, 2026 embedded brains GmbH & Co. KG
|
|
8
|
+
#
|
|
9
|
+
# Redistribution and use in source and binary forms, with or without
|
|
10
|
+
# modification, are permitted provided that the following conditions
|
|
11
|
+
# are met:
|
|
12
|
+
# 1. Redistributions of source code must retain the above copyright
|
|
13
|
+
# notice, this list of conditions and the following disclaimer.
|
|
14
|
+
# 2. Redistributions in binary form must reproduce the above copyright
|
|
15
|
+
# notice, this list of conditions and the following disclaimer in the
|
|
16
|
+
# documentation and/or other materials provided with the distribution.
|
|
17
|
+
#
|
|
18
|
+
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
19
|
+
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
20
|
+
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
21
|
+
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
|
|
22
|
+
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
23
|
+
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
24
|
+
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
25
|
+
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
26
|
+
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
27
|
+
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
28
|
+
# POSSIBILITY OF SUCH DAMAGE.
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import functools
|
|
32
|
+
import logging
|
|
33
|
+
import os
|
|
34
|
+
import sys
|
|
35
|
+
from typing import Any, Optional
|
|
36
|
+
|
|
37
|
+
from specitems import (ClangFormatter, ContentContext, DocumentGlossaryConfig,
|
|
38
|
+
GlossaryConfig, Item, ItemCache, LicenseProvider,
|
|
39
|
+
MarkdownContent, MarkdownMapper, SpecDocumentConfig,
|
|
40
|
+
SphinxContent, SphinxMapper, augment_glossary_terms,
|
|
41
|
+
check_license_items, create_config,
|
|
42
|
+
create_content_context, generate_glossary,
|
|
43
|
+
generate_specification_documentation, item_is_enabled,
|
|
44
|
+
yield_tasks)
|
|
45
|
+
|
|
46
|
+
from specware import (
|
|
47
|
+
run_with_clang_formatter, open_tree, MarkdownInterfaceMapper,
|
|
48
|
+
SphinxInterfaceMapper, add_clang_format_arguments,
|
|
49
|
+
generate_application_configuration, generate_interface_documentation,
|
|
50
|
+
gather_referencing_items, generate_interfaces, generate_validation,
|
|
51
|
+
get_affected_header_files, get_affected_targets,
|
|
52
|
+
is_application_configuration_affected)
|
|
53
|
+
|
|
54
|
+
_DOC_FORMAT = {
|
|
55
|
+
"myst": (MarkdownContent, MarkdownMapper, MarkdownInterfaceMapper),
|
|
56
|
+
"rest": (SphinxContent, SphinxMapper, SphinxInterfaceMapper)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _bind_context(fmt: str, context: ContentContext) -> tuple[Any, Any, Any]:
|
|
61
|
+
"""
|
|
62
|
+
Bind the context to the content and mapper constructors of the format.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
fmt: The documentation format.
|
|
66
|
+
context: The content context of a task.
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
The content constructor, the mapper constructor and the interface
|
|
70
|
+
mapper constructor. Every content which the content constructor
|
|
71
|
+
creates is a work of its own.
|
|
72
|
+
"""
|
|
73
|
+
create_content, create_mapper, create_interface_mapper = _DOC_FORMAT[fmt]
|
|
74
|
+
|
|
75
|
+
def _create_content(*args: Any, **kwargs: Any) -> Any:
|
|
76
|
+
return create_content(*args,
|
|
77
|
+
context=context.for_work(context.licenses.name),
|
|
78
|
+
**kwargs)
|
|
79
|
+
|
|
80
|
+
return (_create_content, functools.partial(create_mapper, context=context),
|
|
81
|
+
functools.partial(create_interface_mapper, context=context))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
#: The keys of a task which its configuration object takes not.
|
|
85
|
+
_TASK_KEYS = ("task-name", "task-type", "params", "license",
|
|
86
|
+
"accepted-licenses", "automatically-generated-warning")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _task_config(task: dict) -> dict:
|
|
90
|
+
""" Get the settings of the task without the keys of every task. """
|
|
91
|
+
return {key: value for key, value in task.items() if key not in _TASK_KEYS}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _generate_appl_config(task: dict, group_uids: list[str],
|
|
95
|
+
item_cache: ItemCache, args: argparse.Namespace,
|
|
96
|
+
provider: LicenseProvider,
|
|
97
|
+
formatter: Optional[ClangFormatter],
|
|
98
|
+
write_documentation: bool) -> None:
|
|
99
|
+
# pylint: disable=too-many-arguments
|
|
100
|
+
# pylint: disable=too-many-positional-arguments
|
|
101
|
+
# The task produces a Doxygen source and documentation sources, and each
|
|
102
|
+
# kind states its own license.
|
|
103
|
+
create_content, _, create_interface_mapper = _bind_context(
|
|
104
|
+
args.format, create_content_context(task, provider, "documentation-"))
|
|
105
|
+
generate_application_configuration(task,
|
|
106
|
+
group_uids,
|
|
107
|
+
item_cache,
|
|
108
|
+
create_interface_mapper,
|
|
109
|
+
create_content,
|
|
110
|
+
create_content_context(
|
|
111
|
+
task, provider, "doxygen-"),
|
|
112
|
+
formatter,
|
|
113
|
+
write_documentation=write_documentation)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _get_group_uids(config: Item) -> list[str]:
|
|
117
|
+
return [
|
|
118
|
+
doc["group"] for task in yield_tasks(config, "interface-documentation")
|
|
119
|
+
for doc in task["groups"]
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _parse_args(argv: list[str]) -> argparse.Namespace:
|
|
124
|
+
parser = argparse.ArgumentParser(description=cliexport.__doc__)
|
|
125
|
+
parser.add_argument("--config-file",
|
|
126
|
+
type=str,
|
|
127
|
+
default=None,
|
|
128
|
+
help="use this configuration file")
|
|
129
|
+
parser.add_argument("--format",
|
|
130
|
+
choices=["myst", "rest"],
|
|
131
|
+
type=str.lower,
|
|
132
|
+
default="myst",
|
|
133
|
+
help="the output format of documentation files")
|
|
134
|
+
parser.add_argument("--no-application-configuration-code",
|
|
135
|
+
action="store_true",
|
|
136
|
+
help="do not generate application configuration code")
|
|
137
|
+
parser.add_argument("--no-code",
|
|
138
|
+
action="store_true",
|
|
139
|
+
help="do not generate source code")
|
|
140
|
+
parser.add_argument("--no-documentation",
|
|
141
|
+
action="store_true",
|
|
142
|
+
help="do not generate documentation sources")
|
|
143
|
+
parser.add_argument("--no-interface-code",
|
|
144
|
+
action="store_true",
|
|
145
|
+
help="do not generate interface code")
|
|
146
|
+
parser.add_argument("--no-validation-code",
|
|
147
|
+
action="store_true",
|
|
148
|
+
help="do not generate validation code")
|
|
149
|
+
add_clang_format_arguments(parser)
|
|
150
|
+
parser.add_argument(
|
|
151
|
+
"targets",
|
|
152
|
+
metavar="TARGET",
|
|
153
|
+
nargs="*",
|
|
154
|
+
help=("a specification item file if it ends with '.yml', otherwise a "
|
|
155
|
+
"target file of a specification item; only the files "
|
|
156
|
+
"associated with the specification items are exported; "
|
|
157
|
+
"documentation files are not exported if a TARGET is present; "
|
|
158
|
+
"it is an error if a specification item file is associated "
|
|
159
|
+
"with no item, for example if the file was removed, run the "
|
|
160
|
+
"command with no TARGET to export all files in this case"))
|
|
161
|
+
return parser.parse_args(argv[1:])
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def _split_targets(targets: list[str],
|
|
165
|
+
invocation_directory: str) -> tuple[list[str], list[str]]:
|
|
166
|
+
"""
|
|
167
|
+
Split the targets into the target files and the specification item files.
|
|
168
|
+
|
|
169
|
+
The specification item files are made absolute with respect to the
|
|
170
|
+
directory in which the command was invoked.
|
|
171
|
+
"""
|
|
172
|
+
target_files: list[str] = []
|
|
173
|
+
item_files: list[str] = []
|
|
174
|
+
for target in targets:
|
|
175
|
+
if os.path.splitext(target)[1] == ".yml":
|
|
176
|
+
item_files.append(
|
|
177
|
+
os.path.abspath(os.path.join(invocation_directory, target)))
|
|
178
|
+
else:
|
|
179
|
+
target_files.append(target)
|
|
180
|
+
return target_files, item_files
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
def _resolve_item_files(item_cache: ItemCache,
|
|
184
|
+
item_files: list[str]) -> Optional[set[str]]:
|
|
185
|
+
"""
|
|
186
|
+
Resolve the specification item files to the UIDs of the corresponding
|
|
187
|
+
items.
|
|
188
|
+
|
|
189
|
+
Return None if a file is associated with no item. Several files may
|
|
190
|
+
denote the same item, so the count of the UIDs tells nothing about the
|
|
191
|
+
success of the resolution.
|
|
192
|
+
"""
|
|
193
|
+
uid_by_file = dict((item.file, item.uid) for item in item_cache.values())
|
|
194
|
+
uids: set[str] = set()
|
|
195
|
+
unresolved: list[str] = []
|
|
196
|
+
for item_file in item_files:
|
|
197
|
+
uid = uid_by_file.get(item_file)
|
|
198
|
+
if uid is None:
|
|
199
|
+
unresolved.append(item_file)
|
|
200
|
+
else:
|
|
201
|
+
uids.add(uid)
|
|
202
|
+
if not unresolved:
|
|
203
|
+
return uids
|
|
204
|
+
# The item files are absolute, however, they may denote a symbolic link or
|
|
205
|
+
# the specification may be reached through one. Resolving the links of
|
|
206
|
+
# every item is expensive, so it is done only if necessary.
|
|
207
|
+
uid_by_file = dict(
|
|
208
|
+
(os.path.realpath(file), uid) for file, uid in uid_by_file.items())
|
|
209
|
+
resolved = True
|
|
210
|
+
for item_file in unresolved:
|
|
211
|
+
uid = uid_by_file.get(os.path.realpath(item_file))
|
|
212
|
+
if uid is None:
|
|
213
|
+
logging.error(
|
|
214
|
+
"no specification item is associated with the file "
|
|
215
|
+
"'%s'", item_file)
|
|
216
|
+
resolved = False
|
|
217
|
+
else:
|
|
218
|
+
uids.add(uid)
|
|
219
|
+
return uids if resolved else None
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _generate_selected(item_cache: ItemCache, config: Item,
|
|
223
|
+
args: argparse.Namespace, provider: LicenseProvider,
|
|
224
|
+
formatter: Optional[ClangFormatter],
|
|
225
|
+
uids: set[str]) -> None:
|
|
226
|
+
# pylint: disable=too-many-arguments
|
|
227
|
+
# pylint: disable=too-many-positional-arguments
|
|
228
|
+
if args.no_code:
|
|
229
|
+
return
|
|
230
|
+
group_uids = _get_group_uids(config)
|
|
231
|
+
if not args.no_interface_code:
|
|
232
|
+
header_file_uids = get_affected_header_files(item_cache, uids)
|
|
233
|
+
if header_file_uids:
|
|
234
|
+
for task in yield_tasks(config, "interface"):
|
|
235
|
+
generate_interfaces(task, item_cache,
|
|
236
|
+
create_content_context(task, provider),
|
|
237
|
+
formatter, header_file_uids)
|
|
238
|
+
if not args.no_application_configuration_code:
|
|
239
|
+
for task in yield_tasks(config, "appl-config"):
|
|
240
|
+
if not is_application_configuration_affected(
|
|
241
|
+
task, item_cache, uids):
|
|
242
|
+
continue
|
|
243
|
+
_generate_appl_config(task, group_uids, item_cache, args, provider,
|
|
244
|
+
formatter, False)
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
def _generate_validation(item_cache: ItemCache, config: Item,
|
|
248
|
+
args: argparse.Namespace, provider: LicenseProvider,
|
|
249
|
+
formatter: Optional[ClangFormatter],
|
|
250
|
+
working_directory: str, target_files: list[str],
|
|
251
|
+
uids: set[str]) -> None:
|
|
252
|
+
# pylint: disable=too-many-arguments
|
|
253
|
+
# pylint: disable=too-many-positional-arguments
|
|
254
|
+
for task in yield_tasks(config, "validation"):
|
|
255
|
+
for mapping in task["base-directory-map"]:
|
|
256
|
+
for key, value in mapping.items():
|
|
257
|
+
mapping[key] = os.path.normpath(
|
|
258
|
+
os.path.join(working_directory, value))
|
|
259
|
+
context = create_content_context(task, provider)
|
|
260
|
+
if not args.targets:
|
|
261
|
+
generate_validation(task, item_cache, context, None, formatter)
|
|
262
|
+
continue
|
|
263
|
+
targets = list(target_files)
|
|
264
|
+
if uids:
|
|
265
|
+
targets.extend(sorted(get_affected_targets(item_cache, uids)))
|
|
266
|
+
# An empty target list makes generate_validation() generate all test
|
|
267
|
+
# source files. Generate nothing if a selection was made which is
|
|
268
|
+
# associated with no test source file at all.
|
|
269
|
+
if targets:
|
|
270
|
+
generate_validation(task, item_cache, context, targets, formatter)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def _generate_code(item_cache: ItemCache, config: Item,
|
|
274
|
+
args: argparse.Namespace, provider: LicenseProvider,
|
|
275
|
+
formatter: Optional[ClangFormatter]) -> None:
|
|
276
|
+
group_uids = _get_group_uids(config)
|
|
277
|
+
if not args.no_interface_code:
|
|
278
|
+
for task in yield_tasks(config, "interface"):
|
|
279
|
+
generate_interfaces(task, item_cache,
|
|
280
|
+
create_content_context(task, provider),
|
|
281
|
+
formatter)
|
|
282
|
+
if args.no_application_configuration_code:
|
|
283
|
+
return
|
|
284
|
+
for task in yield_tasks(config, "appl-config"):
|
|
285
|
+
_generate_appl_config(task, group_uids, item_cache, args, provider,
|
|
286
|
+
formatter, True)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
def _generate_spec_documentation(item_cache: ItemCache, config: Item,
|
|
290
|
+
args: argparse.Namespace,
|
|
291
|
+
provider: LicenseProvider) -> None:
|
|
292
|
+
some_item = next(iter(item_cache.values()))
|
|
293
|
+
for task in yield_tasks(config, "spec-documentation"):
|
|
294
|
+
context = create_content_context(task, provider)
|
|
295
|
+
create_content, create_mapper, _ = _bind_context(args.format, context)
|
|
296
|
+
mapper = create_mapper(some_item)
|
|
297
|
+
content = create_content()
|
|
298
|
+
spec_doc_config = create_config(_task_config(task), SpecDocumentConfig)
|
|
299
|
+
spec_doc_config.add_get_spec_name(mapper, content)
|
|
300
|
+
generate_specification_documentation(content, spec_doc_config, mapper)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def _generate_documentation(item_cache: ItemCache, config: Item,
|
|
304
|
+
args: argparse.Namespace,
|
|
305
|
+
provider: LicenseProvider) -> None:
|
|
306
|
+
some_item = next(iter(item_cache.values()))
|
|
307
|
+
group_uids = _get_group_uids(config)
|
|
308
|
+
_generate_spec_documentation(item_cache, config, args, provider)
|
|
309
|
+
for task in yield_tasks(config, "glossary"):
|
|
310
|
+
context = create_content_context(task, provider)
|
|
311
|
+
create_content, _, create_interface_mapper = _bind_context(
|
|
312
|
+
args.format, context)
|
|
313
|
+
settings = _task_config(task)
|
|
314
|
+
documents = settings.pop("documents", [])
|
|
315
|
+
glossary_config = create_config(settings, GlossaryConfig)
|
|
316
|
+
for document in documents:
|
|
317
|
+
glossary_config.documents.append(
|
|
318
|
+
create_config(document, DocumentGlossaryConfig))
|
|
319
|
+
generate_glossary(glossary_config, item_cache,
|
|
320
|
+
create_interface_mapper(some_item, group_uids),
|
|
321
|
+
create_content)
|
|
322
|
+
for task in yield_tasks(config, "interface-documentation"):
|
|
323
|
+
context = create_content_context(task, provider)
|
|
324
|
+
create_content, _, create_interface_mapper = _bind_context(
|
|
325
|
+
args.format, context)
|
|
326
|
+
generate_interface_documentation(task, item_cache,
|
|
327
|
+
create_interface_mapper,
|
|
328
|
+
create_content)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _generate_more(item_cache: ItemCache, config: Item,
|
|
332
|
+
args: argparse.Namespace, provider: LicenseProvider,
|
|
333
|
+
formatter: Optional[ClangFormatter]) -> None:
|
|
334
|
+
if not args.no_code:
|
|
335
|
+
_generate_code(item_cache, config, args, provider, formatter)
|
|
336
|
+
if not args.no_documentation:
|
|
337
|
+
_generate_documentation(item_cache, config, args, provider)
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def _check_interface_domains(config: Item) -> bool:
|
|
341
|
+
tasks_by_domain: dict[str, list[str]] = {}
|
|
342
|
+
for task in yield_tasks(config, "interface"):
|
|
343
|
+
for domain in task["domains"]:
|
|
344
|
+
tasks_by_domain.setdefault(domain, []).append(task["task-name"])
|
|
345
|
+
unique = True
|
|
346
|
+
for domain, names in sorted(tasks_by_domain.items()):
|
|
347
|
+
if len(names) > 1:
|
|
348
|
+
logging.error("the interface tasks %s map the domain %s",
|
|
349
|
+
", ".join(names), domain)
|
|
350
|
+
unique = False
|
|
351
|
+
return unique
|
|
352
|
+
|
|
353
|
+
|
|
354
|
+
def _export(args: argparse.Namespace, formatter: Optional[ClangFormatter],
|
|
355
|
+
invocation_directory: str) -> None:
|
|
356
|
+
target_files, item_files = _split_targets(args.targets,
|
|
357
|
+
invocation_directory)
|
|
358
|
+
with open_tree(args.config_file,
|
|
359
|
+
item_is_enabled) as (config, item_cache, working_directory):
|
|
360
|
+
provider = LicenseProvider(item_cache.values())
|
|
361
|
+
check_license_items(config, provider)
|
|
362
|
+
if not _check_interface_domains(config):
|
|
363
|
+
return
|
|
364
|
+
for task in yield_tasks(config, "glossary"):
|
|
365
|
+
for uid in task["project-groups"]:
|
|
366
|
+
group = item_cache[uid]
|
|
367
|
+
assert group.type == "glossary/group"
|
|
368
|
+
augment_glossary_terms(group, [])
|
|
369
|
+
|
|
370
|
+
uids: set[str] = set()
|
|
371
|
+
if item_files:
|
|
372
|
+
resolved = _resolve_item_files(item_cache, item_files)
|
|
373
|
+
if resolved is None:
|
|
374
|
+
return
|
|
375
|
+
uids = resolved
|
|
376
|
+
if uids:
|
|
377
|
+
# An item which substitutes an attribute of a changed item
|
|
378
|
+
# generates different content, so it changed as well.
|
|
379
|
+
uids.update(gather_referencing_items(item_cache, uids))
|
|
380
|
+
|
|
381
|
+
if not args.no_code and not args.no_validation_code:
|
|
382
|
+
_generate_validation(item_cache, config, args, provider, formatter,
|
|
383
|
+
working_directory, target_files, uids)
|
|
384
|
+
|
|
385
|
+
if not args.targets:
|
|
386
|
+
_generate_more(item_cache, config, args, provider, formatter)
|
|
387
|
+
elif uids:
|
|
388
|
+
_generate_selected(item_cache, config, args, provider, formatter,
|
|
389
|
+
uids)
|
|
390
|
+
|
|
391
|
+
|
|
392
|
+
def cliexport(argv: list[str] = sys.argv):
|
|
393
|
+
"""
|
|
394
|
+
Export the specification to the target source and documentation files.
|
|
395
|
+
"""
|
|
396
|
+
args = _parse_args(argv)
|
|
397
|
+
invocation_directory = os.getcwd()
|
|
398
|
+
return run_with_clang_formatter(
|
|
399
|
+
args, lambda formatter: _export(args, formatter, invocation_directory))
|