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,180 @@
1
+ """Nl command implementation - number lines."""
2
+
3
+ from ...types import CommandContext, ExecResult
4
+
5
+
6
+ class NlCommand:
7
+ """The nl command - number lines."""
8
+
9
+ name = "nl"
10
+
11
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
12
+ """Execute the nl command."""
13
+ body_style = "t" # t=non-empty, a=all, n=none
14
+ number_format = "rn" # rn=right, ln=left, rz=right-zero
15
+ width = 6
16
+ separator = "\t"
17
+ start_num = 1
18
+ increment = 1
19
+ files: list[str] = []
20
+
21
+ i = 0
22
+ while i < len(args):
23
+ arg = args[i]
24
+ if arg == "-b" and i + 1 < len(args):
25
+ i += 1
26
+ body_style = args[i]
27
+ elif arg.startswith("-b"):
28
+ body_style = arg[2:]
29
+ elif arg == "-n" and i + 1 < len(args):
30
+ i += 1
31
+ number_format = args[i]
32
+ elif arg.startswith("-n"):
33
+ number_format = arg[2:]
34
+ elif arg == "-w" and i + 1 < len(args):
35
+ i += 1
36
+ try:
37
+ width = int(args[i])
38
+ except ValueError:
39
+ return ExecResult(
40
+ stdout="",
41
+ stderr=f"nl: invalid width: '{args[i]}'\n",
42
+ exit_code=1,
43
+ )
44
+ elif arg.startswith("-w"):
45
+ try:
46
+ width = int(arg[2:])
47
+ except ValueError:
48
+ return ExecResult(
49
+ stdout="",
50
+ stderr=f"nl: invalid width: '{arg[2:]}'\n",
51
+ exit_code=1,
52
+ )
53
+ elif arg == "-s" and i + 1 < len(args):
54
+ i += 1
55
+ separator = args[i]
56
+ elif arg.startswith("-s"):
57
+ separator = arg[2:]
58
+ elif arg == "-v" and i + 1 < len(args):
59
+ i += 1
60
+ try:
61
+ start_num = int(args[i])
62
+ except ValueError:
63
+ return ExecResult(
64
+ stdout="",
65
+ stderr=f"nl: invalid starting number: '{args[i]}'\n",
66
+ exit_code=1,
67
+ )
68
+ elif arg.startswith("-v"):
69
+ try:
70
+ start_num = int(arg[2:])
71
+ except ValueError:
72
+ return ExecResult(
73
+ stdout="",
74
+ stderr=f"nl: invalid starting number: '{arg[2:]}'\n",
75
+ exit_code=1,
76
+ )
77
+ elif arg == "-i" and i + 1 < len(args):
78
+ i += 1
79
+ try:
80
+ increment = int(args[i])
81
+ except ValueError:
82
+ return ExecResult(
83
+ stdout="",
84
+ stderr=f"nl: invalid increment: '{args[i]}'\n",
85
+ exit_code=1,
86
+ )
87
+ elif arg.startswith("-i"):
88
+ try:
89
+ increment = int(arg[2:])
90
+ except ValueError:
91
+ return ExecResult(
92
+ stdout="",
93
+ stderr=f"nl: invalid increment: '{arg[2:]}'\n",
94
+ exit_code=1,
95
+ )
96
+ elif arg == "--help":
97
+ return ExecResult(
98
+ stdout="Usage: nl [OPTION]... [FILE]...\n",
99
+ stderr="",
100
+ exit_code=0,
101
+ )
102
+ elif arg == "--":
103
+ files.extend(args[i + 1:])
104
+ break
105
+ elif arg.startswith("-") and len(arg) > 1:
106
+ return ExecResult(
107
+ stdout="",
108
+ stderr=f"nl: invalid option -- '{arg[1]}'\n",
109
+ exit_code=1,
110
+ )
111
+ else:
112
+ files.append(arg)
113
+ i += 1
114
+
115
+ # Validate body style
116
+ if body_style not in ("a", "t", "n"):
117
+ return ExecResult(
118
+ stdout="",
119
+ stderr=f"nl: invalid body numbering style: '{body_style}'\n",
120
+ exit_code=1,
121
+ )
122
+
123
+ # Validate number format
124
+ if number_format not in ("ln", "rn", "rz"):
125
+ return ExecResult(
126
+ stdout="",
127
+ stderr=f"nl: invalid line number format: '{number_format}'\n",
128
+ exit_code=1,
129
+ )
130
+
131
+ # Read content
132
+ if files:
133
+ content_parts = []
134
+ for f in files:
135
+ try:
136
+ path = ctx.fs.resolve_path(ctx.cwd, f)
137
+ content_parts.append(await ctx.fs.read_file(path))
138
+ except FileNotFoundError:
139
+ return ExecResult(
140
+ stdout="",
141
+ stderr=f"nl: {f}: No such file or directory\n",
142
+ exit_code=1,
143
+ )
144
+ content = "".join(content_parts)
145
+ else:
146
+ content = ctx.stdin
147
+
148
+ if not content:
149
+ return ExecResult(stdout="", stderr="", exit_code=0)
150
+
151
+ # Number lines
152
+ lines = content.splitlines()
153
+ output_lines = []
154
+ line_num = start_num
155
+
156
+ for line in lines:
157
+ should_number = False
158
+ if body_style == "a":
159
+ should_number = True
160
+ elif body_style == "t":
161
+ should_number = bool(line.strip())
162
+ # body_style == "n" means never number
163
+
164
+ if should_number:
165
+ if number_format == "ln":
166
+ num_str = str(line_num).ljust(width)
167
+ elif number_format == "rz":
168
+ num_str = str(line_num).zfill(width)
169
+ else: # rn
170
+ num_str = str(line_num).rjust(width)
171
+ output_lines.append(f"{num_str}{separator}{line}")
172
+ line_num += increment
173
+ else:
174
+ output_lines.append(" " * width + separator + line)
175
+
176
+ output = "\n".join(output_lines)
177
+ if content.endswith("\n"):
178
+ output += "\n"
179
+
180
+ return ExecResult(stdout=output, stderr="", exit_code=0)
@@ -0,0 +1,5 @@
1
+ """Od command."""
2
+
3
+ from .od import OdCommand
4
+
5
+ __all__ = ["OdCommand"]
@@ -0,0 +1,157 @@
1
+ """Od command implementation."""
2
+
3
+ from ...types import CommandContext, ExecResult
4
+
5
+
6
+ class OdCommand:
7
+ """The od command - dump files in various formats."""
8
+
9
+ name = "od"
10
+
11
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
12
+ """Execute the od command."""
13
+ format_type = "o" # octal (default)
14
+ address_format = "o" # octal addresses
15
+ suppress_address = False
16
+ files: list[str] = []
17
+
18
+ i = 0
19
+ while i < len(args):
20
+ arg = args[i]
21
+ if arg == "--help":
22
+ return ExecResult(
23
+ stdout="Usage: od [OPTION]... [FILE]...\nDump files in various formats.\n",
24
+ stderr="",
25
+ exit_code=0,
26
+ )
27
+ elif arg == "-c":
28
+ format_type = "c" # character
29
+ elif arg == "-x":
30
+ format_type = "x" # hexadecimal
31
+ elif arg == "-o":
32
+ format_type = "o" # octal
33
+ elif arg == "-d":
34
+ format_type = "d" # decimal
35
+ elif arg == "-An":
36
+ suppress_address = True
37
+ elif arg == "-Ad":
38
+ address_format = "d"
39
+ elif arg == "-Ao":
40
+ address_format = "o"
41
+ elif arg == "-Ax":
42
+ address_format = "x"
43
+ elif arg == "--":
44
+ files.extend(args[i + 1:])
45
+ break
46
+ elif arg.startswith("-") and len(arg) > 1:
47
+ return ExecResult(
48
+ stdout="",
49
+ stderr=f"od: invalid option -- '{arg[1]}'\n",
50
+ exit_code=1,
51
+ )
52
+ else:
53
+ files.append(arg)
54
+ i += 1
55
+
56
+ # Read from stdin if no files
57
+ if not files:
58
+ content = ctx.stdin.encode("utf-8", errors="replace")
59
+ result = self._dump(content, format_type, address_format, suppress_address)
60
+ return ExecResult(stdout=result, stderr="", exit_code=0)
61
+
62
+ stdout_parts = []
63
+ stderr = ""
64
+ exit_code = 0
65
+
66
+ for file in files:
67
+ try:
68
+ if file == "-":
69
+ content = ctx.stdin.encode("utf-8", errors="replace")
70
+ else:
71
+ path = ctx.fs.resolve_path(ctx.cwd, file)
72
+ content = await ctx.fs.read_file_bytes(path)
73
+
74
+ result = self._dump(content, format_type, address_format, suppress_address)
75
+ stdout_parts.append(result)
76
+
77
+ except FileNotFoundError:
78
+ stderr += f"od: {file}: No such file or directory\n"
79
+ exit_code = 1
80
+
81
+ return ExecResult(stdout="".join(stdout_parts), stderr=stderr, exit_code=exit_code)
82
+
83
+ def _dump(
84
+ self, data: bytes, format_type: str, address_format: str, suppress_address: bool
85
+ ) -> str:
86
+ """Dump data in specified format."""
87
+ result_lines = []
88
+ bytes_per_line = 16
89
+ offset = 0
90
+
91
+ while offset < len(data):
92
+ line_data = data[offset:offset + bytes_per_line]
93
+ parts = []
94
+
95
+ # Add address
96
+ if not suppress_address:
97
+ if address_format == "d":
98
+ parts.append(f"{offset:07d}")
99
+ elif address_format == "x":
100
+ parts.append(f"{offset:07x}")
101
+ else:
102
+ parts.append(f"{offset:07o}")
103
+
104
+ # Add data
105
+ if format_type == "c":
106
+ # Character format
107
+ chars = []
108
+ for byte in line_data:
109
+ if byte == 0:
110
+ chars.append("\\0")
111
+ elif byte == 7:
112
+ chars.append("\\a")
113
+ elif byte == 8:
114
+ chars.append("\\b")
115
+ elif byte == 9:
116
+ chars.append("\\t")
117
+ elif byte == 10:
118
+ chars.append("\\n")
119
+ elif byte == 11:
120
+ chars.append("\\v")
121
+ elif byte == 12:
122
+ chars.append("\\f")
123
+ elif byte == 13:
124
+ chars.append("\\r")
125
+ elif 32 <= byte <= 126:
126
+ chars.append(f" {chr(byte)}")
127
+ else:
128
+ chars.append(f"{byte:03o}")
129
+ parts.append(" ".join(chars))
130
+ elif format_type == "x":
131
+ # Hexadecimal format
132
+ hex_vals = [f"{byte:02x}" for byte in line_data]
133
+ parts.append(" ".join(hex_vals))
134
+ elif format_type == "d":
135
+ # Decimal format
136
+ dec_vals = [f"{byte:3d}" for byte in line_data]
137
+ parts.append(" ".join(dec_vals))
138
+ else:
139
+ # Octal format (default)
140
+ oct_vals = [f"{byte:03o}" for byte in line_data]
141
+ parts.append(" ".join(oct_vals))
142
+
143
+ result_lines.append(" ".join(parts))
144
+ offset += bytes_per_line
145
+
146
+ # Final address marker
147
+ if not suppress_address and data:
148
+ if address_format == "d":
149
+ result_lines.append(f"{len(data):07d}")
150
+ elif address_format == "x":
151
+ result_lines.append(f"{len(data):07x}")
152
+ else:
153
+ result_lines.append(f"{len(data):07o}")
154
+
155
+ if result_lines:
156
+ return "\n".join(result_lines) + "\n"
157
+ return ""
@@ -0,0 +1,5 @@
1
+ """Paste command."""
2
+
3
+ from .paste import PasteCommand
4
+
5
+ __all__ = ["PasteCommand"]
@@ -0,0 +1,100 @@
1
+ """Paste command implementation."""
2
+
3
+ from ...types import CommandContext, ExecResult
4
+
5
+
6
+ class PasteCommand:
7
+ """The paste command - merge lines of files."""
8
+
9
+ name = "paste"
10
+
11
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
12
+ """Execute the paste command."""
13
+ delimiter = "\t"
14
+ serial = False
15
+ files: list[str] = []
16
+
17
+ i = 0
18
+ while i < len(args):
19
+ arg = args[i]
20
+ if arg == "-d" and i + 1 < len(args):
21
+ i += 1
22
+ delimiter = args[i]
23
+ elif arg.startswith("-d"):
24
+ delimiter = arg[2:]
25
+ elif arg in ("-s", "--serial"):
26
+ serial = True
27
+ elif arg == "--help":
28
+ return ExecResult(
29
+ stdout="Usage: paste [OPTION]... [FILE]...\n",
30
+ stderr="",
31
+ exit_code=0,
32
+ )
33
+ elif arg == "--":
34
+ files.extend(args[i + 1:])
35
+ break
36
+ elif arg.startswith("--"):
37
+ return ExecResult(
38
+ stdout="",
39
+ stderr=f"paste: unrecognized option '{arg}'\n",
40
+ exit_code=1,
41
+ )
42
+ elif arg.startswith("-") and len(arg) > 1 and arg != "-":
43
+ return ExecResult(
44
+ stdout="",
45
+ stderr=f"paste: invalid option -- '{arg[1]}'\n",
46
+ exit_code=1,
47
+ )
48
+ else:
49
+ files.append(arg)
50
+ i += 1
51
+
52
+ if not files:
53
+ return ExecResult(
54
+ stdout="",
55
+ stderr="paste: missing operand\n",
56
+ exit_code=1,
57
+ )
58
+
59
+ # Read all files
60
+ file_contents: list[list[str]] = []
61
+ for f in files:
62
+ try:
63
+ if f == "-":
64
+ content = ctx.stdin
65
+ else:
66
+ path = ctx.fs.resolve_path(ctx.cwd, f)
67
+ content = await ctx.fs.read_file(path)
68
+ file_contents.append(content.splitlines())
69
+ except FileNotFoundError:
70
+ return ExecResult(
71
+ stdout="",
72
+ stderr=f"paste: {f}: No such file or directory\n",
73
+ exit_code=1,
74
+ )
75
+
76
+ if serial:
77
+ # Serial mode: paste all lines of each file into one line
78
+ output_lines = []
79
+ for lines in file_contents:
80
+ output_lines.append(delimiter.join(lines))
81
+ output = "\n".join(output_lines) + "\n"
82
+ else:
83
+ # Normal mode: paste corresponding lines
84
+ max_lines = max(len(lines) for lines in file_contents) if file_contents else 0
85
+ output_lines = []
86
+
87
+ for line_idx in range(max_lines):
88
+ parts = []
89
+ for file_idx, lines in enumerate(file_contents):
90
+ if line_idx < len(lines):
91
+ parts.append(lines[line_idx])
92
+ else:
93
+ parts.append("")
94
+ output_lines.append(delimiter.join(parts))
95
+
96
+ output = "\n".join(output_lines)
97
+ if output:
98
+ output += "\n"
99
+
100
+ return ExecResult(stdout=output, stderr="", exit_code=0)
@@ -0,0 +1,5 @@
1
+ """Printf command."""
2
+
3
+ from .printf import PrintfCommand
4
+
5
+ __all__ = ["PrintfCommand"]
@@ -0,0 +1,157 @@
1
+ """Printf command implementation."""
2
+
3
+ import re
4
+ from ...types import CommandContext, ExecResult
5
+
6
+
7
+ class PrintfCommand:
8
+ """The printf command."""
9
+
10
+ name = "printf"
11
+
12
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
13
+ """Execute the printf command."""
14
+ if not args:
15
+ return ExecResult(
16
+ stdout="",
17
+ stderr="printf: usage: printf format [arguments]\n",
18
+ exit_code=2,
19
+ )
20
+
21
+ format_str = args[0]
22
+ arguments = args[1:]
23
+
24
+ try:
25
+ output = self._format(format_str, arguments)
26
+ return ExecResult(stdout=output, stderr="", exit_code=0)
27
+ except ValueError as e:
28
+ return ExecResult(stdout="", stderr=f"printf: {e}\n", exit_code=1)
29
+
30
+ def _format(self, format_str: str, arguments: list[str]) -> str:
31
+ """Format the string with arguments."""
32
+ result = []
33
+ arg_index = 0
34
+ i = 0
35
+
36
+ while i < len(format_str):
37
+ if format_str[i] == "\\" and i + 1 < len(format_str):
38
+ # Handle escape sequences
39
+ escape_char = format_str[i + 1]
40
+ if escape_char == "n":
41
+ result.append("\n")
42
+ elif escape_char == "t":
43
+ result.append("\t")
44
+ elif escape_char == "r":
45
+ result.append("\r")
46
+ elif escape_char == "\\":
47
+ result.append("\\")
48
+ elif escape_char == "a":
49
+ result.append("\a")
50
+ elif escape_char == "b":
51
+ result.append("\b")
52
+ elif escape_char == "f":
53
+ result.append("\f")
54
+ elif escape_char == "v":
55
+ result.append("\v")
56
+ elif escape_char == "e" or escape_char == "E":
57
+ result.append("\x1b")
58
+ elif escape_char == "0":
59
+ # Octal escape
60
+ octal = ""
61
+ j = i + 2
62
+ while j < len(format_str) and len(octal) < 3 and format_str[j] in "01234567":
63
+ octal += format_str[j]
64
+ j += 1
65
+ if octal:
66
+ result.append(chr(int(octal, 8)))
67
+ i = j
68
+ continue
69
+ else:
70
+ result.append("\0")
71
+ elif escape_char == "x":
72
+ # Hex escape
73
+ hex_digits = ""
74
+ j = i + 2
75
+ while j < len(format_str) and len(hex_digits) < 2 and format_str[j] in "0123456789abcdefABCDEF":
76
+ hex_digits += format_str[j]
77
+ j += 1
78
+ if hex_digits:
79
+ result.append(chr(int(hex_digits, 16)))
80
+ i = j
81
+ continue
82
+ else:
83
+ result.append(escape_char)
84
+ else:
85
+ result.append(escape_char)
86
+ i += 2
87
+ elif format_str[i] == "%" and i + 1 < len(format_str):
88
+ # Handle format specifiers
89
+ if format_str[i + 1] == "%":
90
+ result.append("%")
91
+ i += 2
92
+ continue
93
+
94
+ # Parse format specifier
95
+ spec_match = re.match(r"-?(\d+)?(\.\d+)?([diouxXeEfFgGsbc])", format_str[i + 1:])
96
+ if spec_match:
97
+ spec = spec_match.group(0)
98
+ full_spec = "%" + spec
99
+ spec_type = spec_match.group(3)
100
+
101
+ # Get argument
102
+ if arg_index < len(arguments):
103
+ arg = arguments[arg_index]
104
+ arg_index += 1
105
+ else:
106
+ arg = ""
107
+
108
+ # Format based on type
109
+ try:
110
+ if spec_type in "diouxX":
111
+ val = int(arg) if arg else 0
112
+ result.append(full_spec % val)
113
+ elif spec_type in "eEfFgG":
114
+ val = float(arg) if arg else 0.0
115
+ result.append(full_spec % val)
116
+ elif spec_type == "s":
117
+ result.append(full_spec % arg)
118
+ elif spec_type == "c":
119
+ result.append(arg[0] if arg else "")
120
+ elif spec_type == "b":
121
+ # %b is like %s but interprets escapes
122
+ result.append(self._process_escapes(arg))
123
+ except (ValueError, TypeError):
124
+ result.append(full_spec % 0 if spec_type in "diouxXeEfFgG" else "")
125
+
126
+ i += 1 + len(spec)
127
+ else:
128
+ result.append(format_str[i])
129
+ i += 1
130
+ else:
131
+ result.append(format_str[i])
132
+ i += 1
133
+
134
+ return "".join(result)
135
+
136
+ def _process_escapes(self, s: str) -> str:
137
+ """Process escape sequences in a string."""
138
+ result = []
139
+ i = 0
140
+ while i < len(s):
141
+ if s[i] == "\\" and i + 1 < len(s):
142
+ c = s[i + 1]
143
+ if c == "n":
144
+ result.append("\n")
145
+ elif c == "t":
146
+ result.append("\t")
147
+ elif c == "r":
148
+ result.append("\r")
149
+ elif c == "\\":
150
+ result.append("\\")
151
+ else:
152
+ result.append(c)
153
+ i += 2
154
+ else:
155
+ result.append(s[i])
156
+ i += 1
157
+ return "".join(result)
@@ -0,0 +1,5 @@
1
+ """Pwd command implementation."""
2
+
3
+ from .pwd import PwdCommand
4
+
5
+ __all__ = ["PwdCommand"]
@@ -0,0 +1,23 @@
1
+ """Pwd command implementation.
2
+
3
+ Usage: pwd [-LP]
4
+
5
+ Print the name of the current working directory.
6
+
7
+ Options:
8
+ -L Print the value of $PWD if it names the current working directory
9
+ -P Print the physical directory, without any symbolic links
10
+ """
11
+
12
+ from ...types import CommandContext, ExecResult
13
+
14
+
15
+ class PwdCommand:
16
+ """The pwd command."""
17
+
18
+ name = "pwd"
19
+
20
+ async def execute(self, args: list[str], ctx: CommandContext) -> ExecResult:
21
+ """Execute the pwd command."""
22
+ # For now, just return cwd (ignore -L/-P options in virtual fs)
23
+ return ExecResult(stdout=f"{ctx.cwd}\n", stderr="", exit_code=0)
@@ -0,0 +1,5 @@
1
+ """Read command module."""
2
+
3
+ from .read import ReadCommand
4
+
5
+ __all__ = ["ReadCommand"]