simplibs-extractor 0.1.0__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 (140) hide show
  1. simplibs/extractor/__init__.py +121 -0
  2. simplibs/extractor/__main__.py +12 -0
  3. simplibs/extractor/annotations/FileStatus.py +16 -0
  4. simplibs/extractor/annotations/OutputFormat.py +25 -0
  5. simplibs/extractor/annotations/OverwritePolicy.py +24 -0
  6. simplibs/extractor/annotations/StepsSpec.py +26 -0
  7. simplibs/extractor/annotations/__init__.py +26 -0
  8. simplibs/extractor/annotations/_helpers/__init__.py +15 -0
  9. simplibs/extractor/annotations/_helpers/is_output_format.py +36 -0
  10. simplibs/extractor/cli/__init__.py +32 -0
  11. simplibs/extractor/cli/_helpers/__init__.py +22 -0
  12. simplibs/extractor/cli/_helpers/describe_result.py +34 -0
  13. simplibs/extractor/cli/_helpers/is_profile_reference.py +34 -0
  14. simplibs/extractor/cli/_helpers/print_error.py +25 -0
  15. simplibs/extractor/cli/_helpers/profile_options.py +25 -0
  16. simplibs/extractor/cli/_helpers/system_options.py +22 -0
  17. simplibs/extractor/cli/_validations/__init__.py +22 -0
  18. simplibs/extractor/cli/_validations/raise_duplicate_output_error.py +32 -0
  19. simplibs/extractor/cli/_validations/raise_list_unknown_system_error.py +34 -0
  20. simplibs/extractor/cli/_validations/raise_output_is_file_error.py +30 -0
  21. simplibs/extractor/cli/_validations/raise_too_many_positionals_error.py +37 -0
  22. simplibs/extractor/cli/_validations/raise_unknown_system_error.py +32 -0
  23. simplibs/extractor/cli/build_configs.py +77 -0
  24. simplibs/extractor/cli/build_parser.py +68 -0
  25. simplibs/extractor/cli/collect_dry_run.py +39 -0
  26. simplibs/extractor/cli/collect_profile_keys.py +53 -0
  27. simplibs/extractor/cli/collect_source.py +44 -0
  28. simplibs/extractor/cli/handle_list_options.py +47 -0
  29. simplibs/extractor/cli/normalize_positionals.py +56 -0
  30. simplibs/extractor/cli/run_cli.py +101 -0
  31. simplibs/extractor/cli/run_init.py +65 -0
  32. simplibs/extractor/constants/DEFAULT_MAX_FILE_SIZE.py +18 -0
  33. simplibs/extractor/constants/DEFAULT_OUTPUT_NAME.py +14 -0
  34. simplibs/extractor/constants/DEFAULT_PROFILE_KEY.py +14 -0
  35. simplibs/extractor/constants/DEFAULT_SCRIPT_NAME.py +15 -0
  36. simplibs/extractor/constants/GENERATED_MARKER.py +20 -0
  37. simplibs/extractor/constants/LANGUAGE_BY_SUFFIX.py +34 -0
  38. simplibs/extractor/constants/MARKER_SCAN_LINES.py +18 -0
  39. simplibs/extractor/constants/PLUGIN_GROUP.py +20 -0
  40. simplibs/extractor/constants/SCRIPT_TEMPLATE.py +32 -0
  41. simplibs/extractor/constants/__init__.py +30 -0
  42. simplibs/extractor/definitions/Entry.py +26 -0
  43. simplibs/extractor/definitions/ExtractionConfig.py +75 -0
  44. simplibs/extractor/definitions/ExtractionResult.py +74 -0
  45. simplibs/extractor/definitions/FileReport.py +39 -0
  46. simplibs/extractor/definitions/Profile.py +95 -0
  47. simplibs/extractor/definitions/Resolved.py +28 -0
  48. simplibs/extractor/definitions/__init__.py +24 -0
  49. simplibs/extractor/definitions/_helpers/__init__.py +16 -0
  50. simplibs/extractor/definitions/_helpers/is_key_part.py +31 -0
  51. simplibs/extractor/definitions/_helpers/is_suffix_key.py +35 -0
  52. simplibs/extractor/definitions/_validations/__init__.py +18 -0
  53. simplibs/extractor/definitions/_validations/raise_profile_key_part_error.py +34 -0
  54. simplibs/extractor/definitions/_validations/raise_profile_output_name_error.py +33 -0
  55. simplibs/extractor/definitions/_validations/raise_profile_steps_key_error.py +33 -0
  56. simplibs/extractor/logic/__init__.py +90 -0
  57. simplibs/extractor/logic/_common/__init__.py +15 -0
  58. simplibs/extractor/logic/_common/describe_failure.py +39 -0
  59. simplibs/extractor/logic/_helpers/RunLog.py +57 -0
  60. simplibs/extractor/logic/_helpers/__init__.py +18 -0
  61. simplibs/extractor/logic/_helpers/build_title.py +28 -0
  62. simplibs/extractor/logic/_helpers/process_source.py +59 -0
  63. simplibs/extractor/logic/extract.py +119 -0
  64. simplibs/extractor/logic/extract_many.py +60 -0
  65. simplibs/extractor/logic/failure_result.py +47 -0
  66. simplibs/extractor/logic/read/__init__.py +16 -0
  67. simplibs/extractor/logic/read/is_generated_file.py +45 -0
  68. simplibs/extractor/logic/read/read_source.py +93 -0
  69. simplibs/extractor/logic/render/RenderContext.py +34 -0
  70. simplibs/extractor/logic/render/__init__.py +31 -0
  71. simplibs/extractor/logic/render/_helpers/__init__.py +14 -0
  72. simplibs/extractor/logic/render/_helpers/format_tree_lines.py +35 -0
  73. simplibs/extractor/logic/render/build_tree.py +36 -0
  74. simplibs/extractor/logic/render/register_builtin_renderers.py +26 -0
  75. simplibs/extractor/logic/render/render_markdown.py +84 -0
  76. simplibs/extractor/logic/render/render_plain.py +57 -0
  77. simplibs/extractor/logic/report/__init__.py +16 -0
  78. simplibs/extractor/logic/report/_validations/__init__.py +14 -0
  79. simplibs/extractor/logic/report/_validations/raise_chars_per_token_error.py +29 -0
  80. simplibs/extractor/logic/report/estimate_tokens.py +47 -0
  81. simplibs/extractor/logic/report/format_summary.py +67 -0
  82. simplibs/extractor/logic/select/__init__.py +24 -0
  83. simplibs/extractor/logic/select/_validations/__init__.py +18 -0
  84. simplibs/extractor/logic/select/_validations/raise_missing_source_error.py +26 -0
  85. simplibs/extractor/logic/select/_validations/raise_source_not_found_error.py +37 -0
  86. simplibs/extractor/logic/select/_validations/raise_unknown_parameter_error.py +35 -0
  87. simplibs/extractor/logic/select/all_of.py +32 -0
  88. simplibs/extractor/logic/select/any_of.py +29 -0
  89. simplibs/extractor/logic/select/resolve_config.py +85 -0
  90. simplibs/extractor/logic/select/resolve_ignore_names.py +36 -0
  91. simplibs/extractor/logic/select/resolve_profile.py +38 -0
  92. simplibs/extractor/logic/select/select_sources.py +85 -0
  93. simplibs/extractor/logic/transform/__init__.py +16 -0
  94. simplibs/extractor/logic/transform/_validations/__init__.py +14 -0
  95. simplibs/extractor/logic/transform/_validations/raise_step_result_error.py +42 -0
  96. simplibs/extractor/logic/transform/apply_steps.py +57 -0
  97. simplibs/extractor/logic/transform/steps_for.py +40 -0
  98. simplibs/extractor/logic/write/__init__.py +14 -0
  99. simplibs/extractor/logic/write/_validations/__init__.py +16 -0
  100. simplibs/extractor/logic/write/_validations/raise_target_exists_error.py +34 -0
  101. simplibs/extractor/logic/write/_validations/raise_target_is_dir_error.py +34 -0
  102. simplibs/extractor/logic/write/write_output.py +77 -0
  103. simplibs/extractor/plugins/__init__.py +14 -0
  104. simplibs/extractor/plugins/load_plugins.py +64 -0
  105. simplibs/extractor/py.typed +1 -0
  106. simplibs/extractor/registry/__init__.py +37 -0
  107. simplibs/extractor/registry/_helpers/GENERIC_ALL.py +24 -0
  108. simplibs/extractor/registry/_helpers/PROFILE_REGISTRY.py +21 -0
  109. simplibs/extractor/registry/_helpers/RENDERER_REGISTRY.py +21 -0
  110. simplibs/extractor/registry/_helpers/__init__.py +18 -0
  111. simplibs/extractor/registry/_validations/__init__.py +24 -0
  112. simplibs/extractor/registry/_validations/raise_missing_system_error.py +33 -0
  113. simplibs/extractor/registry/_validations/raise_no_profile_given_error.py +33 -0
  114. simplibs/extractor/registry/_validations/raise_profile_exists_error.py +33 -0
  115. simplibs/extractor/registry/_validations/raise_profile_not_found_error.py +45 -0
  116. simplibs/extractor/registry/_validations/raise_renderer_exists_error.py +30 -0
  117. simplibs/extractor/registry/_validations/raise_unknown_renderer_error.py +37 -0
  118. simplibs/extractor/registry/expand_profile_keys.py +60 -0
  119. simplibs/extractor/registry/get_profile.py +39 -0
  120. simplibs/extractor/registry/get_renderer.py +40 -0
  121. simplibs/extractor/registry/list_formats.py +20 -0
  122. simplibs/extractor/registry/list_profiles.py +32 -0
  123. simplibs/extractor/registry/list_systems.py +20 -0
  124. simplibs/extractor/registry/register_profile.py +46 -0
  125. simplibs/extractor/registry/register_renderer.py +47 -0
  126. simplibs/extractor/scripts/__init__.py +18 -0
  127. simplibs/extractor/scripts/_helpers/__init__.py +14 -0
  128. simplibs/extractor/scripts/_helpers/print_result.py +32 -0
  129. simplibs/extractor/scripts/_validations/__init__.py +16 -0
  130. simplibs/extractor/scripts/_validations/raise_invalid_output_name_error.py +30 -0
  131. simplibs/extractor/scripts/_validations/raise_not_python_script_error.py +30 -0
  132. simplibs/extractor/scripts/extract_here.py +93 -0
  133. simplibs/extractor/scripts/generate_local_script.py +65 -0
  134. simplibs/extractor/scripts/render_script.py +26 -0
  135. simplibs_extractor-0.1.0.dist-info/METADATA +605 -0
  136. simplibs_extractor-0.1.0.dist-info/RECORD +140 -0
  137. simplibs_extractor-0.1.0.dist-info/WHEEL +5 -0
  138. simplibs_extractor-0.1.0.dist-info/entry_points.txt +2 -0
  139. simplibs_extractor-0.1.0.dist-info/licenses/LICENSE +21 -0
  140. simplibs_extractor-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,121 @@
1
+ # Running an extraction
2
+ from .logic import extract, extract_many
3
+ from .scripts import extract_here, generate_local_script
4
+
5
+ # Profiles and formats
6
+ from .registry import (
7
+ expand_profile_keys,
8
+ get_profile,
9
+ get_renderer,
10
+ list_formats,
11
+ list_profiles,
12
+ list_systems,
13
+ register_profile,
14
+ register_renderer,
15
+ )
16
+ from .plugins import load_plugins
17
+
18
+ # Data objects
19
+ from .definitions import Entry, ExtractionConfig, ExtractionResult, FileReport, Profile
20
+ from .logic import RenderContext
21
+
22
+ # Reporting
23
+ from .logic import estimate_tokens, format_summary
24
+
25
+ # Annotations and constants
26
+ from .annotations import FileStatus, OutputFormat, OverwritePolicy, StepsSpec
27
+ from .constants import DEFAULT_PROFILE_KEY, GENERATED_MARKER
28
+
29
+
30
+ __all__ = [
31
+ # Running an extraction
32
+ "extract",
33
+ "extract_many",
34
+ "extract_here",
35
+ "generate_local_script",
36
+ # Profiles and formats
37
+ "expand_profile_keys",
38
+ "get_profile",
39
+ "get_renderer",
40
+ "list_formats",
41
+ "list_profiles",
42
+ "list_systems",
43
+ "register_profile",
44
+ "register_renderer",
45
+ "load_plugins",
46
+ # Data objects
47
+ "Entry",
48
+ "ExtractionConfig",
49
+ "ExtractionResult",
50
+ "FileReport",
51
+ "Profile",
52
+ "RenderContext",
53
+ # Reporting
54
+ "estimate_tokens",
55
+ "format_summary",
56
+ # Annotations and constants
57
+ "FileStatus",
58
+ "OutputFormat",
59
+ "OverwritePolicy",
60
+ "StepsSpec",
61
+ "DEFAULT_PROFILE_KEY",
62
+ "GENERATED_MARKER",
63
+ ]
64
+
65
+
66
+ _DESIGN_NOTES = """
67
+ # simplibs.extractor — Universal File Extractor
68
+
69
+ Gathers the text files of a folder into one document: select, read,
70
+ transform, render, write, report. The core knows no language; add-ons bring
71
+ profiles (`simplibs-code-extractor` is the first).
72
+
73
+ ## Layers (each one looks only to the left)
74
+
75
+ `annotations` <- `constants` <- `exceptions` <- `definitions` <- `registry`
76
+ <- `logic` <- `plugins` <- `scripts` <- `cli`
77
+
78
+ ## The Public Surface
79
+
80
+ Everything a program needs is in this module. The command line is the one
81
+ exception: `from simplibs.extractor.cli import run_cli`. It is not imported
82
+ here, so a program that only calls `extract()` does not load the prompt and
83
+ logger libraries it would never use.
84
+
85
+ ## Internal Components Registry
86
+
87
+ | Component | Type | Description |
88
+ | :----------------------- | :-------- | :------------------------------------------------------------- |
89
+ | `extract` | Function | One extraction through all six phases. |
90
+ | `extract_many` | Function | Several extractions; a failed one does not stop the rest. |
91
+ | `extract_here` | Function | Extracts the folder of a script into a file next to it. |
92
+ | `generate_local_script` | Function | Writes a ready-to-run local extraction script. |
93
+ | `register_profile` | Function | Adds a profile to the registry. |
94
+ | `get_profile` | Function | Returns a profile by its `system/name` key. |
95
+ | `list_profiles` | Function | Lists the registered profiles. |
96
+ | `list_systems` | Function | Lists the systems that have a profile. |
97
+ | `expand_profile_keys` | Function | Turns typed names into full profile keys. |
98
+ | `register_renderer` | Function | Adds an output format. |
99
+ | `get_renderer` | Function | Returns the renderer of a format. |
100
+ | `list_formats` | Function | Lists the registered output formats. |
101
+ | `load_plugins` | Function | Lets every installed add-on register its profiles. |
102
+ | `Profile` | Dataclass | A named set of selection rules and transformation steps. |
103
+ | `ExtractionConfig` | Dataclass | Everything one run needs to know. |
104
+ | `ExtractionResult` | Dataclass | The outcome of one run; the source of every report. |
105
+ | `FileReport` | Dataclass | What happened to one file. |
106
+ | `Entry` | Dataclass | One file ready to be rendered. |
107
+ | `RenderContext` | Dataclass | What a renderer knows besides the entries. |
108
+ | `format_summary` | Function | Readable text from an `ExtractionResult`. |
109
+ | `estimate_tokens` | Function | A rough token count of a text. |
110
+ | `FileStatus` | Type | `ok`, `skipped` or `error`. |
111
+ | `OutputFormat` | Type | The name of an output format. |
112
+ | `OverwritePolicy` | Type | `overwrite`, `unique`, `backup` or `error`. |
113
+ | `StepsSpec` | Type | One action, a suffix -> action mapping, or nothing. |
114
+ | `DEFAULT_PROFILE_KEY` | Constant | The built-in profile `generic/all`. |
115
+ | `GENERATED_MARKER` | Constant | The text that marks a file as an output of this library. |
116
+
117
+ ## Importing Has Two Side Effects
118
+ The built-in profile `generic/all` and the formats `markdown` and `plain` are
119
+ registered. Nothing else happens until a function is called; add-ons are only
120
+ looked up by `load_plugins()` (the command line calls it).
121
+ """
@@ -0,0 +1,12 @@
1
+ from .cli import run_cli
2
+
3
+
4
+ _DESIGN_NOTES = """
5
+ # __main__ — `python -m simplibs.extractor`
6
+
7
+ The same command as the console script `simplibs-extractor`, for
8
+ environments where the script's folder is not on the path.
9
+ """
10
+
11
+ if __name__ == "__main__":
12
+ raise SystemExit(run_cli())
@@ -0,0 +1,16 @@
1
+ from typing import Literal
2
+
3
+
4
+ FileStatus = Literal["ok", "skipped", "error"]
5
+ """The outcome of one file: included in the output, left out on purpose, or failed."""
6
+
7
+
8
+ _DESIGN_NOTES = """
9
+ # FileStatus — Three Outcomes, Nothing Else
10
+
11
+ ## Purpose
12
+ `skipped` is not a failure (binary file, too large, generated by this
13
+ library) and `error` is not a stop (the run continues). Keeping the two apart
14
+ is what lets the report and the exit code tell "nothing wrong" from "something
15
+ needs a look".
16
+ """
@@ -0,0 +1,25 @@
1
+ from typing import Annotated
2
+ # Inners
3
+ from ._helpers import is_output_format
4
+
5
+
6
+ OutputFormat = Annotated[str, is_output_format]
7
+ """The name of an output format (`"markdown"`, `"plain"`, or a registered one).
8
+
9
+ A lowercase name that starts with a letter. Whether a renderer with that
10
+ name exists is checked when the configuration is resolved.
11
+ """
12
+
13
+
14
+ _DESIGN_NOTES = """
15
+ # OutputFormat — a Name, Not a Closed List
16
+
17
+ ## Purpose
18
+ Renderers are registered by name, so the set of formats is open. A `Literal`
19
+ would freeze it at the two built-in formats and make every added renderer a
20
+ typing error.
21
+
22
+ ## Why `Annotated`
23
+ An editor sees a plain `str`; `@validate_call` reads the predicate from the
24
+ metadata and rejects `"Markdown"`, `""` or `"md file"` at the signature.
25
+ """
@@ -0,0 +1,24 @@
1
+ from typing import Literal
2
+
3
+
4
+ OverwritePolicy = Literal["overwrite", "unique", "backup", "error"]
5
+ """What happens when the output file already exists.
6
+
7
+ `"overwrite"` replaces it atomically, `"unique"` writes `name (1).ext` next to
8
+ it, `"backup"` keeps the old file as a backup and then replaces it, `"error"`
9
+ refuses to write.
10
+ """
11
+
12
+
13
+ _DESIGN_NOTES = """
14
+ # OverwritePolicy — Four Answers to "It Already Exists"
15
+
16
+ ## Purpose
17
+ The old code knew only `"error"`. The ecosystem already has the building
18
+ blocks for the other three (`SafeFileWriter`, `make_unique_path`,
19
+ `backup_file`), so the policy is a closed, readable choice.
20
+
21
+ ## Default
22
+ `"overwrite"` — an extractor is re-run constantly and its output is
23
+ generated, never hand-edited. The write itself is atomic.
24
+ """
@@ -0,0 +1,26 @@
1
+ from collections.abc import Mapping
2
+ from typing import TypeAlias
3
+ from simplibs.actions import Action
4
+
5
+
6
+ StepsSpec: TypeAlias = Action | Mapping[str, Action] | None
7
+ """How a profile transforms file text.
8
+
9
+ `None` keeps the text as it is. One `Action` is applied to every file. A
10
+ mapping `suffix -> Action` applies the action of the file's own suffix
11
+ (`".py"`); the key `"*"` is the fallback for every other file.
12
+ """
13
+
14
+
15
+ _DESIGN_NOTES = """
16
+ # StepsSpec — One Pipeline or One Pipeline per Language
17
+
18
+ ## Purpose
19
+ A step receives only the text, never the path, so it cannot know which
20
+ language it is cleaning. A profile that cleans Python and JavaScript
21
+ differently therefore needs a mapping from suffix to action.
22
+
23
+ ## Why Not Always a Mapping
24
+ Most profiles apply one action to everything; forcing `{"*": action}` on them
25
+ would be noise.
26
+ """
@@ -0,0 +1,26 @@
1
+ from .FileStatus import FileStatus
2
+ from .OutputFormat import OutputFormat
3
+ from .OverwritePolicy import OverwritePolicy
4
+ from .StepsSpec import StepsSpec
5
+
6
+
7
+ _DESIGN_NOTES = """
8
+ # annotations — Shared Types of simplibs-extractor
9
+
10
+ Plain imports, no `__all__` (the package root decides what is public).
11
+ `PathLike` and `ByteCount` are not redefined here — they come from
12
+ `simplibs.path` and `simplibs.file`.
13
+
14
+ ## Internal Components Registry
15
+
16
+ | Component | Type | Description |
17
+ | :---------------- | :-------------- | :------------------------------------------------------ |
18
+ | `FileStatus` | Literal Type | Outcome of one file: `ok`, `skipped`, `error`. |
19
+ | `OutputFormat` | Annotated Type | The name of an output format. |
20
+ | `OverwritePolicy` | Literal Type | What to do when the output exists. |
21
+ | `StepsSpec` | Union Type | One action, a suffix -> action mapping, or nothing. |
22
+
23
+ ## Changed in Batch 2
24
+ `is_output_format` is no longer re-exported: `Profile` is now checked by
25
+ `@validate_dataclass`, which reads the `OutputFormat` annotation itself.
26
+ """
@@ -0,0 +1,15 @@
1
+ from .is_output_format import is_output_format
2
+
3
+
4
+ _DESIGN_NOTES = """
5
+ # annotations/_helpers — Private Helpers of the Annotations
6
+
7
+ Plain imports only. Used by the annotations of this folder, not exported
8
+ from the package root.
9
+
10
+ ## Internal Components Registry
11
+
12
+ | Component | Type | Description |
13
+ | :----------------- | :------- | :------------------------------------------------------- |
14
+ | `is_output_format` | Function | Validates whether a value has the shape of a format name. |
15
+ """
@@ -0,0 +1,36 @@
1
+ import re
2
+
3
+
4
+ _FORMAT_NAME = re.compile(r"[a-z][a-z0-9_-]*")
5
+
6
+
7
+ def is_output_format(name: str) -> bool:
8
+ """Check whether a value has the shape of an output format name.
9
+
10
+ Args:
11
+ name: The candidate (`"markdown"`, `"plain"`, `"json-manifest"`).
12
+
13
+ Returns:
14
+ True for a lowercase name made of letters, digits, `_` and `-` that
15
+ starts with a letter; False for anything else, including non-strings.
16
+ """
17
+
18
+ # 1. Only a string can name a format
19
+ if not isinstance(name, str):
20
+ return False
21
+
22
+ # 2. Lowercase word characters only — no spaces, no path separators
23
+ return _FORMAT_NAME.fullmatch(name) is not None
24
+
25
+
26
+ _DESIGN_NOTES = """
27
+ # is_output_format — "Does This Look Like a Format Name?"
28
+
29
+ ## Purpose
30
+ The single rule behind the `OutputFormat` annotation.
31
+
32
+ ## Shape, Not Existence
33
+ Whether a renderer with that name is registered is known only to the
34
+ registry, a layer further to the right. This helper checks the shape; the
35
+ registry check happens when the configuration is resolved.
36
+ """
@@ -0,0 +1,32 @@
1
+ from .build_configs import build_configs
2
+ from .build_parser import build_parser
3
+ from .collect_dry_run import collect_dry_run
4
+ from .collect_profile_keys import collect_profile_keys
5
+ from .collect_source import collect_source
6
+ from .handle_list_options import handle_list_options
7
+ from .normalize_positionals import normalize_positionals
8
+ from .run_cli import run_cli
9
+ from .run_init import run_init
10
+
11
+
12
+ _DESIGN_NOTES = """
13
+ # cli — The Command Line of simplibs-extractor
14
+
15
+ Plain imports, no `__all__`. `run_cli` is the console-script entry point
16
+ (`simplibs-extractor = simplibs.extractor.cli:run_cli`); it returns the exit
17
+ code, which a console script passes to `sys.exit`.
18
+
19
+ ## Internal Components Registry
20
+
21
+ | Component | Type | Description |
22
+ | :---------------------- | :------- | :------------------------------------------------------- |
23
+ | `build_configs` | Function | One config per profile key. |
24
+ | `build_parser` | Function | The argument parser. |
25
+ | `collect_dry_run` | Function | The dry-run flag, asked for only inside the wizard. |
26
+ | `collect_profile_keys` | Function | Profile keys from the command line or from questions. |
27
+ | `collect_source` | Function | The source folder from the command line or a question. |
28
+ | `handle_list_options` | Function | Answers `--list-systems` and `--list-profiles`. |
29
+ | `normalize_positionals` | Function | Sorts the three positional words into their roles. |
30
+ | `run_cli` | Function | The command; returns the exit code. |
31
+ | `run_init` | Function | The `init` subcommand. |
32
+ """
@@ -0,0 +1,22 @@
1
+ from .describe_result import describe_result
2
+ from .is_profile_reference import is_profile_reference
3
+ from .print_error import print_error
4
+ from .profile_options import profile_options
5
+ from .system_options import system_options
6
+
7
+
8
+ _DESIGN_NOTES = """
9
+ # cli/_helpers — Private Helpers of the Command Line
10
+
11
+ Plain imports only.
12
+
13
+ ## Internal Components Registry
14
+
15
+ | Component | Type | Description |
16
+ | :--------------------- | :------- | :------------------------------------------------------- |
17
+ | `describe_result` | Function | One line saying why an extraction failed. |
18
+ | `is_profile_reference` | Function | Whether a word is a registered system or profile keys. |
19
+ | `print_error` | Function | Prints an error card to standard error. |
20
+ | `profile_options` | Function | `{name: description}` of one system's profiles. |
21
+ | `system_options` | Function | `{system: "N profile(s)"}` of every registered system. |
22
+ """
@@ -0,0 +1,34 @@
1
+ # Outers
2
+ from ...definitions import ExtractionResult
3
+
4
+
5
+ def describe_result(result: ExtractionResult) -> str:
6
+ """Describe a failed extraction in one line for the batch report.
7
+
8
+ Args:
9
+ result: A result that did not succeed.
10
+
11
+ Returns:
12
+ `"<profile key>: <why>"`: the fatal reason, the number of failed
13
+ files, or a note that nothing was selected.
14
+ """
15
+
16
+ # 1. The whole run stopped
17
+ if result.fatal:
18
+ return f"{result.profile_key}: {result.fatal}"
19
+
20
+ # 2. Some files failed
21
+ if result.error_files:
22
+ return f"{result.profile_key}: {len(result.error_files)} file(s) failed"
23
+
24
+ # 3. Nothing failed, so nothing was selected
25
+ return f"{result.profile_key}: no file was selected"
26
+
27
+
28
+ _DESIGN_NOTES = """
29
+ # describe_result — Why a Run Counts as Failed
30
+
31
+ ## Purpose
32
+ `ExtractionResult.success` is False for three different reasons. The report
33
+ at the end of a batch should say which.
34
+ """
@@ -0,0 +1,34 @@
1
+ # Outers
2
+ from ...registry import list_profiles, list_systems
3
+
4
+
5
+ def is_profile_reference(value: str) -> bool:
6
+ """Check whether a command-line word names a registered system or registered profile keys.
7
+
8
+ Args:
9
+ value: The word (`"python"`, `"generic/all"`, `"python/a,python/b"`, `"./src"`).
10
+
11
+ Returns:
12
+ True if it is a system, or a comma list made only of registered full keys.
13
+ """
14
+
15
+ # 1. A system
16
+ if value in list_systems():
17
+ return True
18
+
19
+ # 2. Full keys, one or several; every part must be registered
20
+ keys = {profile.key for profile in list_profiles()}
21
+ return all(part.strip() in keys for part in value.split(","))
22
+
23
+
24
+ _DESIGN_NOTES = """
25
+ # is_profile_reference — Profile or Folder?
26
+
27
+ ## Purpose
28
+ `simplibs-extractor src` could mean "the system `src`" or "the folder
29
+ `src`". A word that is a registered system or key is a profile reference;
30
+ anything else may be a folder.
31
+
32
+ ## Why the Registry Decides
33
+ Not the shape of the word: `generic/all` and `./src` both contain a slash.
34
+ """
@@ -0,0 +1,25 @@
1
+ import sys
2
+
3
+
4
+ def print_error(error: BaseException) -> None:
5
+ """Print an error card to standard error.
6
+
7
+ Args:
8
+ error: The exception; a `simplibs` card prints as its framed block.
9
+ """
10
+
11
+ # 1. Diagnostics go to standard error, so they never mix with a piped result
12
+ print(error, file=sys.stderr)
13
+
14
+
15
+ _DESIGN_NOTES = """
16
+ # print_error — One Way to Show a Refused Command
17
+
18
+ ## Purpose
19
+ `run_cli` and `run_init` end a refused command the same way. The card is
20
+ printed as it is: it already says what, why and how to fix it.
21
+
22
+ ## Not the Logger
23
+ A logger prefixes the first line of a multi-line card with time and level,
24
+ which breaks its frame.
25
+ """
@@ -0,0 +1,25 @@
1
+ # Outers
2
+ from ...registry import list_profiles
3
+
4
+
5
+ def profile_options(system: str) -> dict[str, str]:
6
+ """Describe the profiles of one system for a list or a choice.
7
+
8
+ Args:
9
+ system: The system (`"python"`).
10
+
11
+ Returns:
12
+ `{profile name: description}`, sorted by name; empty for an unknown system.
13
+ """
14
+
15
+ # 1. The names are the system's own (`code-clean`), without the `system/` part
16
+ return {profile.name: profile.description for profile in list_profiles(system)}
17
+
18
+
19
+ _DESIGN_NOTES = """
20
+ # profile_options — Names and Descriptions of One System
21
+
22
+ ## Purpose
23
+ Feeds `--list-profiles` and the question "Which profiles?". An empty result
24
+ is how the caller learns that a system does not exist.
25
+ """
@@ -0,0 +1,22 @@
1
+ # Outers
2
+ from ...registry import list_profiles, list_systems
3
+
4
+
5
+ def system_options() -> dict[str, str]:
6
+ """Describe the registered systems for a list or a choice.
7
+
8
+ Returns:
9
+ `{system: "N profile(s)"}`, sorted by system.
10
+ """
11
+
12
+ # 1. One entry per system, with the number of its profiles
13
+ return {system: f"{len(list_profiles(system))} profile(s)" for system in list_systems()}
14
+
15
+
16
+ _DESIGN_NOTES = """
17
+ # system_options — What `--list-systems` and the First Question Show
18
+
19
+ ## Purpose
20
+ One function for both, so a list and a question never disagree about which
21
+ systems exist.
22
+ """
@@ -0,0 +1,22 @@
1
+ from .raise_duplicate_output_error import raise_duplicate_output_error
2
+ from .raise_list_unknown_system_error import raise_list_unknown_system_error
3
+ from .raise_output_is_file_error import raise_output_is_file_error
4
+ from .raise_too_many_positionals_error import raise_too_many_positionals_error
5
+ from .raise_unknown_system_error import raise_unknown_system_error
6
+
7
+
8
+ _DESIGN_NOTES = """
9
+ # cli/_validations — Private Diagnostics of the Command Line
10
+
11
+ Plain imports only. Every function raises one `ParamError` and returns nothing.
12
+
13
+ ## Internal Components Registry
14
+
15
+ | Component | Type | Description |
16
+ | :-------------------------------- | :------- | :------------------------------------------------------------ |
17
+ | `raise_duplicate_output_error` | Function | Two profiles would write the same output file. |
18
+ | `raise_list_unknown_system_error` | Function | `--list-profiles` names a system without profiles. |
19
+ | `raise_output_is_file_error` | Function | `-o` points to a file, not a folder. |
20
+ | `raise_too_many_positionals_error`| Function | Full profile keys were followed by two more words. |
21
+ | `raise_unknown_system_error` | Function | The system named (or chosen) has no registered profile. |
22
+ """
@@ -0,0 +1,32 @@
1
+ from collections.abc import Sequence
2
+ from typing import NoReturn
3
+ from simplibs.exception import ParamError
4
+
5
+
6
+ def raise_duplicate_output_error(keys: Sequence[str]) -> NoReturn:
7
+ """Raise ParamError when two or more profiles would write the same output file.
8
+
9
+ Args:
10
+ keys: The profile keys of the run.
11
+ """
12
+
13
+ # 1. Raise structured ParamError wrapping a ValueError
14
+ raise ParamError(
15
+ error_name="EXTRACTOR_CONFIG_INVALID",
16
+ label="profiles",
17
+ value=list(keys),
18
+ problem="Two or more of these profiles write the same output file.",
19
+ expected="Profiles with different output names, or one profile per run.",
20
+ how_to_fix="Run the profiles separately, or give them different output_name values.",
21
+ exception=ValueError,
22
+ )
23
+
24
+
25
+ _DESIGN_NOTES = """
26
+ # raise_duplicate_output_error — Two Profiles, One File
27
+
28
+ ## Purpose
29
+ The profiles of one run would overwrite each other. Found before anything
30
+ runs, it costs nothing; found by `extract_many` it would cost the files the
31
+ earlier profiles had already written.
32
+ """
@@ -0,0 +1,34 @@
1
+ from typing import NoReturn
2
+ from simplibs.exception import ParamError
3
+ # Outers
4
+ from .._helpers import system_options
5
+
6
+
7
+ def raise_list_unknown_system_error(system: str) -> NoReturn:
8
+ """Raise ParamError when `--list-profiles` names a system without profiles.
9
+
10
+ Args:
11
+ system: The system that was asked for.
12
+ """
13
+
14
+ # 1. Raise structured ParamError wrapping a ValueError
15
+ raise ParamError(
16
+ error_name="EXTRACTOR_CONFIG_INVALID",
17
+ label="option '--list-profiles'",
18
+ value=system,
19
+ problem=f"No profile is registered for the system {system!r}.",
20
+ expected=f"One of the registered systems: {', '.join(system_options()) or '(none)'}.",
21
+ how_to_fix="Run --list-systems to see what is installed.",
22
+ exception=ValueError,
23
+ )
24
+
25
+
26
+ _DESIGN_NOTES = """
27
+ # raise_list_unknown_system_error — A Typo Is Not an Empty List
28
+
29
+ ## Purpose
30
+ An empty list would look like "no profiles exist" and hide the typo.
31
+
32
+ ## Close Relative of `raise_unknown_system_error`
33
+ Same problem, other option: the label and the advice differ.
34
+ """
@@ -0,0 +1,30 @@
1
+ from pathlib import Path
2
+ from typing import NoReturn
3
+ from simplibs.exception import ParamError
4
+
5
+
6
+ def raise_output_is_file_error(out_dir: Path) -> NoReturn:
7
+ """Raise ParamError when the output folder given with `-o` is a file.
8
+
9
+ Args:
10
+ out_dir: The path that was meant to be a folder.
11
+ """
12
+
13
+ # 1. Raise structured ParamError wrapping a ValueError
14
+ raise ParamError(
15
+ error_name="EXTRACTOR_CONFIG_INVALID",
16
+ label="option '-o'",
17
+ value=str(out_dir),
18
+ problem=f"'{out_dir}' is a file, but -o takes a folder.",
19
+ expected="A folder (existing or to be created).",
20
+ how_to_fix="Pass a folder; the file name comes from the profile.",
21
+ exception=ValueError,
22
+ )
23
+
24
+
25
+ _DESIGN_NOTES = """
26
+ # raise_output_is_file_error — `-o` Takes a Folder
27
+
28
+ ## Purpose
29
+ The file name always comes from the profile, so `-o` can only be a folder.
30
+ """