just-bash 0.1.5__py3-none-any.whl → 0.1.10__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.
- just_bash/ast/factory.py +3 -1
- just_bash/bash.py +28 -6
- just_bash/commands/awk/awk.py +362 -17
- just_bash/commands/cat/cat.py +5 -1
- just_bash/commands/echo/echo.py +33 -1
- just_bash/commands/grep/grep.py +141 -3
- just_bash/commands/od/od.py +144 -30
- just_bash/commands/printf/printf.py +289 -87
- just_bash/commands/pwd/pwd.py +32 -2
- just_bash/commands/read/read.py +243 -64
- just_bash/commands/readlink/readlink.py +3 -9
- just_bash/commands/registry.py +32 -0
- just_bash/commands/rmdir/__init__.py +5 -0
- just_bash/commands/rmdir/rmdir.py +160 -0
- just_bash/commands/sed/sed.py +142 -31
- just_bash/commands/shuf/__init__.py +5 -0
- just_bash/commands/shuf/shuf.py +242 -0
- just_bash/commands/stat/stat.py +9 -0
- just_bash/commands/time/__init__.py +5 -0
- just_bash/commands/time/time.py +74 -0
- just_bash/commands/touch/touch.py +118 -8
- just_bash/commands/whoami/__init__.py +5 -0
- just_bash/commands/whoami/whoami.py +18 -0
- just_bash/fs/in_memory_fs.py +22 -0
- just_bash/fs/overlay_fs.py +22 -1
- just_bash/interpreter/__init__.py +1 -1
- just_bash/interpreter/builtins/__init__.py +2 -0
- just_bash/interpreter/builtins/control.py +4 -8
- just_bash/interpreter/builtins/declare.py +321 -24
- just_bash/interpreter/builtins/getopts.py +163 -0
- just_bash/interpreter/builtins/let.py +2 -2
- just_bash/interpreter/builtins/local.py +71 -5
- just_bash/interpreter/builtins/misc.py +22 -6
- just_bash/interpreter/builtins/readonly.py +38 -10
- just_bash/interpreter/builtins/set.py +58 -8
- just_bash/interpreter/builtins/test.py +136 -19
- just_bash/interpreter/builtins/unset.py +62 -10
- just_bash/interpreter/conditionals.py +29 -4
- just_bash/interpreter/control_flow.py +61 -17
- just_bash/interpreter/expansion.py +1647 -104
- just_bash/interpreter/interpreter.py +436 -69
- just_bash/interpreter/types.py +263 -2
- just_bash/parser/__init__.py +2 -0
- just_bash/parser/lexer.py +295 -26
- just_bash/parser/parser.py +523 -64
- just_bash/types.py +11 -0
- {just_bash-0.1.5.dist-info → just_bash-0.1.10.dist-info}/METADATA +40 -1
- {just_bash-0.1.5.dist-info → just_bash-0.1.10.dist-info}/RECORD +49 -40
- {just_bash-0.1.5.dist-info → just_bash-0.1.10.dist-info}/WHEEL +0 -0
just_bash/commands/echo/echo.py
CHANGED
|
@@ -53,7 +53,7 @@ def _process_escapes(s: str) -> str:
|
|
|
53
53
|
octal += s[j]
|
|
54
54
|
j += 1
|
|
55
55
|
if octal:
|
|
56
|
-
result.append(chr(int(octal, 8)))
|
|
56
|
+
result.append(chr(int(octal, 8) & 0xFF))
|
|
57
57
|
else:
|
|
58
58
|
result.append("\0")
|
|
59
59
|
i = j
|
|
@@ -70,6 +70,38 @@ def _process_escapes(s: str) -> str:
|
|
|
70
70
|
else:
|
|
71
71
|
result.append(s[i])
|
|
72
72
|
i += 1
|
|
73
|
+
elif next_char == "u":
|
|
74
|
+
# Unicode escape: \uHHHH (4 hex digits)
|
|
75
|
+
hex_digits = ""
|
|
76
|
+
j = i + 2
|
|
77
|
+
while j < len(s) and len(hex_digits) < 4 and s[j] in "0123456789abcdefABCDEF":
|
|
78
|
+
hex_digits += s[j]
|
|
79
|
+
j += 1
|
|
80
|
+
if hex_digits:
|
|
81
|
+
try:
|
|
82
|
+
result.append(chr(int(hex_digits, 16)))
|
|
83
|
+
except (ValueError, OverflowError):
|
|
84
|
+
result.append("\\u" + hex_digits)
|
|
85
|
+
i = j
|
|
86
|
+
else:
|
|
87
|
+
result.append("\\u")
|
|
88
|
+
i += 2
|
|
89
|
+
elif next_char == "U":
|
|
90
|
+
# Unicode escape: \UHHHHHHHH (8 hex digits)
|
|
91
|
+
hex_digits = ""
|
|
92
|
+
j = i + 2
|
|
93
|
+
while j < len(s) and len(hex_digits) < 8 and s[j] in "0123456789abcdefABCDEF":
|
|
94
|
+
hex_digits += s[j]
|
|
95
|
+
j += 1
|
|
96
|
+
if hex_digits:
|
|
97
|
+
try:
|
|
98
|
+
result.append(chr(int(hex_digits, 16)))
|
|
99
|
+
except (ValueError, OverflowError):
|
|
100
|
+
result.append("\\U" + hex_digits)
|
|
101
|
+
i = j
|
|
102
|
+
else:
|
|
103
|
+
result.append("\\U")
|
|
104
|
+
i += 2
|
|
73
105
|
elif next_char == "c":
|
|
74
106
|
# \c stops output
|
|
75
107
|
return "".join(result)
|
just_bash/commands/grep/grep.py
CHANGED
|
@@ -152,7 +152,10 @@ class GrepCommand:
|
|
|
152
152
|
elif arg == "-e":
|
|
153
153
|
patterns.append(val)
|
|
154
154
|
else:
|
|
155
|
-
|
|
155
|
+
chars = arg[1:]
|
|
156
|
+
ci = 0
|
|
157
|
+
while ci < len(chars):
|
|
158
|
+
c = chars[ci]
|
|
156
159
|
if c == 'i':
|
|
157
160
|
ignore_case = True
|
|
158
161
|
elif c == 'v':
|
|
@@ -186,12 +189,38 @@ class GrepCommand:
|
|
|
186
189
|
word_regexp = True
|
|
187
190
|
elif c == 'x':
|
|
188
191
|
line_regexp = True
|
|
192
|
+
elif c in ('A', 'B', 'C', 'm', 'e'):
|
|
193
|
+
# These flags take a value: rest of string or next arg
|
|
194
|
+
rest = chars[ci + 1:]
|
|
195
|
+
if rest:
|
|
196
|
+
val = rest
|
|
197
|
+
elif i + 1 < len(args):
|
|
198
|
+
i += 1
|
|
199
|
+
val = args[i]
|
|
200
|
+
else:
|
|
201
|
+
return ExecResult(
|
|
202
|
+
stdout="",
|
|
203
|
+
stderr=f"grep: option requires an argument -- '{c}'\n",
|
|
204
|
+
exit_code=2,
|
|
205
|
+
)
|
|
206
|
+
if c == 'A':
|
|
207
|
+
after_context = int(val)
|
|
208
|
+
elif c == 'B':
|
|
209
|
+
before_context = int(val)
|
|
210
|
+
elif c == 'C':
|
|
211
|
+
before_context = after_context = int(val)
|
|
212
|
+
elif c == 'm':
|
|
213
|
+
max_count = int(val)
|
|
214
|
+
elif c == 'e':
|
|
215
|
+
patterns.append(val)
|
|
216
|
+
break # Rest of chars consumed as value
|
|
189
217
|
else:
|
|
190
218
|
return ExecResult(
|
|
191
219
|
stdout="",
|
|
192
220
|
stderr=f"grep: invalid option -- '{c}'\n",
|
|
193
221
|
exit_code=2,
|
|
194
222
|
)
|
|
223
|
+
ci += 1
|
|
195
224
|
elif pattern is None and not patterns:
|
|
196
225
|
pattern = arg
|
|
197
226
|
else:
|
|
@@ -203,8 +232,18 @@ class GrepCommand:
|
|
|
203
232
|
if pattern:
|
|
204
233
|
# If pattern was set, it's actually a file
|
|
205
234
|
files.insert(0, pattern)
|
|
206
|
-
pattern
|
|
207
|
-
|
|
235
|
+
# Convert each pattern from BRE before combining (if not ERE mode)
|
|
236
|
+
if not extended_regexp and not fixed_strings:
|
|
237
|
+
converted = [self._bre_to_python_regex(p) for p in patterns]
|
|
238
|
+
pattern = "|".join(f"({p})" for p in converted)
|
|
239
|
+
else:
|
|
240
|
+
pattern = "|".join(f"({p})" for p in patterns)
|
|
241
|
+
# Mark as already converted
|
|
242
|
+
patterns_already_converted = True
|
|
243
|
+
else:
|
|
244
|
+
patterns_already_converted = False
|
|
245
|
+
|
|
246
|
+
if pattern is None and not patterns:
|
|
208
247
|
return ExecResult(
|
|
209
248
|
stdout="",
|
|
210
249
|
stderr="grep: pattern not specified\n",
|
|
@@ -220,6 +259,10 @@ class GrepCommand:
|
|
|
220
259
|
if fixed_strings:
|
|
221
260
|
# Escape all regex metacharacters
|
|
222
261
|
pattern = re.escape(pattern)
|
|
262
|
+
elif not extended_regexp and not patterns_already_converted:
|
|
263
|
+
# Convert BRE (Basic Regular Expression) to Python regex
|
|
264
|
+
pattern = self._bre_to_python_regex(pattern)
|
|
265
|
+
|
|
223
266
|
if word_regexp:
|
|
224
267
|
pattern = r'\b' + pattern + r'\b'
|
|
225
268
|
if line_regexp:
|
|
@@ -382,6 +425,101 @@ class GrepCommand:
|
|
|
382
425
|
exit_code = 0 if found_match else 1
|
|
383
426
|
return ExecResult(stdout=stdout, stderr=stderr, exit_code=exit_code)
|
|
384
427
|
|
|
428
|
+
def _bre_to_python_regex(self, pattern: str) -> str:
|
|
429
|
+
"""Convert BRE (Basic Regular Expression) to Python regex.
|
|
430
|
+
|
|
431
|
+
In BRE:
|
|
432
|
+
- \\| is alternation, | is literal
|
|
433
|
+
- \\+ is one-or-more, + is literal
|
|
434
|
+
- \\? is zero-or-one, ? is literal
|
|
435
|
+
- \\( \\) is grouping, ( ) are literal
|
|
436
|
+
- \\{ \\} is repetition, { } are literal
|
|
437
|
+
- \\< \\> is word boundary
|
|
438
|
+
|
|
439
|
+
In Python regex (like ERE):
|
|
440
|
+
- | is alternation, \\| is literal
|
|
441
|
+
- + is one-or-more, \\+ is literal
|
|
442
|
+
- etc.
|
|
443
|
+
"""
|
|
444
|
+
result = []
|
|
445
|
+
i = 0
|
|
446
|
+
while i < len(pattern):
|
|
447
|
+
if pattern[i] == '\\' and i + 1 < len(pattern):
|
|
448
|
+
next_char = pattern[i + 1]
|
|
449
|
+
if next_char == '|':
|
|
450
|
+
# BRE \| -> Python |
|
|
451
|
+
result.append('|')
|
|
452
|
+
i += 2
|
|
453
|
+
elif next_char == '+':
|
|
454
|
+
# BRE \+ -> Python +
|
|
455
|
+
result.append('+')
|
|
456
|
+
i += 2
|
|
457
|
+
elif next_char == '?':
|
|
458
|
+
# BRE \? -> Python ?
|
|
459
|
+
result.append('?')
|
|
460
|
+
i += 2
|
|
461
|
+
elif next_char == '(':
|
|
462
|
+
# BRE \( -> Python (
|
|
463
|
+
result.append('(')
|
|
464
|
+
i += 2
|
|
465
|
+
elif next_char == ')':
|
|
466
|
+
# BRE \) -> Python )
|
|
467
|
+
result.append(')')
|
|
468
|
+
i += 2
|
|
469
|
+
elif next_char == '{':
|
|
470
|
+
# BRE \{ -> Python {
|
|
471
|
+
result.append('{')
|
|
472
|
+
i += 2
|
|
473
|
+
elif next_char == '}':
|
|
474
|
+
# BRE \} -> Python }
|
|
475
|
+
result.append('}')
|
|
476
|
+
i += 2
|
|
477
|
+
elif next_char == '<':
|
|
478
|
+
# BRE \< (word start) -> Python \b
|
|
479
|
+
result.append(r'\b')
|
|
480
|
+
i += 2
|
|
481
|
+
elif next_char == '>':
|
|
482
|
+
# BRE \> (word end) -> Python \b
|
|
483
|
+
result.append(r'\b')
|
|
484
|
+
i += 2
|
|
485
|
+
else:
|
|
486
|
+
# Other escapes pass through as-is
|
|
487
|
+
result.append(pattern[i:i + 2])
|
|
488
|
+
i += 2
|
|
489
|
+
elif pattern[i] == '|':
|
|
490
|
+
# BRE literal | -> Python \|
|
|
491
|
+
result.append(r'\|')
|
|
492
|
+
i += 1
|
|
493
|
+
elif pattern[i] == '+':
|
|
494
|
+
# BRE literal + -> Python \+
|
|
495
|
+
result.append(r'\+')
|
|
496
|
+
i += 1
|
|
497
|
+
elif pattern[i] == '?':
|
|
498
|
+
# BRE literal ? -> Python \?
|
|
499
|
+
result.append(r'\?')
|
|
500
|
+
i += 1
|
|
501
|
+
elif pattern[i] == '(':
|
|
502
|
+
# BRE literal ( -> Python \(
|
|
503
|
+
result.append(r'\(')
|
|
504
|
+
i += 1
|
|
505
|
+
elif pattern[i] == ')':
|
|
506
|
+
# BRE literal ) -> Python \)
|
|
507
|
+
result.append(r'\)')
|
|
508
|
+
i += 1
|
|
509
|
+
elif pattern[i] == '{':
|
|
510
|
+
# BRE literal { -> Python \{
|
|
511
|
+
result.append(r'\{')
|
|
512
|
+
i += 1
|
|
513
|
+
elif pattern[i] == '}':
|
|
514
|
+
# BRE literal } -> Python \}
|
|
515
|
+
result.append(r'\}')
|
|
516
|
+
i += 1
|
|
517
|
+
else:
|
|
518
|
+
result.append(pattern[i])
|
|
519
|
+
i += 1
|
|
520
|
+
|
|
521
|
+
return ''.join(result)
|
|
522
|
+
|
|
385
523
|
async def _get_files_recursive(self, ctx: CommandContext, path: str) -> list[str]:
|
|
386
524
|
"""Get all files in a directory recursively."""
|
|
387
525
|
files = []
|
just_bash/commands/od/od.py
CHANGED
|
@@ -13,6 +13,8 @@ class OdCommand:
|
|
|
13
13
|
format_type = "o" # octal (default)
|
|
14
14
|
address_format = "o" # octal addresses
|
|
15
15
|
suppress_address = False
|
|
16
|
+
skip_bytes = 0
|
|
17
|
+
read_count = -1 # -1 means read all
|
|
16
18
|
files: list[str] = []
|
|
17
19
|
|
|
18
20
|
i = 0
|
|
@@ -34,12 +36,97 @@ class OdCommand:
|
|
|
34
36
|
format_type = "d" # decimal
|
|
35
37
|
elif arg == "-An":
|
|
36
38
|
suppress_address = True
|
|
37
|
-
elif arg == "-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
elif arg == "-A":
|
|
40
|
+
# -A RADIX: address radix
|
|
41
|
+
if i + 1 < len(args):
|
|
42
|
+
i += 1
|
|
43
|
+
radix = args[i]
|
|
44
|
+
if radix == "n":
|
|
45
|
+
suppress_address = True
|
|
46
|
+
elif radix in ("d", "o", "x"):
|
|
47
|
+
address_format = radix
|
|
48
|
+
else:
|
|
49
|
+
return ExecResult(
|
|
50
|
+
stdout="",
|
|
51
|
+
stderr="od: option requires an argument -- 'A'\n",
|
|
52
|
+
exit_code=1,
|
|
53
|
+
)
|
|
54
|
+
elif arg.startswith("-A") and len(arg) == 3:
|
|
55
|
+
radix = arg[2]
|
|
56
|
+
if radix == "n":
|
|
57
|
+
suppress_address = True
|
|
58
|
+
elif radix in ("d", "o", "x"):
|
|
59
|
+
address_format = radix
|
|
60
|
+
elif arg == "-j":
|
|
61
|
+
# -j BYTES: skip bytes
|
|
62
|
+
if i + 1 < len(args):
|
|
63
|
+
i += 1
|
|
64
|
+
try:
|
|
65
|
+
skip_bytes = int(args[i])
|
|
66
|
+
except ValueError:
|
|
67
|
+
return ExecResult(
|
|
68
|
+
stdout="",
|
|
69
|
+
stderr=f"od: invalid argument '{args[i]}' for skip\n",
|
|
70
|
+
exit_code=1,
|
|
71
|
+
)
|
|
72
|
+
else:
|
|
73
|
+
return ExecResult(
|
|
74
|
+
stdout="",
|
|
75
|
+
stderr="od: option requires an argument -- 'j'\n",
|
|
76
|
+
exit_code=1,
|
|
77
|
+
)
|
|
78
|
+
elif arg.startswith("-j") and len(arg) > 2:
|
|
79
|
+
try:
|
|
80
|
+
skip_bytes = int(arg[2:])
|
|
81
|
+
except ValueError:
|
|
82
|
+
return ExecResult(
|
|
83
|
+
stdout="",
|
|
84
|
+
stderr=f"od: invalid argument '{arg[2:]}' for skip\n",
|
|
85
|
+
exit_code=1,
|
|
86
|
+
)
|
|
87
|
+
elif arg == "-N":
|
|
88
|
+
# -N BYTES: read count
|
|
89
|
+
if i + 1 < len(args):
|
|
90
|
+
i += 1
|
|
91
|
+
try:
|
|
92
|
+
read_count = int(args[i])
|
|
93
|
+
except ValueError:
|
|
94
|
+
return ExecResult(
|
|
95
|
+
stdout="",
|
|
96
|
+
stderr=f"od: invalid argument '{args[i]}' for count\n",
|
|
97
|
+
exit_code=1,
|
|
98
|
+
)
|
|
99
|
+
else:
|
|
100
|
+
return ExecResult(
|
|
101
|
+
stdout="",
|
|
102
|
+
stderr="od: option requires an argument -- 'N'\n",
|
|
103
|
+
exit_code=1,
|
|
104
|
+
)
|
|
105
|
+
elif arg.startswith("-N") and len(arg) > 2:
|
|
106
|
+
try:
|
|
107
|
+
read_count = int(arg[2:])
|
|
108
|
+
except ValueError:
|
|
109
|
+
return ExecResult(
|
|
110
|
+
stdout="",
|
|
111
|
+
stderr=f"od: invalid argument '{arg[2:]}' for count\n",
|
|
112
|
+
exit_code=1,
|
|
113
|
+
)
|
|
114
|
+
elif arg == "-t":
|
|
115
|
+
# -t TYPE: type specifier follows
|
|
116
|
+
if i + 1 < len(args):
|
|
117
|
+
i += 1
|
|
118
|
+
type_spec = args[i]
|
|
119
|
+
format_type = self._parse_type_spec(type_spec)
|
|
120
|
+
else:
|
|
121
|
+
return ExecResult(
|
|
122
|
+
stdout="",
|
|
123
|
+
stderr="od: option requires an argument -- 't'\n",
|
|
124
|
+
exit_code=1,
|
|
125
|
+
)
|
|
126
|
+
elif arg.startswith("-t"):
|
|
127
|
+
# -tTYPE: type specifier attached
|
|
128
|
+
type_spec = arg[2:]
|
|
129
|
+
format_type = self._parse_type_spec(type_spec)
|
|
43
130
|
elif arg == "--":
|
|
44
131
|
files.extend(args[i + 1:])
|
|
45
132
|
break
|
|
@@ -55,7 +142,12 @@ class OdCommand:
|
|
|
55
142
|
|
|
56
143
|
# Read from stdin if no files
|
|
57
144
|
if not files:
|
|
58
|
-
content = ctx.stdin.encode("
|
|
145
|
+
content = ctx.stdin.encode("latin-1", errors="replace")
|
|
146
|
+
# Apply skip and count
|
|
147
|
+
if skip_bytes > 0:
|
|
148
|
+
content = content[skip_bytes:]
|
|
149
|
+
if read_count >= 0:
|
|
150
|
+
content = content[:read_count]
|
|
59
151
|
result = self._dump(content, format_type, address_format, suppress_address)
|
|
60
152
|
return ExecResult(stdout=result, stderr="", exit_code=0)
|
|
61
153
|
|
|
@@ -66,11 +158,17 @@ class OdCommand:
|
|
|
66
158
|
for file in files:
|
|
67
159
|
try:
|
|
68
160
|
if file == "-":
|
|
69
|
-
content = ctx.stdin.encode("
|
|
161
|
+
content = ctx.stdin.encode("latin-1", errors="replace")
|
|
70
162
|
else:
|
|
71
163
|
path = ctx.fs.resolve_path(ctx.cwd, file)
|
|
72
164
|
content = await ctx.fs.read_file_bytes(path)
|
|
73
165
|
|
|
166
|
+
# Apply skip and count
|
|
167
|
+
if skip_bytes > 0:
|
|
168
|
+
content = content[skip_bytes:]
|
|
169
|
+
if read_count >= 0:
|
|
170
|
+
content = content[:read_count]
|
|
171
|
+
|
|
74
172
|
result = self._dump(content, format_type, address_format, suppress_address)
|
|
75
173
|
stdout_parts.append(result)
|
|
76
174
|
|
|
@@ -80,6 +178,22 @@ class OdCommand:
|
|
|
80
178
|
|
|
81
179
|
return ExecResult(stdout="".join(stdout_parts), stderr=stderr, exit_code=exit_code)
|
|
82
180
|
|
|
181
|
+
def _parse_type_spec(self, spec: str) -> str:
|
|
182
|
+
"""Parse a -t type specifier."""
|
|
183
|
+
if not spec:
|
|
184
|
+
return "o"
|
|
185
|
+
first_char = spec[0].lower()
|
|
186
|
+
if first_char == "c":
|
|
187
|
+
return "c"
|
|
188
|
+
elif first_char == "x":
|
|
189
|
+
return "x"
|
|
190
|
+
elif first_char == "o":
|
|
191
|
+
return "o"
|
|
192
|
+
elif first_char == "d" or first_char == "u":
|
|
193
|
+
return "d"
|
|
194
|
+
else:
|
|
195
|
+
return "o"
|
|
196
|
+
|
|
83
197
|
def _dump(
|
|
84
198
|
self, data: bytes, format_type: str, address_format: str, suppress_address: bool
|
|
85
199
|
) -> str:
|
|
@@ -101,44 +215,44 @@ class OdCommand:
|
|
|
101
215
|
else:
|
|
102
216
|
parts.append(f"{offset:07o}")
|
|
103
217
|
|
|
104
|
-
# Add data
|
|
218
|
+
# Add data with proper 4-char field formatting
|
|
105
219
|
if format_type == "c":
|
|
106
|
-
# Character format
|
|
220
|
+
# Character format: each char in 4-char field
|
|
107
221
|
chars = []
|
|
108
222
|
for byte in line_data:
|
|
109
223
|
if byte == 0:
|
|
110
|
-
chars.append("\\0")
|
|
224
|
+
chars.append(" \\0")
|
|
111
225
|
elif byte == 7:
|
|
112
|
-
chars.append("\\a")
|
|
226
|
+
chars.append(" \\a")
|
|
113
227
|
elif byte == 8:
|
|
114
|
-
chars.append("\\b")
|
|
228
|
+
chars.append(" \\b")
|
|
115
229
|
elif byte == 9:
|
|
116
|
-
chars.append("\\t")
|
|
230
|
+
chars.append(" \\t")
|
|
117
231
|
elif byte == 10:
|
|
118
|
-
chars.append("\\n")
|
|
232
|
+
chars.append(" \\n")
|
|
119
233
|
elif byte == 11:
|
|
120
|
-
chars.append("\\v")
|
|
234
|
+
chars.append(" \\v")
|
|
121
235
|
elif byte == 12:
|
|
122
|
-
chars.append("\\f")
|
|
236
|
+
chars.append(" \\f")
|
|
123
237
|
elif byte == 13:
|
|
124
|
-
chars.append("\\r")
|
|
238
|
+
chars.append(" \\r")
|
|
125
239
|
elif 32 <= byte <= 126:
|
|
126
|
-
chars.append(f"
|
|
240
|
+
chars.append(f" {chr(byte)}")
|
|
127
241
|
else:
|
|
128
|
-
chars.append(f"{byte:03o}")
|
|
129
|
-
parts.append("
|
|
242
|
+
chars.append(f" {byte:03o}")
|
|
243
|
+
parts.append("".join(chars))
|
|
130
244
|
elif format_type == "x":
|
|
131
|
-
# Hexadecimal format
|
|
132
|
-
hex_vals = [f"{byte:02x}" for byte in line_data]
|
|
133
|
-
parts.append("
|
|
245
|
+
# Hexadecimal format: 4-char fields
|
|
246
|
+
hex_vals = [f" {byte:02x}" for byte in line_data]
|
|
247
|
+
parts.append("".join(hex_vals))
|
|
134
248
|
elif format_type == "d":
|
|
135
|
-
# Decimal format
|
|
136
|
-
dec_vals = [f"{byte:3d}" for byte in line_data]
|
|
137
|
-
parts.append("
|
|
249
|
+
# Decimal format: 4-char fields
|
|
250
|
+
dec_vals = [f" {byte:3d}" for byte in line_data]
|
|
251
|
+
parts.append("".join(dec_vals))
|
|
138
252
|
else:
|
|
139
|
-
# Octal format (default)
|
|
140
|
-
oct_vals = [f"{byte:03o}" for byte in line_data]
|
|
141
|
-
parts.append("
|
|
253
|
+
# Octal format (default): 4-char fields
|
|
254
|
+
oct_vals = [f" {byte:03o}" for byte in line_data]
|
|
255
|
+
parts.append("".join(oct_vals))
|
|
142
256
|
|
|
143
257
|
result_lines.append(" ".join(parts))
|
|
144
258
|
offset += bytes_per_line
|