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.
Files changed (193) hide show
  1. just_bash/__init__.py +55 -0
  2. just_bash/ast/__init__.py +213 -0
  3. just_bash/ast/factory.py +320 -0
  4. just_bash/ast/types.py +953 -0
  5. just_bash/bash.py +220 -0
  6. just_bash/commands/__init__.py +23 -0
  7. just_bash/commands/argv/__init__.py +5 -0
  8. just_bash/commands/argv/argv.py +21 -0
  9. just_bash/commands/awk/__init__.py +5 -0
  10. just_bash/commands/awk/awk.py +1168 -0
  11. just_bash/commands/base64/__init__.py +5 -0
  12. just_bash/commands/base64/base64.py +138 -0
  13. just_bash/commands/basename/__init__.py +5 -0
  14. just_bash/commands/basename/basename.py +72 -0
  15. just_bash/commands/bash/__init__.py +5 -0
  16. just_bash/commands/bash/bash.py +188 -0
  17. just_bash/commands/cat/__init__.py +5 -0
  18. just_bash/commands/cat/cat.py +173 -0
  19. just_bash/commands/checksum/__init__.py +5 -0
  20. just_bash/commands/checksum/checksum.py +179 -0
  21. just_bash/commands/chmod/__init__.py +5 -0
  22. just_bash/commands/chmod/chmod.py +216 -0
  23. just_bash/commands/column/__init__.py +5 -0
  24. just_bash/commands/column/column.py +180 -0
  25. just_bash/commands/comm/__init__.py +5 -0
  26. just_bash/commands/comm/comm.py +150 -0
  27. just_bash/commands/compression/__init__.py +5 -0
  28. just_bash/commands/compression/compression.py +298 -0
  29. just_bash/commands/cp/__init__.py +5 -0
  30. just_bash/commands/cp/cp.py +149 -0
  31. just_bash/commands/curl/__init__.py +5 -0
  32. just_bash/commands/curl/curl.py +801 -0
  33. just_bash/commands/cut/__init__.py +5 -0
  34. just_bash/commands/cut/cut.py +327 -0
  35. just_bash/commands/date/__init__.py +5 -0
  36. just_bash/commands/date/date.py +258 -0
  37. just_bash/commands/diff/__init__.py +5 -0
  38. just_bash/commands/diff/diff.py +118 -0
  39. just_bash/commands/dirname/__init__.py +5 -0
  40. just_bash/commands/dirname/dirname.py +56 -0
  41. just_bash/commands/du/__init__.py +5 -0
  42. just_bash/commands/du/du.py +150 -0
  43. just_bash/commands/echo/__init__.py +5 -0
  44. just_bash/commands/echo/echo.py +125 -0
  45. just_bash/commands/env/__init__.py +5 -0
  46. just_bash/commands/env/env.py +163 -0
  47. just_bash/commands/expand/__init__.py +5 -0
  48. just_bash/commands/expand/expand.py +299 -0
  49. just_bash/commands/expr/__init__.py +5 -0
  50. just_bash/commands/expr/expr.py +273 -0
  51. just_bash/commands/file/__init__.py +5 -0
  52. just_bash/commands/file/file.py +274 -0
  53. just_bash/commands/find/__init__.py +5 -0
  54. just_bash/commands/find/find.py +623 -0
  55. just_bash/commands/fold/__init__.py +5 -0
  56. just_bash/commands/fold/fold.py +160 -0
  57. just_bash/commands/grep/__init__.py +5 -0
  58. just_bash/commands/grep/grep.py +418 -0
  59. just_bash/commands/head/__init__.py +5 -0
  60. just_bash/commands/head/head.py +167 -0
  61. just_bash/commands/help/__init__.py +5 -0
  62. just_bash/commands/help/help.py +67 -0
  63. just_bash/commands/hostname/__init__.py +5 -0
  64. just_bash/commands/hostname/hostname.py +21 -0
  65. just_bash/commands/html_to_markdown/__init__.py +5 -0
  66. just_bash/commands/html_to_markdown/html_to_markdown.py +191 -0
  67. just_bash/commands/join/__init__.py +5 -0
  68. just_bash/commands/join/join.py +252 -0
  69. just_bash/commands/jq/__init__.py +5 -0
  70. just_bash/commands/jq/jq.py +280 -0
  71. just_bash/commands/ln/__init__.py +5 -0
  72. just_bash/commands/ln/ln.py +127 -0
  73. just_bash/commands/ls/__init__.py +5 -0
  74. just_bash/commands/ls/ls.py +280 -0
  75. just_bash/commands/mkdir/__init__.py +5 -0
  76. just_bash/commands/mkdir/mkdir.py +92 -0
  77. just_bash/commands/mv/__init__.py +5 -0
  78. just_bash/commands/mv/mv.py +142 -0
  79. just_bash/commands/nl/__init__.py +5 -0
  80. just_bash/commands/nl/nl.py +180 -0
  81. just_bash/commands/od/__init__.py +5 -0
  82. just_bash/commands/od/od.py +157 -0
  83. just_bash/commands/paste/__init__.py +5 -0
  84. just_bash/commands/paste/paste.py +100 -0
  85. just_bash/commands/printf/__init__.py +5 -0
  86. just_bash/commands/printf/printf.py +157 -0
  87. just_bash/commands/pwd/__init__.py +5 -0
  88. just_bash/commands/pwd/pwd.py +23 -0
  89. just_bash/commands/read/__init__.py +5 -0
  90. just_bash/commands/read/read.py +185 -0
  91. just_bash/commands/readlink/__init__.py +5 -0
  92. just_bash/commands/readlink/readlink.py +86 -0
  93. just_bash/commands/registry.py +844 -0
  94. just_bash/commands/rev/__init__.py +5 -0
  95. just_bash/commands/rev/rev.py +74 -0
  96. just_bash/commands/rg/__init__.py +5 -0
  97. just_bash/commands/rg/rg.py +1048 -0
  98. just_bash/commands/rm/__init__.py +5 -0
  99. just_bash/commands/rm/rm.py +106 -0
  100. just_bash/commands/search_engine/__init__.py +13 -0
  101. just_bash/commands/search_engine/matcher.py +170 -0
  102. just_bash/commands/search_engine/regex.py +159 -0
  103. just_bash/commands/sed/__init__.py +5 -0
  104. just_bash/commands/sed/sed.py +863 -0
  105. just_bash/commands/seq/__init__.py +5 -0
  106. just_bash/commands/seq/seq.py +190 -0
  107. just_bash/commands/shell/__init__.py +5 -0
  108. just_bash/commands/shell/shell.py +206 -0
  109. just_bash/commands/sleep/__init__.py +5 -0
  110. just_bash/commands/sleep/sleep.py +62 -0
  111. just_bash/commands/sort/__init__.py +5 -0
  112. just_bash/commands/sort/sort.py +411 -0
  113. just_bash/commands/split/__init__.py +5 -0
  114. just_bash/commands/split/split.py +237 -0
  115. just_bash/commands/sqlite3/__init__.py +5 -0
  116. just_bash/commands/sqlite3/sqlite3_cmd.py +505 -0
  117. just_bash/commands/stat/__init__.py +5 -0
  118. just_bash/commands/stat/stat.py +150 -0
  119. just_bash/commands/strings/__init__.py +5 -0
  120. just_bash/commands/strings/strings.py +150 -0
  121. just_bash/commands/tac/__init__.py +5 -0
  122. just_bash/commands/tac/tac.py +158 -0
  123. just_bash/commands/tail/__init__.py +5 -0
  124. just_bash/commands/tail/tail.py +180 -0
  125. just_bash/commands/tar/__init__.py +5 -0
  126. just_bash/commands/tar/tar.py +1067 -0
  127. just_bash/commands/tee/__init__.py +5 -0
  128. just_bash/commands/tee/tee.py +63 -0
  129. just_bash/commands/timeout/__init__.py +5 -0
  130. just_bash/commands/timeout/timeout.py +188 -0
  131. just_bash/commands/touch/__init__.py +5 -0
  132. just_bash/commands/touch/touch.py +91 -0
  133. just_bash/commands/tr/__init__.py +5 -0
  134. just_bash/commands/tr/tr.py +297 -0
  135. just_bash/commands/tree/__init__.py +5 -0
  136. just_bash/commands/tree/tree.py +139 -0
  137. just_bash/commands/true/__init__.py +5 -0
  138. just_bash/commands/true/true.py +32 -0
  139. just_bash/commands/uniq/__init__.py +5 -0
  140. just_bash/commands/uniq/uniq.py +323 -0
  141. just_bash/commands/wc/__init__.py +5 -0
  142. just_bash/commands/wc/wc.py +169 -0
  143. just_bash/commands/which/__init__.py +5 -0
  144. just_bash/commands/which/which.py +52 -0
  145. just_bash/commands/xan/__init__.py +5 -0
  146. just_bash/commands/xan/xan.py +1663 -0
  147. just_bash/commands/xargs/__init__.py +5 -0
  148. just_bash/commands/xargs/xargs.py +136 -0
  149. just_bash/commands/yq/__init__.py +5 -0
  150. just_bash/commands/yq/yq.py +848 -0
  151. just_bash/fs/__init__.py +29 -0
  152. just_bash/fs/in_memory_fs.py +621 -0
  153. just_bash/fs/mountable_fs.py +504 -0
  154. just_bash/fs/overlay_fs.py +894 -0
  155. just_bash/fs/read_write_fs.py +455 -0
  156. just_bash/interpreter/__init__.py +37 -0
  157. just_bash/interpreter/builtins/__init__.py +92 -0
  158. just_bash/interpreter/builtins/alias.py +154 -0
  159. just_bash/interpreter/builtins/cd.py +76 -0
  160. just_bash/interpreter/builtins/control.py +127 -0
  161. just_bash/interpreter/builtins/declare.py +336 -0
  162. just_bash/interpreter/builtins/export.py +56 -0
  163. just_bash/interpreter/builtins/let.py +44 -0
  164. just_bash/interpreter/builtins/local.py +57 -0
  165. just_bash/interpreter/builtins/mapfile.py +152 -0
  166. just_bash/interpreter/builtins/misc.py +378 -0
  167. just_bash/interpreter/builtins/readonly.py +80 -0
  168. just_bash/interpreter/builtins/set.py +234 -0
  169. just_bash/interpreter/builtins/shopt.py +201 -0
  170. just_bash/interpreter/builtins/source.py +136 -0
  171. just_bash/interpreter/builtins/test.py +290 -0
  172. just_bash/interpreter/builtins/unset.py +53 -0
  173. just_bash/interpreter/conditionals.py +387 -0
  174. just_bash/interpreter/control_flow.py +381 -0
  175. just_bash/interpreter/errors.py +116 -0
  176. just_bash/interpreter/expansion.py +1156 -0
  177. just_bash/interpreter/interpreter.py +813 -0
  178. just_bash/interpreter/types.py +134 -0
  179. just_bash/network/__init__.py +1 -0
  180. just_bash/parser/__init__.py +39 -0
  181. just_bash/parser/lexer.py +948 -0
  182. just_bash/parser/parser.py +2162 -0
  183. just_bash/py.typed +0 -0
  184. just_bash/query_engine/__init__.py +83 -0
  185. just_bash/query_engine/builtins/__init__.py +1283 -0
  186. just_bash/query_engine/evaluator.py +578 -0
  187. just_bash/query_engine/parser.py +525 -0
  188. just_bash/query_engine/tokenizer.py +329 -0
  189. just_bash/query_engine/types.py +373 -0
  190. just_bash/types.py +180 -0
  191. just_bash-0.1.5.dist-info/METADATA +410 -0
  192. just_bash-0.1.5.dist-info/RECORD +193 -0
  193. just_bash-0.1.5.dist-info/WHEEL +4 -0
@@ -0,0 +1,5 @@
1
+ """Base64 command implementation."""
2
+
3
+ from .base64 import Base64Command
4
+
5
+ __all__ = ["Base64Command"]
@@ -0,0 +1,138 @@
1
+ """Base64 command implementation.
2
+
3
+ Usage: base64 [OPTION]... [FILE]
4
+
5
+ Base64 encode or decode FILE, or standard input.
6
+
7
+ Options:
8
+ -d, --decode decode data
9
+ -w, --wrap=COLS wrap encoded lines after COLS characters (default 76)
10
+ """
11
+
12
+ import base64 as b64
13
+ from ...types import CommandContext, ExecResult
14
+
15
+
16
+ class Base64Command:
17
+ """The base64 command."""
18
+
19
+ name = "base64"
20
+
21
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
22
+ """Execute the base64 command."""
23
+ decode = False
24
+ wrap_cols = 76
25
+ files: list[str] = []
26
+
27
+ # Parse arguments
28
+ i = 0
29
+ while i < len(args):
30
+ arg = args[i]
31
+ if arg == "--":
32
+ files.extend(args[i + 1:])
33
+ break
34
+ elif arg.startswith("--"):
35
+ if arg == "--decode":
36
+ decode = True
37
+ elif arg.startswith("--wrap="):
38
+ try:
39
+ wrap_cols = int(arg[7:])
40
+ except ValueError:
41
+ return ExecResult(
42
+ stdout="",
43
+ stderr=f"base64: invalid wrap size: '{arg[7:]}'\n",
44
+ exit_code=1,
45
+ )
46
+ else:
47
+ return ExecResult(
48
+ stdout="",
49
+ stderr=f"base64: unrecognized option '{arg}'\n",
50
+ exit_code=1,
51
+ )
52
+ elif arg.startswith("-") and arg != "-":
53
+ j = 1
54
+ while j < len(arg):
55
+ c = arg[j]
56
+ if c == "d":
57
+ decode = True
58
+ elif c == "w":
59
+ # -w requires a value
60
+ if j + 1 < len(arg):
61
+ try:
62
+ wrap_cols = int(arg[j + 1:])
63
+ except ValueError:
64
+ return ExecResult(
65
+ stdout="",
66
+ stderr=f"base64: invalid wrap size\n",
67
+ exit_code=1,
68
+ )
69
+ break
70
+ elif i + 1 < len(args):
71
+ i += 1
72
+ try:
73
+ wrap_cols = int(args[i])
74
+ except ValueError:
75
+ return ExecResult(
76
+ stdout="",
77
+ stderr=f"base64: invalid wrap size: '{args[i]}'\n",
78
+ exit_code=1,
79
+ )
80
+ break
81
+ else:
82
+ return ExecResult(
83
+ stdout="",
84
+ stderr="base64: option requires an argument -- 'w'\n",
85
+ exit_code=1,
86
+ )
87
+ else:
88
+ return ExecResult(
89
+ stdout="",
90
+ stderr=f"base64: invalid option -- '{c}'\n",
91
+ exit_code=1,
92
+ )
93
+ j += 1
94
+ else:
95
+ files.append(arg)
96
+ i += 1
97
+
98
+ # Default to stdin
99
+ if not files:
100
+ files = ["-"]
101
+
102
+ stdout = ""
103
+ stderr = ""
104
+ exit_code = 0
105
+
106
+ for f in files:
107
+ try:
108
+ if f == "-":
109
+ content = ctx.stdin
110
+ else:
111
+ path = ctx.fs.resolve_path(ctx.cwd, f)
112
+ content = await ctx.fs.read_file(path)
113
+
114
+ if decode:
115
+ # Decode base64
116
+ # Strip whitespace
117
+ content = "".join(content.split())
118
+ try:
119
+ decoded = b64.b64decode(content)
120
+ stdout += decoded.decode("utf-8", errors="replace")
121
+ except Exception as e:
122
+ stderr += f"base64: invalid input\n"
123
+ exit_code = 1
124
+ else:
125
+ # Encode to base64
126
+ encoded = b64.b64encode(content.encode("utf-8")).decode("utf-8")
127
+ # Wrap lines
128
+ if wrap_cols > 0:
129
+ lines = [encoded[i:i + wrap_cols] for i in range(0, len(encoded), wrap_cols)]
130
+ stdout += "\n".join(lines) + "\n"
131
+ else:
132
+ stdout += encoded
133
+
134
+ except FileNotFoundError:
135
+ stderr += f"base64: {f}: No such file or directory\n"
136
+ exit_code = 1
137
+
138
+ return ExecResult(stdout=stdout, stderr=stderr, exit_code=exit_code)
@@ -0,0 +1,5 @@
1
+ """Basename command."""
2
+
3
+ from .basename import BasenameCommand
4
+
5
+ __all__ = ["BasenameCommand"]
@@ -0,0 +1,72 @@
1
+ """Basename command implementation."""
2
+
3
+ import os
4
+ from ...types import CommandContext, ExecResult
5
+
6
+
7
+ class BasenameCommand:
8
+ """The basename command."""
9
+
10
+ name = "basename"
11
+
12
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
13
+ """Execute the basename command."""
14
+ suffix = None
15
+ multiple = False
16
+ paths: list[str] = []
17
+
18
+ i = 0
19
+ while i < len(args):
20
+ arg = args[i]
21
+ if arg == "-s" and i + 1 < len(args):
22
+ i += 1
23
+ suffix = args[i]
24
+ elif arg.startswith("--suffix="):
25
+ suffix = arg[9:]
26
+ elif arg == "-a" or arg == "--multiple":
27
+ multiple = True
28
+ elif arg == "--help":
29
+ return ExecResult(
30
+ stdout="Usage: basename NAME [SUFFIX]\n basename OPTION... NAME...\n",
31
+ stderr="",
32
+ exit_code=0,
33
+ )
34
+ elif arg == "--":
35
+ paths.extend(args[i + 1:])
36
+ break
37
+ elif arg.startswith("-") and len(arg) > 1:
38
+ return ExecResult(
39
+ stdout="",
40
+ stderr=f"basename: invalid option -- '{arg[1]}'\n",
41
+ exit_code=1,
42
+ )
43
+ else:
44
+ paths.append(arg)
45
+ i += 1
46
+
47
+ if not paths:
48
+ return ExecResult(
49
+ stdout="",
50
+ stderr="basename: missing operand\n",
51
+ exit_code=1,
52
+ )
53
+
54
+ # If not multiple mode and exactly 2 args, second is suffix
55
+ if not multiple and not suffix and len(paths) == 2:
56
+ suffix = paths[1]
57
+ paths = [paths[0]]
58
+
59
+ results = []
60
+ for path in paths:
61
+ base = os.path.basename(path.rstrip("/"))
62
+ if not base:
63
+ base = "/"
64
+ if suffix and base.endswith(suffix):
65
+ base = base[:-len(suffix)]
66
+ results.append(base)
67
+
68
+ return ExecResult(
69
+ stdout="\n".join(results) + "\n",
70
+ stderr="",
71
+ exit_code=0,
72
+ )
@@ -0,0 +1,5 @@
1
+ """Bash and sh commands."""
2
+
3
+ from .bash import BashCommand, ShCommand
4
+
5
+ __all__ = ["BashCommand", "ShCommand"]
@@ -0,0 +1,188 @@
1
+ """Bash and sh command implementations."""
2
+
3
+ from ...types import CommandContext, ExecResult
4
+
5
+
6
+ class BashCommand:
7
+ """The bash command - execute shell commands or scripts."""
8
+
9
+ name = "bash"
10
+
11
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
12
+ """Execute the bash command."""
13
+ if "--help" in args:
14
+ return ExecResult(
15
+ stdout=(
16
+ "Usage: bash [OPTIONS] [SCRIPT_FILE] [ARGUMENTS...]\n"
17
+ "Execute shell commands or scripts.\n\n"
18
+ "Options:\n"
19
+ " -c COMMAND execute COMMAND string\n"
20
+ " --help display this help and exit\n\n"
21
+ "Without -c, reads and executes commands from SCRIPT_FILE.\n"
22
+ ),
23
+ stderr="",
24
+ exit_code=0,
25
+ )
26
+
27
+ # Handle -c flag
28
+ # With -c: bash -c 'command' arg0 arg1 arg2
29
+ # arg0 becomes $0, arg1 becomes $1, arg2 becomes $2
30
+ if len(args) >= 2 and args[0] == "-c":
31
+ command = args[1]
32
+ script_name = args[2] if len(args) > 2 else "bash"
33
+ script_args = args[3:] if len(args) > 3 else []
34
+ return await self._execute_script(command, script_name, script_args, ctx)
35
+
36
+ # No arguments - in real bash this would be interactive mode
37
+ # In our implementation, we just return success
38
+ if not args:
39
+ return ExecResult(stdout="", stderr="", exit_code=0)
40
+
41
+ # Read and execute script file
42
+ script_path = args[0]
43
+ script_args = args[1:]
44
+
45
+ try:
46
+ full_path = ctx.fs.resolve_path(ctx.cwd, script_path)
47
+ script_content = await ctx.fs.read_file(full_path)
48
+ return await self._execute_script(script_content, script_path, script_args, ctx)
49
+ except FileNotFoundError:
50
+ return ExecResult(
51
+ stdout="",
52
+ stderr=f"bash: {script_path}: No such file or directory\n",
53
+ exit_code=127,
54
+ )
55
+
56
+ async def _execute_script(
57
+ self,
58
+ script: str,
59
+ script_name: str,
60
+ script_args: list[str],
61
+ ctx: CommandContext,
62
+ ) -> ExecResult:
63
+ """Execute a script with positional parameters."""
64
+ if not ctx.exec:
65
+ return ExecResult(
66
+ stdout="",
67
+ stderr="bash: internal error: exec function not available\n",
68
+ exit_code=1,
69
+ )
70
+
71
+ # Build positional parameters for the exec env option
72
+ positional_env: dict[str, str] = {
73
+ "0": script_name,
74
+ "#": str(len(script_args)),
75
+ "@": " ".join(script_args),
76
+ "*": " ".join(script_args),
77
+ }
78
+ for i, arg in enumerate(script_args):
79
+ positional_env[str(i + 1)] = arg
80
+
81
+ # Skip shebang line if present
82
+ script_to_run = script
83
+ if script_to_run.startswith("#!"):
84
+ first_newline = script_to_run.find("\n")
85
+ if first_newline != -1:
86
+ script_to_run = script_to_run[first_newline + 1:]
87
+
88
+ # Process the script line by line, filtering out comments and empty lines
89
+ lines = script_to_run.split("\n")
90
+ commands: list[str] = []
91
+ for line in lines:
92
+ trimmed = line.strip()
93
+ # Skip empty lines and comment lines
94
+ if trimmed and not trimmed.startswith("#"):
95
+ commands.append(trimmed)
96
+
97
+ # Execute all commands joined by semicolons
98
+ command_string = "; ".join(commands)
99
+ result = await ctx.exec(command_string, {"env": positional_env, "cwd": ctx.cwd})
100
+ return result
101
+
102
+
103
+ class ShCommand:
104
+ """The sh command - execute shell commands or scripts (POSIX shell)."""
105
+
106
+ name = "sh"
107
+
108
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
109
+ """Execute the sh command."""
110
+ if "--help" in args:
111
+ return ExecResult(
112
+ stdout=(
113
+ "Usage: sh [OPTIONS] [SCRIPT_FILE] [ARGUMENTS...]\n"
114
+ "Execute shell commands or scripts (POSIX shell).\n\n"
115
+ "Options:\n"
116
+ " -c COMMAND execute COMMAND string\n"
117
+ " --help display this help and exit\n\n"
118
+ "Without -c, reads and executes commands from SCRIPT_FILE.\n"
119
+ ),
120
+ stderr="",
121
+ exit_code=0,
122
+ )
123
+
124
+ # Same implementation as bash
125
+ # Handle -c flag
126
+ if len(args) >= 2 and args[0] == "-c":
127
+ command = args[1]
128
+ script_name = args[2] if len(args) > 2 else "sh"
129
+ script_args = args[3:] if len(args) > 3 else []
130
+ return await self._execute_script(command, script_name, script_args, ctx)
131
+
132
+ if not args:
133
+ return ExecResult(stdout="", stderr="", exit_code=0)
134
+
135
+ script_path = args[0]
136
+ script_args = args[1:]
137
+
138
+ try:
139
+ full_path = ctx.fs.resolve_path(ctx.cwd, script_path)
140
+ script_content = await ctx.fs.read_file(full_path)
141
+ return await self._execute_script(script_content, script_path, script_args, ctx)
142
+ except FileNotFoundError:
143
+ return ExecResult(
144
+ stdout="",
145
+ stderr=f"sh: {script_path}: No such file or directory\n",
146
+ exit_code=127,
147
+ )
148
+
149
+ async def _execute_script(
150
+ self,
151
+ script: str,
152
+ script_name: str,
153
+ script_args: list[str],
154
+ ctx: CommandContext,
155
+ ) -> ExecResult:
156
+ """Execute a script with positional parameters."""
157
+ if not ctx.exec:
158
+ return ExecResult(
159
+ stdout="",
160
+ stderr="sh: internal error: exec function not available\n",
161
+ exit_code=1,
162
+ )
163
+
164
+ positional_env: dict[str, str] = {
165
+ "0": script_name,
166
+ "#": str(len(script_args)),
167
+ "@": " ".join(script_args),
168
+ "*": " ".join(script_args),
169
+ }
170
+ for i, arg in enumerate(script_args):
171
+ positional_env[str(i + 1)] = arg
172
+
173
+ script_to_run = script
174
+ if script_to_run.startswith("#!"):
175
+ first_newline = script_to_run.find("\n")
176
+ if first_newline != -1:
177
+ script_to_run = script_to_run[first_newline + 1:]
178
+
179
+ lines = script_to_run.split("\n")
180
+ commands: list[str] = []
181
+ for line in lines:
182
+ trimmed = line.strip()
183
+ if trimmed and not trimmed.startswith("#"):
184
+ commands.append(trimmed)
185
+
186
+ command_string = "; ".join(commands)
187
+ result = await ctx.exec(command_string, {"env": positional_env, "cwd": ctx.cwd})
188
+ return result
@@ -0,0 +1,5 @@
1
+ """Cat command implementation."""
2
+
3
+ from .cat import CatCommand
4
+
5
+ __all__ = ["CatCommand"]
@@ -0,0 +1,173 @@
1
+ """Cat command implementation.
2
+
3
+ Usage: cat [OPTION]... [FILE]...
4
+
5
+ Concatenate FILE(s) to standard output.
6
+ With no FILE, or when FILE is -, read standard input.
7
+
8
+ Options:
9
+ -n, --number number all output lines
10
+ -b, --number-nonblank number nonempty output lines
11
+ -s, --squeeze-blank suppress repeated empty output lines
12
+ -E, --show-ends display $ at end of each line
13
+ -T, --show-tabs display TAB characters as ^I
14
+ -v, --show-nonprinting use ^ and M- notation, except for LFD and TAB
15
+ -A, --show-all equivalent to -vET
16
+ -e equivalent to -vE
17
+ -t equivalent to -vT
18
+ """
19
+
20
+ from ...types import Command, CommandContext, ExecResult
21
+
22
+
23
+ class CatCommand:
24
+ """The cat command."""
25
+
26
+ name = "cat"
27
+
28
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
29
+ """Execute the cat command."""
30
+ # Options
31
+ number_lines = False
32
+ number_nonblank = False
33
+ squeeze_blank = False
34
+ show_ends = False
35
+ show_tabs = False
36
+ show_nonprinting = False
37
+
38
+ files: list[str] = []
39
+
40
+ # Parse arguments
41
+ i = 0
42
+ while i < len(args):
43
+ arg = args[i]
44
+ if arg == "--":
45
+ files.extend(args[i + 1 :])
46
+ break
47
+ elif arg.startswith("--"):
48
+ if arg == "--number":
49
+ number_lines = True
50
+ elif arg == "--number-nonblank":
51
+ number_nonblank = True
52
+ elif arg == "--squeeze-blank":
53
+ squeeze_blank = True
54
+ elif arg == "--show-ends":
55
+ show_ends = True
56
+ elif arg == "--show-tabs":
57
+ show_tabs = True
58
+ elif arg == "--show-nonprinting":
59
+ show_nonprinting = True
60
+ elif arg == "--show-all":
61
+ show_nonprinting = True
62
+ show_ends = True
63
+ show_tabs = True
64
+ else:
65
+ return ExecResult(
66
+ stdout="",
67
+ stderr=f"cat: unrecognized option '{arg}'\n",
68
+ exit_code=1,
69
+ )
70
+ elif arg.startswith("-") and arg != "-":
71
+ for c in arg[1:]:
72
+ if c == "n":
73
+ number_lines = True
74
+ elif c == "b":
75
+ number_nonblank = True
76
+ elif c == "s":
77
+ squeeze_blank = True
78
+ elif c == "E":
79
+ show_ends = True
80
+ elif c == "T":
81
+ show_tabs = True
82
+ elif c == "v":
83
+ show_nonprinting = True
84
+ elif c == "A":
85
+ show_nonprinting = True
86
+ show_ends = True
87
+ show_tabs = True
88
+ elif c == "e":
89
+ show_nonprinting = True
90
+ show_ends = True
91
+ elif c == "t":
92
+ show_nonprinting = True
93
+ show_tabs = True
94
+ else:
95
+ return ExecResult(
96
+ stdout="",
97
+ stderr=f"cat: invalid option -- '{c}'\n",
98
+ exit_code=1,
99
+ )
100
+ else:
101
+ files.append(arg)
102
+ i += 1
103
+
104
+ # If number_nonblank is set, don't number all lines
105
+ if number_nonblank:
106
+ number_lines = False
107
+
108
+ # If no files, read from stdin
109
+ if not files:
110
+ files = ["-"]
111
+
112
+ stdout = ""
113
+ stderr = ""
114
+ exit_code = 0
115
+ line_number = 1
116
+ prev_blank = False
117
+
118
+ for file in files:
119
+ try:
120
+ if file == "-":
121
+ content = ctx.stdin
122
+ else:
123
+ # Resolve path
124
+ path = ctx.fs.resolve_path(ctx.cwd, file)
125
+ content = await ctx.fs.read_file(path)
126
+
127
+ lines = content.split("\n")
128
+
129
+ # Process each line
130
+ for j, line in enumerate(lines):
131
+ # Don't output trailing empty string from split
132
+ if j == len(lines) - 1 and line == "":
133
+ break
134
+
135
+ is_blank = line == ""
136
+
137
+ # Squeeze blank lines
138
+ if squeeze_blank and is_blank and prev_blank:
139
+ continue
140
+ prev_blank = is_blank
141
+
142
+ # Process line
143
+ output_line = line
144
+
145
+ # Show tabs
146
+ if show_tabs:
147
+ output_line = output_line.replace("\t", "^I")
148
+
149
+ # Show nonprinting (simplified - just shows tab replacement already done)
150
+ # Full implementation would handle all non-printable chars
151
+
152
+ # Show ends
153
+ if show_ends:
154
+ output_line += "$"
155
+
156
+ # Number lines
157
+ if number_lines or (number_nonblank and not is_blank):
158
+ output_line = f"{line_number:6}\t{output_line}"
159
+ line_number += 1
160
+
161
+ stdout += output_line + "\n"
162
+
163
+ except FileNotFoundError:
164
+ stderr += f"cat: {file}: No such file or directory\n"
165
+ exit_code = 1
166
+ except IsADirectoryError:
167
+ stderr += f"cat: {file}: Is a directory\n"
168
+ exit_code = 1
169
+ except PermissionError:
170
+ stderr += f"cat: {file}: Permission denied\n"
171
+ exit_code = 1
172
+
173
+ return ExecResult(stdout=stdout, stderr=stderr, exit_code=exit_code)
@@ -0,0 +1,5 @@
1
+ """Checksum commands."""
2
+
3
+ from .checksum import Md5sumCommand, Sha1sumCommand, Sha256sumCommand
4
+
5
+ __all__ = ["Md5sumCommand", "Sha1sumCommand", "Sha256sumCommand"]