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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: specware
3
- Version: 1.2.28
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.6.23
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.2.28"
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.6.23",
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, Content, EnabledSet, GenericContent,
33
- get_value_plural, Item, ItemCache, ItemGetValueContext,
34
- ItemMapper, TextContent)
35
-
36
- from .contentc import (CContent, get_value_double_colon,
37
- get_value_doxygen_function, get_value_doxygen_group,
38
- get_value_doxygen_ref, get_value_hash,
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, mapper: ItemMapper, content: Any) -> None:
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, uid: str, name: str, description: str) -> None:
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
- with self.content.comment_block():
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, uid: str, name: str,
102
+ def add_option(self, item: Item, name: str,
100
103
  index_entries: list[str]) -> None:
101
104
  """ Add the option. """
102
- with self.content.comment_block():
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, mapper: ItemMapper, content: TextContent) -> None:
152
- super().__init__(mapper, content)
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
- formatter: Optional[ClangFormatter] = None) -> None:
161
- super().__init__(mapper, CContent())
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, uid: str, name: str, description: str) -> None:
184
+ def add_group(self, group: Item, name: str, description: str) -> None:
178
185
  identifier = f"RTEMSApplConfig{name.replace(' ', '')}"
179
- self.content.add(f"/* Generated from spec:{uid} */")
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, uid: str, name: str,
192
+ def add_option(self, item: Item, name: str,
186
193
  _index_entries: list[str]) -> None:
187
- self.content.add(f"/* Generated from spec:{uid} */")
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 = Content("BSD-2-Clause")
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.uid, group["name"],
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.uid, item["name"], item["index-entries"])
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 generation 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
- _generate(group, options, config["enabled-documentation"], text_content)
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(doxygen_mapper, formatter)
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: dict[str, Item] = {}
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(text_mapper, create_content()))
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
- for item in item_cache.values():
125
- tests = ["test-case", "requirement/functional/action"]
126
- if item.type in tests and item["test-header"] and item.is_enabled(
127
- enabled_set):
128
- source_files.append(item["test-header"]["target"])
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))