basebender 0.2.1__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.
- basebender/STRUCTURE.md +13 -0
- basebender/__init__.py +8 -0
- basebender/api/STRUCTURE.md +7 -0
- basebender/api/main.py +249 -0
- basebender/cli.py +231 -0
- basebender/gui/STRUCTURE.md +8 -0
- basebender/gui/__init__.py +3 -0
- basebender/gui/main_window.py +546 -0
- basebender/rebaser/STRUCTURE.md +16 -0
- basebender/rebaser/__init__.py +4 -0
- basebender/rebaser/config_loader.py +269 -0
- basebender/rebaser/digit_set_rebaser.py +303 -0
- basebender/rebaser/digit_sets.py +84 -0
- basebender/rebaser/generated/STRUCTURE.md +10 -0
- basebender/rebaser/generated/__init__.py +0 -0
- basebender/rebaser/generated/app_resources_rc.py +204 -0
- basebender/rebaser/models.py +38 -0
- basebender/rebaser/resources/STRUCTURE.md +12 -0
- basebender/rebaser/resources/app_resources.qrc +8 -0
- basebender/rebaser/resources/data/STRUCTURE.md +7 -0
- basebender/rebaser/resources/data/default_digit_sets.toml +27 -0
- basebender/rebaser/resources/icons/STRUCTURE.md +10 -0
- basebender/rebaser/resources/icons/input.svg +5 -0
- basebender/rebaser/resources/icons/output.svg +4 -0
- basebender/rebaser/resources/icons/source.svg +5 -0
- basebender/rebaser/resources/icons/target.svg +5 -0
- basebender-0.2.1.dist-info/METADATA +174 -0
- basebender-0.2.1.dist-info/RECORD +31 -0
- basebender-0.2.1.dist-info/WHEEL +4 -0
- basebender-0.2.1.dist-info/entry_points.txt +4 -0
- basebender-0.2.1.dist-info/licenses/LICENSE +9 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module handles
|
|
3
|
+
loading and saving configuration for digit sets and UI state.
|
|
4
|
+
|
|
5
|
+
It defines paths for package, system, and user configuration files,
|
|
6
|
+
and provides functions to load digit sets from TOML files
|
|
7
|
+
and manage the application's UI state.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import importlib.resources
|
|
11
|
+
import logging
|
|
12
|
+
import os
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, cast
|
|
15
|
+
|
|
16
|
+
import toml
|
|
17
|
+
|
|
18
|
+
from .models import DigitSet
|
|
19
|
+
|
|
20
|
+
logger = logging.getLogger(__name__)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _get_config_paths() -> tuple[Path, Path | None, Path | None, Path | None]:
|
|
24
|
+
"""
|
|
25
|
+
Determines the paths for package, system, and user configuration files.
|
|
26
|
+
|
|
27
|
+
This function calculates the expected paths for `default_digit_sets.toml`
|
|
28
|
+
(within the package), `digit_sets.toml` (system-wide and user-specific),
|
|
29
|
+
and `ui_state.toml` (user-specific) based on the operating system.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
A tuple containing:
|
|
33
|
+
- package_config_path (Path): Path to the default digit sets TOML file
|
|
34
|
+
within the installed package.
|
|
35
|
+
- system_config_path (Optional[Path]): Path to the system-wide digit
|
|
36
|
+
sets TOML file, or None if not applicable for the OS.
|
|
37
|
+
- user_config_path (Optional[Path]): Path to the user-specific digit
|
|
38
|
+
sets TOML file, or None if not applicable for the OS.
|
|
39
|
+
- ui_state_path (Optional[Path]): Path to the user-specific UI state
|
|
40
|
+
TOML file, or None if not applicable for the OS.
|
|
41
|
+
"""
|
|
42
|
+
package_config_path = Path(
|
|
43
|
+
cast(
|
|
44
|
+
str,
|
|
45
|
+
importlib.resources.files("basebender.rebaser.resources.data")
|
|
46
|
+
/ "default_digit_sets.toml",
|
|
47
|
+
)
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
# System config path (platform-dependent)
|
|
51
|
+
if os.name == "posix": # Linux, macOS
|
|
52
|
+
system_config_path = Path("/etc") / "basebender" / "digit_sets.toml"
|
|
53
|
+
elif os.name == "nt": # Windows
|
|
54
|
+
system_config_path = (
|
|
55
|
+
Path(os.environ.get("PROGRAMDATA", "C:\\ProgramData"))
|
|
56
|
+
/ "basebender"
|
|
57
|
+
/ "digit_sets.toml"
|
|
58
|
+
)
|
|
59
|
+
else:
|
|
60
|
+
system_config_path = None # Fallback for unknown OS
|
|
61
|
+
|
|
62
|
+
# User config path (platform-dependent)
|
|
63
|
+
user_config_dir = None
|
|
64
|
+
if os.name == "posix":
|
|
65
|
+
user_config_dir = Path.home() / ".config" / "basebender"
|
|
66
|
+
elif os.name == "nt":
|
|
67
|
+
user_config_dir = Path(os.environ.get("APPDATA", Path.home())) / "basebender"
|
|
68
|
+
|
|
69
|
+
user_config_path = user_config_dir / "digit_sets.toml" if user_config_dir else None
|
|
70
|
+
ui_state_path = user_config_dir / "ui_state.toml" if user_config_dir else None
|
|
71
|
+
|
|
72
|
+
return (
|
|
73
|
+
package_config_path,
|
|
74
|
+
system_config_path,
|
|
75
|
+
user_config_path,
|
|
76
|
+
ui_state_path,
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def load_digit_sets_from_toml(filepath: Path, source_type: str) -> list[DigitSet]:
|
|
81
|
+
"""
|
|
82
|
+
Loads digit sets from a TOML file, associating them with a given source type.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
filepath: The path to the TOML file containing digit set definitions.
|
|
86
|
+
source_type: A string indicating the source of these digit sets (e.g.,
|
|
87
|
+
"package", "system", "user").
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
A list of `DigitSet` objects loaded from the file. Returns an empty list
|
|
91
|
+
if the file is not found, is malformed, or contains invalid entries.
|
|
92
|
+
"""
|
|
93
|
+
loaded_digit_sets: list[DigitSet] = []
|
|
94
|
+
try:
|
|
95
|
+
with open(filepath, encoding="utf-8") as file_ptr:
|
|
96
|
+
config = toml.load(file_ptr)
|
|
97
|
+
|
|
98
|
+
digit_sets_data = config.get("digit_sets", [])
|
|
99
|
+
if not isinstance(digit_sets_data, list):
|
|
100
|
+
logger.warning("'%s' in %s is not a list. Skipping.", "digit_sets", filepath)
|
|
101
|
+
return loaded_digit_sets
|
|
102
|
+
|
|
103
|
+
for digit_set_entry in digit_sets_data:
|
|
104
|
+
if not isinstance(digit_set_entry, dict):
|
|
105
|
+
logger.warning(
|
|
106
|
+
"Found non-dictionary item in 'digit_sets' in %s. Skipping: %s",
|
|
107
|
+
filepath,
|
|
108
|
+
digit_set_entry,
|
|
109
|
+
)
|
|
110
|
+
continue
|
|
111
|
+
|
|
112
|
+
digit_set_name = digit_set_entry.get("name")
|
|
113
|
+
digits = digit_set_entry.get("digits")
|
|
114
|
+
|
|
115
|
+
if not isinstance(digit_set_name, str) or not digit_set_name:
|
|
116
|
+
logger.warning(
|
|
117
|
+
"No 'name' found for a digit set entry in %s. Skipping: %s",
|
|
118
|
+
filepath,
|
|
119
|
+
digit_set_entry,
|
|
120
|
+
)
|
|
121
|
+
continue
|
|
122
|
+
if not isinstance(digits, str) or not digits:
|
|
123
|
+
logger.warning(
|
|
124
|
+
"Digit set entry '%s' in %s missing or invalid 'digits'. Skipping.",
|
|
125
|
+
digit_set_name,
|
|
126
|
+
filepath,
|
|
127
|
+
)
|
|
128
|
+
continue
|
|
129
|
+
|
|
130
|
+
loaded_digit_sets.append(
|
|
131
|
+
DigitSet(name=digit_set_name, digits=digits, source=source_type)
|
|
132
|
+
)
|
|
133
|
+
except FileNotFoundError:
|
|
134
|
+
pass # No digit sets from this file, which is fine
|
|
135
|
+
except toml.TomlDecodeError as exc:
|
|
136
|
+
logger.warning("Error decoding TOML file %s: %s", filepath, exc)
|
|
137
|
+
except OSError as exc: # Use OSError for modern Python
|
|
138
|
+
logger.warning("An unexpected error occurred while loading %s: %s", filepath, exc)
|
|
139
|
+
return loaded_digit_sets
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def get_all_digit_sets() -> dict[str, DigitSet]:
|
|
143
|
+
"""
|
|
144
|
+
Loads and merges digit sets from package, system, and user configurations.
|
|
145
|
+
|
|
146
|
+
The loading order defines precedence: user configuration overrides system,
|
|
147
|
+
and system overrides package defaults. Each digit set is assigned a unique
|
|
148
|
+
ID based on its source and name (e.g., "package:ASCII").
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
A dictionary where keys are unique IDs (e.g., "package:ASCII") and
|
|
152
|
+
values are `DigitSet` objects.
|
|
153
|
+
"""
|
|
154
|
+
package_path, system_path, user_path, _ = _get_config_paths()
|
|
155
|
+
|
|
156
|
+
all_digit_sets_list: list[DigitSet] = []
|
|
157
|
+
|
|
158
|
+
# Load package digit sets
|
|
159
|
+
all_digit_sets_list.extend(load_digit_sets_from_toml(package_path, "package"))
|
|
160
|
+
|
|
161
|
+
# Load system digit sets
|
|
162
|
+
if system_path and system_path.exists():
|
|
163
|
+
all_digit_sets_list.extend(load_digit_sets_from_toml(system_path, "system"))
|
|
164
|
+
|
|
165
|
+
# Load user digit sets (highest precedence)
|
|
166
|
+
if user_path and user_path.exists():
|
|
167
|
+
all_digit_sets_list.extend(load_digit_sets_from_toml(user_path, "user"))
|
|
168
|
+
|
|
169
|
+
# Convert list to a dictionary with unique IDs, handling precedence.
|
|
170
|
+
# Process in reverse (user -> system -> package) so highest-precedence entries
|
|
171
|
+
# are written first and lower-precedence entries overwrite them last.
|
|
172
|
+
final_digit_sets: dict[str, DigitSet] = {}
|
|
173
|
+
for current_digit_set_obj in reversed(all_digit_sets_list):
|
|
174
|
+
unique_id = f"{current_digit_set_obj.source}:{current_digit_set_obj.name}"
|
|
175
|
+
final_digit_sets[unique_id] = current_digit_set_obj
|
|
176
|
+
|
|
177
|
+
return final_digit_sets
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def get_ui_state_path() -> Path | None:
|
|
181
|
+
"""
|
|
182
|
+
Returns the path to the user's UI state configuration file.
|
|
183
|
+
|
|
184
|
+
This file is typically located in a user-specific configuration directory.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
A `Path` object pointing to the UI state file, or `None` if the
|
|
188
|
+
user configuration directory cannot be determined for the current OS.
|
|
189
|
+
"""
|
|
190
|
+
_, _, _, ui_state_path = _get_config_paths()
|
|
191
|
+
return ui_state_path
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def load_ui_state() -> dict[str, Any]:
|
|
195
|
+
"""
|
|
196
|
+
Loads the UI state from the user's configuration file.
|
|
197
|
+
|
|
198
|
+
Returns:
|
|
199
|
+
A dictionary containing the loaded UI state. Returns an empty dictionary
|
|
200
|
+
if the file does not exist, is malformed, or an error occurs during loading.
|
|
201
|
+
"""
|
|
202
|
+
ui_state_path = get_ui_state_path()
|
|
203
|
+
if ui_state_path and ui_state_path.exists():
|
|
204
|
+
try:
|
|
205
|
+
with open(ui_state_path, encoding="utf-8") as file_ptr:
|
|
206
|
+
return cast(dict[str, Any], toml.load(file_ptr))
|
|
207
|
+
except toml.TomlDecodeError as exc:
|
|
208
|
+
logger.warning("Error decoding UI state TOML file %s: %s", ui_state_path, exc)
|
|
209
|
+
except OSError as exc: # Use OSError for modern Python
|
|
210
|
+
logger.warning(
|
|
211
|
+
"An unexpected error occurred while loading UI state from %s: %s",
|
|
212
|
+
ui_state_path,
|
|
213
|
+
exc,
|
|
214
|
+
)
|
|
215
|
+
return {} # Return empty dict if file not found or error
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def save_ui_state(state_data: dict[str, Any]) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Saves the provided UI state data to the user's configuration file.
|
|
221
|
+
|
|
222
|
+
The function ensures that the parent directory for the UI state file exists.
|
|
223
|
+
Any errors during saving are logged.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
state_data: A dictionary containing the UI state to be saved.
|
|
227
|
+
"""
|
|
228
|
+
ui_state_path = get_ui_state_path()
|
|
229
|
+
if ui_state_path:
|
|
230
|
+
try:
|
|
231
|
+
ui_state_path.parent.mkdir(parents=True, exist_ok=True)
|
|
232
|
+
with open(ui_state_path, "w", encoding="utf-8") as file_ptr:
|
|
233
|
+
toml.dump(state_data, file_ptr)
|
|
234
|
+
except OSError as exc: # Use OSError for modern Python
|
|
235
|
+
logger.error("Could not save UI state to %s: %s", ui_state_path, exc)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
if __name__ == "__main__":
|
|
239
|
+
# Example usage for testing
|
|
240
|
+
print("--- Testing Digit Set Loading ---")
|
|
241
|
+
digit_sets = get_all_digit_sets()
|
|
242
|
+
print("Loaded Digit Sets:")
|
|
243
|
+
for name_key, digit_set_obj in digit_sets.items():
|
|
244
|
+
print(
|
|
245
|
+
f" {name_key}: {digit_set_obj.digits} "
|
|
246
|
+
f"(Name: {digit_set_obj.name}, Source: {digit_set_obj.source})"
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
print("\n--- Testing UI State Saving/Loading ---")
|
|
250
|
+
test_state = {
|
|
251
|
+
"last_input": "Hello World",
|
|
252
|
+
"last_source_digit_set": "package:ASCII",
|
|
253
|
+
"last_target_digit_set": "package:Binary",
|
|
254
|
+
"realtime_enabled": True,
|
|
255
|
+
}
|
|
256
|
+
print(f"Saving test UI state: {test_state}")
|
|
257
|
+
save_ui_state(test_state)
|
|
258
|
+
|
|
259
|
+
loaded_state = load_ui_state()
|
|
260
|
+
print(f"Loaded UI state: {loaded_state}")
|
|
261
|
+
assert loaded_state == test_state, "Loaded state does not match saved state!"
|
|
262
|
+
|
|
263
|
+
# Clean up test state file
|
|
264
|
+
ui_path = get_ui_state_path() # pylint: disable=invalid-name
|
|
265
|
+
if ui_path and ui_path.exists():
|
|
266
|
+
os.remove(ui_path)
|
|
267
|
+
print(f"Cleaned up {ui_path}")
|
|
268
|
+
else:
|
|
269
|
+
print("UI state file not found for cleanup.")
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module provides the core logic for rebasing strings between different
|
|
3
|
+
digit sets.
|
|
4
|
+
|
|
5
|
+
It includes the `DigitSetRebaser` class, which handles the conversion of
|
|
6
|
+
strings from an input digit set to an output digit set, supporting dynamic
|
|
7
|
+
derivation of the input digit set and various rebase operations.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from .models import DigitSet
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DigitSetRebaser:
|
|
14
|
+
"""
|
|
15
|
+
A class to rebase strings between different digit sets (positional number
|
|
16
|
+
systems).
|
|
17
|
+
|
|
18
|
+
It supports explicit input and output digit sets, or dynamic derivation of
|
|
19
|
+
the input digit set based on the input string.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
out_digit_set: DigitSet | None = None,
|
|
25
|
+
in_digit_set: DigitSet | None = None,
|
|
26
|
+
) -> None:
|
|
27
|
+
self._initial_input_digit_set: DigitSet | None = in_digit_set
|
|
28
|
+
self._initial_output_digit_set: DigitSet | None = out_digit_set
|
|
29
|
+
self._in_digit_set_map: dict[str, int] = {}
|
|
30
|
+
self._in_digit_set_list: list[str] = []
|
|
31
|
+
self._out_digit_set_map: dict[str, int] = {}
|
|
32
|
+
self._out_digit_set_list: list[str] = []
|
|
33
|
+
|
|
34
|
+
# Output digit set is always explicitly set or None;
|
|
35
|
+
# not dynamically determined in __init__
|
|
36
|
+
if out_digit_set:
|
|
37
|
+
out_digits = DigitSet.deduplicate_digits(out_digit_set.digits)
|
|
38
|
+
self._out_digit_set_map = {char: i for i, char in enumerate(out_digits)}
|
|
39
|
+
self._out_digit_set_list = list(out_digits)
|
|
40
|
+
|
|
41
|
+
# Input digit set is dynamically determined in rebase
|
|
42
|
+
# if _initial_input_digit_set is None
|
|
43
|
+
if in_digit_set:
|
|
44
|
+
in_digits = DigitSet.deduplicate_digits(in_digit_set.digits)
|
|
45
|
+
self._in_digit_set_map = {char: i for i, char in enumerate(in_digits)}
|
|
46
|
+
self._in_digit_set_list = list(in_digits)
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def initial_input_digit_set(
|
|
50
|
+
self,
|
|
51
|
+
) -> DigitSet | None:
|
|
52
|
+
"""
|
|
53
|
+
The initial input digit set provided during initialization.
|
|
54
|
+
|
|
55
|
+
Examples:
|
|
56
|
+
>>> rebaser = DigitSetRebaser(in_digit_set=DigitSet("0123456789"))
|
|
57
|
+
>>> rebaser.initial_input_digit_set.digits
|
|
58
|
+
'0123456789'
|
|
59
|
+
"""
|
|
60
|
+
return self._initial_input_digit_set
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def initial_output_digit_set(
|
|
64
|
+
self,
|
|
65
|
+
) -> DigitSet | None:
|
|
66
|
+
"""
|
|
67
|
+
The initial output digit set provided during initialization.
|
|
68
|
+
|
|
69
|
+
Examples:
|
|
70
|
+
>>> rebaser = DigitSetRebaser(out_digit_set=DigitSet("abc"))
|
|
71
|
+
>>> rebaser.initial_output_digit_set.digits
|
|
72
|
+
'abc'
|
|
73
|
+
"""
|
|
74
|
+
return self._initial_output_digit_set
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def input_digit_set_map(
|
|
78
|
+
self,
|
|
79
|
+
) -> dict[str, int]:
|
|
80
|
+
"""
|
|
81
|
+
A dictionary mapping characters to their integer positions for the input digit set.
|
|
82
|
+
|
|
83
|
+
Examples:
|
|
84
|
+
>>> rebaser = DigitSetRebaser(in_digit_set=DigitSet("012"))
|
|
85
|
+
>>> rebaser.input_digit_set_map
|
|
86
|
+
{'0': 0, '1': 1, '2': 2}
|
|
87
|
+
"""
|
|
88
|
+
return self._in_digit_set_map
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def input_digit_set_list(
|
|
92
|
+
self,
|
|
93
|
+
) -> list[str]:
|
|
94
|
+
"""
|
|
95
|
+
An ordered list of characters representing the input digit set.
|
|
96
|
+
|
|
97
|
+
Examples:
|
|
98
|
+
>>> rebaser = DigitSetRebaser(in_digit_set=DigitSet("abc"))
|
|
99
|
+
>>> rebaser.input_digit_set_list
|
|
100
|
+
['a', 'b', 'c']
|
|
101
|
+
"""
|
|
102
|
+
return self._in_digit_set_list
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def output_digit_set_map(
|
|
106
|
+
self,
|
|
107
|
+
) -> dict[str, int]:
|
|
108
|
+
"""
|
|
109
|
+
A dictionary mapping characters to their integer positions for the output digit set.
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
>>> rebaser = DigitSetRebaser(out_digit_set=DigitSet("xyz"))
|
|
113
|
+
>>> rebaser.output_digit_set_map
|
|
114
|
+
{'x': 0, 'y': 1, 'z': 2}
|
|
115
|
+
"""
|
|
116
|
+
return self._out_digit_set_map
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def output_digit_set_list(
|
|
120
|
+
self,
|
|
121
|
+
) -> list[str]:
|
|
122
|
+
"""
|
|
123
|
+
An ordered list of characters representing the output digit set.
|
|
124
|
+
|
|
125
|
+
Examples:
|
|
126
|
+
>>> rebaser = DigitSetRebaser(out_digit_set=DigitSet("789"))
|
|
127
|
+
>>> rebaser.output_digit_set_list
|
|
128
|
+
['7', '8', '9']
|
|
129
|
+
"""
|
|
130
|
+
return self._out_digit_set_list
|
|
131
|
+
|
|
132
|
+
@staticmethod
|
|
133
|
+
def char_to_position(char: str, digit_set_map: dict[str, int]) -> int:
|
|
134
|
+
"""
|
|
135
|
+
Converts a character to its numerical position within a given digit set map.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
char: The character to convert.
|
|
139
|
+
digit_set_map: A dictionary mapping characters to their integer positions.
|
|
140
|
+
|
|
141
|
+
Returns:
|
|
142
|
+
The integer position of the character.
|
|
143
|
+
|
|
144
|
+
Raises:
|
|
145
|
+
ValueError: If the character is not found in the digit set.
|
|
146
|
+
"""
|
|
147
|
+
if char not in digit_set_map:
|
|
148
|
+
raise ValueError(f"Character '{char}' not found in the digit set.")
|
|
149
|
+
return digit_set_map[char]
|
|
150
|
+
|
|
151
|
+
@staticmethod
|
|
152
|
+
def position_to_char(position: int, digit_set_list: list[str]) -> str:
|
|
153
|
+
"""
|
|
154
|
+
Converts a numerical position to its corresponding character in a digit set list.
|
|
155
|
+
|
|
156
|
+
Args:
|
|
157
|
+
position: The integer position to convert.
|
|
158
|
+
digit_set_list: An ordered list of characters representing the digit set.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
The character at the specified position.
|
|
162
|
+
|
|
163
|
+
Raises:
|
|
164
|
+
IndexError: If the position is out of bounds for the digit set.
|
|
165
|
+
"""
|
|
166
|
+
if not 0 <= position < len(digit_set_list):
|
|
167
|
+
raise IndexError(f"Position {position} is out of bounds for the digit set.")
|
|
168
|
+
return digit_set_list[position]
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def string_to_int_from_base(input_str: str, digit_set_map: dict[str, int], base: int) -> int:
|
|
172
|
+
"""
|
|
173
|
+
Converts a string representation of a number from a given base to an integer.
|
|
174
|
+
|
|
175
|
+
Characters not present in the `digit_set_map` are ignored.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
input_str: The input string to convert.
|
|
179
|
+
digit_set_map: A dictionary mapping characters to their integer positions
|
|
180
|
+
in the source digit set.
|
|
181
|
+
base: The base of the input string's number system (length of the source digit set).
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
The integer representation of the input string.
|
|
185
|
+
"""
|
|
186
|
+
filtered_input_str = [char for char in input_str if char in digit_set_map]
|
|
187
|
+
|
|
188
|
+
if not filtered_input_str:
|
|
189
|
+
return 0
|
|
190
|
+
|
|
191
|
+
integer_value = 0
|
|
192
|
+
power = 0
|
|
193
|
+
for char in reversed(filtered_input_str):
|
|
194
|
+
position = digit_set_map[char]
|
|
195
|
+
integer_value += position * (base**power)
|
|
196
|
+
power += 1
|
|
197
|
+
return integer_value
|
|
198
|
+
|
|
199
|
+
@staticmethod
|
|
200
|
+
def int_to_string_in_base(integer_value: int, digit_set_list: list[str], base: int) -> str:
|
|
201
|
+
"""
|
|
202
|
+
Converts an integer to its string representation in a given base.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
integer_value: The integer to convert.
|
|
206
|
+
digit_set_list: An ordered list of characters representing the target digit set.
|
|
207
|
+
base: The base of the target number system (length of the target digit set).
|
|
208
|
+
|
|
209
|
+
Returns:
|
|
210
|
+
The string representation of the integer in the target base.
|
|
211
|
+
|
|
212
|
+
Raises:
|
|
213
|
+
ValueError: If the base is not greater than 0.
|
|
214
|
+
"""
|
|
215
|
+
if base <= 0:
|
|
216
|
+
raise ValueError("Base must be greater than 0 for integer to string rebase.")
|
|
217
|
+
|
|
218
|
+
if base == 1:
|
|
219
|
+
return ""
|
|
220
|
+
|
|
221
|
+
if integer_value == 0:
|
|
222
|
+
return digit_set_list[0]
|
|
223
|
+
|
|
224
|
+
result_chars: list[str] = []
|
|
225
|
+
while integer_value > 0:
|
|
226
|
+
remainder = integer_value % base
|
|
227
|
+
result_chars.append(digit_set_list[remainder])
|
|
228
|
+
integer_value //= base
|
|
229
|
+
|
|
230
|
+
return "".join(reversed(result_chars))
|
|
231
|
+
|
|
232
|
+
def rebase(self, input_string: str) -> str:
|
|
233
|
+
"""
|
|
234
|
+
Rebases the input string from its determined input digit set to the
|
|
235
|
+
specified output digit set.
|
|
236
|
+
|
|
237
|
+
This method handles various scenarios:
|
|
238
|
+
- If `input_string` is empty, returns the first character of the output
|
|
239
|
+
digit set if available, otherwise an empty string.
|
|
240
|
+
- If neither input nor output digit sets are explicitly provided,
|
|
241
|
+
returns the input string as-is.
|
|
242
|
+
- If only the output digit set is provided, the input digit set is
|
|
243
|
+
dynamically derived from the `input_string`.
|
|
244
|
+
- If only the input digit set is provided, the input string is filtered
|
|
245
|
+
to include only characters present in the input digit set.
|
|
246
|
+
- If both are provided or derived, performs the full rebase operation.
|
|
247
|
+
|
|
248
|
+
Args:
|
|
249
|
+
input_string: The string to be rebased.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
The rebased string.
|
|
253
|
+
"""
|
|
254
|
+
if not input_string:
|
|
255
|
+
return self._out_digit_set_list[0] if self._out_digit_set_list else ""
|
|
256
|
+
|
|
257
|
+
effective_input_digit_set_map: dict[str, int]
|
|
258
|
+
effective_input_digit_set_list: list[str]
|
|
259
|
+
|
|
260
|
+
if self._initial_input_digit_set:
|
|
261
|
+
effective_input_digit_set_map = self._in_digit_set_map
|
|
262
|
+
effective_input_digit_set_list = self._in_digit_set_list
|
|
263
|
+
else:
|
|
264
|
+
# Dynamically derive input digit set from input_string
|
|
265
|
+
derived_digits = DigitSet.deduplicate_digits(input_string)
|
|
266
|
+
effective_input_digit_set_map = {char: i for i, char in enumerate(derived_digits)}
|
|
267
|
+
effective_input_digit_set_list = list(derived_digits)
|
|
268
|
+
|
|
269
|
+
# Scenario 1: No explicit output digit set, and no initial input digit set
|
|
270
|
+
# (meaning input digit set was dynamically derived).
|
|
271
|
+
# In this case, just return the input string as is.
|
|
272
|
+
if self._initial_output_digit_set is None and self._initial_input_digit_set is None:
|
|
273
|
+
return input_string
|
|
274
|
+
|
|
275
|
+
# Scenario 2: No explicit output digit set, but an initial input digit set
|
|
276
|
+
# was provided. Filter the input string based on the provided input digit set.
|
|
277
|
+
if self._initial_output_digit_set is None and self._initial_input_digit_set is not None:
|
|
278
|
+
filtered_string = "".join(
|
|
279
|
+
char for char in input_string if char in effective_input_digit_set_map
|
|
280
|
+
)
|
|
281
|
+
return filtered_string
|
|
282
|
+
|
|
283
|
+
# If the effective input digit set is empty or has only one character,
|
|
284
|
+
# and we are supposed to rebase, return the first char of output or empty.
|
|
285
|
+
if not effective_input_digit_set_list or len(effective_input_digit_set_list) <= 1:
|
|
286
|
+
return self._out_digit_set_list[0] if self._out_digit_set_list else ""
|
|
287
|
+
|
|
288
|
+
# If the output digit set is empty, return an empty string
|
|
289
|
+
if not self._out_digit_set_list:
|
|
290
|
+
return ""
|
|
291
|
+
|
|
292
|
+
# Perform the full rebase
|
|
293
|
+
integer_value = self.string_to_int_from_base(
|
|
294
|
+
input_string,
|
|
295
|
+
effective_input_digit_set_map,
|
|
296
|
+
len(effective_input_digit_set_list),
|
|
297
|
+
)
|
|
298
|
+
final_rebased_string = self.int_to_string_in_base(
|
|
299
|
+
integer_value,
|
|
300
|
+
self._out_digit_set_list,
|
|
301
|
+
len(self._out_digit_set_list),
|
|
302
|
+
)
|
|
303
|
+
return final_rebased_string
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module manages predefined digit sets and provides utility functions for
|
|
3
|
+
suggesting digit sets based on input strings.
|
|
4
|
+
|
|
5
|
+
It includes caching mechanisms for efficient retrieval of digit set data.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import functools
|
|
9
|
+
|
|
10
|
+
from .config_loader import get_all_digit_sets
|
|
11
|
+
from .models import DigitSet
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@functools.cache
|
|
15
|
+
def get_predefined_digit_sets() -> dict[str, DigitSet]:
|
|
16
|
+
"""
|
|
17
|
+
Returns a dictionary of all loaded predefined digit sets.
|
|
18
|
+
|
|
19
|
+
This function loads digit sets from various configuration sources (package,
|
|
20
|
+
system, user) and caches the result for efficient retrieval on subsequent calls.
|
|
21
|
+
The loading order defines precedence: user configuration overrides system,
|
|
22
|
+
and system overrides package defaults.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
A dictionary where keys are unique IDs (e.g., "package:ASCII") and
|
|
26
|
+
values are `DigitSet` objects.
|
|
27
|
+
"""
|
|
28
|
+
return get_all_digit_sets()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def suggest_digit_sets(input_string: str) -> list[str]:
|
|
32
|
+
"""
|
|
33
|
+
Suggests predefined digit sets that the `input_string` might belong to.
|
|
34
|
+
|
|
35
|
+
This function iterates through all known predefined digit sets and identifies
|
|
36
|
+
those that contain all characters present in the `input_string`.
|
|
37
|
+
|
|
38
|
+
Args:
|
|
39
|
+
input_string: The string for which to suggest digit sets.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
A list of digit set IDs (strings) that are relevant to the input string.
|
|
43
|
+
The list is currently not sophisticatedly ranked beyond basic matching.
|
|
44
|
+
"""
|
|
45
|
+
if not input_string:
|
|
46
|
+
return []
|
|
47
|
+
|
|
48
|
+
predefined_digit_sets = get_predefined_digit_sets()
|
|
49
|
+
suggestions: list[str] = []
|
|
50
|
+
|
|
51
|
+
for digit_set_id, digit_set_info in predefined_digit_sets.items():
|
|
52
|
+
if all(char in digit_set_info.digits for char in input_string):
|
|
53
|
+
suggestions.append(digit_set_id)
|
|
54
|
+
|
|
55
|
+
# Basic ordering: exact matches first (if any), then others.
|
|
56
|
+
# For now, just return the list as is,
|
|
57
|
+
# more sophisticated ranking can be added later.
|
|
58
|
+
return suggestions
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def main() -> None:
|
|
62
|
+
"""
|
|
63
|
+
Main function for example usage and testing of digit set functionalities.
|
|
64
|
+
"""
|
|
65
|
+
print("All Predefined Digit Sets:")
|
|
66
|
+
for example_ds_id, example_ds_info in get_predefined_digit_sets().items():
|
|
67
|
+
print(
|
|
68
|
+
f" {example_ds_id} (Name: {example_ds_info.name}, "
|
|
69
|
+
f"Source: {example_ds_info.source}): {example_ds_info.digits}"
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
test_string_binary = "010110" # pylint: disable=invalid-name
|
|
73
|
+
test_string_decimal = "12345" # pylint: disable=invalid-name
|
|
74
|
+
test_string_hex = "DEADBEEF" # pylint: disable=invalid-name
|
|
75
|
+
test_string_mixed = "Hello World 123" # pylint: disable=invalid-name
|
|
76
|
+
|
|
77
|
+
print(f"\nSuggestions for '{test_string_binary}': {suggest_digit_sets(test_string_binary)}")
|
|
78
|
+
print(f"Suggestions for '{test_string_decimal}': {suggest_digit_sets(test_string_decimal)}")
|
|
79
|
+
print(f"Suggestions for '{test_string_hex}': {suggest_digit_sets(test_string_hex)}")
|
|
80
|
+
print(f"Suggestions for '{test_string_mixed}': {suggest_digit_sets(test_string_mixed)}")
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
if __name__ == "__main__":
|
|
84
|
+
main()
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# `src/rebaser/generated` Directory Structure
|
|
2
|
+
|
|
3
|
+
This directory contains generated source files, created by `bin/update`.
|
|
4
|
+
|
|
5
|
+
## Folders:
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
## Files:
|
|
9
|
+
|
|
10
|
+
* [`app_resources_rc.py`](src/rebaser/generated/app_resources_rc.py): Python module generated from `app_resources.qrc` for Qt resources.
|
|
File without changes
|