pre-commit-crocodile 2.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.
- pre_commit_crocodile/__init__.py +5 -0
- pre_commit_crocodile/__main__.py +5 -0
- pre_commit_crocodile/assets/.cz.yaml +188 -0
- pre_commit_crocodile/assets/.pre-commit-config.yaml +68 -0
- pre_commit_crocodile/assets/__init__.py +0 -0
- pre_commit_crocodile/cli/__init__.py +0 -0
- pre_commit_crocodile/cli/entrypoint.py +414 -0
- pre_commit_crocodile/cli/main.py +269 -0
- pre_commit_crocodile/hooks/__init__.py +0 -0
- pre_commit_crocodile/hooks/configurations.py +322 -0
- pre_commit_crocodile/hooks/prepare_commit_message.py +258 -0
- pre_commit_crocodile/package/__init__.py +0 -0
- pre_commit_crocodile/package/bundle.py +34 -0
- pre_commit_crocodile/package/settings.py +148 -0
- pre_commit_crocodile/package/updates.py +228 -0
- pre_commit_crocodile/package/version.py +80 -0
- pre_commit_crocodile/prints/__init__.py +0 -0
- pre_commit_crocodile/prints/boxes.py +82 -0
- pre_commit_crocodile/prints/colors.py +85 -0
- pre_commit_crocodile/system/__init__.py +0 -0
- pre_commit_crocodile/system/commands.py +90 -0
- pre_commit_crocodile/system/platform.py +74 -0
- pre_commit_crocodile/types/__init__.py +0 -0
- pre_commit_crocodile/types/environments.py +92 -0
- pre_commit_crocodile/types/strings.py +152 -0
- pre_commit_crocodile-2.0.0.dist-info/LICENSE +201 -0
- pre_commit_crocodile-2.0.0.dist-info/METADATA +212 -0
- pre_commit_crocodile-2.0.0.dist-info/RECORD +31 -0
- pre_commit_crocodile-2.0.0.dist-info/WHEEL +5 -0
- pre_commit_crocodile-2.0.0.dist-info/entry_points.txt +3 -0
- pre_commit_crocodile-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from os import environ
|
|
5
|
+
from sys import version_info
|
|
6
|
+
|
|
7
|
+
# Components
|
|
8
|
+
from ..system.platform import Platform
|
|
9
|
+
from .bundle import Bundle
|
|
10
|
+
|
|
11
|
+
# Version class
|
|
12
|
+
class Version:
|
|
13
|
+
|
|
14
|
+
# Getter
|
|
15
|
+
@staticmethod
|
|
16
|
+
def get() -> str:
|
|
17
|
+
|
|
18
|
+
# Fake test version
|
|
19
|
+
if Bundle.ENV_DEBUG_VERSION_FAKE in environ:
|
|
20
|
+
return environ[Bundle.ENV_DEBUG_VERSION_FAKE]
|
|
21
|
+
|
|
22
|
+
# Acquire version from metadata
|
|
23
|
+
try:
|
|
24
|
+
from importlib import metadata # pylint: disable=import-outside-toplevel
|
|
25
|
+
return metadata.version(Bundle.PACKAGE)
|
|
26
|
+
except Exception: # pylint: disable=broad-exception-caught # pragma: no cover
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
# Acquire version from resources
|
|
30
|
+
try: # pragma: no cover
|
|
31
|
+
from pkg_resources import require # pylint: disable=import-outside-toplevel
|
|
32
|
+
name = __name__.split('.', maxsplit=1)[0]
|
|
33
|
+
return str(require(name)[0].version)
|
|
34
|
+
|
|
35
|
+
# Default fallback
|
|
36
|
+
except Exception: # pylint: disable=broad-exception-caught # pragma: no cover
|
|
37
|
+
return '0.0.0'
|
|
38
|
+
|
|
39
|
+
# Path
|
|
40
|
+
@staticmethod
|
|
41
|
+
def path() -> str:
|
|
42
|
+
|
|
43
|
+
# Acquire path
|
|
44
|
+
path = __file__
|
|
45
|
+
|
|
46
|
+
# Strip package path
|
|
47
|
+
index = path.rfind(Platform.PATH_SEPARATOR)
|
|
48
|
+
index = path.rfind(Platform.PATH_SEPARATOR, 0, index)
|
|
49
|
+
path = path[0:index]
|
|
50
|
+
|
|
51
|
+
# Result
|
|
52
|
+
return path
|
|
53
|
+
|
|
54
|
+
# Python
|
|
55
|
+
@staticmethod
|
|
56
|
+
def python() -> str:
|
|
57
|
+
|
|
58
|
+
# Acquire Python version
|
|
59
|
+
version = f'{version_info.major}.{version_info.minor}'
|
|
60
|
+
|
|
61
|
+
# Result
|
|
62
|
+
return version
|
|
63
|
+
|
|
64
|
+
# Revision
|
|
65
|
+
@staticmethod
|
|
66
|
+
def revision() -> str:
|
|
67
|
+
|
|
68
|
+
# Force package revision
|
|
69
|
+
if Bundle.ENV_DEBUG_REVISION_SHA in environ:
|
|
70
|
+
return environ[Bundle.ENV_DEBUG_REVISION_SHA]
|
|
71
|
+
|
|
72
|
+
# Acquire package version
|
|
73
|
+
version: str = Version.get()
|
|
74
|
+
|
|
75
|
+
# Handle development version
|
|
76
|
+
if '+g' in version:
|
|
77
|
+
return version.split('+g')[1].split('.')[0]
|
|
78
|
+
|
|
79
|
+
# Result
|
|
80
|
+
return version
|
|
File without changes
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from shutil import get_terminal_size
|
|
5
|
+
from typing import List
|
|
6
|
+
|
|
7
|
+
# Components
|
|
8
|
+
from ..system.platform import Platform
|
|
9
|
+
from ..types.strings import Strings
|
|
10
|
+
from .colors import Colors
|
|
11
|
+
|
|
12
|
+
# Boxes class
|
|
13
|
+
class Boxes:
|
|
14
|
+
|
|
15
|
+
# Constants
|
|
16
|
+
__TOP_LEFT: str = '╭' if Platform.IS_TTY_UTF8 else '-'
|
|
17
|
+
__TOP_LINE: str = '─' if Platform.IS_TTY_UTF8 else '-'
|
|
18
|
+
__TOP_RIGHT: str = '╮' if Platform.IS_TTY_UTF8 else '-'
|
|
19
|
+
__MIDDLE_LEFT: str = '│' if Platform.IS_TTY_UTF8 else '|'
|
|
20
|
+
__MIDDLE_RIGHT: str = '│' if Platform.IS_TTY_UTF8 else '|'
|
|
21
|
+
__BOTTOM_LEFT: str = '╰' if Platform.IS_TTY_UTF8 else '-'
|
|
22
|
+
__BOTTOM_LINE: str = '─' if Platform.IS_TTY_UTF8 else '-'
|
|
23
|
+
__BOTTOM_RIGHT: str = '╯' if Platform.IS_TTY_UTF8 else '-'
|
|
24
|
+
__OFFSET_LINE: int = 2
|
|
25
|
+
__PADDING_LINE: int = 2
|
|
26
|
+
|
|
27
|
+
# Members
|
|
28
|
+
__lines: List[str]
|
|
29
|
+
|
|
30
|
+
# Constructor
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
|
|
33
|
+
# Initialize members
|
|
34
|
+
self.__lines = []
|
|
35
|
+
|
|
36
|
+
# Adder
|
|
37
|
+
def add(self, line: str) -> None:
|
|
38
|
+
|
|
39
|
+
# Add line
|
|
40
|
+
self.__lines += [line]
|
|
41
|
+
|
|
42
|
+
# Printer
|
|
43
|
+
def print(self) -> None:
|
|
44
|
+
|
|
45
|
+
# Evaluate lines length
|
|
46
|
+
length = max(len(Colors.strip(line)) for line in self.__lines)
|
|
47
|
+
|
|
48
|
+
# Acquire terminal width
|
|
49
|
+
columns, _ = get_terminal_size()
|
|
50
|
+
|
|
51
|
+
# Limit line length
|
|
52
|
+
limit = columns - Boxes.__OFFSET_LINE - len(
|
|
53
|
+
Boxes.__MIDDLE_LEFT) - 2 * Boxes.__PADDING_LINE - len(Boxes.__MIDDLE_RIGHT)
|
|
54
|
+
limit = max(limit, 1)
|
|
55
|
+
length = min(length, limit)
|
|
56
|
+
|
|
57
|
+
# Header
|
|
58
|
+
print(' ')
|
|
59
|
+
|
|
60
|
+
# Print header line
|
|
61
|
+
print(f"{' ' * Boxes.__OFFSET_LINE}{Colors.YELLOW}{Boxes.__TOP_LEFT}" \
|
|
62
|
+
f'{Boxes.__TOP_LINE * (length + 2 * Boxes.__PADDING_LINE)}{Boxes.__TOP_RIGHT}')
|
|
63
|
+
|
|
64
|
+
# Print content lines
|
|
65
|
+
for line in self.__lines:
|
|
66
|
+
for part in Strings.wrap(line, length=length):
|
|
67
|
+
print(
|
|
68
|
+
f"{' ' * Boxes.__OFFSET_LINE}{Colors.YELLOW}{Boxes.__MIDDLE_LEFT}" \
|
|
69
|
+
f"{' ' * Boxes.__PADDING_LINE}{Strings.center(part, length)}" \
|
|
70
|
+
f"{' ' * Boxes.__PADDING_LINE}{Colors.YELLOW}{Boxes.__MIDDLE_RIGHT}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
# Print bottom line
|
|
74
|
+
print(
|
|
75
|
+
f"{' ' * Boxes.__OFFSET_LINE}{Colors.YELLOW}{Boxes.__BOTTOM_LEFT}" \
|
|
76
|
+
f'{Boxes.__BOTTOM_LINE * (length + 2 * Boxes.__PADDING_LINE)}' \
|
|
77
|
+
f'{Boxes.__BOTTOM_RIGHT}{Colors.RESET}'
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
# Footer
|
|
81
|
+
print(' ')
|
|
82
|
+
Platform.flush()
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
# Modules libraries
|
|
7
|
+
try:
|
|
8
|
+
try: # colored>=2.0.0
|
|
9
|
+
from colored import Colored
|
|
10
|
+
except ImportError: # colored<2.0.0 # pragma: no cover
|
|
11
|
+
from colored import colored as Colored
|
|
12
|
+
except ModuleNotFoundError: # pragma: no cover
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
# Colors class, pylint: disable=too-few-public-methods
|
|
16
|
+
class Colors:
|
|
17
|
+
|
|
18
|
+
# Attributes
|
|
19
|
+
ALL: List[str] = []
|
|
20
|
+
BOLD = ''
|
|
21
|
+
CYAN = ''
|
|
22
|
+
GREEN = ''
|
|
23
|
+
GREY = ''
|
|
24
|
+
RED = ''
|
|
25
|
+
RESET = ''
|
|
26
|
+
YELLOW = ''
|
|
27
|
+
YELLOW_LIGHT = ''
|
|
28
|
+
|
|
29
|
+
# Enabled
|
|
30
|
+
@staticmethod
|
|
31
|
+
def enabled() -> bool:
|
|
32
|
+
|
|
33
|
+
# Result
|
|
34
|
+
try:
|
|
35
|
+
return bool(Colored('').enabled())
|
|
36
|
+
except (NameError, TypeError): # pragma: no cover
|
|
37
|
+
return False
|
|
38
|
+
|
|
39
|
+
# Prepare
|
|
40
|
+
@staticmethod
|
|
41
|
+
def prepare() -> None:
|
|
42
|
+
|
|
43
|
+
# Colors enabled
|
|
44
|
+
if Colors.enabled():
|
|
45
|
+
Colors.RESET = Colored('reset').attribute()
|
|
46
|
+
Colors.BOLD = Colors.RESET + Colored('bold').attribute()
|
|
47
|
+
Colors.CYAN = Colors.BOLD + Colored('cyan').foreground()
|
|
48
|
+
Colors.GREEN = Colors.BOLD + Colored('green').foreground()
|
|
49
|
+
Colors.GREY = Colors.BOLD + Colored('light_gray').foreground()
|
|
50
|
+
Colors.RED = Colors.BOLD + Colored('red').foreground()
|
|
51
|
+
Colors.YELLOW = Colors.BOLD + Colored('yellow').foreground()
|
|
52
|
+
Colors.YELLOW_LIGHT = Colors.BOLD + Colored('light_yellow').foreground()
|
|
53
|
+
Colors.ALL = [
|
|
54
|
+
Colors.CYAN,
|
|
55
|
+
Colors.GREEN,
|
|
56
|
+
Colors.GREY,
|
|
57
|
+
Colors.RED,
|
|
58
|
+
Colors.YELLOW,
|
|
59
|
+
Colors.YELLOW_LIGHT,
|
|
60
|
+
Colors.BOLD,
|
|
61
|
+
Colors.RESET,
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
# Colors disabled
|
|
65
|
+
else:
|
|
66
|
+
Colors.BOLD = ''
|
|
67
|
+
Colors.CYAN = ''
|
|
68
|
+
Colors.GREEN = ''
|
|
69
|
+
Colors.GREY = ''
|
|
70
|
+
Colors.RED = ''
|
|
71
|
+
Colors.RESET = ''
|
|
72
|
+
Colors.YELLOW = ''
|
|
73
|
+
Colors.YELLOW_LIGHT = ''
|
|
74
|
+
Colors.ALL = []
|
|
75
|
+
|
|
76
|
+
# Strip
|
|
77
|
+
@staticmethod
|
|
78
|
+
def strip(string: str) -> str:
|
|
79
|
+
|
|
80
|
+
# Strip all colors
|
|
81
|
+
for item in Colors.ALL:
|
|
82
|
+
string = string.replace(item, '')
|
|
83
|
+
|
|
84
|
+
# Result
|
|
85
|
+
return string
|
|
File without changes
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from shutil import which
|
|
6
|
+
import subprocess
|
|
7
|
+
from typing import List
|
|
8
|
+
|
|
9
|
+
# Commands
|
|
10
|
+
class Commands:
|
|
11
|
+
|
|
12
|
+
# Exists
|
|
13
|
+
@staticmethod
|
|
14
|
+
def exists(binary: str) -> bool:
|
|
15
|
+
|
|
16
|
+
# Check system binary exists
|
|
17
|
+
return which(binary) is not None
|
|
18
|
+
|
|
19
|
+
# Grep
|
|
20
|
+
@staticmethod
|
|
21
|
+
def grep(file: Path, string: str) -> bool:
|
|
22
|
+
|
|
23
|
+
# Ignore missing file
|
|
24
|
+
if not file.exists():
|
|
25
|
+
return False
|
|
26
|
+
|
|
27
|
+
# Check file contains string
|
|
28
|
+
with open(
|
|
29
|
+
file,
|
|
30
|
+
encoding='utf8',
|
|
31
|
+
mode='r',
|
|
32
|
+
) as f:
|
|
33
|
+
for line in f.readlines():
|
|
34
|
+
|
|
35
|
+
# Find string in line
|
|
36
|
+
if string in line:
|
|
37
|
+
return True
|
|
38
|
+
|
|
39
|
+
# Fallback
|
|
40
|
+
return False # pragma: no cover
|
|
41
|
+
|
|
42
|
+
# Output
|
|
43
|
+
@staticmethod
|
|
44
|
+
def output(binary: str, arguments: List[str]) -> str:
|
|
45
|
+
|
|
46
|
+
# Get system output
|
|
47
|
+
try:
|
|
48
|
+
return subprocess.check_output(
|
|
49
|
+
args=[binary] + arguments,
|
|
50
|
+
cwd=None,
|
|
51
|
+
shell=False,
|
|
52
|
+
).strip().decode()
|
|
53
|
+
except subprocess.CalledProcessError as err:
|
|
54
|
+
return str(err.output.decode())
|
|
55
|
+
except FileNotFoundError:
|
|
56
|
+
return ''
|
|
57
|
+
|
|
58
|
+
# Command
|
|
59
|
+
@staticmethod
|
|
60
|
+
def pip(arguments: List[str]) -> bool:
|
|
61
|
+
|
|
62
|
+
# Run with pipx
|
|
63
|
+
if Commands.exists('pipx'):
|
|
64
|
+
return Commands.run(
|
|
65
|
+
'pipx',
|
|
66
|
+
arguments,
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Run with pip # pragma: no cover
|
|
70
|
+
return Commands.run(
|
|
71
|
+
'sudo',
|
|
72
|
+
['pip'] + arguments,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
# Command
|
|
76
|
+
@staticmethod
|
|
77
|
+
def run(binary: str, arguments: List[str]) -> bool:
|
|
78
|
+
|
|
79
|
+
# Run system command
|
|
80
|
+
try:
|
|
81
|
+
process = subprocess.run(
|
|
82
|
+
args=[binary] + arguments,
|
|
83
|
+
cwd=None,
|
|
84
|
+
check=True,
|
|
85
|
+
shell=False,
|
|
86
|
+
)
|
|
87
|
+
return process.returncode == 0
|
|
88
|
+
except subprocess.CalledProcessError as e:
|
|
89
|
+
print(f"Error executing command: {e}")
|
|
90
|
+
return False
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from os import access, environ, R_OK, sep
|
|
5
|
+
from os.path import expanduser
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from sys import platform, stdin, stdout
|
|
8
|
+
|
|
9
|
+
# Platform
|
|
10
|
+
class Platform:
|
|
11
|
+
|
|
12
|
+
# Environment
|
|
13
|
+
ENV_SIMULATE_MAC_OS: str = 'SIMULATE_MAC_OS'
|
|
14
|
+
ENV_SUDO_USER: str = 'SUDO_USER'
|
|
15
|
+
|
|
16
|
+
# Constants
|
|
17
|
+
IS_LINUX: bool = platform in ['linux', 'linux2']
|
|
18
|
+
IS_MAC_OS: bool = platform in ['darwin'] or ENV_SIMULATE_MAC_OS in environ
|
|
19
|
+
IS_SIMULATED: bool = ENV_SIMULATE_MAC_OS in environ
|
|
20
|
+
IS_WINDOWS: bool = platform in ['win32', 'win64']
|
|
21
|
+
|
|
22
|
+
# Separators
|
|
23
|
+
PATH_SEPARATOR: str = sep
|
|
24
|
+
|
|
25
|
+
# TTYs
|
|
26
|
+
IS_TTY_STDIN: bool = stdin.isatty() and stdin.encoding != 'cp1252'
|
|
27
|
+
IS_TTY_STDOUT: bool = stdout.isatty()
|
|
28
|
+
IS_TTY_UTF8: bool = str(stdout.encoding).lower() == 'utf-8'
|
|
29
|
+
|
|
30
|
+
# Outputs
|
|
31
|
+
IS_FLUSH_ENABLED: bool = IS_TTY_STDOUT or IS_WINDOWS
|
|
32
|
+
|
|
33
|
+
# Users
|
|
34
|
+
IS_USER_SUDO: bool = ENV_SUDO_USER in environ
|
|
35
|
+
USER_SUDO: str = environ[ENV_SUDO_USER] if IS_USER_SUDO else ''
|
|
36
|
+
|
|
37
|
+
# Flush
|
|
38
|
+
@staticmethod
|
|
39
|
+
def flush() -> None:
|
|
40
|
+
|
|
41
|
+
# Flush output
|
|
42
|
+
print(
|
|
43
|
+
'',
|
|
44
|
+
end='',
|
|
45
|
+
flush=Platform.IS_FLUSH_ENABLED,
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
# Userspace
|
|
49
|
+
@staticmethod
|
|
50
|
+
def userspace(name: str) -> Path:
|
|
51
|
+
|
|
52
|
+
# Variables
|
|
53
|
+
home: None | Path = None
|
|
54
|
+
|
|
55
|
+
# Elevated home
|
|
56
|
+
if Platform.IS_USER_SUDO:
|
|
57
|
+
home = Path(expanduser(f'~{Platform.USER_SUDO}'))
|
|
58
|
+
if not access(home, R_OK): # pragma: no cover
|
|
59
|
+
home = None
|
|
60
|
+
|
|
61
|
+
# Default home
|
|
62
|
+
if not home or not home.is_dir():
|
|
63
|
+
home = Path.home()
|
|
64
|
+
|
|
65
|
+
# Windows userspace
|
|
66
|
+
if Platform.IS_WINDOWS:
|
|
67
|
+
return home / 'AppData' / 'Local' / name
|
|
68
|
+
|
|
69
|
+
# macOS userspace
|
|
70
|
+
if Platform.IS_MAC_OS:
|
|
71
|
+
return home / 'Library' / 'Preferences' / name
|
|
72
|
+
|
|
73
|
+
# Linux userspace
|
|
74
|
+
return home / '.config' / name
|
|
File without changes
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from os import environ
|
|
5
|
+
from typing import Dict, List, NamedTuple
|
|
6
|
+
|
|
7
|
+
# Environments class
|
|
8
|
+
class Environments:
|
|
9
|
+
|
|
10
|
+
# Constants
|
|
11
|
+
LINE_EOL: str = '\n'
|
|
12
|
+
|
|
13
|
+
# Variable
|
|
14
|
+
class Variable(NamedTuple):
|
|
15
|
+
|
|
16
|
+
# Variables
|
|
17
|
+
name: str
|
|
18
|
+
description: str
|
|
19
|
+
fallback: str
|
|
20
|
+
|
|
21
|
+
# Value
|
|
22
|
+
@property
|
|
23
|
+
def value(self) -> str:
|
|
24
|
+
if self.fallback:
|
|
25
|
+
return environ.get(self.name, environ.get(self.fallback, ''))
|
|
26
|
+
return environ.get(self.name, '')
|
|
27
|
+
|
|
28
|
+
# Members
|
|
29
|
+
__group: str
|
|
30
|
+
__variables: Dict[str, Variable]
|
|
31
|
+
|
|
32
|
+
# Constructor
|
|
33
|
+
def __init__(self) -> None:
|
|
34
|
+
self.__group = ''
|
|
35
|
+
self.__variables = {}
|
|
36
|
+
|
|
37
|
+
# Group
|
|
38
|
+
@property
|
|
39
|
+
def group(self) -> str:
|
|
40
|
+
return self.__group
|
|
41
|
+
|
|
42
|
+
# Group
|
|
43
|
+
@group.setter
|
|
44
|
+
def group(self, value: str) -> None:
|
|
45
|
+
self.__group = value
|
|
46
|
+
|
|
47
|
+
# Add
|
|
48
|
+
def add(
|
|
49
|
+
self,
|
|
50
|
+
key: str,
|
|
51
|
+
name: str,
|
|
52
|
+
description: str,
|
|
53
|
+
fallback: str = '',
|
|
54
|
+
) -> None:
|
|
55
|
+
|
|
56
|
+
# Append variable to list
|
|
57
|
+
self.__variables[key] = Environments.Variable(
|
|
58
|
+
name=name,
|
|
59
|
+
description=description,
|
|
60
|
+
fallback=fallback,
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
# Help
|
|
64
|
+
def help(self, help_position: int) -> str:
|
|
65
|
+
|
|
66
|
+
# Variables
|
|
67
|
+
lines: List[str] = []
|
|
68
|
+
|
|
69
|
+
# Append group
|
|
70
|
+
if self.__group:
|
|
71
|
+
lines += [f'{self.__group}:']
|
|
72
|
+
|
|
73
|
+
# Append variables
|
|
74
|
+
for _, variable in self.__variables.items():
|
|
75
|
+
line = f' {variable.name: <{help_position - 2}}'
|
|
76
|
+
line += f'{variable.description}'
|
|
77
|
+
if variable.fallback:
|
|
78
|
+
line += f' (fallback: {variable.fallback})'
|
|
79
|
+
lines += [line]
|
|
80
|
+
|
|
81
|
+
# Result
|
|
82
|
+
return Environments.LINE_EOL.join(lines)
|
|
83
|
+
|
|
84
|
+
# Value
|
|
85
|
+
def value(self, key: str) -> str:
|
|
86
|
+
|
|
87
|
+
# Get value of declared variable
|
|
88
|
+
if key in self.__variables:
|
|
89
|
+
return self.__variables[key].value
|
|
90
|
+
|
|
91
|
+
# Fallback
|
|
92
|
+
return ''
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
|
|
3
|
+
# Standard libraries
|
|
4
|
+
from random import choices
|
|
5
|
+
from string import ascii_letters, digits
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
# Components
|
|
9
|
+
from ..prints.colors import Colors
|
|
10
|
+
|
|
11
|
+
# Strings class
|
|
12
|
+
class Strings:
|
|
13
|
+
|
|
14
|
+
# Center
|
|
15
|
+
@staticmethod
|
|
16
|
+
def center(string: str, length: int) -> str:
|
|
17
|
+
|
|
18
|
+
# Extract text
|
|
19
|
+
text: str = Colors.strip(string)
|
|
20
|
+
|
|
21
|
+
# Center string
|
|
22
|
+
if len(text) < length:
|
|
23
|
+
paddings = length - len(text)
|
|
24
|
+
left = paddings // 2
|
|
25
|
+
right = -(-paddings // 2)
|
|
26
|
+
return ' ' * left + string + ' ' * right
|
|
27
|
+
|
|
28
|
+
# Default string
|
|
29
|
+
return string
|
|
30
|
+
|
|
31
|
+
# Quote
|
|
32
|
+
@staticmethod
|
|
33
|
+
def quote(string: str) -> str: # pragma: no cover
|
|
34
|
+
|
|
35
|
+
# Single quotes
|
|
36
|
+
if '\'' not in string:
|
|
37
|
+
return '\'' + string + '\''
|
|
38
|
+
|
|
39
|
+
# Double quotes
|
|
40
|
+
if '\"' not in string:
|
|
41
|
+
return '\"' + string + '\"'
|
|
42
|
+
|
|
43
|
+
# Adaptive quotes
|
|
44
|
+
return '\'' + string.replace('\'', '\\\'') + '\''
|
|
45
|
+
|
|
46
|
+
# Random
|
|
47
|
+
@staticmethod
|
|
48
|
+
def random(length: int) -> str: # pragma: no cover
|
|
49
|
+
|
|
50
|
+
# Generate random string
|
|
51
|
+
return ''.join(choices(ascii_letters + digits, k=length))
|
|
52
|
+
|
|
53
|
+
# Wrap
|
|
54
|
+
@staticmethod
|
|
55
|
+
def wrap(string: str, length: int) -> List[str]:
|
|
56
|
+
|
|
57
|
+
# Variables
|
|
58
|
+
color: str = ''
|
|
59
|
+
index: int = 0
|
|
60
|
+
line_data: str = ''
|
|
61
|
+
line_length: int = 0
|
|
62
|
+
lines: List[str] = []
|
|
63
|
+
space: str = ''
|
|
64
|
+
word: str = ''
|
|
65
|
+
|
|
66
|
+
# Length limitations
|
|
67
|
+
length = max(length, 1)
|
|
68
|
+
|
|
69
|
+
# Append line
|
|
70
|
+
def append_line() -> None:
|
|
71
|
+
nonlocal line_data, line_length, lines
|
|
72
|
+
if line_length > 0:
|
|
73
|
+
lines += [line_data]
|
|
74
|
+
line_data = ''
|
|
75
|
+
line_length = 0
|
|
76
|
+
|
|
77
|
+
# Store word
|
|
78
|
+
def store_word() -> None:
|
|
79
|
+
nonlocal color, length, line_data, line_length, space, word
|
|
80
|
+
if len(word) > 0:
|
|
81
|
+
|
|
82
|
+
# Word overflows
|
|
83
|
+
if line_length + len(space) + len(word) > length:
|
|
84
|
+
word_full = word
|
|
85
|
+
if len(word_full) > length:
|
|
86
|
+
while len(word_full) > 0:
|
|
87
|
+
word = word_full[0:length]
|
|
88
|
+
store_word()
|
|
89
|
+
word_full = word_full[length:]
|
|
90
|
+
append_line()
|
|
91
|
+
|
|
92
|
+
# Word spacing
|
|
93
|
+
if line_length > 0 and space:
|
|
94
|
+
line_data += space
|
|
95
|
+
line_length += len(space)
|
|
96
|
+
space = ''
|
|
97
|
+
|
|
98
|
+
# Word appendation
|
|
99
|
+
line_data += color + word
|
|
100
|
+
line_length += len(word)
|
|
101
|
+
word = ''
|
|
102
|
+
|
|
103
|
+
# Line wrapping
|
|
104
|
+
if line_length >= length:
|
|
105
|
+
append_line()
|
|
106
|
+
|
|
107
|
+
# Add char
|
|
108
|
+
def add_char(char: str) -> None:
|
|
109
|
+
nonlocal index, word
|
|
110
|
+
index += 1
|
|
111
|
+
word += char
|
|
112
|
+
|
|
113
|
+
# Iterate through chars
|
|
114
|
+
while index < len(string):
|
|
115
|
+
|
|
116
|
+
# Space separator
|
|
117
|
+
if string[index] == ' ':
|
|
118
|
+
|
|
119
|
+
# Store last word
|
|
120
|
+
store_word()
|
|
121
|
+
|
|
122
|
+
# Reset word data
|
|
123
|
+
space += ' '
|
|
124
|
+
index += 1
|
|
125
|
+
continue
|
|
126
|
+
|
|
127
|
+
# Color marker
|
|
128
|
+
for item in Colors.ALL:
|
|
129
|
+
if item and string[index:].startswith(item):
|
|
130
|
+
|
|
131
|
+
# Store last word
|
|
132
|
+
store_word()
|
|
133
|
+
|
|
134
|
+
# Store new color
|
|
135
|
+
color = item
|
|
136
|
+
index += len(color)
|
|
137
|
+
break
|
|
138
|
+
|
|
139
|
+
# Text content
|
|
140
|
+
else:
|
|
141
|
+
|
|
142
|
+
# Append character
|
|
143
|
+
add_char(string[index])
|
|
144
|
+
|
|
145
|
+
# Store last word
|
|
146
|
+
store_word()
|
|
147
|
+
|
|
148
|
+
# Append last line
|
|
149
|
+
append_line()
|
|
150
|
+
|
|
151
|
+
# Result
|
|
152
|
+
return lines
|