stbkit 0.1.0a9.dev1__py3-none-any.whl

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.
Files changed (125) hide show
  1. stbkit/__main__.py +10 -0
  2. stbkit/api/__init__.py +48 -0
  3. stbkit/api/experimental/__init__.py +19 -0
  4. stbkit/api/experimental/repository/__init__.py +30 -0
  5. stbkit/api/py.typed +0 -0
  6. stbkit/api/stb_latest.py +7 -0
  7. stbkit/api/stb_v2_0_2.py +11 -0
  8. stbkit/api/stb_v2_1_1.py +9 -0
  9. stbkit/cli/__init__.py +17 -0
  10. stbkit/cli/_internal/cli_main.py +114 -0
  11. stbkit/cli/_internal/common.py +126 -0
  12. stbkit/cli/_internal/constants_exit_code.py +59 -0
  13. stbkit/cli/_internal/converter.py +693 -0
  14. stbkit/cli/_internal/diff.py +213 -0
  15. stbkit/cli/_internal/entry_points.py +25 -0
  16. stbkit/cli/_internal/global_parent.py +70 -0
  17. stbkit/cli/_internal/show_versions.py +36 -0
  18. stbkit/cli/_internal/validate.py +310 -0
  19. stbkit/cli/commands/convert.py +16 -0
  20. stbkit/cli/commands/diff.py +16 -0
  21. stbkit/cli/commands/validate.py +16 -0
  22. stbkit/cli/py.typed +0 -0
  23. stbkit/tools/__init__.py +12 -0
  24. stbkit/tools/_internal/__init__.py +5 -0
  25. stbkit/tools/_internal/constants.py +21 -0
  26. stbkit/tools/_internal/data_model/building_geometry.py +1208 -0
  27. stbkit/tools/_internal/data_model/building_geometry_web.py +106 -0
  28. stbkit/tools/_internal/data_model/element_data.py +93 -0
  29. stbkit/tools/_internal/data_model/ifc_data/entity_data.py +67 -0
  30. stbkit/tools/_internal/data_model/ifc_data/entity_names.py +32 -0
  31. stbkit/tools/_internal/data_model/shape_data.py +1089 -0
  32. stbkit/tools/_internal/diff/__init__.py +5 -0
  33. stbkit/tools/_internal/diff/common.py +50 -0
  34. stbkit/tools/_internal/diff/diff_xml.py +796 -0
  35. stbkit/tools/_internal/matrix.py +51 -0
  36. stbkit/tools/_internal/optional_dependencies/__init__.py +5 -0
  37. stbkit/tools/_internal/optional_dependencies/ifc_common.py +28 -0
  38. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_entity_exporter.py +473 -0
  39. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_profile_exporter.py +267 -0
  40. stbkit/tools/_internal/optional_dependencies/ifc_exporter/ifc_representation_exporter.py +178 -0
  41. stbkit/tools/_internal/optional_dependencies/ifc_pipeline.py +133 -0
  42. stbkit/tools/_internal/optional_dependencies/ifc_to_building_geometry.py +64 -0
  43. stbkit/tools/_internal/stb_io.py +116 -0
  44. stbkit/tools/_internal/stb_repair/_common.py +24 -0
  45. stbkit/tools/_internal/stb_repair/_stb_repair.py +197 -0
  46. stbkit/tools/_internal/stb_repair/_stb_v2_0_2_repair.py +53 -0
  47. stbkit/tools/_internal/stb_repair/_stb_v2_1_0_repair.py +109 -0
  48. stbkit/tools/_internal/stb_repair/_stb_v2_1_1_repair.py +109 -0
  49. stbkit/tools/_internal/studio_report.py +49 -0
  50. stbkit/tools/_internal/utils/atomic_writer.py +57 -0
  51. stbkit/tools/_internal/utils/float_rounding.py +49 -0
  52. stbkit/tools/_internal/utils/girder_utils.py +91 -0
  53. stbkit/tools/_internal/utils/guid_utils.py +30 -0
  54. stbkit/tools/_internal/utils/mesh_utils.py +1305 -0
  55. stbkit/tools/_internal/utils/node_utils.py +50 -0
  56. stbkit/tools/_internal/utils/unit_utils.py +23 -0
  57. stbkit/tools/_internal/vectors.py +222 -0
  58. stbkit/tools/converters/__init__.py +39 -0
  59. stbkit/tools/converters/_internal/__init__.py +5 -0
  60. stbkit/tools/converters/_internal/building_geometry_to_ply_text.py +32 -0
  61. stbkit/tools/converters/_internal/coord_converter.py +151 -0
  62. stbkit/tools/converters/_internal/element_data_to_building_geometry.py +549 -0
  63. stbkit/tools/converters/_internal/ifc_to_ply_text.py +21 -0
  64. stbkit/tools/converters/_internal/name_converter.py +23 -0
  65. stbkit/tools/converters/_internal/pipeline/__init__.py +5 -0
  66. stbkit/tools/converters/_internal/pipeline/data.py +188 -0
  67. stbkit/tools/converters/_internal/pipeline/execution.py +569 -0
  68. stbkit/tools/converters/_internal/pipeline/ply_pipeline.py +52 -0
  69. stbkit/tools/converters/_internal/pipeline/stb_pipeline.py +226 -0
  70. stbkit/tools/converters/_internal/pipeline/stb_pipeline_latest.py +39 -0
  71. stbkit/tools/converters/_internal/pipeline/step.py +139 -0
  72. stbkit/tools/converters/_internal/stb_latest_to_element_data/__init__.py +17 -0
  73. stbkit/tools/converters/_internal/stb_latest_to_element_data/_stb_latest_to_element_data.py +186 -0
  74. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_footing.py +49 -0
  75. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_line.py +533 -0
  76. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_pile.py +100 -0
  77. stbkit/tools/converters/_internal/stb_latest_to_element_data/member/member_plane.py +103 -0
  78. stbkit/tools/converters/_internal/stb_latest_to_element_data/open.py +70 -0
  79. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec.py +214 -0
  80. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_rc.py +67 -0
  81. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_s.py +97 -0
  82. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_beam_src.py +28 -0
  83. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_brace_s.py +49 -0
  84. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_cft.py +217 -0
  85. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_rc.py +41 -0
  86. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_s.py +127 -0
  87. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_column_src.py +173 -0
  88. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_foundation.py +225 -0
  89. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_parapet_rc.py +53 -0
  90. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_pile_rc.py +106 -0
  91. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_pile_s.py +100 -0
  92. stbkit/tools/converters/_internal/stb_latest_to_element_data/section/sec_steel.py +432 -0
  93. stbkit/tools/converters/_internal/stb_to_building_geometry.py +44 -0
  94. stbkit/tools/converters/_internal/stb_to_ifc_data/__init__.py +5 -0
  95. stbkit/tools/converters/_internal/stb_to_ifc_data/_stb_to_ifc_data.py +59 -0
  96. stbkit/tools/converters/_internal/stb_to_ifc_data/convert_option.py +22 -0
  97. stbkit/tools/converters/_internal/stb_to_ply_text.py +22 -0
  98. stbkit/tools/py.typed +0 -0
  99. stbkit/tools/upgrade/__init__.py +13 -0
  100. stbkit/tools/upgrade/_internal/__init__.py +5 -0
  101. stbkit/tools/upgrade/_internal/common.py +205 -0
  102. stbkit/tools/upgrade/_internal/upgrade.py +228 -0
  103. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/__init__.py +60 -0
  104. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_common.py +62 -0
  105. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_joint_beam_shape_h.py +44 -0
  106. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_pile_rc.py +101 -0
  107. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_pile_s.py +78 -0
  108. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_slab_deck.py +56 -0
  109. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/hook_sec_slab_rc.py +128 -0
  110. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_girder.py +126 -0
  111. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_open.py +110 -0
  112. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_sec_beam_rc.py +227 -0
  113. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hook_funcs/post_sec_beam_s.py +1153 -0
  114. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/hooks.py +22 -0
  115. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/post.py +42 -0
  116. stbkit/tools/upgrade/_internal/v2_0_2_to_v2_1_0/tables.py +99 -0
  117. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/__init__.py +58 -0
  118. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/hooks.py +32 -0
  119. stbkit/tools/upgrade/_internal/v2_1_0_to_v2_1_1/tables.py +56 -0
  120. stbkit-0.1.0a9.dev1.dist-info/METADATA +196 -0
  121. stbkit-0.1.0a9.dev1.dist-info/RECORD +125 -0
  122. stbkit-0.1.0a9.dev1.dist-info/WHEEL +5 -0
  123. stbkit-0.1.0a9.dev1.dist-info/entry_points.txt +2 -0
  124. stbkit-0.1.0a9.dev1.dist-info/licenses/LICENSE +373 -0
  125. stbkit-0.1.0a9.dev1.dist-info/top_level.txt +1 -0
@@ -0,0 +1,213 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from __future__ import annotations
8
+
9
+ import math
10
+ import sys
11
+ from argparse import ArgumentParser, Namespace, _SubParsersAction
12
+ from pathlib import Path
13
+ from typing import Any, Never
14
+
15
+ from stbkit.core._internal.io_text import load_text
16
+ from stbkit.core.stb_io._internal.loader import _detect_encoding
17
+ from stbkit.core.stb_io._internal.stream_reader import check_no_doctype
18
+
19
+ from stbkit.tools._internal.diff.diff_xml import (
20
+ XmlCompareOption,
21
+ compare_xml_str,
22
+ )
23
+
24
+ from . import common
25
+ from .constants_exit_code import (
26
+ EXIT_EXECUTION_ERROR,
27
+ EXIT_INVALID_ARGUMENT,
28
+ EXIT_ISSUE_FOUND,
29
+ EXIT_OK,
30
+ )
31
+ from .global_parent import get_max_size, make_global_parent
32
+
33
+
34
+ def register(subparsers: _SubParsersAction[Any]) -> None:
35
+ parser = subparsers.add_parser(
36
+ "diff",
37
+ parents=[make_global_parent()],
38
+ help="2つのモデルを比較します。",
39
+ )
40
+ diff_subparsers = parser.add_subparsers(
41
+ dest="diff_command",
42
+ title="比較方式",
43
+ )
44
+ _register_xml_command(diff_subparsers)
45
+
46
+ # ``stbkit diff`` 単独では比較せず、利用可能な方式を表示する。
47
+ parser.set_defaults(func=_show_diff_help, _parser=parser)
48
+
49
+
50
+ def _register_xml_command(subparsers: _SubParsersAction[Any]) -> None:
51
+ parser = subparsers.add_parser(
52
+ "xml",
53
+ help="2つのST-Bridge XMLをXML構造として比較します。",
54
+ )
55
+ _use_common_arguments(parser)
56
+ parser.add_argument(
57
+ "--ignore-guid",
58
+ action="store_true",
59
+ help="guid属性を比較対象から除外します。",
60
+ )
61
+ parser.add_argument(
62
+ "--compare-by-order",
63
+ action="store_true",
64
+ help=("guidやidで対応付けず、同じタグの子要素をタグ内の出現順で比較します。"),
65
+ )
66
+ parser.add_argument(
67
+ "--compare-tag-order",
68
+ action="store_true",
69
+ help=("異なるタグを含む子要素のタグの並び順も差分として検出します。"),
70
+ )
71
+ parser.add_argument(
72
+ "--combine-attribute-diffs",
73
+ action="store_true",
74
+ help="同じ要素の複数の属性差分を1行にまとめます。",
75
+ )
76
+ parser.set_defaults(func=_run_xml)
77
+
78
+
79
+ def _use_common_arguments(parser: ArgumentParser) -> None:
80
+ parser.add_argument(
81
+ "reference",
82
+ type=Path,
83
+ metavar="REFERENCE",
84
+ help="基準、期待値、または変更前のファイルです。",
85
+ )
86
+ parser.add_argument(
87
+ "candidate",
88
+ type=Path,
89
+ metavar="CANDIDATE",
90
+ help="比較対象、実測値、または変更後のファイルです。",
91
+ )
92
+ parser.add_argument(
93
+ "-o",
94
+ "--output",
95
+ default="-",
96
+ metavar="PATH",
97
+ help="比較レポートの出力先です。省略時は標準出力へ出力します。",
98
+ )
99
+ parser.add_argument(
100
+ "--exit-zero",
101
+ action="store_true",
102
+ help="差分が見つかった場合も終了コード0を返します。",
103
+ )
104
+
105
+
106
+ def _show_diff_help(args: Namespace) -> Never:
107
+ parser: ArgumentParser = args._parser
108
+ parser.print_help()
109
+ raise SystemExit(EXIT_OK)
110
+
111
+
112
+ def _run_xml(args: Namespace) -> None:
113
+ reference: Path = args.reference
114
+ candidate: Path = args.candidate
115
+
116
+ try:
117
+ max_size: int = get_max_size(args)
118
+ _validate_output_path(args.output, reference, candidate)
119
+ reference_xml = _read_xml(reference, max_size=max_size)
120
+ candidate_xml = _read_xml(candidate, max_size=max_size)
121
+ compare_option = XmlCompareOption()
122
+ if args.ignore_guid:
123
+ compare_option.filter_option.global_attribute_filter.exclude.append("guid")
124
+ compare_option.ignore_order_children = not bool(args.compare_by_order)
125
+ compare_option.compare_tag_order = bool(args.compare_tag_order)
126
+ result = compare_xml_str(reference_xml, candidate_xml, compare_option)
127
+ if args.combine_attribute_diffs:
128
+ result = result.combine_attribute_diffs()
129
+ report_text = result.to_reporting_result().to_text()
130
+ _write_report(report_text, args.output)
131
+ except FileNotFoundError as error:
132
+ # 指定されたパスが誤っている場合。比較を始められていないため、
133
+ # 比較中の失敗とは区別する。
134
+ _exit_with_invalid_argument(error)
135
+ except Exception as error: # noqa: BLE001
136
+ _exit_with_error("XML比較に失敗しました", error)
137
+
138
+ _exit_for_result(equal=result.equal, exit_zero=bool(args.exit_zero))
139
+
140
+
141
+ def _read_xml(path: Path, *, max_size: int) -> str:
142
+ if not path.is_file():
143
+ raise FileNotFoundError(f"入力ファイルが見つかりません: {path}")
144
+ xml: str = load_text(path, encoding=_detect_encoding(path), max_size=max_size)
145
+ check_no_doctype(xml)
146
+ return xml
147
+
148
+
149
+ def _validate_geometry_inputs(reference: Path, candidate: Path) -> None:
150
+ for path in (reference, candidate):
151
+ if not path.is_file():
152
+ raise FileNotFoundError(f"入力ファイルが見つかりません: {path}")
153
+
154
+ suffixes = (reference.suffix.lower(), candidate.suffix.lower())
155
+ supported = {
156
+ (".stb", ".stb"),
157
+ (".stb", ".ifc"),
158
+ (".ifc", ".stb"),
159
+ }
160
+ if suffixes not in supported:
161
+ raise ValueError(
162
+ "対応している組合せはST-Bridge同士、またはST-BridgeとIFCです: "
163
+ f"{reference.suffix or '(拡張子なし)'} / "
164
+ f"{candidate.suffix or '(拡張子なし)'}"
165
+ )
166
+
167
+
168
+ def _validate_distance_tolerance(value: float | None) -> float | None:
169
+ if value is None:
170
+ return None
171
+ if not math.isfinite(value) or value < 0.0:
172
+ raise ValueError("--distance-toleranceには0以上の有限値を指定してください。")
173
+ return value
174
+
175
+
176
+ def _validate_output_path(output: str, reference: Path, candidate: Path) -> None:
177
+ try:
178
+ common.ensure_output_not_input(output, (reference, candidate))
179
+ except ValueError as error:
180
+ raise ValueError(f"比較レポートの{error}") from None
181
+
182
+
183
+ def _write_report(report_text: str, output: str) -> None:
184
+ if output == "-":
185
+ if report_text:
186
+ sys.stdout.write(report_text)
187
+ if not report_text.endswith("\n"):
188
+ sys.stdout.write("\n")
189
+ sys.stdout.flush()
190
+ return
191
+
192
+ common.write_text_via_temporary_file(
193
+ output=output,
194
+ text=report_text,
195
+ encoding="utf-8",
196
+ )
197
+
198
+
199
+ def _exit_for_result(*, equal: bool, exit_zero: bool) -> Never:
200
+ exit_code = EXIT_OK if equal or exit_zero else EXIT_ISSUE_FOUND
201
+ raise SystemExit(exit_code)
202
+
203
+
204
+ def _exit_with_error(message: str, error: Exception) -> Never:
205
+ print(f"{message}: {error}", file=sys.stderr)
206
+ raise SystemExit(EXIT_EXECUTION_ERROR) from None
207
+
208
+
209
+ def _exit_with_invalid_argument(error: Exception) -> Never:
210
+ print(str(error), file=sys.stderr)
211
+ raise SystemExit(EXIT_INVALID_ARGUMENT) from None
212
+
213
+
@@ -0,0 +1,25 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from importlib import metadata
8
+ from typing import Final
9
+
10
+ COMMAND_GROUP: Final = "stbkit.commands"
11
+
12
+ # pyproject.tomlの[project.entry-points."stbkit.commands"]に該当。
13
+ # 将来的にpackageを分離した際にpyproject.tomlへ書けるように、この形式で登録する。
14
+ BUILTIN_COMMANDS: Final[dict[str, str]] = {
15
+ "convert": "stbkit.cli.commands.convert:register",
16
+ "diff": "stbkit.cli.commands.diff:register",
17
+ "validate": "stbkit.cli.commands.validate:register",
18
+ }
19
+
20
+
21
+ def load_command_entry_points() -> metadata.EntryPoints:
22
+ return metadata.EntryPoints(
23
+ metadata.EntryPoint(name=name, value=value, group=COMMAND_GROUP)
24
+ for name, value in sorted(BUILTIN_COMMANDS.items())
25
+ )
@@ -0,0 +1,70 @@
1
+ # Copyright 2025-2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ import argparse
8
+ import logging
9
+ from argparse import Namespace
10
+ from logging import Logger
11
+
12
+ from stbkit.core._internal.constants import BYTES_PER_MB, DEFAULT_MAX_XML_SIZE
13
+ from stbkit.core._internal.stb_logger import get_logger as get_stb_logger
14
+
15
+ DEFAULT_MAX_SIZE_MB: int = DEFAULT_MAX_XML_SIZE // BYTES_PER_MB
16
+
17
+
18
+ def make_global_parent() -> argparse.ArgumentParser:
19
+ # 共通引数に既定値を持たせるとサブコマンドより前で指定した値がサブコマンドで上書きされてしまうため、
20
+ # 共通引数は既定値を持たせず、下のget_xxxを用いる。
21
+ parent = argparse.ArgumentParser(add_help=False)
22
+ parent.add_argument(
23
+ "-V",
24
+ "--version",
25
+ action="store_true",
26
+ default=argparse.SUPPRESS,
27
+ help="バージョン情報",
28
+ )
29
+ parent.add_argument(
30
+ "-v",
31
+ "--verbose",
32
+ action="count",
33
+ default=argparse.SUPPRESS,
34
+ help="詳細ログを出力する場合に指定します (-v, -vv, -vvv)",
35
+ )
36
+ parent.add_argument(
37
+ "--max-size-mb",
38
+ type=int,
39
+ default=argparse.SUPPRESS,
40
+ help=f"読み込むXMLのサイズ上限(MB)。既定は{DEFAULT_MAX_SIZE_MB}です。",
41
+ )
42
+ parent.add_argument(
43
+ "--interactive",
44
+ action="store_true",
45
+ default=argparse.SUPPRESS,
46
+ help="対話モードで実行します。",
47
+ )
48
+
49
+ return parent
50
+
51
+
52
+ def get_max_size(args: Namespace) -> int:
53
+ max_size_mb: int = getattr(args, "max_size_mb", DEFAULT_MAX_SIZE_MB)
54
+ return max_size_mb * BYTES_PER_MB
55
+
56
+
57
+ def get_interactive(args: Namespace) -> bool:
58
+ interactive: bool = getattr(args, "interactive", False)
59
+ return interactive
60
+
61
+
62
+ def get_logger(args: Namespace) -> Logger:
63
+ verbose: int = getattr(args, "verbose", 0)
64
+ if verbose >= 2:
65
+ logging_level = logging.DEBUG
66
+ elif verbose >= 1:
67
+ logging_level = logging.INFO
68
+ else:
69
+ logging_level = logging.WARNING
70
+ return get_stb_logger(level=logging_level)
@@ -0,0 +1,36 @@
1
+ # Copyright 2025-2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from importlib import metadata
8
+
9
+
10
+ def print_versions(dist_name: str) -> None:
11
+ try:
12
+ version = metadata.version(dist_name)
13
+ print(f"{dist_name}: {version}")
14
+ except metadata.PackageNotFoundError:
15
+ print(f"{dist_name}: unknown")
16
+
17
+
18
+ def show_versions(entry_points: metadata.EntryPoints) -> None:
19
+ print_versions("stbkit")
20
+ stbkit_set: set[str] = set()
21
+ for dist in metadata.distributions():
22
+ name = dist.metadata.get("Name")
23
+ if not isinstance(name, str):
24
+ continue
25
+ dist_name: str = name.strip()
26
+ if dist_name.replace("_", "-").lower().startswith("stbkit-"):
27
+ stbkit_set.add(dist_name)
28
+ for package in sorted(stbkit_set):
29
+ print_versions(package)
30
+ stbkit_set.add("stbkit")
31
+ for ep in entry_points:
32
+ if ep.dist:
33
+ dist_name = ep.dist.name
34
+ if dist_name not in stbkit_set:
35
+ stbkit_set.add(dist_name)
36
+ print_versions(dist_name)
@@ -0,0 +1,310 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+ from argparse import Namespace, _SubParsersAction
11
+ from pathlib import Path
12
+ from typing import Any, Never
13
+ from xml.etree.ElementTree import ParseError
14
+
15
+ from stbkit.core._internal.io_text import load_text
16
+ from stbkit.core.stb_exceptions import DeveloperError, StbError
17
+ from stbkit.core.stb_io._internal.loader import _detect_encoding
18
+ from stbkit.core.stb_reporting import (
19
+ Code,
20
+ CollectingReporter,
21
+ Phase,
22
+ Severity,
23
+ )
24
+ from stbkit.core.validation._internal.validator import validate_schema
25
+
26
+ from . import common
27
+ from .constants_exit_code import (
28
+ EXIT_EXECUTION_ERROR,
29
+ EXIT_INVALID_ARGUMENT,
30
+ EXIT_ISSUE_FOUND,
31
+ EXIT_OK,
32
+ EXIT_OPTIONAL_DEPENDENCY_MISSING,
33
+ )
34
+ from .global_parent import get_interactive, get_max_size, make_global_parent
35
+
36
+
37
+ def _check_optional_dependency(reporter: CollectingReporter) -> bool:
38
+ return any(
39
+ item.code == Code.OPTIONAL_DEPENDENCY_MISSING.value for item in reporter.report
40
+ )
41
+
42
+
43
+ def _try_validate_report(
44
+ input_path: str,
45
+ *,
46
+ schema_path: str | None,
47
+ max_size: int,
48
+ require_xsd: bool,
49
+ exclude_legal_extensions: bool,
50
+ ) -> tuple[CollectingReporter | None, str]:
51
+ # XMLとして読み込めなかった場合は、チェック結果ではなく実行の失敗なので(None, エラーメッセージ)を返す。
52
+ # ST-Bridgeとしての不正はチェック結果であるため、エラーとしない。
53
+ try:
54
+ reporter: CollectingReporter = _validate_report(
55
+ input_path,
56
+ schema_path=schema_path,
57
+ max_size=max_size,
58
+ require_xsd=require_xsd,
59
+ exclude_legal_extensions=exclude_legal_extensions,
60
+ )
61
+ except DeveloperError:
62
+ raise
63
+ except (StbError, ParseError, UnicodeDecodeError) as error:
64
+ return None, f"入力ファイルを処理できません: {error}"
65
+ return reporter, ""
66
+
67
+
68
+ def _has_error(reporter: CollectingReporter) -> bool:
69
+ # 任意依存の不足以外
70
+ return any(
71
+ item.severity >= Severity.WARNING.value
72
+ and item.code != Code.OPTIONAL_DEPENDENCY_MISSING.value
73
+ for item in reporter.report
74
+ )
75
+
76
+
77
+ def register(subparsers: _SubParsersAction[Any]) -> None:
78
+ parser = subparsers.add_parser(
79
+ "validate",
80
+ parents=[make_global_parent()],
81
+ help=(
82
+ "ST-Bridgeのバリデーションを行います。\n"
83
+ "--schemaを指定すると、指定されたXSDスキーマでの検査を行います。\n"
84
+ "XSDスキーマの指定は環境変数でも可能です\n"
85
+ "XSDスキーマの指定を行わない場合は、stbkit-coreのデータモデルが持つ情報でチェックを行います。"
86
+ ),
87
+ )
88
+ common.use_input(parser)
89
+ common.use_output(parser)
90
+ common.use_yes(parser)
91
+ parser.add_argument(
92
+ "--schema",
93
+ help="チェックに利用するXSDスキーマファイルのパス",
94
+ )
95
+ parser.add_argument(
96
+ "--require-xsd",
97
+ action="store_true",
98
+ help="XSDスキーマによる検査を必須にします",
99
+ )
100
+ parser.add_argument(
101
+ "--exclude-legal-extensions",
102
+ action="store_true",
103
+ help="StbExtensionsで定義された拡張に由来するXSDスキーマチェック時のエラーをXSD結果から除外します。\n"
104
+ "XSDを利用しない場合はこのオプションは不要です。",
105
+ )
106
+ common.use_quiet(parser)
107
+
108
+ parser.set_defaults(func=_run, _parser=parser)
109
+
110
+
111
+ def _run(args: Namespace) -> None:
112
+ input_path_str: str = common.get_input(args)
113
+ output_path: str = common.get_output(args)
114
+ yes: bool = common.get_yes(args)
115
+ quiet: bool = common.get_quiet(args)
116
+ interactive: bool = get_interactive(args)
117
+ schema: str | None = args.schema
118
+ require_xsd: bool = bool(args.require_xsd)
119
+ exclude_legal_extensions: bool = bool(args.exclude_legal_extensions)
120
+ max_size: int = get_max_size(args)
121
+
122
+ if schema:
123
+ schema_path = Path(schema)
124
+ if not schema_path.is_file():
125
+ _exit_with_error(
126
+ f"指定されたXSDスキーマファイルが存在しません: {schema_path}"
127
+ )
128
+
129
+ is_valid: bool = True
130
+ from_stdin: bool = input_path_str == "-"
131
+ input_path: Path = Path(input_path_str)
132
+
133
+ if not from_stdin:
134
+ try:
135
+ common.ensure_output_not_input(output_path, (input_path,))
136
+ except ValueError as error:
137
+ _exit_with_error(f"バリデーション結果の{error}")
138
+
139
+ if not common.confirm_overwrite(
140
+ output=output_path,
141
+ yes=yes,
142
+ interactive=interactive,
143
+ ):
144
+ _exit_with_error(
145
+ "出力先ファイルが既に存在します。上書きするには-y/--yesを指定してください。"
146
+ )
147
+
148
+ result: list[str] = []
149
+ lacks_optional_dependency = False
150
+ has_target_issue = False
151
+ has_unreadable_input = False
152
+
153
+ if from_stdin or input_path.is_file():
154
+ result.append("入力: 標準入力" if from_stdin else f"ファイル: {input_path}")
155
+ reporter, read_error = _try_validate_report(
156
+ input_path_str,
157
+ schema_path=schema,
158
+ max_size=max_size,
159
+ require_xsd=require_xsd,
160
+ exclude_legal_extensions=exclude_legal_extensions,
161
+ )
162
+ if reporter is None:
163
+ print(read_error, file=sys.stderr)
164
+ sys.exit(EXIT_EXECUTION_ERROR)
165
+ result.extend(_output_report(reporter, quiet=quiet))
166
+ is_valid = reporter.is_valid()
167
+ lacks_optional_dependency = _check_optional_dependency(reporter)
168
+ has_target_issue = _has_error(reporter)
169
+ elif input_path.is_dir():
170
+ result.append(f"ディレクトリ: {input_path}")
171
+ result.append("ディレクトリ内のST-Bridgeファイルを再帰的に検査します。")
172
+ xml_files: list[Path] = list(input_path.rglob("*.stb"))
173
+ try:
174
+ common.ensure_output_not_input(output_path, xml_files)
175
+ except ValueError as error:
176
+ _exit_with_error(f"バリデーション結果の{error}")
177
+ if not xml_files:
178
+ result.append("ディレクトリ内にST-Bridgeファイルが見つかりませんでした。")
179
+ is_valid = False
180
+ else:
181
+ result_files: list[str] = []
182
+ error_file_count: list[tuple[str, int]] = []
183
+ for xml_file in xml_files:
184
+ reporter, read_error = _try_validate_report(
185
+ str(xml_file),
186
+ schema_path=schema,
187
+ max_size=max_size,
188
+ require_xsd=require_xsd,
189
+ exclude_legal_extensions=exclude_legal_extensions,
190
+ )
191
+ if reporter is None:
192
+ # 1つのファイルが読めなくても残りのバリデーションは続ける
193
+ result_files.append("-" * 3)
194
+ result_files.append(f"ファイル: {xml_file}")
195
+ result_files.append(read_error)
196
+ has_unreadable_input = True
197
+ is_valid = False
198
+ continue
199
+ file_result = _output_report(reporter, quiet=quiet)
200
+ result_files.append("-" * 3)
201
+ result_files.append(f"ファイル: {xml_file}")
202
+ result_files.extend(file_result)
203
+ if _check_optional_dependency(reporter):
204
+ lacks_optional_dependency = True
205
+ if _has_error(reporter):
206
+ has_target_issue = True
207
+ if not reporter.is_valid():
208
+ error_file_count.append((str(xml_file), len(reporter.report)))
209
+ is_valid = False
210
+ result.append(f"{len(xml_files)}ファイルが見つかりました。")
211
+ if is_valid:
212
+ result.append("すべてのファイルがOKです。")
213
+ else:
214
+ result.append(
215
+ f"{len(error_file_count)}ファイルにエラーが見つかりました。"
216
+ )
217
+ for file, count in error_file_count:
218
+ result.append(f" {file}: {count}件のエラー")
219
+ result.extend(result_files)
220
+ else:
221
+ _exit_with_error(
222
+ f"入力パスはファイルまたはディレクトリである必要があります: {input_path}"
223
+ )
224
+
225
+ if is_valid:
226
+ if not quiet:
227
+ if output_path == "-":
228
+ print("\n".join(result))
229
+ else:
230
+ common.write_text_via_temporary_file(
231
+ output=output_path,
232
+ text="\n".join(result),
233
+ encoding="utf-8",
234
+ )
235
+ sys.exit(EXIT_OK)
236
+ else:
237
+ error_text: str = "\n".join(result)
238
+ if output_path == "-":
239
+ print(error_text, file=sys.stderr)
240
+ else:
241
+ common.write_text_via_temporary_file(
242
+ output=output_path,
243
+ text=error_text,
244
+ encoding="utf-8",
245
+ )
246
+ print(error_text, file=sys.stderr)
247
+ # 検査が実行できた場合は、検査のエラーを優先する。
248
+ # 検査で問題を見つけられなかった場合は、実行できなかった原因を返す。
249
+ if not has_target_issue:
250
+ if has_unreadable_input:
251
+ sys.exit(EXIT_EXECUTION_ERROR)
252
+ if lacks_optional_dependency:
253
+ sys.exit(EXIT_OPTIONAL_DEPENDENCY_MISSING)
254
+ sys.exit(EXIT_ISSUE_FOUND)
255
+
256
+
257
+ def _output_report(reporter: CollectingReporter, quiet: bool) -> list[str]:
258
+ result: list[str] = []
259
+ if reporter.is_valid():
260
+ result.append("結果: OK")
261
+ else:
262
+ result.append("結果: NG")
263
+ result.append("エラー数: " + str(len(reporter.report)))
264
+ result.append("エラー詳細:")
265
+ result.append(reporter.report.to_text())
266
+ return result
267
+
268
+
269
+ def _validate_report(
270
+ input_path: str,
271
+ schema_path: str | None,
272
+ *,
273
+ max_size: int,
274
+ require_xsd: bool,
275
+ exclude_legal_extensions: bool,
276
+ ) -> CollectingReporter:
277
+ xsd_path: Path | None = Path(schema_path) if schema_path else None
278
+
279
+ reporter: CollectingReporter = CollectingReporter(
280
+ default_code=Code.SCHEMA_ERROR, default_phase=Phase.VALIDATE
281
+ )
282
+
283
+ if xsd_path is not None and not xsd_path.is_file():
284
+ reporter.error(f"指定されたXSDスキーマファイルが存在しません: {xsd_path}")
285
+ return reporter
286
+
287
+ xml_text: str
288
+ if input_path == "-":
289
+ xml_text = common.read_stdin_xml(max_size=max_size)
290
+ else:
291
+ xml_path: Path = Path(input_path)
292
+ if not xml_path.is_file():
293
+ reporter.error(f"指定されたST-Bridgeファイルが存在しません: {xml_path}")
294
+ return reporter
295
+ encoding: str = _detect_encoding(xml_path)
296
+ xml_text = load_text(xml_path, encoding=encoding, max_size=max_size)
297
+
298
+ validate_schema(
299
+ xml_text,
300
+ reporter=reporter,
301
+ xsd_path=xsd_path,
302
+ require_xsd=require_xsd,
303
+ exclude_legal_extensions=exclude_legal_extensions,
304
+ )
305
+ return reporter
306
+
307
+
308
+ def _exit_with_error(message: str) -> Never:
309
+ print(message, file=sys.stderr)
310
+ sys.exit(EXIT_INVALID_ARGUMENT)
@@ -0,0 +1,16 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from __future__ import annotations
8
+
9
+ from argparse import _SubParsersAction
10
+ from typing import Any
11
+
12
+ from .._internal import converter
13
+
14
+
15
+ def register(subparsers: _SubParsersAction[Any]) -> None:
16
+ converter.register(subparsers)
@@ -0,0 +1,16 @@
1
+ # Copyright 2026 TAISEI CORPORATION
2
+ # SPDX-License-Identifier: MPL-2.0
3
+ # This Source Code Form is subject to the terms of the Mozilla Public
4
+ # License, v. 2.0. If a copy of the MPL was not distributed with this
5
+ # file, You can obtain one at https://mozilla.org/MPL/2.0/.
6
+
7
+ from __future__ import annotations
8
+
9
+ from argparse import _SubParsersAction
10
+ from typing import Any
11
+
12
+ from .._internal import diff
13
+
14
+
15
+ def register(subparsers: _SubParsersAction[Any]) -> None:
16
+ diff.register(subparsers)