py-posix-shell 0.1.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.
- py_posix_shell/__init__.py +4 -0
- py_posix_shell/__main__.py +6 -0
- py_posix_shell/builtins.py +418 -0
- py_posix_shell/cli.py +60 -0
- py_posix_shell/errors.py +26 -0
- py_posix_shell/expansion.py +346 -0
- py_posix_shell/lexer.py +293 -0
- py_posix_shell/parser.py +184 -0
- py_posix_shell/shell.py +467 -0
- py_posix_shell-0.1.0.dist-info/METADATA +93 -0
- py_posix_shell-0.1.0.dist-info/RECORD +14 -0
- py_posix_shell-0.1.0.dist-info/WHEEL +4 -0
- py_posix_shell-0.1.0.dist-info/entry_points.txt +3 -0
- py_posix_shell-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,418 @@
|
|
|
1
|
+
"""Builtin utilities."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
from typing import Callable, TextIO
|
|
8
|
+
|
|
9
|
+
from .errors import ShellExit
|
|
10
|
+
from .lexer import is_name
|
|
11
|
+
|
|
12
|
+
Builtin = Callable[[object, list[str], TextIO, TextIO, TextIO], int]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
SPECIAL_BUILTINS = {
|
|
16
|
+
":",
|
|
17
|
+
".",
|
|
18
|
+
"break",
|
|
19
|
+
"continue",
|
|
20
|
+
"eval",
|
|
21
|
+
"exec",
|
|
22
|
+
"exit",
|
|
23
|
+
"export",
|
|
24
|
+
"readonly",
|
|
25
|
+
"return",
|
|
26
|
+
"set",
|
|
27
|
+
"shift",
|
|
28
|
+
"times",
|
|
29
|
+
"trap",
|
|
30
|
+
"unset",
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def builtin_colon(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
35
|
+
return 0
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def builtin_true(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
39
|
+
return 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def builtin_false(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
43
|
+
return 1
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def builtin_echo(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
47
|
+
args = argv[1:]
|
|
48
|
+
newline = True
|
|
49
|
+
if args and args[0] == "-n":
|
|
50
|
+
newline = False
|
|
51
|
+
args = args[1:]
|
|
52
|
+
stdout.write(" ".join(args))
|
|
53
|
+
if newline:
|
|
54
|
+
stdout.write("\n")
|
|
55
|
+
return 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def builtin_pwd(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
59
|
+
stdout.write(os.getcwd() + "\n")
|
|
60
|
+
return 0
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def builtin_cd(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
64
|
+
if len(argv) > 2:
|
|
65
|
+
stderr.write("cd: too many arguments\n")
|
|
66
|
+
return 2
|
|
67
|
+
target = argv[1] if len(argv) == 2 else shell.get_parameter("HOME")
|
|
68
|
+
if not target:
|
|
69
|
+
stderr.write("cd: HOME not set\n")
|
|
70
|
+
return 1
|
|
71
|
+
print_new_path = False
|
|
72
|
+
if target == "-":
|
|
73
|
+
target = shell.get_parameter("OLDPWD")
|
|
74
|
+
if not target:
|
|
75
|
+
stderr.write("cd: OLDPWD not set\n")
|
|
76
|
+
return 1
|
|
77
|
+
print_new_path = True
|
|
78
|
+
oldpwd = os.getcwd()
|
|
79
|
+
try:
|
|
80
|
+
os.chdir(os.path.expanduser(target))
|
|
81
|
+
except OSError as exc:
|
|
82
|
+
stderr.write(f"cd: {target}: {exc.strerror or exc}\n")
|
|
83
|
+
return 1
|
|
84
|
+
newpwd = os.getcwd()
|
|
85
|
+
shell.set_parameter("OLDPWD", oldpwd, export=True)
|
|
86
|
+
shell.set_parameter("PWD", newpwd, export=True)
|
|
87
|
+
if print_new_path:
|
|
88
|
+
stdout.write(newpwd + "\n")
|
|
89
|
+
return 0
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def builtin_exit(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
93
|
+
if len(argv) > 2:
|
|
94
|
+
stderr.write("exit: too many arguments\n")
|
|
95
|
+
return 2
|
|
96
|
+
if len(argv) == 1:
|
|
97
|
+
raise ShellExit(shell.last_status)
|
|
98
|
+
try:
|
|
99
|
+
status = int(argv[1], 10) & 0xFF
|
|
100
|
+
except ValueError:
|
|
101
|
+
stderr.write(f"exit: {argv[1]}: numeric argument required\n")
|
|
102
|
+
raise ShellExit(2)
|
|
103
|
+
raise ShellExit(status)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def builtin_export(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
107
|
+
if len(argv) == 1 or argv[1:] == ["-p"]:
|
|
108
|
+
for name in sorted(shell.env):
|
|
109
|
+
stdout.write(f"export {name}={quote_for_display(shell.env[name])}\n")
|
|
110
|
+
return 0
|
|
111
|
+
|
|
112
|
+
status = 0
|
|
113
|
+
for arg in argv[1:]:
|
|
114
|
+
if arg == "-p":
|
|
115
|
+
continue
|
|
116
|
+
if "=" in arg:
|
|
117
|
+
name, value = arg.split("=", 1)
|
|
118
|
+
else:
|
|
119
|
+
name = arg
|
|
120
|
+
value = shell.get_parameter(name)
|
|
121
|
+
if not is_name(name):
|
|
122
|
+
stderr.write(f"export: {arg}: not a valid identifier\n")
|
|
123
|
+
status = 1
|
|
124
|
+
continue
|
|
125
|
+
shell.set_parameter(name, value, export=True)
|
|
126
|
+
return status
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def builtin_unset(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
130
|
+
status = 0
|
|
131
|
+
for name in argv[1:]:
|
|
132
|
+
if not is_name(name):
|
|
133
|
+
stderr.write(f"unset: {name}: not a valid identifier\n")
|
|
134
|
+
status = 1
|
|
135
|
+
continue
|
|
136
|
+
shell.unset_parameter(name)
|
|
137
|
+
return status
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def builtin_set(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
141
|
+
if len(argv) == 1:
|
|
142
|
+
merged = dict(shell.env)
|
|
143
|
+
merged.update(shell.vars)
|
|
144
|
+
for name in sorted(merged):
|
|
145
|
+
stdout.write(f"{name}={quote_for_display(merged[name])}\n")
|
|
146
|
+
return 0
|
|
147
|
+
if argv[1] == "--":
|
|
148
|
+
shell.positional = argv[2:]
|
|
149
|
+
return 0
|
|
150
|
+
stderr.write("set: only 'set' and 'set -- args...' are implemented\n")
|
|
151
|
+
return 2
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def builtin_shift(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
155
|
+
if len(argv) > 2:
|
|
156
|
+
stderr.write("shift: too many arguments\n")
|
|
157
|
+
return 2
|
|
158
|
+
try:
|
|
159
|
+
count = int(argv[1], 10) if len(argv) == 2 else 1
|
|
160
|
+
except ValueError:
|
|
161
|
+
stderr.write(f"shift: {argv[1]}: numeric argument required\n")
|
|
162
|
+
return 2
|
|
163
|
+
if count < 0 or count > len(shell.positional):
|
|
164
|
+
stderr.write("shift: shift count out of range\n")
|
|
165
|
+
return 1
|
|
166
|
+
del shell.positional[:count]
|
|
167
|
+
return 0
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def builtin_printf(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
171
|
+
if len(argv) == 1:
|
|
172
|
+
return 0
|
|
173
|
+
fmt = decode_escapes(argv[1])
|
|
174
|
+
args = argv[2:] or [""]
|
|
175
|
+
index = 0
|
|
176
|
+
while index < len(args):
|
|
177
|
+
wrote_conversion = False
|
|
178
|
+
i = 0
|
|
179
|
+
while i < len(fmt):
|
|
180
|
+
char = fmt[i]
|
|
181
|
+
if char != "%":
|
|
182
|
+
stdout.write(char)
|
|
183
|
+
i += 1
|
|
184
|
+
continue
|
|
185
|
+
if i + 1 < len(fmt) and fmt[i + 1] == "%":
|
|
186
|
+
stdout.write("%")
|
|
187
|
+
i += 2
|
|
188
|
+
continue
|
|
189
|
+
spec_start = i
|
|
190
|
+
i += 1
|
|
191
|
+
while i < len(fmt) and fmt[i] in "#0- +0123456789.":
|
|
192
|
+
i += 1
|
|
193
|
+
if i >= len(fmt):
|
|
194
|
+
stdout.write(fmt[spec_start:])
|
|
195
|
+
break
|
|
196
|
+
spec = fmt[i]
|
|
197
|
+
i += 1
|
|
198
|
+
arg = args[index] if index < len(args) else ""
|
|
199
|
+
index += 1
|
|
200
|
+
wrote_conversion = True
|
|
201
|
+
if spec == "s":
|
|
202
|
+
stdout.write(arg)
|
|
203
|
+
elif spec == "b":
|
|
204
|
+
stdout.write(decode_escapes(arg))
|
|
205
|
+
elif spec in "diu":
|
|
206
|
+
try:
|
|
207
|
+
stdout.write(str(int(arg, 0)))
|
|
208
|
+
except ValueError:
|
|
209
|
+
stdout.write("0")
|
|
210
|
+
elif spec in "xX":
|
|
211
|
+
try:
|
|
212
|
+
value = int(arg, 0)
|
|
213
|
+
except ValueError:
|
|
214
|
+
value = 0
|
|
215
|
+
stdout.write(format(value, spec))
|
|
216
|
+
else:
|
|
217
|
+
stdout.write("%" + spec)
|
|
218
|
+
if not wrote_conversion:
|
|
219
|
+
break
|
|
220
|
+
return 0
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
def builtin_read(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
224
|
+
args = argv[1:]
|
|
225
|
+
raw = False
|
|
226
|
+
if args and args[0] == "-r":
|
|
227
|
+
raw = True
|
|
228
|
+
args = args[1:]
|
|
229
|
+
if not args:
|
|
230
|
+
args = ["REPLY"]
|
|
231
|
+
line = stdin.readline()
|
|
232
|
+
if line == "":
|
|
233
|
+
return 1
|
|
234
|
+
line = line.rstrip("\n")
|
|
235
|
+
if not raw:
|
|
236
|
+
line = line.replace("\\\n", "")
|
|
237
|
+
values = line.split()
|
|
238
|
+
for index, name in enumerate(args):
|
|
239
|
+
if not is_name(name):
|
|
240
|
+
stderr.write(f"read: {name}: not a valid identifier\n")
|
|
241
|
+
return 2
|
|
242
|
+
if index == len(args) - 1:
|
|
243
|
+
value = " ".join(values[index:])
|
|
244
|
+
else:
|
|
245
|
+
value = values[index] if index < len(values) else ""
|
|
246
|
+
shell.set_parameter(name, value)
|
|
247
|
+
return 0
|
|
248
|
+
|
|
249
|
+
|
|
250
|
+
def builtin_type(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
251
|
+
if len(argv) == 1:
|
|
252
|
+
stderr.write("type: missing operand\n")
|
|
253
|
+
return 2
|
|
254
|
+
status = 0
|
|
255
|
+
for name in argv[1:]:
|
|
256
|
+
if shell.is_builtin(name):
|
|
257
|
+
stdout.write(f"{name} is a shell builtin\n")
|
|
258
|
+
continue
|
|
259
|
+
path = shell.which(name)
|
|
260
|
+
if path:
|
|
261
|
+
stdout.write(f"{name} is {path}\n")
|
|
262
|
+
else:
|
|
263
|
+
stderr.write(f"type: {name}: not found\n")
|
|
264
|
+
status = 1
|
|
265
|
+
return status
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def builtin_command(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
269
|
+
args = argv[1:]
|
|
270
|
+
if not args:
|
|
271
|
+
return 0
|
|
272
|
+
if args[0] in {"-v", "-V"}:
|
|
273
|
+
status = 0
|
|
274
|
+
for name in args[1:]:
|
|
275
|
+
if shell.is_builtin(name):
|
|
276
|
+
stdout.write(f"{name}\n" if args[0] == "-v" else f"{name} is a shell builtin\n")
|
|
277
|
+
continue
|
|
278
|
+
path = shell.which(name)
|
|
279
|
+
if path:
|
|
280
|
+
stdout.write(path + "\n")
|
|
281
|
+
else:
|
|
282
|
+
status = 1
|
|
283
|
+
return status
|
|
284
|
+
return shell.run_preexpanded(args, stdin=stdin, stdout=stdout, stderr=stderr)
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def builtin_env(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
288
|
+
args = argv[1:]
|
|
289
|
+
env = {} if args and args[0] == "-i" else dict(shell.env)
|
|
290
|
+
if args and args[0] == "-i":
|
|
291
|
+
args = args[1:]
|
|
292
|
+
while args and "=" in args[0]:
|
|
293
|
+
name, value = args[0].split("=", 1)
|
|
294
|
+
env[name] = value
|
|
295
|
+
args = args[1:]
|
|
296
|
+
if not args:
|
|
297
|
+
for name in sorted(env):
|
|
298
|
+
stdout.write(f"{name}={env[name]}\n")
|
|
299
|
+
return 0
|
|
300
|
+
return shell.run_external(args, env=env, stdin=stdin, stdout=stdout, stderr=stderr)
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
def builtin_test(shell, argv: list[str], stdin: TextIO, stdout: TextIO, stderr: TextIO) -> int:
|
|
304
|
+
args = argv[1:]
|
|
305
|
+
if argv[0] == "[":
|
|
306
|
+
if not args or args[-1] != "]":
|
|
307
|
+
stderr.write("[: missing closing ]\n")
|
|
308
|
+
return 2
|
|
309
|
+
args = args[:-1]
|
|
310
|
+
return 0 if eval_test(args) else 1
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
def eval_test(args: list[str]) -> bool:
|
|
314
|
+
if not args:
|
|
315
|
+
return False
|
|
316
|
+
if len(args) == 1:
|
|
317
|
+
return args[0] != ""
|
|
318
|
+
if len(args) == 2:
|
|
319
|
+
op, value = args
|
|
320
|
+
if op == "!":
|
|
321
|
+
return not eval_test([value])
|
|
322
|
+
if op == "-n":
|
|
323
|
+
return value != ""
|
|
324
|
+
if op == "-z":
|
|
325
|
+
return value == ""
|
|
326
|
+
if op == "-e":
|
|
327
|
+
return os.path.exists(value)
|
|
328
|
+
if op == "-f":
|
|
329
|
+
return os.path.isfile(value)
|
|
330
|
+
if op == "-d":
|
|
331
|
+
return os.path.isdir(value)
|
|
332
|
+
if len(args) == 3:
|
|
333
|
+
left, op, right = args
|
|
334
|
+
if op == "=":
|
|
335
|
+
return left == right
|
|
336
|
+
if op == "!=":
|
|
337
|
+
return left != right
|
|
338
|
+
if op in {"-eq", "-ne", "-gt", "-ge", "-lt", "-le"}:
|
|
339
|
+
try:
|
|
340
|
+
a = int(left, 10)
|
|
341
|
+
b = int(right, 10)
|
|
342
|
+
except ValueError:
|
|
343
|
+
return False
|
|
344
|
+
return {
|
|
345
|
+
"-eq": a == b,
|
|
346
|
+
"-ne": a != b,
|
|
347
|
+
"-gt": a > b,
|
|
348
|
+
"-ge": a >= b,
|
|
349
|
+
"-lt": a < b,
|
|
350
|
+
"-le": a <= b,
|
|
351
|
+
}[op]
|
|
352
|
+
return False
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def quote_for_display(value: str) -> str:
|
|
356
|
+
return "'" + value.replace("'", "'\\''") + "'"
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def decode_escapes(value: str) -> str:
|
|
360
|
+
result: list[str] = []
|
|
361
|
+
i = 0
|
|
362
|
+
while i < len(value):
|
|
363
|
+
char = value[i]
|
|
364
|
+
if char != "\\":
|
|
365
|
+
result.append(char)
|
|
366
|
+
i += 1
|
|
367
|
+
continue
|
|
368
|
+
if i + 1 >= len(value):
|
|
369
|
+
result.append("\\")
|
|
370
|
+
break
|
|
371
|
+
nxt = value[i + 1]
|
|
372
|
+
mapping = {
|
|
373
|
+
"a": "\a",
|
|
374
|
+
"b": "\b",
|
|
375
|
+
"f": "\f",
|
|
376
|
+
"n": "\n",
|
|
377
|
+
"r": "\r",
|
|
378
|
+
"t": "\t",
|
|
379
|
+
"v": "\v",
|
|
380
|
+
"\\": "\\",
|
|
381
|
+
}
|
|
382
|
+
if nxt in mapping:
|
|
383
|
+
result.append(mapping[nxt])
|
|
384
|
+
i += 2
|
|
385
|
+
continue
|
|
386
|
+
if nxt in "01234567":
|
|
387
|
+
j = i + 1
|
|
388
|
+
while j < len(value) and j < i + 4 and value[j] in "01234567":
|
|
389
|
+
j += 1
|
|
390
|
+
result.append(chr(int(value[i + 1 : j], 8)))
|
|
391
|
+
i = j
|
|
392
|
+
continue
|
|
393
|
+
result.append(nxt)
|
|
394
|
+
i += 2
|
|
395
|
+
return "".join(result)
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
BUILTINS: dict[str, Builtin] = {
|
|
399
|
+
":": builtin_colon,
|
|
400
|
+
"true": builtin_true,
|
|
401
|
+
"false": builtin_false,
|
|
402
|
+
"echo": builtin_echo,
|
|
403
|
+
"pwd": builtin_pwd,
|
|
404
|
+
"cd": builtin_cd,
|
|
405
|
+
"exit": builtin_exit,
|
|
406
|
+
"export": builtin_export,
|
|
407
|
+
"unset": builtin_unset,
|
|
408
|
+
"set": builtin_set,
|
|
409
|
+
"shift": builtin_shift,
|
|
410
|
+
"printf": builtin_printf,
|
|
411
|
+
"read": builtin_read,
|
|
412
|
+
"type": builtin_type,
|
|
413
|
+
"command": builtin_command,
|
|
414
|
+
"env": builtin_env,
|
|
415
|
+
"test": builtin_test,
|
|
416
|
+
"[": builtin_test,
|
|
417
|
+
}
|
|
418
|
+
|
py_posix_shell/cli.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Command-line interface."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from . import __version__
|
|
9
|
+
from .errors import ShellExit
|
|
10
|
+
from .shell import Shell
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
14
|
+
parser = argparse.ArgumentParser(
|
|
15
|
+
prog="pysh",
|
|
16
|
+
description="Run a small cross-platform POSIX-style shell.",
|
|
17
|
+
)
|
|
18
|
+
parser.add_argument("-c", dest="command", help="read commands from COMMAND")
|
|
19
|
+
parser.add_argument("-i", dest="interactive", action="store_true", help="force interactive mode")
|
|
20
|
+
parser.add_argument("--version", action="store_true", help="print version and exit")
|
|
21
|
+
parser.add_argument("script", nargs="?", help="shell script to execute")
|
|
22
|
+
parser.add_argument("args", nargs=argparse.REMAINDER, help="arguments for the script")
|
|
23
|
+
return parser
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def main(argv: list[str] | None = None) -> int:
|
|
27
|
+
args = build_parser().parse_args(argv)
|
|
28
|
+
if args.version:
|
|
29
|
+
print(f"py-posix-shell {__version__}")
|
|
30
|
+
return 0
|
|
31
|
+
|
|
32
|
+
if args.command is not None:
|
|
33
|
+
shell = Shell(argv0="pysh", positional=args.args)
|
|
34
|
+
try:
|
|
35
|
+
return shell.execute(args.command)
|
|
36
|
+
except ShellExit as exc:
|
|
37
|
+
return exc.status
|
|
38
|
+
|
|
39
|
+
if args.script:
|
|
40
|
+
try:
|
|
41
|
+
with open(args.script, "r", encoding="utf-8") as file:
|
|
42
|
+
source = file.read()
|
|
43
|
+
except OSError as exc:
|
|
44
|
+
print(f"pysh: {args.script}: {exc}", file=sys.stderr)
|
|
45
|
+
return 1
|
|
46
|
+
shell = Shell(argv0=args.script, positional=args.args)
|
|
47
|
+
try:
|
|
48
|
+
return shell.execute(source)
|
|
49
|
+
except ShellExit as exc:
|
|
50
|
+
return exc.status
|
|
51
|
+
|
|
52
|
+
interactive = args.interactive or sys.stdin.isatty()
|
|
53
|
+
shell = Shell(argv0="pysh", interactive=interactive)
|
|
54
|
+
if interactive:
|
|
55
|
+
return shell.repl()
|
|
56
|
+
try:
|
|
57
|
+
return shell.execute(sys.stdin.read())
|
|
58
|
+
except ShellExit as exc:
|
|
59
|
+
return exc.status
|
|
60
|
+
|
py_posix_shell/errors.py
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Shared exception types for py-posix-shell."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ShellError(Exception):
|
|
5
|
+
"""Base class for shell errors."""
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LexerError(ShellError):
|
|
9
|
+
"""Raised when shell source cannot be tokenized."""
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ParseError(ShellError):
|
|
13
|
+
"""Raised when shell tokens do not form a valid command list."""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class ExpansionError(ShellError):
|
|
17
|
+
"""Raised when shell expansion fails."""
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ShellExit(BaseException):
|
|
21
|
+
"""Internal control-flow exception for the exit builtin."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, status: int = 0) -> None:
|
|
24
|
+
self.status = status
|
|
25
|
+
super().__init__(status)
|
|
26
|
+
|