cmdbox-cli 1.0.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.
- cmdbox/__init__.py +0 -0
- cmdbox/cli/__init__.py +0 -0
- cmdbox/cli/app.py +125 -0
- cmdbox/cli/commands/__init__.py +0 -0
- cmdbox/cli/commands/alias_fallback.py +102 -0
- cmdbox/cli/commands/command_crud.py +429 -0
- cmdbox/cli/commands/command_run.py +255 -0
- cmdbox/cli/commands/history.py +109 -0
- cmdbox/cli/commands/init.py +54 -0
- cmdbox/cli/commands/settings.py +62 -0
- cmdbox/cli/commands/tag_crud.py +277 -0
- cmdbox/cli/commands/variable_crud.py +349 -0
- cmdbox/cli/common/__init__.py +0 -0
- cmdbox/cli/common/errors.py +58 -0
- cmdbox/cli/common/update_fields.py +88 -0
- cmdbox/cli/completions/__init__.py +0 -0
- cmdbox/cli/completions/commands.py +26 -0
- cmdbox/cli/completions/fields.py +31 -0
- cmdbox/cli/completions/tags.py +24 -0
- cmdbox/cli/completions/variables.py +26 -0
- cmdbox/cli/handlers/__init__.py +0 -0
- cmdbox/cli/handlers/command_handlers.py +357 -0
- cmdbox/cli/handlers/common_handlers.py +15 -0
- cmdbox/cli/handlers/history_handlers.py +94 -0
- cmdbox/cli/handlers/init_handler.py +127 -0
- cmdbox/cli/handlers/run_handler.py +178 -0
- cmdbox/cli/handlers/settings_handler.py +59 -0
- cmdbox/cli/handlers/tag_handlers.py +220 -0
- cmdbox/cli/handlers/variable_handlers.py +272 -0
- cmdbox/cli/prompts/__init__.py +0 -0
- cmdbox/cli/prompts/completers.py +161 -0
- cmdbox/cli/prompts/prompts.py +108 -0
- cmdbox/cli/prompts/validators.py +46 -0
- cmdbox/cli/ui/__init__.py +0 -0
- cmdbox/cli/ui/console.py +31 -0
- cmdbox/cli/ui/editor.py +141 -0
- cmdbox/cli/ui/presenters/__init__.py +0 -0
- cmdbox/cli/ui/presenters/app_presenter.py +8 -0
- cmdbox/cli/ui/presenters/command_presenter.py +168 -0
- cmdbox/cli/ui/presenters/history_presenter.py +83 -0
- cmdbox/cli/ui/presenters/init_instructions.py +52 -0
- cmdbox/cli/ui/presenters/init_presenter.py +57 -0
- cmdbox/cli/ui/presenters/result_presenter.py +144 -0
- cmdbox/cli/ui/presenters/settings_presenter.py +130 -0
- cmdbox/cli/ui/presenters/tag_presenter.py +97 -0
- cmdbox/cli/ui/presenters/variable_presenter.py +103 -0
- cmdbox/cli/ui/primitives.py +410 -0
- cmdbox/cli/ui/theme.py +43 -0
- cmdbox/cli/ui/theme_builder.py +49 -0
- cmdbox/common/__init__.py +0 -0
- cmdbox/common/io.py +34 -0
- cmdbox/container.py +156 -0
- cmdbox/core/__init__.py +0 -0
- cmdbox/core/fields.py +48 -0
- cmdbox/core/paths.py +52 -0
- cmdbox/database.py +65 -0
- cmdbox/exceptions.py +10 -0
- cmdbox/init/__init__.py +0 -0
- cmdbox/init/detect.py +82 -0
- cmdbox/init/integrations/bash.sh +10 -0
- cmdbox/init/integrations/cmd.bat +14 -0
- cmdbox/init/integrations/fish.fish +11 -0
- cmdbox/init/integrations/powershell.ps1 +14 -0
- cmdbox/init/integrations/zsh.sh +10 -0
- cmdbox/init/io.py +68 -0
- cmdbox/init/specs.py +54 -0
- cmdbox/logging_setup/__init__.py +0 -0
- cmdbox/logging_setup/log_config.py +123 -0
- cmdbox/logging_setup/log_decorators.py +40 -0
- cmdbox/logging_setup/log_handlers.py +94 -0
- cmdbox/migrations/__init__.py +1 -0
- cmdbox/migrations/errors.py +10 -0
- cmdbox/migrations/runner.py +127 -0
- cmdbox/migrations/versions/__init__.py +0 -0
- cmdbox/models.py +165 -0
- cmdbox/repositories/__init__.py +0 -0
- cmdbox/repositories/base_repository.py +181 -0
- cmdbox/repositories/command_repository.py +391 -0
- cmdbox/repositories/errors.py +120 -0
- cmdbox/repositories/history_repository.py +155 -0
- cmdbox/repositories/results.py +37 -0
- cmdbox/repositories/tag_repository.py +91 -0
- cmdbox/repositories/validators.py +256 -0
- cmdbox/repositories/variable_repository.py +324 -0
- cmdbox/resolve/__init__.py +0 -0
- cmdbox/resolve/errors.py +65 -0
- cmdbox/resolve/lookup.py +137 -0
- cmdbox/resolve/resolver.py +402 -0
- cmdbox/resolve/type_defs.py +96 -0
- cmdbox/runtime/__init__.py +0 -0
- cmdbox/runtime/executor.py +454 -0
- cmdbox/runtime/results.py +25 -0
- cmdbox/runtime/shell.py +90 -0
- cmdbox/services/__init__.py +0 -0
- cmdbox/services/command_services.py +261 -0
- cmdbox/services/errors.py +37 -0
- cmdbox/services/field_selection.py +162 -0
- cmdbox/services/history_service.py +68 -0
- cmdbox/services/run_service.py +204 -0
- cmdbox/services/tag_services.py +134 -0
- cmdbox/services/variable_services.py +224 -0
- cmdbox/settings/__init__.py +0 -0
- cmdbox/settings/models.py +129 -0
- cmdbox/settings/settings_repository.py +36 -0
- cmdbox/settings/settings_service.py +144 -0
- cmdbox/version.py +1 -0
- cmdbox_cli-1.0.0.dist-info/METADATA +125 -0
- cmdbox_cli-1.0.0.dist-info/RECORD +112 -0
- cmdbox_cli-1.0.0.dist-info/WHEEL +5 -0
- cmdbox_cli-1.0.0.dist-info/entry_points.txt +2 -0
- cmdbox_cli-1.0.0.dist-info/licenses/LICENSE +21 -0
- cmdbox_cli-1.0.0.dist-info/top_level.txt +1 -0
cmdbox/core/fields.py
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
COMMAND_DISPLAY_FIELDS = [
|
|
2
|
+
"alias",
|
|
3
|
+
"template",
|
|
4
|
+
"description",
|
|
5
|
+
"cwd",
|
|
6
|
+
"shell",
|
|
7
|
+
"env",
|
|
8
|
+
"timeout",
|
|
9
|
+
"date_created",
|
|
10
|
+
"last_updated",
|
|
11
|
+
"used",
|
|
12
|
+
"last_used",
|
|
13
|
+
]
|
|
14
|
+
COMMAND_SEARCH_FIELDS = [
|
|
15
|
+
"alias",
|
|
16
|
+
"template",
|
|
17
|
+
"description",
|
|
18
|
+
"date_created",
|
|
19
|
+
"last_updated",
|
|
20
|
+
"used",
|
|
21
|
+
"last_used",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
VARIABLE_DISPLAY_FIELDS = [
|
|
25
|
+
"name",
|
|
26
|
+
"value",
|
|
27
|
+
"date_created",
|
|
28
|
+
"last_updated",
|
|
29
|
+
]
|
|
30
|
+
VARIABLE_SEARCH_FIELDS = [
|
|
31
|
+
"name",
|
|
32
|
+
"value",
|
|
33
|
+
"date_created",
|
|
34
|
+
"last_updated",
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
TAG_DISPLAY_FIELDS = [
|
|
38
|
+
"name",
|
|
39
|
+
"description",
|
|
40
|
+
"date_created",
|
|
41
|
+
"last_updated",
|
|
42
|
+
]
|
|
43
|
+
TAG_SEARCH_FIELDS = [
|
|
44
|
+
"name",
|
|
45
|
+
"description",
|
|
46
|
+
"date_created",
|
|
47
|
+
"last_updated",
|
|
48
|
+
]
|
cmdbox/core/paths.py
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
VENDOR = "SomeGuySoftware"
|
|
6
|
+
APP_NAME = "CmdBox"
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def get_app_data_dir() -> Path:
|
|
10
|
+
"""
|
|
11
|
+
Gets the application data directory for the current platform.
|
|
12
|
+
|
|
13
|
+
This function determines the appropriate location where application data
|
|
14
|
+
should be stored for the current operating system. The directory varies
|
|
15
|
+
based on the operating system, ensuring compatibility and proper organization
|
|
16
|
+
of application data.
|
|
17
|
+
|
|
18
|
+
Returns:
|
|
19
|
+
Path: A `Path` object representing the full path to the application
|
|
20
|
+
data directory for the current platform.
|
|
21
|
+
"""
|
|
22
|
+
if sys.platform == "win32":
|
|
23
|
+
base = Path(os.environ["APPDATA"])
|
|
24
|
+
elif sys.platform == "darwin":
|
|
25
|
+
base = Path.home() / "Library" / "Application Support"
|
|
26
|
+
else:
|
|
27
|
+
base = Path.home() / ".local" / "share"
|
|
28
|
+
return base / VENDOR / APP_NAME
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
APP_DATA_DIR = get_app_data_dir()
|
|
32
|
+
APP_DATA_DIR.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def get_log_dir() -> Path:
|
|
36
|
+
"""
|
|
37
|
+
Retrieves the directory path designated for storing log files.
|
|
38
|
+
|
|
39
|
+
This function ensures the existence of the 'logs' directory within the application
|
|
40
|
+
data directory. If the directory does not exist, it gets created with necessary
|
|
41
|
+
parent directories.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Path: A `Path` object representing the log directory.
|
|
45
|
+
"""
|
|
46
|
+
log_dir = APP_DATA_DIR / "logs"
|
|
47
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
return log_dir
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_log_file_path() -> Path:
|
|
52
|
+
return get_log_dir() / "cmdbox.log"
|
cmdbox/database.py
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
from peewee import SqliteDatabase
|
|
4
|
+
|
|
5
|
+
from cmdbox.core.paths import get_app_data_dir
|
|
6
|
+
|
|
7
|
+
log = logging.getLogger(__name__)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
DB_PATH = get_app_data_dir() / "cmdbox.db"
|
|
11
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
db = SqliteDatabase(None)
|
|
15
|
+
_db_initialized = False
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def init_database(testing: bool = False) -> None:
|
|
19
|
+
"""
|
|
20
|
+
Initializes the database connection based on the environment or testing flag.
|
|
21
|
+
|
|
22
|
+
This function determines whether to use an in-memory SQLite database for
|
|
23
|
+
testing purposes or the standard SQLite database file path for production
|
|
24
|
+
or other environments. The behavior is controlled by the `testing` argument
|
|
25
|
+
and the `CMDBOX_ENV` environment variable.
|
|
26
|
+
|
|
27
|
+
Args:
|
|
28
|
+
testing (bool | None): A flag to indicate whether to use the testing
|
|
29
|
+
(in-memory) database. If None, the value is derived from the
|
|
30
|
+
`CMDBOX_ENV` environment variable. Defaults to None.
|
|
31
|
+
"""
|
|
32
|
+
global _db_initialized
|
|
33
|
+
if _db_initialized:
|
|
34
|
+
return
|
|
35
|
+
if testing is None:
|
|
36
|
+
env = os.environ.get("CMDBOX_ENV", "production")
|
|
37
|
+
testing = env == "testing"
|
|
38
|
+
|
|
39
|
+
if testing:
|
|
40
|
+
db_path = ":memory:"
|
|
41
|
+
else:
|
|
42
|
+
db_path = str(DB_PATH)
|
|
43
|
+
db.init(db_path)
|
|
44
|
+
|
|
45
|
+
if not testing:
|
|
46
|
+
from cmdbox.migrations.runner import ensure_migrated
|
|
47
|
+
|
|
48
|
+
ensure_migrated(db_path)
|
|
49
|
+
|
|
50
|
+
_db_initialized = True
|
|
51
|
+
log.debug("Database initialized %s", "in testing mode" if testing else "")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def get_db(testing: bool = False) -> SqliteDatabase:
|
|
55
|
+
init_database(testing)
|
|
56
|
+
if db.is_closed():
|
|
57
|
+
db.connect(reuse_if_open=True)
|
|
58
|
+
return db
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def ensure_schema() -> None:
|
|
62
|
+
from cmdbox.models import ALL_MODELS
|
|
63
|
+
|
|
64
|
+
_db = get_db()
|
|
65
|
+
_db.create_tables(ALL_MODELS, safe=True)
|
cmdbox/exceptions.py
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class CmdboxError(Exception):
|
|
2
|
+
"""
|
|
3
|
+
Base class for exceptions in the cmdbox module.
|
|
4
|
+
|
|
5
|
+
This custom exception class serves as a base for all exceptions raised
|
|
6
|
+
within the cmdbox module. It provides a means to handle module-specific
|
|
7
|
+
errors in a structured manner.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
pass
|
cmdbox/init/__init__.py
ADDED
|
File without changes
|
cmdbox/init/detect.py
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
import psutil
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def detect_shell() -> str:
|
|
9
|
+
"""
|
|
10
|
+
Detect the shell being used in the current operating environment.
|
|
11
|
+
|
|
12
|
+
The function identifies the shell by examining the parent process of the current
|
|
13
|
+
script and its name. On Unix systems, it also checks the `SHELL` environment
|
|
14
|
+
variable as a fallback. For Windows, it supports shell detection for cmd,
|
|
15
|
+
PowerShell, and Git Bash.
|
|
16
|
+
|
|
17
|
+
Returns:
|
|
18
|
+
str: The name of the detected shell. If the shell cannot be determined using
|
|
19
|
+
process information, it falls back to environment-based detection.
|
|
20
|
+
"""
|
|
21
|
+
parent = psutil.Process(os.getpid()).parent()
|
|
22
|
+
name = ((parent.name() if parent else "") or "").lower()
|
|
23
|
+
|
|
24
|
+
# Windows
|
|
25
|
+
if name in {"pwsh.exe", "powershell.exe"}:
|
|
26
|
+
return "powershell"
|
|
27
|
+
if name == "cmd.exe":
|
|
28
|
+
return "cmd"
|
|
29
|
+
if name in {"bash.exe", "git-bash.exe", "msys2.exe"}:
|
|
30
|
+
return "bash"
|
|
31
|
+
|
|
32
|
+
# Unix
|
|
33
|
+
if name in {"bash", "zsh", "fish"}:
|
|
34
|
+
return name
|
|
35
|
+
|
|
36
|
+
# Fallback
|
|
37
|
+
p = os.environ.get("SHELL")
|
|
38
|
+
if p:
|
|
39
|
+
sh = Path(p).name.lower()
|
|
40
|
+
if sh in {"bash", "zsh", "fish"}:
|
|
41
|
+
return sh
|
|
42
|
+
|
|
43
|
+
# Default to env detection if everything else fails
|
|
44
|
+
return detect_shell_env()
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def detect_shell_env() -> str:
|
|
48
|
+
"""
|
|
49
|
+
Detects the shell being used on the operating system.
|
|
50
|
+
|
|
51
|
+
This function determines the active shell based on environment variables,
|
|
52
|
+
platform information, and commonly used shell identifiers. It identifies
|
|
53
|
+
the shell for both Windows and Unix-based systems.
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
str: A string representing the detected shell. Possible values include
|
|
57
|
+
'bash', 'powershell', 'cmd', 'zsh', and 'fish'. Defaults to 'bash'
|
|
58
|
+
if unable to determine the specific shell.
|
|
59
|
+
"""
|
|
60
|
+
if sys.platform.startswith("win"):
|
|
61
|
+
# Git bash / MSYS
|
|
62
|
+
if (
|
|
63
|
+
os.environ.get("MSYSTEM")
|
|
64
|
+
or os.environ.get("MINGW_PREFIX")
|
|
65
|
+
or os.environ.get("SHELL")
|
|
66
|
+
):
|
|
67
|
+
return "bash"
|
|
68
|
+
# Powershell
|
|
69
|
+
if os.environ.get("PSModulePath") or os.environ.get(
|
|
70
|
+
"POWERSHELL_DISTRIBUTION_CHANNEL"
|
|
71
|
+
):
|
|
72
|
+
return "powershell"
|
|
73
|
+
# Default to cmd
|
|
74
|
+
return "cmd"
|
|
75
|
+
|
|
76
|
+
sh = os.environ.get("SHELL")
|
|
77
|
+
if sh:
|
|
78
|
+
base = Path(sh).name.lower()
|
|
79
|
+
if base in {"bash", "zsh", "fish"}:
|
|
80
|
+
return base
|
|
81
|
+
|
|
82
|
+
return "bash"
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
@echo off
|
|
2
|
+
setlocal EnableExtensions EnableDelayedExpansion
|
|
3
|
+
|
|
4
|
+
set "_tmp=%TEMP%\cmdbox_emit_%RANDOM%%RANDOM%.cmd"
|
|
5
|
+
|
|
6
|
+
cb %* --emit > "%_tmp%"
|
|
7
|
+
if errorlevel 1 (
|
|
8
|
+
del "%_tmp%" >nul 2>&1
|
|
9
|
+
exit /b %errorlevel%
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
call "%_tmp%"
|
|
13
|
+
del "%_tmp%" >nul 2>&1
|
|
14
|
+
exit /b 0
|
cmdbox/init/io.py
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import shutil
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from importlib import resources
|
|
5
|
+
|
|
6
|
+
from cmdbox.logging_setup.log_decorators import log_action
|
|
7
|
+
|
|
8
|
+
START_MARK = "# >>> cmdbox shell integration >>>"
|
|
9
|
+
END_MARK = "# <<< cmdbox shell integration <<<"
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def load_integration_text(filename: str) -> str:
|
|
13
|
+
"""
|
|
14
|
+
Loads a text file from the 'cmdbox.init.integrations' resource directory, reads its contents, and
|
|
15
|
+
returns the text stripped of trailing whitespace with a newline character appended.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
filename (str): The name of the file to be loaded and read.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
str: The contents of the specified file as a string, with trailing whitespace removed
|
|
22
|
+
and a newline character appended at the end.
|
|
23
|
+
"""
|
|
24
|
+
return (
|
|
25
|
+
resources.files("cmdbox.init.integrations")
|
|
26
|
+
.joinpath(filename)
|
|
27
|
+
.read_text(encoding="utf-8")
|
|
28
|
+
.rstrip()
|
|
29
|
+
+ "\n"
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@log_action(__name__, "upsert_marked_block")
|
|
34
|
+
def upsert_marked_block(profile_path: Path, block_text: str) -> None:
|
|
35
|
+
"""
|
|
36
|
+
Updates or inserts a marked text block into a given file at the specified path.
|
|
37
|
+
If the marked block already exists in the file, it is replaced with the provided
|
|
38
|
+
new content. Otherwise, the block is added to the end of the file. Additionally,
|
|
39
|
+
a backup of the original file is created if it already exists.
|
|
40
|
+
|
|
41
|
+
Args:
|
|
42
|
+
profile_path (Path): The path to the file where the marked block will be
|
|
43
|
+
updated or inserted.
|
|
44
|
+
block_text (str): The content to be added or updated as part of the marked
|
|
45
|
+
block.
|
|
46
|
+
|
|
47
|
+
"""
|
|
48
|
+
profile_path.parent.mkdir(parents=True, exist_ok=True)
|
|
49
|
+
existing = profile_path.read_text(encoding="utf-8") if profile_path.exists() else ""
|
|
50
|
+
|
|
51
|
+
marked = f"{START_MARK}\n{block_text.rstrip()}\n{END_MARK}\n"
|
|
52
|
+
|
|
53
|
+
pattern = re.compile(
|
|
54
|
+
re.escape(START_MARK) + r".*?" + re.escape(END_MARK) + r"\n?",
|
|
55
|
+
flags=re.DOTALL,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if pattern.search(existing):
|
|
59
|
+
new_text = pattern.sub(marked, existing)
|
|
60
|
+
else:
|
|
61
|
+
sep = "\n" if existing and not existing.endswith("\n") else ""
|
|
62
|
+
new_text = existing + sep + marked
|
|
63
|
+
|
|
64
|
+
if profile_path.exists():
|
|
65
|
+
backup = profile_path.with_suffix(profile_path.suffix + ".bak")
|
|
66
|
+
shutil.copy2(profile_path, backup)
|
|
67
|
+
|
|
68
|
+
profile_path.write_text(new_text, encoding="utf-8")
|
cmdbox/init/specs.py
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True)
|
|
8
|
+
class ShellSpec:
|
|
9
|
+
name: str
|
|
10
|
+
filename: str
|
|
11
|
+
default_path_fn: Callable[[], Path] | None
|
|
12
|
+
install_mode: str # "profile_block" or "write_file" or "wrapper_hint"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def default_bashrc() -> Path:
|
|
16
|
+
return Path.home() / ".bashrc"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def default_zshrc() -> Path:
|
|
20
|
+
return Path.home() / ".zshrc"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def default_fish_function() -> Path:
|
|
24
|
+
return (
|
|
25
|
+
Path.home() / ".config" / "fish" / "functions" / "cb.fish"
|
|
26
|
+
) # TODO: Does this need to be cbe.fish?
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def default_powershell_profile() -> Path:
|
|
30
|
+
# Approximation that works for typical pwsh installations
|
|
31
|
+
# Users can override with --path if needed
|
|
32
|
+
docs = os.environ.get("USERPROFILE")
|
|
33
|
+
if not docs:
|
|
34
|
+
return (
|
|
35
|
+
Path.home()
|
|
36
|
+
/ "Documents"
|
|
37
|
+
/ "PowerShell"
|
|
38
|
+
/ "Microsoft.PowerShell_profile.ps1"
|
|
39
|
+
)
|
|
40
|
+
return Path(docs) / "Documents" / "PowerShell" / "Microsoft.PowerShell_profile.ps1"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
SHELLS: dict[str, ShellSpec] = {
|
|
44
|
+
"bash": ShellSpec("bash", "bash.sh", default_bashrc, "profile_block"),
|
|
45
|
+
"zsh": ShellSpec("zsh", "zsh.sh", default_zshrc, "profile_block"),
|
|
46
|
+
"fish": ShellSpec("fish", "fish.fish", default_fish_function, "write_file"),
|
|
47
|
+
"powershell": ShellSpec(
|
|
48
|
+
"powershell", "powershell.ps1", default_powershell_profile, "profile_block"
|
|
49
|
+
),
|
|
50
|
+
"pwsh": ShellSpec(
|
|
51
|
+
"pwsh", "powershell.ps1", default_powershell_profile, "profile_block"
|
|
52
|
+
),
|
|
53
|
+
"cmd": ShellSpec("cmd", "cmd.bat", None, "wrapper_hint"),
|
|
54
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from cmdbox.core.paths import get_log_file_path
|
|
6
|
+
|
|
7
|
+
LOGGER_NAME = "cmdbox"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(frozen=True)
|
|
11
|
+
class LogConfig:
|
|
12
|
+
console_level: int
|
|
13
|
+
file_enabled: bool
|
|
14
|
+
file_level: int
|
|
15
|
+
file_path: Path
|
|
16
|
+
max_bytes: int
|
|
17
|
+
backups: int
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _level(level_str: str) -> int:
|
|
21
|
+
s = (level_str or "").upper().strip()
|
|
22
|
+
return getattr(logging, s, logging.INFO)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def build_log_config(
|
|
26
|
+
settings, *, verbose: bool, debug: bool, file_logs: bool | None
|
|
27
|
+
) -> LogConfig:
|
|
28
|
+
"""
|
|
29
|
+
Builds and returns a LogConfig instance based on the provided settings and flags.
|
|
30
|
+
|
|
31
|
+
This function determines the appropriate logging configuration for the application
|
|
32
|
+
by evaluating the verbosity, debug settings, and file-based logging preferences. It
|
|
33
|
+
retrieves logging levels and file configurations from the provided settings.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
settings: Application-specific configuration object containing logging
|
|
37
|
+
settings such as file size limits and backup count.
|
|
38
|
+
verbose: Flag indicating whether verbose logging is enabled.
|
|
39
|
+
debug: Flag indicating whether debug-level logging is enabled.
|
|
40
|
+
file_logs: Flag indicating whether file-based logging is enabled. If None,
|
|
41
|
+
it defaults to the settings specified in the application configuration.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
LogConfig: A fully constructed LogConfig instance containing the logging
|
|
45
|
+
configuration details, such as console logging level, file logging level,
|
|
46
|
+
file path, maximum file size, and backup count.
|
|
47
|
+
"""
|
|
48
|
+
console_level = get_console_level(settings, verbose=verbose, debug=debug)
|
|
49
|
+
|
|
50
|
+
file_enabled = get_file_enabled(settings, file_logs=file_logs)
|
|
51
|
+
file_level = get_file_level(settings, verbose=verbose, debug=debug)
|
|
52
|
+
|
|
53
|
+
file_path = get_log_file_path()
|
|
54
|
+
|
|
55
|
+
return LogConfig(
|
|
56
|
+
console_level=console_level,
|
|
57
|
+
file_enabled=file_enabled,
|
|
58
|
+
file_level=file_level,
|
|
59
|
+
file_path=file_path,
|
|
60
|
+
max_bytes=settings.logging.file.max_bytes,
|
|
61
|
+
backups=settings.logging.file.backups,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def get_console_level(settings, *, verbose: bool, debug: bool) -> int:
|
|
66
|
+
"""
|
|
67
|
+
Determines and returns the appropriate logging level for console output.
|
|
68
|
+
|
|
69
|
+
Args:
|
|
70
|
+
settings: Application settings object containing logging configuration.
|
|
71
|
+
verbose: Flag indicating whether verbose mode is enabled.
|
|
72
|
+
debug: Flag indicating whether debug mode is enabled.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
int: Numerical value representing the logging level.
|
|
76
|
+
"""
|
|
77
|
+
if debug:
|
|
78
|
+
return logging.DEBUG
|
|
79
|
+
elif verbose:
|
|
80
|
+
return logging.INFO
|
|
81
|
+
else:
|
|
82
|
+
return _level(settings.logging.console_level)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def get_file_enabled(settings, *, file_logs: bool | None) -> bool:
|
|
86
|
+
"""
|
|
87
|
+
Returns whether file logging is enabled based on the provided settings and optional override.
|
|
88
|
+
|
|
89
|
+
Args:
|
|
90
|
+
settings: Application configuration containing logging settings.
|
|
91
|
+
file_logs (bool | None): Optional override for enabling or disabling file logging.
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
bool: True if file logging is enabled; otherwise, False.
|
|
95
|
+
"""
|
|
96
|
+
if file_logs is not None:
|
|
97
|
+
return file_logs
|
|
98
|
+
return bool(settings.logging.file.enabled)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def get_file_level(settings, *, verbose: bool, debug: bool) -> int:
|
|
102
|
+
"""
|
|
103
|
+
Determines the appropriate logging level for file-based logging based on the provided settings
|
|
104
|
+
and debug/verbose flags.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
settings: Configuration settings that include logging level information.
|
|
108
|
+
verbose: Enables verbose logging if set to True.
|
|
109
|
+
debug: Enables debug-level logging if set to True.
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
int: The resolved logging level for file-based logging.
|
|
113
|
+
"""
|
|
114
|
+
if debug:
|
|
115
|
+
return logging.DEBUG
|
|
116
|
+
elif verbose:
|
|
117
|
+
return logging.INFO
|
|
118
|
+
else:
|
|
119
|
+
return _level(settings.logging.file.level)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def get_logger() -> logging.Logger:
|
|
123
|
+
return logging.getLogger(LOGGER_NAME)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import time
|
|
3
|
+
from functools import wraps
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def log_action(
|
|
7
|
+
module_name: str,
|
|
8
|
+
action_name: str,
|
|
9
|
+
):
|
|
10
|
+
"""
|
|
11
|
+
Decorator to log the execution of a function, including the start, end, and elapsed time in milliseconds.
|
|
12
|
+
Logs will be captured using the specified module name and include details about the specified action name.
|
|
13
|
+
|
|
14
|
+
The decorator also logs exceptions if the wrapped function raises one.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
module_name (str): The name of the module to associate with the logger.
|
|
18
|
+
action_name (str): The name of the action to log.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
def decorator(func):
|
|
22
|
+
@wraps(func)
|
|
23
|
+
def wrapper(*args, **kwargs):
|
|
24
|
+
log = logging.getLogger(module_name)
|
|
25
|
+
start_time = time.perf_counter()
|
|
26
|
+
log.info("%s start", action_name)
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
result = func(*args, **kwargs)
|
|
30
|
+
return result
|
|
31
|
+
except Exception:
|
|
32
|
+
log.error("%s failed", action_name)
|
|
33
|
+
raise
|
|
34
|
+
finally:
|
|
35
|
+
elapsed_ms = (time.perf_counter() - start_time) * 1000
|
|
36
|
+
log.info("%s finished in %.2f ms", action_name, elapsed_ms)
|
|
37
|
+
|
|
38
|
+
return wrapper
|
|
39
|
+
|
|
40
|
+
return decorator
|