just-bash 0.1.5__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/__init__.py +55 -0
- just_bash/ast/__init__.py +213 -0
- just_bash/ast/factory.py +320 -0
- just_bash/ast/types.py +953 -0
- just_bash/bash.py +220 -0
- just_bash/commands/__init__.py +23 -0
- just_bash/commands/argv/__init__.py +5 -0
- just_bash/commands/argv/argv.py +21 -0
- just_bash/commands/awk/__init__.py +5 -0
- just_bash/commands/awk/awk.py +1168 -0
- just_bash/commands/base64/__init__.py +5 -0
- just_bash/commands/base64/base64.py +138 -0
- just_bash/commands/basename/__init__.py +5 -0
- just_bash/commands/basename/basename.py +72 -0
- just_bash/commands/bash/__init__.py +5 -0
- just_bash/commands/bash/bash.py +188 -0
- just_bash/commands/cat/__init__.py +5 -0
- just_bash/commands/cat/cat.py +173 -0
- just_bash/commands/checksum/__init__.py +5 -0
- just_bash/commands/checksum/checksum.py +179 -0
- just_bash/commands/chmod/__init__.py +5 -0
- just_bash/commands/chmod/chmod.py +216 -0
- just_bash/commands/column/__init__.py +5 -0
- just_bash/commands/column/column.py +180 -0
- just_bash/commands/comm/__init__.py +5 -0
- just_bash/commands/comm/comm.py +150 -0
- just_bash/commands/compression/__init__.py +5 -0
- just_bash/commands/compression/compression.py +298 -0
- just_bash/commands/cp/__init__.py +5 -0
- just_bash/commands/cp/cp.py +149 -0
- just_bash/commands/curl/__init__.py +5 -0
- just_bash/commands/curl/curl.py +801 -0
- just_bash/commands/cut/__init__.py +5 -0
- just_bash/commands/cut/cut.py +327 -0
- just_bash/commands/date/__init__.py +5 -0
- just_bash/commands/date/date.py +258 -0
- just_bash/commands/diff/__init__.py +5 -0
- just_bash/commands/diff/diff.py +118 -0
- just_bash/commands/dirname/__init__.py +5 -0
- just_bash/commands/dirname/dirname.py +56 -0
- just_bash/commands/du/__init__.py +5 -0
- just_bash/commands/du/du.py +150 -0
- just_bash/commands/echo/__init__.py +5 -0
- just_bash/commands/echo/echo.py +125 -0
- just_bash/commands/env/__init__.py +5 -0
- just_bash/commands/env/env.py +163 -0
- just_bash/commands/expand/__init__.py +5 -0
- just_bash/commands/expand/expand.py +299 -0
- just_bash/commands/expr/__init__.py +5 -0
- just_bash/commands/expr/expr.py +273 -0
- just_bash/commands/file/__init__.py +5 -0
- just_bash/commands/file/file.py +274 -0
- just_bash/commands/find/__init__.py +5 -0
- just_bash/commands/find/find.py +623 -0
- just_bash/commands/fold/__init__.py +5 -0
- just_bash/commands/fold/fold.py +160 -0
- just_bash/commands/grep/__init__.py +5 -0
- just_bash/commands/grep/grep.py +418 -0
- just_bash/commands/head/__init__.py +5 -0
- just_bash/commands/head/head.py +167 -0
- just_bash/commands/help/__init__.py +5 -0
- just_bash/commands/help/help.py +67 -0
- just_bash/commands/hostname/__init__.py +5 -0
- just_bash/commands/hostname/hostname.py +21 -0
- just_bash/commands/html_to_markdown/__init__.py +5 -0
- just_bash/commands/html_to_markdown/html_to_markdown.py +191 -0
- just_bash/commands/join/__init__.py +5 -0
- just_bash/commands/join/join.py +252 -0
- just_bash/commands/jq/__init__.py +5 -0
- just_bash/commands/jq/jq.py +280 -0
- just_bash/commands/ln/__init__.py +5 -0
- just_bash/commands/ln/ln.py +127 -0
- just_bash/commands/ls/__init__.py +5 -0
- just_bash/commands/ls/ls.py +280 -0
- just_bash/commands/mkdir/__init__.py +5 -0
- just_bash/commands/mkdir/mkdir.py +92 -0
- just_bash/commands/mv/__init__.py +5 -0
- just_bash/commands/mv/mv.py +142 -0
- just_bash/commands/nl/__init__.py +5 -0
- just_bash/commands/nl/nl.py +180 -0
- just_bash/commands/od/__init__.py +5 -0
- just_bash/commands/od/od.py +157 -0
- just_bash/commands/paste/__init__.py +5 -0
- just_bash/commands/paste/paste.py +100 -0
- just_bash/commands/printf/__init__.py +5 -0
- just_bash/commands/printf/printf.py +157 -0
- just_bash/commands/pwd/__init__.py +5 -0
- just_bash/commands/pwd/pwd.py +23 -0
- just_bash/commands/read/__init__.py +5 -0
- just_bash/commands/read/read.py +185 -0
- just_bash/commands/readlink/__init__.py +5 -0
- just_bash/commands/readlink/readlink.py +86 -0
- just_bash/commands/registry.py +844 -0
- just_bash/commands/rev/__init__.py +5 -0
- just_bash/commands/rev/rev.py +74 -0
- just_bash/commands/rg/__init__.py +5 -0
- just_bash/commands/rg/rg.py +1048 -0
- just_bash/commands/rm/__init__.py +5 -0
- just_bash/commands/rm/rm.py +106 -0
- just_bash/commands/search_engine/__init__.py +13 -0
- just_bash/commands/search_engine/matcher.py +170 -0
- just_bash/commands/search_engine/regex.py +159 -0
- just_bash/commands/sed/__init__.py +5 -0
- just_bash/commands/sed/sed.py +863 -0
- just_bash/commands/seq/__init__.py +5 -0
- just_bash/commands/seq/seq.py +190 -0
- just_bash/commands/shell/__init__.py +5 -0
- just_bash/commands/shell/shell.py +206 -0
- just_bash/commands/sleep/__init__.py +5 -0
- just_bash/commands/sleep/sleep.py +62 -0
- just_bash/commands/sort/__init__.py +5 -0
- just_bash/commands/sort/sort.py +411 -0
- just_bash/commands/split/__init__.py +5 -0
- just_bash/commands/split/split.py +237 -0
- just_bash/commands/sqlite3/__init__.py +5 -0
- just_bash/commands/sqlite3/sqlite3_cmd.py +505 -0
- just_bash/commands/stat/__init__.py +5 -0
- just_bash/commands/stat/stat.py +150 -0
- just_bash/commands/strings/__init__.py +5 -0
- just_bash/commands/strings/strings.py +150 -0
- just_bash/commands/tac/__init__.py +5 -0
- just_bash/commands/tac/tac.py +158 -0
- just_bash/commands/tail/__init__.py +5 -0
- just_bash/commands/tail/tail.py +180 -0
- just_bash/commands/tar/__init__.py +5 -0
- just_bash/commands/tar/tar.py +1067 -0
- just_bash/commands/tee/__init__.py +5 -0
- just_bash/commands/tee/tee.py +63 -0
- just_bash/commands/timeout/__init__.py +5 -0
- just_bash/commands/timeout/timeout.py +188 -0
- just_bash/commands/touch/__init__.py +5 -0
- just_bash/commands/touch/touch.py +91 -0
- just_bash/commands/tr/__init__.py +5 -0
- just_bash/commands/tr/tr.py +297 -0
- just_bash/commands/tree/__init__.py +5 -0
- just_bash/commands/tree/tree.py +139 -0
- just_bash/commands/true/__init__.py +5 -0
- just_bash/commands/true/true.py +32 -0
- just_bash/commands/uniq/__init__.py +5 -0
- just_bash/commands/uniq/uniq.py +323 -0
- just_bash/commands/wc/__init__.py +5 -0
- just_bash/commands/wc/wc.py +169 -0
- just_bash/commands/which/__init__.py +5 -0
- just_bash/commands/which/which.py +52 -0
- just_bash/commands/xan/__init__.py +5 -0
- just_bash/commands/xan/xan.py +1663 -0
- just_bash/commands/xargs/__init__.py +5 -0
- just_bash/commands/xargs/xargs.py +136 -0
- just_bash/commands/yq/__init__.py +5 -0
- just_bash/commands/yq/yq.py +848 -0
- just_bash/fs/__init__.py +29 -0
- just_bash/fs/in_memory_fs.py +621 -0
- just_bash/fs/mountable_fs.py +504 -0
- just_bash/fs/overlay_fs.py +894 -0
- just_bash/fs/read_write_fs.py +455 -0
- just_bash/interpreter/__init__.py +37 -0
- just_bash/interpreter/builtins/__init__.py +92 -0
- just_bash/interpreter/builtins/alias.py +154 -0
- just_bash/interpreter/builtins/cd.py +76 -0
- just_bash/interpreter/builtins/control.py +127 -0
- just_bash/interpreter/builtins/declare.py +336 -0
- just_bash/interpreter/builtins/export.py +56 -0
- just_bash/interpreter/builtins/let.py +44 -0
- just_bash/interpreter/builtins/local.py +57 -0
- just_bash/interpreter/builtins/mapfile.py +152 -0
- just_bash/interpreter/builtins/misc.py +378 -0
- just_bash/interpreter/builtins/readonly.py +80 -0
- just_bash/interpreter/builtins/set.py +234 -0
- just_bash/interpreter/builtins/shopt.py +201 -0
- just_bash/interpreter/builtins/source.py +136 -0
- just_bash/interpreter/builtins/test.py +290 -0
- just_bash/interpreter/builtins/unset.py +53 -0
- just_bash/interpreter/conditionals.py +387 -0
- just_bash/interpreter/control_flow.py +381 -0
- just_bash/interpreter/errors.py +116 -0
- just_bash/interpreter/expansion.py +1156 -0
- just_bash/interpreter/interpreter.py +813 -0
- just_bash/interpreter/types.py +134 -0
- just_bash/network/__init__.py +1 -0
- just_bash/parser/__init__.py +39 -0
- just_bash/parser/lexer.py +948 -0
- just_bash/parser/parser.py +2162 -0
- just_bash/py.typed +0 -0
- just_bash/query_engine/__init__.py +83 -0
- just_bash/query_engine/builtins/__init__.py +1283 -0
- just_bash/query_engine/evaluator.py +578 -0
- just_bash/query_engine/parser.py +525 -0
- just_bash/query_engine/tokenizer.py +329 -0
- just_bash/query_engine/types.py +373 -0
- just_bash/types.py +180 -0
- just_bash-0.1.5.dist-info/METADATA +410 -0
- just_bash-0.1.5.dist-info/RECORD +193 -0
- just_bash-0.1.5.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""Diff command implementation."""
|
|
2
|
+
|
|
3
|
+
import difflib
|
|
4
|
+
from ...types import CommandContext, ExecResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DiffCommand:
|
|
8
|
+
"""The diff command."""
|
|
9
|
+
|
|
10
|
+
name = "diff"
|
|
11
|
+
|
|
12
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
13
|
+
"""Execute the diff command."""
|
|
14
|
+
brief = False
|
|
15
|
+
report_identical = False
|
|
16
|
+
ignore_case = False
|
|
17
|
+
files: list[str] = []
|
|
18
|
+
|
|
19
|
+
i = 0
|
|
20
|
+
while i < len(args):
|
|
21
|
+
arg = args[i]
|
|
22
|
+
if arg in ("-q", "--brief"):
|
|
23
|
+
brief = True
|
|
24
|
+
elif arg in ("-s", "--report-identical-files"):
|
|
25
|
+
report_identical = True
|
|
26
|
+
elif arg in ("-i", "--ignore-case"):
|
|
27
|
+
ignore_case = True
|
|
28
|
+
elif arg == "--help":
|
|
29
|
+
return ExecResult(
|
|
30
|
+
stdout="Usage: diff [OPTION]... FILES\n",
|
|
31
|
+
stderr="",
|
|
32
|
+
exit_code=0,
|
|
33
|
+
)
|
|
34
|
+
elif arg == "--":
|
|
35
|
+
files.extend(args[i + 1:])
|
|
36
|
+
break
|
|
37
|
+
elif arg.startswith("-") and len(arg) > 1 and arg != "-":
|
|
38
|
+
return ExecResult(
|
|
39
|
+
stdout="",
|
|
40
|
+
stderr=f"diff: invalid option -- '{arg[1]}'\n",
|
|
41
|
+
exit_code=1,
|
|
42
|
+
)
|
|
43
|
+
else:
|
|
44
|
+
files.append(arg)
|
|
45
|
+
i += 1
|
|
46
|
+
|
|
47
|
+
if len(files) < 2:
|
|
48
|
+
return ExecResult(
|
|
49
|
+
stdout="",
|
|
50
|
+
stderr="diff: missing operand\n",
|
|
51
|
+
exit_code=2,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
file1, file2 = files[0], files[1]
|
|
55
|
+
|
|
56
|
+
# Read files
|
|
57
|
+
try:
|
|
58
|
+
if file1 == "-":
|
|
59
|
+
content1 = ctx.stdin
|
|
60
|
+
else:
|
|
61
|
+
path1 = ctx.fs.resolve_path(ctx.cwd, file1)
|
|
62
|
+
content1 = await ctx.fs.read_file(path1)
|
|
63
|
+
except FileNotFoundError:
|
|
64
|
+
return ExecResult(
|
|
65
|
+
stdout="",
|
|
66
|
+
stderr=f"diff: {file1}: No such file or directory\n",
|
|
67
|
+
exit_code=2,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
if file2 == "-":
|
|
72
|
+
content2 = ctx.stdin
|
|
73
|
+
else:
|
|
74
|
+
path2 = ctx.fs.resolve_path(ctx.cwd, file2)
|
|
75
|
+
content2 = await ctx.fs.read_file(path2)
|
|
76
|
+
except FileNotFoundError:
|
|
77
|
+
return ExecResult(
|
|
78
|
+
stdout="",
|
|
79
|
+
stderr=f"diff: {file2}: No such file or directory\n",
|
|
80
|
+
exit_code=2,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Compare
|
|
84
|
+
if ignore_case:
|
|
85
|
+
compare1 = content1.lower()
|
|
86
|
+
compare2 = content2.lower()
|
|
87
|
+
else:
|
|
88
|
+
compare1 = content1
|
|
89
|
+
compare2 = content2
|
|
90
|
+
|
|
91
|
+
if compare1 == compare2:
|
|
92
|
+
if report_identical:
|
|
93
|
+
return ExecResult(
|
|
94
|
+
stdout=f"Files {file1} and {file2} are identical\n",
|
|
95
|
+
stderr="",
|
|
96
|
+
exit_code=0,
|
|
97
|
+
)
|
|
98
|
+
return ExecResult(stdout="", stderr="", exit_code=0)
|
|
99
|
+
|
|
100
|
+
# Files differ
|
|
101
|
+
if brief:
|
|
102
|
+
return ExecResult(
|
|
103
|
+
stdout=f"Files {file1} and {file2} differ\n",
|
|
104
|
+
stderr="",
|
|
105
|
+
exit_code=1,
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
# Generate unified diff
|
|
109
|
+
lines1 = content1.splitlines(keepends=True)
|
|
110
|
+
lines2 = content2.splitlines(keepends=True)
|
|
111
|
+
|
|
112
|
+
diff = difflib.unified_diff(
|
|
113
|
+
lines1, lines2,
|
|
114
|
+
fromfile=file1, tofile=file2,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
output = "".join(diff)
|
|
118
|
+
return ExecResult(stdout=output, stderr="", exit_code=1)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"""Dirname command implementation."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
from ...types import CommandContext, ExecResult
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DirnameCommand:
|
|
8
|
+
"""The dirname command."""
|
|
9
|
+
|
|
10
|
+
name = "dirname"
|
|
11
|
+
|
|
12
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
13
|
+
"""Execute the dirname command."""
|
|
14
|
+
paths: list[str] = []
|
|
15
|
+
|
|
16
|
+
i = 0
|
|
17
|
+
while i < len(args):
|
|
18
|
+
arg = args[i]
|
|
19
|
+
if arg == "--help":
|
|
20
|
+
return ExecResult(
|
|
21
|
+
stdout="Usage: dirname [OPTION] NAME...\n",
|
|
22
|
+
stderr="",
|
|
23
|
+
exit_code=0,
|
|
24
|
+
)
|
|
25
|
+
elif arg == "--":
|
|
26
|
+
paths.extend(args[i + 1:])
|
|
27
|
+
break
|
|
28
|
+
elif arg.startswith("-") and len(arg) > 1:
|
|
29
|
+
return ExecResult(
|
|
30
|
+
stdout="",
|
|
31
|
+
stderr=f"dirname: invalid option -- '{arg[1]}'\n",
|
|
32
|
+
exit_code=1,
|
|
33
|
+
)
|
|
34
|
+
else:
|
|
35
|
+
paths.append(arg)
|
|
36
|
+
i += 1
|
|
37
|
+
|
|
38
|
+
if not paths:
|
|
39
|
+
return ExecResult(
|
|
40
|
+
stdout="",
|
|
41
|
+
stderr="dirname: missing operand\n",
|
|
42
|
+
exit_code=1,
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
results = []
|
|
46
|
+
for path in paths:
|
|
47
|
+
dirname = os.path.dirname(path.rstrip("/"))
|
|
48
|
+
if not dirname:
|
|
49
|
+
dirname = "."
|
|
50
|
+
results.append(dirname)
|
|
51
|
+
|
|
52
|
+
return ExecResult(
|
|
53
|
+
stdout="\n".join(results) + "\n",
|
|
54
|
+
stderr="",
|
|
55
|
+
exit_code=0,
|
|
56
|
+
)
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
"""Du command implementation - disk usage."""
|
|
2
|
+
|
|
3
|
+
from ...types import CommandContext, ExecResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class DuCommand:
|
|
7
|
+
"""The du command - disk usage."""
|
|
8
|
+
|
|
9
|
+
name = "du"
|
|
10
|
+
|
|
11
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
12
|
+
"""Execute the du command."""
|
|
13
|
+
show_all = False
|
|
14
|
+
summary_only = False
|
|
15
|
+
human_readable = False
|
|
16
|
+
show_total = False
|
|
17
|
+
max_depth = None
|
|
18
|
+
paths: list[str] = []
|
|
19
|
+
|
|
20
|
+
i = 0
|
|
21
|
+
while i < len(args):
|
|
22
|
+
arg = args[i]
|
|
23
|
+
if arg == "-a" or arg == "--all":
|
|
24
|
+
show_all = True
|
|
25
|
+
elif arg == "-s" or arg == "--summarize":
|
|
26
|
+
summary_only = True
|
|
27
|
+
elif arg == "-h" or arg == "--human-readable":
|
|
28
|
+
human_readable = True
|
|
29
|
+
elif arg == "-c" or arg == "--total":
|
|
30
|
+
show_total = True
|
|
31
|
+
elif arg.startswith("--max-depth="):
|
|
32
|
+
try:
|
|
33
|
+
max_depth = int(arg[12:])
|
|
34
|
+
except ValueError:
|
|
35
|
+
return ExecResult(
|
|
36
|
+
stdout="",
|
|
37
|
+
stderr=f"du: invalid maximum depth '{arg[12:]}'\n",
|
|
38
|
+
exit_code=1,
|
|
39
|
+
)
|
|
40
|
+
elif arg == "--help":
|
|
41
|
+
return ExecResult(
|
|
42
|
+
stdout="Usage: du [OPTION]... [FILE]...\n",
|
|
43
|
+
stderr="",
|
|
44
|
+
exit_code=0,
|
|
45
|
+
)
|
|
46
|
+
elif arg.startswith("-"):
|
|
47
|
+
pass # Ignore unknown options
|
|
48
|
+
else:
|
|
49
|
+
paths.append(arg)
|
|
50
|
+
i += 1
|
|
51
|
+
|
|
52
|
+
if not paths:
|
|
53
|
+
paths = ["."]
|
|
54
|
+
|
|
55
|
+
output_lines = []
|
|
56
|
+
total_size = 0
|
|
57
|
+
|
|
58
|
+
for path in paths:
|
|
59
|
+
try:
|
|
60
|
+
resolved = ctx.fs.resolve_path(ctx.cwd, path)
|
|
61
|
+
stat = await ctx.fs.stat(resolved)
|
|
62
|
+
|
|
63
|
+
if stat.is_directory:
|
|
64
|
+
size = await self._get_dir_size(
|
|
65
|
+
ctx, resolved, path, show_all, summary_only,
|
|
66
|
+
max_depth, 0, output_lines, human_readable
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
size = stat.size
|
|
70
|
+
size_str = self._format_size(size, human_readable)
|
|
71
|
+
output_lines.append(f"{size_str}\t{path}")
|
|
72
|
+
|
|
73
|
+
total_size += size
|
|
74
|
+
|
|
75
|
+
except FileNotFoundError:
|
|
76
|
+
return ExecResult(
|
|
77
|
+
stdout="",
|
|
78
|
+
stderr=f"du: cannot access '{path}': No such file or directory\n",
|
|
79
|
+
exit_code=1,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
if show_total:
|
|
83
|
+
size_str = self._format_size(total_size, human_readable)
|
|
84
|
+
output_lines.append(f"{size_str}\ttotal")
|
|
85
|
+
|
|
86
|
+
return ExecResult(
|
|
87
|
+
stdout="\n".join(output_lines) + "\n" if output_lines else "",
|
|
88
|
+
stderr="",
|
|
89
|
+
exit_code=0,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def _get_dir_size(
|
|
93
|
+
self, ctx: CommandContext, path: str, display_path: str,
|
|
94
|
+
show_all: bool, summary_only: bool, max_depth: int | None,
|
|
95
|
+
current_depth: int, output_lines: list[str], human_readable: bool
|
|
96
|
+
) -> int:
|
|
97
|
+
"""Get directory size recursively."""
|
|
98
|
+
total = 0
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
entries = await ctx.fs.readdir(path)
|
|
102
|
+
|
|
103
|
+
for entry in entries:
|
|
104
|
+
entry_path = f"{path}/{entry}"
|
|
105
|
+
entry_display = f"{display_path}/{entry}"
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
stat = await ctx.fs.stat(entry_path)
|
|
109
|
+
|
|
110
|
+
if stat.is_directory:
|
|
111
|
+
size = await self._get_dir_size(
|
|
112
|
+
ctx, entry_path, entry_display, show_all,
|
|
113
|
+
summary_only, max_depth, current_depth + 1,
|
|
114
|
+
output_lines, human_readable
|
|
115
|
+
)
|
|
116
|
+
else:
|
|
117
|
+
size = stat.size
|
|
118
|
+
if show_all and not summary_only:
|
|
119
|
+
if max_depth is None or current_depth < max_depth:
|
|
120
|
+
size_str = self._format_size(size, human_readable)
|
|
121
|
+
output_lines.append(f"{size_str}\t{entry_display}")
|
|
122
|
+
|
|
123
|
+
total += size
|
|
124
|
+
except Exception:
|
|
125
|
+
pass
|
|
126
|
+
|
|
127
|
+
except Exception:
|
|
128
|
+
pass
|
|
129
|
+
|
|
130
|
+
# Output this directory
|
|
131
|
+
if not summary_only or current_depth == 0:
|
|
132
|
+
if max_depth is None or current_depth <= max_depth:
|
|
133
|
+
size_str = self._format_size(total, human_readable)
|
|
134
|
+
output_lines.append(f"{size_str}\t{display_path}")
|
|
135
|
+
|
|
136
|
+
return total
|
|
137
|
+
|
|
138
|
+
def _format_size(self, size: int, human_readable: bool) -> str:
|
|
139
|
+
"""Format size for display."""
|
|
140
|
+
if not human_readable:
|
|
141
|
+
return str(size // 1024 or 1) # Return in KB, minimum 1
|
|
142
|
+
|
|
143
|
+
if size < 1024:
|
|
144
|
+
return f"{size}B"
|
|
145
|
+
elif size < 1024 * 1024:
|
|
146
|
+
return f"{size // 1024}K"
|
|
147
|
+
elif size < 1024 * 1024 * 1024:
|
|
148
|
+
return f"{size // (1024 * 1024)}M"
|
|
149
|
+
else:
|
|
150
|
+
return f"{size // (1024 * 1024 * 1024)}G"
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"""Echo command implementation.
|
|
2
|
+
|
|
3
|
+
Usage: echo [-neE] [string ...]
|
|
4
|
+
|
|
5
|
+
Options:
|
|
6
|
+
-n Do not output the trailing newline
|
|
7
|
+
-e Enable interpretation of backslash escapes
|
|
8
|
+
-E Disable interpretation of backslash escapes (default)
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from ...types import Command, CommandContext, ExecResult
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def _process_escapes(s: str) -> str:
|
|
15
|
+
"""Process backslash escape sequences in a string."""
|
|
16
|
+
result = []
|
|
17
|
+
i = 0
|
|
18
|
+
while i < len(s):
|
|
19
|
+
if s[i] == "\\" and i + 1 < len(s):
|
|
20
|
+
next_char = s[i + 1]
|
|
21
|
+
if next_char == "n":
|
|
22
|
+
result.append("\n")
|
|
23
|
+
i += 2
|
|
24
|
+
elif next_char == "t":
|
|
25
|
+
result.append("\t")
|
|
26
|
+
i += 2
|
|
27
|
+
elif next_char == "r":
|
|
28
|
+
result.append("\r")
|
|
29
|
+
i += 2
|
|
30
|
+
elif next_char == "\\":
|
|
31
|
+
result.append("\\")
|
|
32
|
+
i += 2
|
|
33
|
+
elif next_char == "a":
|
|
34
|
+
result.append("\a")
|
|
35
|
+
i += 2
|
|
36
|
+
elif next_char == "b":
|
|
37
|
+
result.append("\b")
|
|
38
|
+
i += 2
|
|
39
|
+
elif next_char == "f":
|
|
40
|
+
result.append("\f")
|
|
41
|
+
i += 2
|
|
42
|
+
elif next_char == "v":
|
|
43
|
+
result.append("\v")
|
|
44
|
+
i += 2
|
|
45
|
+
elif next_char == "e":
|
|
46
|
+
result.append("\x1b")
|
|
47
|
+
i += 2
|
|
48
|
+
elif next_char == "0":
|
|
49
|
+
# Octal escape: \0nnn
|
|
50
|
+
octal = ""
|
|
51
|
+
j = i + 2
|
|
52
|
+
while j < len(s) and len(octal) < 3 and s[j] in "01234567":
|
|
53
|
+
octal += s[j]
|
|
54
|
+
j += 1
|
|
55
|
+
if octal:
|
|
56
|
+
result.append(chr(int(octal, 8)))
|
|
57
|
+
else:
|
|
58
|
+
result.append("\0")
|
|
59
|
+
i = j
|
|
60
|
+
elif next_char == "x":
|
|
61
|
+
# Hex escape: \xHH
|
|
62
|
+
hex_digits = ""
|
|
63
|
+
j = i + 2
|
|
64
|
+
while j < len(s) and len(hex_digits) < 2 and s[j] in "0123456789abcdefABCDEF":
|
|
65
|
+
hex_digits += s[j]
|
|
66
|
+
j += 1
|
|
67
|
+
if hex_digits:
|
|
68
|
+
result.append(chr(int(hex_digits, 16)))
|
|
69
|
+
i = j
|
|
70
|
+
else:
|
|
71
|
+
result.append(s[i])
|
|
72
|
+
i += 1
|
|
73
|
+
elif next_char == "c":
|
|
74
|
+
# \c stops output
|
|
75
|
+
return "".join(result)
|
|
76
|
+
else:
|
|
77
|
+
result.append(s[i])
|
|
78
|
+
i += 1
|
|
79
|
+
else:
|
|
80
|
+
result.append(s[i])
|
|
81
|
+
i += 1
|
|
82
|
+
return "".join(result)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class EchoCommand:
|
|
86
|
+
"""The echo command."""
|
|
87
|
+
|
|
88
|
+
name = "echo"
|
|
89
|
+
|
|
90
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
91
|
+
"""Execute the echo command."""
|
|
92
|
+
newline = True
|
|
93
|
+
enable_escapes = False
|
|
94
|
+
|
|
95
|
+
# Parse options
|
|
96
|
+
i = 0
|
|
97
|
+
while i < len(args):
|
|
98
|
+
arg = args[i]
|
|
99
|
+
if arg.startswith("-") and len(arg) > 1 and all(c in "neE" for c in arg[1:]):
|
|
100
|
+
for c in arg[1:]:
|
|
101
|
+
if c == "n":
|
|
102
|
+
newline = False
|
|
103
|
+
elif c == "e":
|
|
104
|
+
enable_escapes = True
|
|
105
|
+
elif c == "E":
|
|
106
|
+
enable_escapes = False
|
|
107
|
+
i += 1
|
|
108
|
+
else:
|
|
109
|
+
break
|
|
110
|
+
|
|
111
|
+
# Get remaining arguments
|
|
112
|
+
text_args = args[i:]
|
|
113
|
+
|
|
114
|
+
# Build output
|
|
115
|
+
output = " ".join(text_args)
|
|
116
|
+
|
|
117
|
+
# Process escape sequences if enabled
|
|
118
|
+
if enable_escapes:
|
|
119
|
+
output = _process_escapes(output)
|
|
120
|
+
|
|
121
|
+
# Add newline if needed
|
|
122
|
+
if newline:
|
|
123
|
+
output += "\n"
|
|
124
|
+
|
|
125
|
+
return ExecResult(stdout=output, stderr="", exit_code=0)
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"""Env and printenv command implementations."""
|
|
2
|
+
|
|
3
|
+
from ...types import CommandContext, ExecResult
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class EnvCommand:
|
|
7
|
+
"""The env command - run a command with modified environment."""
|
|
8
|
+
|
|
9
|
+
name = "env"
|
|
10
|
+
|
|
11
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
12
|
+
"""Execute the env command."""
|
|
13
|
+
ignore_environment = False
|
|
14
|
+
unset_vars: list[str] = []
|
|
15
|
+
set_vars: dict[str, str] = {}
|
|
16
|
+
command: list[str] = []
|
|
17
|
+
|
|
18
|
+
i = 0
|
|
19
|
+
while i < len(args):
|
|
20
|
+
arg = args[i]
|
|
21
|
+
|
|
22
|
+
if arg == "--help":
|
|
23
|
+
return ExecResult(
|
|
24
|
+
stdout=(
|
|
25
|
+
"Usage: env [OPTION]... [NAME=VALUE]... [COMMAND [ARG]...]\n"
|
|
26
|
+
"Set each NAME to VALUE in the environment and run COMMAND.\n\n"
|
|
27
|
+
"Options:\n"
|
|
28
|
+
" -i, --ignore-environment start with an empty environment\n"
|
|
29
|
+
" -u, --unset=NAME remove variable from the environment\n"
|
|
30
|
+
" --help display this help and exit\n"
|
|
31
|
+
),
|
|
32
|
+
stderr="",
|
|
33
|
+
exit_code=0,
|
|
34
|
+
)
|
|
35
|
+
elif arg in ("-i", "--ignore-environment"):
|
|
36
|
+
ignore_environment = True
|
|
37
|
+
elif arg == "-u" and i + 1 < len(args):
|
|
38
|
+
i += 1
|
|
39
|
+
unset_vars.append(args[i])
|
|
40
|
+
elif arg.startswith("-u"):
|
|
41
|
+
unset_vars.append(arg[2:])
|
|
42
|
+
elif arg.startswith("--unset="):
|
|
43
|
+
unset_vars.append(arg[8:])
|
|
44
|
+
elif arg == "--unset" and i + 1 < len(args):
|
|
45
|
+
i += 1
|
|
46
|
+
unset_vars.append(args[i])
|
|
47
|
+
elif arg == "--":
|
|
48
|
+
# Everything after -- is the command
|
|
49
|
+
command = args[i + 1:]
|
|
50
|
+
break
|
|
51
|
+
elif "=" in arg and not arg.startswith("-"):
|
|
52
|
+
# NAME=VALUE assignment
|
|
53
|
+
eq_idx = arg.index("=")
|
|
54
|
+
name = arg[:eq_idx]
|
|
55
|
+
value = arg[eq_idx + 1:]
|
|
56
|
+
set_vars[name] = value
|
|
57
|
+
elif arg.startswith("-"):
|
|
58
|
+
# Unknown option
|
|
59
|
+
return ExecResult(
|
|
60
|
+
stdout="",
|
|
61
|
+
stderr=f"env: invalid option -- '{arg[1:]}'\n",
|
|
62
|
+
exit_code=1,
|
|
63
|
+
)
|
|
64
|
+
else:
|
|
65
|
+
# Start of command
|
|
66
|
+
command = args[i:]
|
|
67
|
+
break
|
|
68
|
+
i += 1
|
|
69
|
+
|
|
70
|
+
# Build the environment
|
|
71
|
+
if ignore_environment:
|
|
72
|
+
new_env = {}
|
|
73
|
+
else:
|
|
74
|
+
new_env = dict(ctx.env)
|
|
75
|
+
|
|
76
|
+
# Remove unset variables
|
|
77
|
+
for var in unset_vars:
|
|
78
|
+
new_env.pop(var, None)
|
|
79
|
+
|
|
80
|
+
# Add new variables
|
|
81
|
+
new_env.update(set_vars)
|
|
82
|
+
|
|
83
|
+
# If no command, print the environment
|
|
84
|
+
if not command:
|
|
85
|
+
lines = [f"{k}={v}" for k, v in sorted(new_env.items())]
|
|
86
|
+
return ExecResult(
|
|
87
|
+
stdout="\n".join(lines) + "\n" if lines else "",
|
|
88
|
+
stderr="",
|
|
89
|
+
exit_code=0,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Execute the command with the new environment
|
|
93
|
+
if not ctx.exec:
|
|
94
|
+
return ExecResult(
|
|
95
|
+
stdout="",
|
|
96
|
+
stderr="env: cannot execute commands\n",
|
|
97
|
+
exit_code=126,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
# Quote arguments properly for shell execution
|
|
101
|
+
def quote(s: str) -> str:
|
|
102
|
+
if not s or any(c in s for c in " \t\n'\"\\$`!"):
|
|
103
|
+
return "'" + s.replace("'", "'\"'\"'") + "'"
|
|
104
|
+
return s
|
|
105
|
+
|
|
106
|
+
cmd_str = " ".join(quote(c) for c in command)
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
result = await ctx.exec(cmd_str, {"cwd": ctx.cwd, "env": new_env})
|
|
110
|
+
return result
|
|
111
|
+
except Exception as e:
|
|
112
|
+
return ExecResult(
|
|
113
|
+
stdout="",
|
|
114
|
+
stderr=f"env: {e}\n",
|
|
115
|
+
exit_code=1,
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
class PrintenvCommand:
|
|
120
|
+
"""The printenv command - print environment variables."""
|
|
121
|
+
|
|
122
|
+
name = "printenv"
|
|
123
|
+
|
|
124
|
+
async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
|
|
125
|
+
"""Execute the printenv command."""
|
|
126
|
+
var_names: list[str] = []
|
|
127
|
+
|
|
128
|
+
for arg in args:
|
|
129
|
+
if arg == "--help":
|
|
130
|
+
return ExecResult(
|
|
131
|
+
stdout="Usage: printenv [OPTION]... [VARIABLE]...\n",
|
|
132
|
+
stderr="",
|
|
133
|
+
exit_code=0,
|
|
134
|
+
)
|
|
135
|
+
elif arg.startswith("-"):
|
|
136
|
+
pass # Ignore options
|
|
137
|
+
else:
|
|
138
|
+
var_names.append(arg)
|
|
139
|
+
|
|
140
|
+
if not var_names:
|
|
141
|
+
# Print all
|
|
142
|
+
lines = [f"{k}={v}" for k, v in sorted(ctx.env.items())]
|
|
143
|
+
return ExecResult(
|
|
144
|
+
stdout="\n".join(lines) + "\n" if lines else "",
|
|
145
|
+
stderr="",
|
|
146
|
+
exit_code=0,
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
# Print specific variables
|
|
150
|
+
output_lines = []
|
|
151
|
+
exit_code = 0
|
|
152
|
+
|
|
153
|
+
for name in var_names:
|
|
154
|
+
if name in ctx.env:
|
|
155
|
+
output_lines.append(ctx.env[name])
|
|
156
|
+
else:
|
|
157
|
+
exit_code = 1
|
|
158
|
+
|
|
159
|
+
output = "\n".join(output_lines)
|
|
160
|
+
if output:
|
|
161
|
+
output += "\n"
|
|
162
|
+
|
|
163
|
+
return ExecResult(stdout=output, stderr="", exit_code=exit_code)
|