dbrownell-toolsdirectory 0.7.2__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.
- dbrownell_toolsdirectory-0.7.2/PKG-INFO +78 -0
- dbrownell_toolsdirectory-0.7.2/README.md +57 -0
- dbrownell_toolsdirectory-0.7.2/pyproject.toml +100 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/CreateShellCommands.py +127 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Scripts/activate.bat +74 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Shell/BashCommandVisitor.py +225 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Shell/BatchCommandVisitor.py +228 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Shell/CommandVisitor.py +161 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Shell/Commands.py +159 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/Shell/__init__.py +0 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/ToolInfo.py +444 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/__init__.py +4 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/__main__.py +267 -0
- dbrownell_toolsdirectory-0.7.2/src/dbrownell_ToolsDirectory/py.typed +0 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: dbrownell-toolsdirectory
|
|
3
|
+
Version: 0.7.2
|
|
4
|
+
Summary: Creates functionality to manipulate the shell in support of tools defined within a tools directory.
|
|
5
|
+
Author: David Brownell
|
|
6
|
+
Author-email: David Brownell <github@DavidBrownell.com>
|
|
7
|
+
License: MIT
|
|
8
|
+
Classifier: Operating System :: MacOS
|
|
9
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
10
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
11
|
+
Classifier: Programming Language :: Python
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: python-dotenv>=1.2.1
|
|
14
|
+
Requires-Dist: semantic-version>=2.10.0
|
|
15
|
+
Requires-Dist: typer>=0.20.1
|
|
16
|
+
Requires-Python: >=3.14
|
|
17
|
+
Project-URL: Documentation, https://github.com/davidbrownell/dbrownell_ToolsDirectory
|
|
18
|
+
Project-URL: Homepage, https://github.com/davidbrownell/dbrownell_ToolsDirectory
|
|
19
|
+
Project-URL: Repository, https://github.com/davidbrownell/dbrownell_ToolsDirectory
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
**Project:**
|
|
23
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/master/LICENSE)
|
|
24
|
+
|
|
25
|
+
**Package:**
|
|
26
|
+
[](https://pypi.org/project/dbrownell_ToolsDirectory/)
|
|
27
|
+
[](https://pypi.org/project/dbrownell_ToolsDirectory/)
|
|
28
|
+
[](https://pypistats.org/packages/dbrownell-toolsdirectory)
|
|
29
|
+
|
|
30
|
+
**Development:**
|
|
31
|
+
[](https://github.com/astral-sh/uv)
|
|
32
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/actions/workflows/CICD.yml)
|
|
33
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/actions)
|
|
34
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/commits/main/)
|
|
35
|
+
|
|
36
|
+
<!-- Content above this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
37
|
+
|
|
38
|
+
## Contents
|
|
39
|
+
- [Overview](#overview)
|
|
40
|
+
- [Installation](#installation)
|
|
41
|
+
- [Development](#development)
|
|
42
|
+
- [Additional Information](#additional-information)
|
|
43
|
+
- [License](#license)
|
|
44
|
+
|
|
45
|
+
## Overview
|
|
46
|
+
TODO: Complete this section
|
|
47
|
+
|
|
48
|
+
### How to use `dbrownell_ToolsDirectory`
|
|
49
|
+
TODO: Complete this section
|
|
50
|
+
|
|
51
|
+
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
52
|
+
|
|
53
|
+
## Installation
|
|
54
|
+
|
|
55
|
+
| Installation Method | Command |
|
|
56
|
+
| --- | --- |
|
|
57
|
+
| Via [uv](https://github.com/astral-sh/uv) | `uv add dbrownell_ToolsDirectory` |
|
|
58
|
+
| Via [pip](https://pip.pypa.io/en/stable/) | `pip install dbrownell_ToolsDirectory` |
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
## Development
|
|
63
|
+
Please visit [Contributing](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CONTRIBUTING.md) and [Development](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/DEVELOPMENT.md) for information on contributing to this project.
|
|
64
|
+
|
|
65
|
+
## Additional Information
|
|
66
|
+
Additional information can be found at these locations.
|
|
67
|
+
|
|
68
|
+
| Title | Document | Description |
|
|
69
|
+
| --- | --- | --- |
|
|
70
|
+
| Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CODE_OF_CONDUCT.md) | Information about the norms, rules, and responsibilities we adhere to when participating in this open source community. |
|
|
71
|
+
| Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CONTRIBUTING.md) | Information about contributing to this project. |
|
|
72
|
+
| Development | [DEVELOPMENT.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
|
|
73
|
+
| Governance | [GOVERNANCE.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
|
|
74
|
+
| Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
|
|
75
|
+
| Security | [SECURITY.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
`dbrownell_ToolsDirectory` is licensed under the <a href="https://choosealicense.com/licenses/MIT/" target="_blank">MIT</a> license.
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
**Project:**
|
|
2
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/master/LICENSE)
|
|
3
|
+
|
|
4
|
+
**Package:**
|
|
5
|
+
[](https://pypi.org/project/dbrownell_ToolsDirectory/)
|
|
6
|
+
[](https://pypi.org/project/dbrownell_ToolsDirectory/)
|
|
7
|
+
[](https://pypistats.org/packages/dbrownell-toolsdirectory)
|
|
8
|
+
|
|
9
|
+
**Development:**
|
|
10
|
+
[](https://github.com/astral-sh/uv)
|
|
11
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/actions/workflows/CICD.yml)
|
|
12
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/actions)
|
|
13
|
+
[](https://github.com/davidbrownell/dbrownell_ToolsDirectory/commits/main/)
|
|
14
|
+
|
|
15
|
+
<!-- Content above this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
- [Overview](#overview)
|
|
19
|
+
- [Installation](#installation)
|
|
20
|
+
- [Development](#development)
|
|
21
|
+
- [Additional Information](#additional-information)
|
|
22
|
+
- [License](#license)
|
|
23
|
+
|
|
24
|
+
## Overview
|
|
25
|
+
TODO: Complete this section
|
|
26
|
+
|
|
27
|
+
### How to use `dbrownell_ToolsDirectory`
|
|
28
|
+
TODO: Complete this section
|
|
29
|
+
|
|
30
|
+
<!-- Content below this delimiter will be copied to the generated README.md file. DO NOT REMOVE THIS COMMENT, as it will cause regeneration to fail. -->
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
| Installation Method | Command |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| Via [uv](https://github.com/astral-sh/uv) | `uv add dbrownell_ToolsDirectory` |
|
|
37
|
+
| Via [pip](https://pip.pypa.io/en/stable/) | `pip install dbrownell_ToolsDirectory` |
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
## Development
|
|
42
|
+
Please visit [Contributing](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CONTRIBUTING.md) and [Development](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/DEVELOPMENT.md) for information on contributing to this project.
|
|
43
|
+
|
|
44
|
+
## Additional Information
|
|
45
|
+
Additional information can be found at these locations.
|
|
46
|
+
|
|
47
|
+
| Title | Document | Description |
|
|
48
|
+
| --- | --- | --- |
|
|
49
|
+
| Code of Conduct | [CODE_OF_CONDUCT.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CODE_OF_CONDUCT.md) | Information about the norms, rules, and responsibilities we adhere to when participating in this open source community. |
|
|
50
|
+
| Contributing | [CONTRIBUTING.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/CONTRIBUTING.md) | Information about contributing to this project. |
|
|
51
|
+
| Development | [DEVELOPMENT.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/DEVELOPMENT.md) | Information about development activities involved in making changes to this project. |
|
|
52
|
+
| Governance | [GOVERNANCE.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/GOVERNANCE.md) | Information about how this project is governed. |
|
|
53
|
+
| Maintainers | [MAINTAINERS.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/MAINTAINERS.md) | Information about individuals who maintain this project. |
|
|
54
|
+
| Security | [SECURITY.md](https://github.com/davidbrownell/dbrownell_ToolsDirectory/blob/main/SECURITY.md) | Information about how to privately report security issues associated with this project. |
|
|
55
|
+
|
|
56
|
+
## License
|
|
57
|
+
`dbrownell_ToolsDirectory` is licensed under the <a href="https://choosealicense.com/licenses/MIT/" target="_blank">MIT</a> license.
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "dbrownell-toolsdirectory"
|
|
3
|
+
version = "0.7.2"
|
|
4
|
+
# ^^^^^
|
|
5
|
+
# Wheel names will be generated according to this value. Do not manually modify this value; instead
|
|
6
|
+
# update it according to committed changes by running this command from the root of the repository:
|
|
7
|
+
#
|
|
8
|
+
# uv run python -m AutoGitSemVer.scripts.UpdatePythonVersion ./pyproject.toml ./src
|
|
9
|
+
|
|
10
|
+
description = "Creates functionality to manipulate the shell in support of tools defined within a tools directory."
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "David Brownell", email = "github@DavidBrownell.com" }
|
|
14
|
+
]
|
|
15
|
+
requires-python = ">= 3.14"
|
|
16
|
+
dependencies = [
|
|
17
|
+
"python-dotenv>=1.2.1",
|
|
18
|
+
"semantic-version>=2.10.0",
|
|
19
|
+
"typer>=0.20.1",
|
|
20
|
+
]
|
|
21
|
+
classifiers = [
|
|
22
|
+
"Operating System :: MacOS",
|
|
23
|
+
"Operating System :: Microsoft :: Windows",
|
|
24
|
+
"Operating System :: POSIX :: Linux",
|
|
25
|
+
"Programming Language :: Python",
|
|
26
|
+
"Programming Language :: Python :: 3.14",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.license]
|
|
30
|
+
text = "MIT"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Homepage = "https://github.com/davidbrownell/dbrownell_ToolsDirectory"
|
|
34
|
+
Documentation = "https://github.com/davidbrownell/dbrownell_ToolsDirectory"
|
|
35
|
+
Repository = "https://github.com/davidbrownell/dbrownell_ToolsDirectory"
|
|
36
|
+
|
|
37
|
+
[build-system]
|
|
38
|
+
requires = ["uv_build>=0.9.18,<0.10.0"]
|
|
39
|
+
build-backend = "uv_build"
|
|
40
|
+
|
|
41
|
+
[dependency-groups]
|
|
42
|
+
dev = [
|
|
43
|
+
"autogitsemver>=0.9.2",
|
|
44
|
+
"dbrownell-commitemojis>=0.2.0",
|
|
45
|
+
"pre-commit>=4.5.1",
|
|
46
|
+
"pyfakefs>=6.0.0",
|
|
47
|
+
"pytest>=9.0.2",
|
|
48
|
+
"pytest-cov>=7.0.0",
|
|
49
|
+
"ruff>=0.14.10",
|
|
50
|
+
"ty>=0.0.8",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
addopts = "--verbose -vv --capture=no --cov=dbrownell_ToolsDirectory --cov-report html --cov-report term --cov-report xml:coverage.xml --cov-fail-under=95.0"
|
|
55
|
+
python_files = [
|
|
56
|
+
"**/*Test.py",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
[tool.ruff]
|
|
60
|
+
line-length = 110
|
|
61
|
+
|
|
62
|
+
[tool.ruff.lint]
|
|
63
|
+
exclude = ["tests/**"]
|
|
64
|
+
|
|
65
|
+
select = ["ALL"]
|
|
66
|
+
|
|
67
|
+
ignore = [
|
|
68
|
+
"ANN002", # Missing type annotation for `*args`
|
|
69
|
+
"ANN003", # Missing type annotation for `**kwargs`
|
|
70
|
+
"BLE001", # Do not catch blind exception: `Exception`
|
|
71
|
+
"COM812", # Trailing comma missing
|
|
72
|
+
"D104", # Missing docstring in public package
|
|
73
|
+
"D105", # Missing docstring in magic method
|
|
74
|
+
"D107", # Missing docstring in `__init__` method
|
|
75
|
+
"D202", # No blank lines allowed after function docstring
|
|
76
|
+
"E501", # Line too long
|
|
77
|
+
"FIX002", # Line contains TODO, consider resolving the issue
|
|
78
|
+
"I001", # Import block is un-sorted or un-formatted
|
|
79
|
+
"N802", # Function name `xxx` should be lowercase
|
|
80
|
+
"N999", # Invalid module name
|
|
81
|
+
"RSE102", # Unnecessary parentheses on raise exception
|
|
82
|
+
"S101", # Use of assert detected
|
|
83
|
+
"TC006", # Add quotes to type expression in `typing.cast()`
|
|
84
|
+
"TD002", # Missing author in TODO
|
|
85
|
+
"TD003", # Missing issue link for this TODO
|
|
86
|
+
"TRY002", # Create your own exception
|
|
87
|
+
"TRY300", # Consider moving this statement to an `else` block
|
|
88
|
+
"UP032", # Use f-string instead of `format` call
|
|
89
|
+
]
|
|
90
|
+
|
|
91
|
+
[tool.ruff.lint.mccabe]
|
|
92
|
+
max-complexity = 15
|
|
93
|
+
|
|
94
|
+
[tool.ruff.lint.pylint]
|
|
95
|
+
max-args = 10
|
|
96
|
+
max-branches = 20
|
|
97
|
+
max-returns = 20
|
|
98
|
+
|
|
99
|
+
[tool.uv.build-backend]
|
|
100
|
+
module-name = "dbrownell_ToolsDirectory"
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import io
|
|
3
|
+
import logging
|
|
4
|
+
import os
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
from typing import cast, TYPE_CHECKING
|
|
8
|
+
|
|
9
|
+
from dbrownell_Common.ContextlibEx import ExitStack
|
|
10
|
+
from dotenv import dotenv_values
|
|
11
|
+
|
|
12
|
+
from dbrownell_ToolsDirectory.Shell.Commands import Augment, Command, EchoOff, Raw, Set
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
|
|
17
|
+
from dbrownell_Common.Streams.DoneManager import DoneManager
|
|
18
|
+
|
|
19
|
+
from dbrownell_ToolsDirectory.ToolInfo import ToolInfo
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# ----------------------------------------------------------------------
|
|
23
|
+
def CreateShellCommands(
|
|
24
|
+
dm: DoneManager,
|
|
25
|
+
tool_infos: list[ToolInfo],
|
|
26
|
+
) -> list[Command]:
|
|
27
|
+
r"""Create shell commands to set up the environment for the specified tools.
|
|
28
|
+
|
|
29
|
+
For each tool, this function looks for one or more ``.env`` files (as
|
|
30
|
+
determined by :meth:`ToolInfo.GeneratePotentialEnvFiles`) and loads
|
|
31
|
+
environment variables from those files.
|
|
32
|
+
|
|
33
|
+
When reading values from a ``.env`` file, any path segment that begins
|
|
34
|
+
with ``./`` or ``.\`` is treated as relative to the directory that
|
|
35
|
+
contains the ``.env`` file itself. The leading ``./`` or ``.\`` is
|
|
36
|
+
replaced with the absolute path to the ``.env`` file's directory plus
|
|
37
|
+
the appropriate path separator.
|
|
38
|
+
|
|
39
|
+
Parent-directory references such as ``../`` are not resolved or
|
|
40
|
+
rewritten by this logic; they are left in the value as-is. Only
|
|
41
|
+
current-directory references using ``./`` or ``.\`` are expanded.
|
|
42
|
+
|
|
43
|
+
Example:
|
|
44
|
+
* Given an environment file ``/tools/Tool1/Tool1.env`` containing::
|
|
45
|
+
RELATIVE_PATH=./bin
|
|
46
|
+
the value will be expanded to::
|
|
47
|
+
/tools/Tool1/bin
|
|
48
|
+
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
with dm.Nested("Creating shell commands...") as shell_dm:
|
|
52
|
+
relative_path_re = re.compile(r"(?<!\.)(?P<current_dir>\.[/\\])")
|
|
53
|
+
|
|
54
|
+
# ----------------------------------------------------------------------
|
|
55
|
+
def RelativePathReplacement(_: re.Match, env_filename: Path) -> str:
|
|
56
|
+
return str(env_filename.parent) + os.path.sep
|
|
57
|
+
|
|
58
|
+
# ----------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
# Update dotenv's logger to suppress output
|
|
61
|
+
logger_sink = io.StringIO()
|
|
62
|
+
|
|
63
|
+
handler = logging.StreamHandler(logger_sink)
|
|
64
|
+
|
|
65
|
+
with ExitStack(handler.close):
|
|
66
|
+
logger = logging.getLogger("dotenv.main")
|
|
67
|
+
|
|
68
|
+
logger.propagate = False
|
|
69
|
+
logger.setLevel(logging.WARNING)
|
|
70
|
+
|
|
71
|
+
logger.handlers.clear()
|
|
72
|
+
|
|
73
|
+
logger.addHandler(handler)
|
|
74
|
+
with ExitStack(lambda: logger.removeHandler(handler)):
|
|
75
|
+
# Create the commands
|
|
76
|
+
commands: list[Command] = [EchoOff()]
|
|
77
|
+
|
|
78
|
+
for tool_info in tool_infos:
|
|
79
|
+
commands.append(Augment("PATH", str(tool_info.binary_directory)))
|
|
80
|
+
|
|
81
|
+
# Search for and apply environment files
|
|
82
|
+
env_config: dict[str, str] = {}
|
|
83
|
+
|
|
84
|
+
for potential_env_file in tool_info.GeneratePotentialEnvFiles():
|
|
85
|
+
if not potential_env_file.is_file():
|
|
86
|
+
continue
|
|
87
|
+
|
|
88
|
+
logger_sink.seek(0)
|
|
89
|
+
logger_sink.truncate(0)
|
|
90
|
+
|
|
91
|
+
# Load the content
|
|
92
|
+
try:
|
|
93
|
+
with potential_env_file.open("r", encoding="utf-8") as f:
|
|
94
|
+
this_env_config = dotenv_values(stream=f)
|
|
95
|
+
|
|
96
|
+
logger_content = logger_sink.getvalue()
|
|
97
|
+
if logger_content:
|
|
98
|
+
raise Exception(logger_content.rstrip()) # noqa: TRY301
|
|
99
|
+
|
|
100
|
+
except Exception as ex:
|
|
101
|
+
shell_dm.WriteError(
|
|
102
|
+
f"Unable to process the environment file '{potential_env_file}': {ex}\n"
|
|
103
|
+
)
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
# Populate the placeholders
|
|
107
|
+
for key, value in this_env_config.items():
|
|
108
|
+
assert value is not None, key
|
|
109
|
+
|
|
110
|
+
this_env_config[key] = relative_path_re.sub(
|
|
111
|
+
lambda m, potential_env_file=potential_env_file: RelativePathReplacement(
|
|
112
|
+
m,
|
|
113
|
+
potential_env_file,
|
|
114
|
+
),
|
|
115
|
+
value,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
# Commit the results
|
|
119
|
+
env_config.update(cast(dict[str, str], this_env_config))
|
|
120
|
+
|
|
121
|
+
if env_config:
|
|
122
|
+
for key, value in env_config.items():
|
|
123
|
+
commands.append(Set(key, value))
|
|
124
|
+
|
|
125
|
+
commands.append(Raw("\n"))
|
|
126
|
+
|
|
127
|
+
return commands
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
|
|
3
|
+
REM Create a temporary file that contains the output produced by dbrownell_ToolsDirectory.
|
|
4
|
+
call :CreateTempScriptName
|
|
5
|
+
|
|
6
|
+
uv run python -m dbrownell_ToolsDirectory "%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME%" batch %*
|
|
7
|
+
set _DBROWNELL_TOOLS_SCRIPT_GENERATION_RETURN_CODE=%ERRORLEVEL%
|
|
8
|
+
|
|
9
|
+
REM Invoke the script
|
|
10
|
+
if exist "%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME%" (
|
|
11
|
+
call "%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME%"
|
|
12
|
+
)
|
|
13
|
+
set _DBROWNELL_TOOLS_SCRIPT_EXECUTION_RETURN_CODE=%ERRORLEVEL%
|
|
14
|
+
|
|
15
|
+
REM Process errors
|
|
16
|
+
if "%_DBROWNELL_TOOLS_SCRIPT_GENERATION_RETURN_CODE%" NEQ "0" (
|
|
17
|
+
@echo.
|
|
18
|
+
@echo [31m[1mERROR:[0m Errors were encountered and the tool directories have not been activated.
|
|
19
|
+
@echo [31m[1mERROR:[0m
|
|
20
|
+
@echo [31m[1mERROR:[0m [dbrownell_ToolsDirectory failed]
|
|
21
|
+
@echo [31m[1mERROR:[0m
|
|
22
|
+
|
|
23
|
+
goto ErrorExit
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
if "%_DBROWNELL_TOOLS_SCRIPT_EXECUTION_RETURN_CODE%" NEQ "0" (
|
|
27
|
+
@echo.
|
|
28
|
+
@echo [31m[1mERROR:[0m Errors were encountered and the tool directories have not been activated.
|
|
29
|
+
@echo [31m[1mERROR:[0m
|
|
30
|
+
@echo [31m[1mERROR:[0m [%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME% failed]
|
|
31
|
+
@echo [31m[1mERROR:[0m
|
|
32
|
+
@echo.
|
|
33
|
+
|
|
34
|
+
goto ErrorExit
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
REM Success
|
|
38
|
+
del "%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME%"
|
|
39
|
+
|
|
40
|
+
@echo.
|
|
41
|
+
@echo [32m[1mSUCCESS:[0m The tool directories have been activated.
|
|
42
|
+
@echo.
|
|
43
|
+
@echo.
|
|
44
|
+
|
|
45
|
+
@REM ----------------------------------------------------------------------
|
|
46
|
+
set _DBROWNELL_TOOLS_DIRECTORY_RETURN_CODE=0
|
|
47
|
+
goto Exit
|
|
48
|
+
|
|
49
|
+
@REM ----------------------------------------------------------------------
|
|
50
|
+
:ErrorExit
|
|
51
|
+
|
|
52
|
+
set _DBROWNELL_TOOLS_DIRECTORY_RETURN_CODE=-1
|
|
53
|
+
goto Exit
|
|
54
|
+
|
|
55
|
+
@REM ----------------------------------------------------------------------
|
|
56
|
+
:Exit
|
|
57
|
+
set _DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME=
|
|
58
|
+
set _DBROWNELL_TOOLS_SCRIPT_GENERATION_RETURN_CODE=
|
|
59
|
+
set _DBROWNELL_TOOLS_SCRIPT_EXECUTION_RETURN_CODE=
|
|
60
|
+
|
|
61
|
+
exit /B %_DBROWNELL_TOOLS_DIRECTORY_RETURN_CODE%
|
|
62
|
+
|
|
63
|
+
@REM ----------------------------------------------------------------------
|
|
64
|
+
@REM |
|
|
65
|
+
@REM | Internal Functions
|
|
66
|
+
@REM |
|
|
67
|
+
@REM ----------------------------------------------------------------------
|
|
68
|
+
:CreateTempScriptName
|
|
69
|
+
setlocal EnableDelayedExpansion
|
|
70
|
+
set _filename=%CD%\ExecuteImpl-!RANDOM!-!Time:~6,5!.cmd
|
|
71
|
+
endlocal & set _DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME=%_filename%
|
|
72
|
+
|
|
73
|
+
if exist "%_DBROWNELL_TOOLS_DIRECTORY_TEMP_SCRIPT_NAME%" goto CreateTempScriptName
|
|
74
|
+
goto :EOF
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# noqa: D100
|
|
2
|
+
import textwrap
|
|
3
|
+
|
|
4
|
+
from dbrownell_Common.Types import override
|
|
5
|
+
|
|
6
|
+
from dbrownell_ToolsDirectory.Shell.Commands import (
|
|
7
|
+
Message,
|
|
8
|
+
Call,
|
|
9
|
+
Execute,
|
|
10
|
+
Set,
|
|
11
|
+
Augment,
|
|
12
|
+
Exit,
|
|
13
|
+
ExitOnError,
|
|
14
|
+
EchoOff,
|
|
15
|
+
PersistError,
|
|
16
|
+
PushDirectory,
|
|
17
|
+
PopDirectory,
|
|
18
|
+
Raw,
|
|
19
|
+
)
|
|
20
|
+
from dbrownell_ToolsDirectory.Shell.CommandVisitor import CommandVisitor
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# ----------------------------------------------------------------------
|
|
24
|
+
class BashCommandVisitor(CommandVisitor):
|
|
25
|
+
"""Command visitor for Linux bash scripts."""
|
|
26
|
+
|
|
27
|
+
# ----------------------------------------------------------------------
|
|
28
|
+
def __init__(self) -> None:
|
|
29
|
+
super().__init__()
|
|
30
|
+
|
|
31
|
+
self._message_substitution_lookup: dict[str, str] = {
|
|
32
|
+
"$": r"\$",
|
|
33
|
+
'"': r"\"",
|
|
34
|
+
"`": r"\\\`",
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
# ----------------------------------------------------------------------
|
|
38
|
+
@override
|
|
39
|
+
def OnMessage( # noqa: D102
|
|
40
|
+
self,
|
|
41
|
+
command: Message,
|
|
42
|
+
) -> str | None:
|
|
43
|
+
output: list[str] = []
|
|
44
|
+
|
|
45
|
+
for command_line in command.value.split("\n"):
|
|
46
|
+
if not command_line.strip():
|
|
47
|
+
output.append('echo ""')
|
|
48
|
+
continue
|
|
49
|
+
|
|
50
|
+
line = command_line
|
|
51
|
+
|
|
52
|
+
for source, dest in self._message_substitution_lookup.items():
|
|
53
|
+
line = line.replace(source, dest)
|
|
54
|
+
|
|
55
|
+
output.append(f'echo "{line}"')
|
|
56
|
+
|
|
57
|
+
return " && ".join(output) + "\n"
|
|
58
|
+
|
|
59
|
+
# ----------------------------------------------------------------------
|
|
60
|
+
@override
|
|
61
|
+
def OnCall( # noqa: D102
|
|
62
|
+
self,
|
|
63
|
+
command: Call,
|
|
64
|
+
) -> str | None:
|
|
65
|
+
result = f"source {command.command_line}\n"
|
|
66
|
+
if command.exit_on_error:
|
|
67
|
+
exit_on_error_result = self.Accept(
|
|
68
|
+
ExitOnError(use_return_statement=command.exit_via_return_statement)
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
if exit_on_error_result:
|
|
72
|
+
result += exit_on_error_result
|
|
73
|
+
|
|
74
|
+
return result
|
|
75
|
+
|
|
76
|
+
# ----------------------------------------------------------------------
|
|
77
|
+
@override
|
|
78
|
+
def OnExecute( # noqa: D102
|
|
79
|
+
self,
|
|
80
|
+
command: Execute,
|
|
81
|
+
) -> str | None:
|
|
82
|
+
result = command.command_line + "\n"
|
|
83
|
+
|
|
84
|
+
if command.exit_on_error:
|
|
85
|
+
exit_on_error_result = self.Accept(
|
|
86
|
+
ExitOnError(use_return_statement=command.exit_via_return_statement)
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if exit_on_error_result:
|
|
90
|
+
result += exit_on_error_result
|
|
91
|
+
|
|
92
|
+
return result
|
|
93
|
+
|
|
94
|
+
# ----------------------------------------------------------------------
|
|
95
|
+
@override
|
|
96
|
+
def OnSet( # noqa: D102
|
|
97
|
+
self,
|
|
98
|
+
command: Set,
|
|
99
|
+
) -> str | None:
|
|
100
|
+
if command.value_or_values is None:
|
|
101
|
+
return f"unset {command.name}\n"
|
|
102
|
+
|
|
103
|
+
values = ":".join(command.EnumValues())
|
|
104
|
+
|
|
105
|
+
values = values.removeprefix('"')
|
|
106
|
+
values = values.removesuffix('"')
|
|
107
|
+
|
|
108
|
+
return f'export {command.name}="{values}"\n'
|
|
109
|
+
|
|
110
|
+
# ----------------------------------------------------------------------
|
|
111
|
+
@override
|
|
112
|
+
def OnAugment( # noqa: D102
|
|
113
|
+
self,
|
|
114
|
+
command: Augment,
|
|
115
|
+
) -> str | None:
|
|
116
|
+
if command.append_values:
|
|
117
|
+
add_statement_template = f"{{value}}:${{{{{command.name}}}}}"
|
|
118
|
+
else:
|
|
119
|
+
add_statement_template = f"${{{{{command.name}}}}}:{{value}}"
|
|
120
|
+
|
|
121
|
+
add_statement_template = f'export {command.name}="{add_statement_template}"'
|
|
122
|
+
|
|
123
|
+
statement_template = (
|
|
124
|
+
f'[[ ":${{{{{command.name}}}}}:" != *":{{value}}:"* ]] && ' + add_statement_template
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
statements: list[str] = [statement_template.format(value=value) for value in command.EnumValues()]
|
|
128
|
+
|
|
129
|
+
return "\n".join(statements) + "\n"
|
|
130
|
+
|
|
131
|
+
# ----------------------------------------------------------------------
|
|
132
|
+
@override
|
|
133
|
+
def OnExit( # noqa: D102
|
|
134
|
+
self,
|
|
135
|
+
command: Exit,
|
|
136
|
+
) -> str | None:
|
|
137
|
+
return textwrap.dedent(
|
|
138
|
+
"""\
|
|
139
|
+
{success}
|
|
140
|
+
{error}
|
|
141
|
+
return {return_code}
|
|
142
|
+
""",
|
|
143
|
+
).format(
|
|
144
|
+
success=textwrap.dedent(
|
|
145
|
+
"""\
|
|
146
|
+
if [[ $? -eq 0 ]]; then
|
|
147
|
+
read -p "Press [Enter] to continue"
|
|
148
|
+
fi
|
|
149
|
+
""",
|
|
150
|
+
).rstrip()
|
|
151
|
+
if command.pause_on_success
|
|
152
|
+
else "",
|
|
153
|
+
error=textwrap.dedent(
|
|
154
|
+
"""\
|
|
155
|
+
if [[ $? -ne 0 ]]; then
|
|
156
|
+
read -p "Press [Enter] to continue"
|
|
157
|
+
fi
|
|
158
|
+
""",
|
|
159
|
+
).rstrip()
|
|
160
|
+
if command.pause_on_error
|
|
161
|
+
else "",
|
|
162
|
+
return_code=command.return_code or 0,
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
# ----------------------------------------------------------------------
|
|
166
|
+
@override
|
|
167
|
+
def OnExitOnError( # noqa: D102
|
|
168
|
+
self,
|
|
169
|
+
command: ExitOnError,
|
|
170
|
+
) -> str | None:
|
|
171
|
+
variable_name = f"${command.variable_name}" if command.variable_name else "$?"
|
|
172
|
+
|
|
173
|
+
return textwrap.dedent(
|
|
174
|
+
f"""\
|
|
175
|
+
error_code={variable_name}
|
|
176
|
+
if [[ $error_code -ne 0 ]]; then
|
|
177
|
+
{"return" if command.use_return_statement else "exit"} {command.return_code or "$error_code"}
|
|
178
|
+
fi
|
|
179
|
+
""",
|
|
180
|
+
)
|
|
181
|
+
|
|
182
|
+
# ----------------------------------------------------------------------
|
|
183
|
+
@override
|
|
184
|
+
def OnEchoOff( # noqa: D102
|
|
185
|
+
self,
|
|
186
|
+
command: EchoOff, # noqa: ARG002
|
|
187
|
+
) -> str | None:
|
|
188
|
+
return "set +x\n\n"
|
|
189
|
+
|
|
190
|
+
# ----------------------------------------------------------------------
|
|
191
|
+
@override
|
|
192
|
+
def OnPersistError( # noqa: D102
|
|
193
|
+
self,
|
|
194
|
+
command: PersistError,
|
|
195
|
+
) -> str | None:
|
|
196
|
+
return f"{command.variable_name}=$?\n"
|
|
197
|
+
|
|
198
|
+
# ----------------------------------------------------------------------
|
|
199
|
+
@override
|
|
200
|
+
def OnPushDirectory( # noqa: D102
|
|
201
|
+
self,
|
|
202
|
+
command: PushDirectory,
|
|
203
|
+
) -> str | None:
|
|
204
|
+
if command.value is None:
|
|
205
|
+
directory = """$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null 2>&1 && pwd )"""
|
|
206
|
+
else:
|
|
207
|
+
directory = f'"{command.value.as_posix()}"'
|
|
208
|
+
|
|
209
|
+
return f"pushd {directory} > /dev/null\n"
|
|
210
|
+
|
|
211
|
+
# ----------------------------------------------------------------------
|
|
212
|
+
@override
|
|
213
|
+
def OnPopDirectory( # noqa: D102
|
|
214
|
+
self,
|
|
215
|
+
command: PopDirectory, # noqa: ARG002
|
|
216
|
+
) -> str | None:
|
|
217
|
+
return "popd > /dev/null\n"
|
|
218
|
+
|
|
219
|
+
# ----------------------------------------------------------------------
|
|
220
|
+
@override
|
|
221
|
+
def OnRaw( # noqa: D102
|
|
222
|
+
self,
|
|
223
|
+
command: Raw,
|
|
224
|
+
) -> str | None:
|
|
225
|
+
return command.value.removesuffix("\n") + "\n"
|