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,387 @@
1
+ """Conditional Expression Evaluation.
2
+
3
+ Handles:
4
+ - [[ ... ]] conditional commands
5
+ - File tests (-f, -d, -e, etc.)
6
+ - String tests (-z, -n, =, !=)
7
+ - Numeric comparisons (-eq, -ne, -lt, etc.)
8
+ - Pattern matching (==, =~)
9
+ """
10
+
11
+ import fnmatch
12
+ import re
13
+ from typing import TYPE_CHECKING, Union
14
+
15
+ from ..ast.types import (
16
+ CondBinaryNode,
17
+ CondUnaryNode,
18
+ CondNotNode,
19
+ CondAndNode,
20
+ CondOrNode,
21
+ CondGroupNode,
22
+ CondWordNode,
23
+ ConditionalExpressionNode,
24
+ )
25
+
26
+ if TYPE_CHECKING:
27
+ from .types import InterpreterContext
28
+
29
+
30
+ # File test operators
31
+ FILE_TEST_OPS = {"-f", "-d", "-e", "-s", "-r", "-w", "-x", "-h", "-L", "-p", "-S", "-b", "-c", "-t", "-O", "-G", "-u", "-g", "-k", "-N"}
32
+
33
+ # Binary file test operators
34
+ BINARY_FILE_TEST_OPS = {"-nt", "-ot", "-ef"}
35
+
36
+ # String test operators
37
+ STRING_TEST_OPS = {"-z", "-n"}
38
+
39
+ # String comparison operators
40
+ STRING_COMPARE_OPS = {"=", "==", "!="}
41
+
42
+ # Numeric comparison operators
43
+ NUMERIC_OPS = {"-eq", "-ne", "-lt", "-le", "-gt", "-ge"}
44
+
45
+
46
+ async def evaluate_conditional(
47
+ ctx: "InterpreterContext",
48
+ expr: ConditionalExpressionNode,
49
+ ) -> bool:
50
+ """Evaluate a conditional expression from [[ ... ]]."""
51
+ from .expansion import expand_word_async
52
+
53
+ if isinstance(expr, CondBinaryNode):
54
+ left = await expand_word_async(ctx, expr.left) if expr.left else ""
55
+ right = await expand_word_async(ctx, expr.right) if expr.right else ""
56
+
57
+ # Check if RHS is fully quoted (should be treated literally, not as pattern)
58
+ is_rhs_quoted = False
59
+ if expr.right and expr.right.parts:
60
+ is_rhs_quoted = all(
61
+ p.type in ("SingleQuoted", "DoubleQuoted", "Escaped")
62
+ for p in expr.right.parts
63
+ )
64
+
65
+ op = expr.operator
66
+
67
+ # String comparisons (with pattern matching support in [[ ]])
68
+ if op in STRING_COMPARE_OPS:
69
+ return compare_strings(op, left, right, allow_pattern=not is_rhs_quoted)
70
+
71
+ # Numeric comparisons
72
+ if op in NUMERIC_OPS:
73
+ return compare_numeric(op, parse_numeric(left), parse_numeric(right))
74
+
75
+ # Binary file tests
76
+ if op in BINARY_FILE_TEST_OPS:
77
+ return await evaluate_binary_file_test(ctx, op, left, right)
78
+
79
+ # Regex matching
80
+ if op == "=~":
81
+ try:
82
+ match = re.search(right, left)
83
+ if match:
84
+ # Set BASH_REMATCH
85
+ ctx.state.env["BASH_REMATCH_0"] = match.group(0)
86
+ for i, group in enumerate(match.groups(), 1):
87
+ ctx.state.env[f"BASH_REMATCH_{i}"] = group if group else ""
88
+ ctx.state.env["BASH_REMATCH__length"] = str(len(match.groups()) + 1)
89
+ return match is not None
90
+ except re.error:
91
+ # Invalid regex is a syntax error (exit code 2)
92
+ raise ValueError("syntax error in regular expression")
93
+
94
+ # Lexicographic comparison
95
+ if op == "<":
96
+ return left < right
97
+ if op == ">":
98
+ return left > right
99
+
100
+ return False
101
+
102
+ elif isinstance(expr, CondUnaryNode):
103
+ operand = await expand_word_async(ctx, expr.operand) if expr.operand else ""
104
+ op = expr.operator
105
+
106
+ # File test operators
107
+ if op in FILE_TEST_OPS:
108
+ return await evaluate_file_test(ctx, op, operand)
109
+
110
+ # String tests
111
+ if op == "-z":
112
+ return operand == ""
113
+ if op == "-n":
114
+ return operand != ""
115
+
116
+ # Variable test
117
+ if op == "-v":
118
+ return evaluate_variable_test(ctx, operand)
119
+
120
+ # Shell option test
121
+ if op == "-o":
122
+ return evaluate_shell_option(ctx, operand)
123
+
124
+ return False
125
+
126
+ elif isinstance(expr, CondNotNode):
127
+ return not await evaluate_conditional(ctx, expr.operand)
128
+
129
+ elif isinstance(expr, CondAndNode):
130
+ left = await evaluate_conditional(ctx, expr.left)
131
+ if not left:
132
+ return False
133
+ return await evaluate_conditional(ctx, expr.right)
134
+
135
+ elif isinstance(expr, CondOrNode):
136
+ left = await evaluate_conditional(ctx, expr.left)
137
+ if left:
138
+ return True
139
+ return await evaluate_conditional(ctx, expr.right)
140
+
141
+ elif isinstance(expr, CondGroupNode):
142
+ return await evaluate_conditional(ctx, expr.expression)
143
+
144
+ elif isinstance(expr, CondWordNode):
145
+ value = await expand_word_async(ctx, expr.word) if expr.word else ""
146
+ return value != ""
147
+
148
+ return False
149
+
150
+
151
+ def compare_strings(op: str, left: str, right: str, allow_pattern: bool = False) -> bool:
152
+ """Compare strings, optionally with pattern matching."""
153
+ if op == "=" or op == "==":
154
+ if allow_pattern:
155
+ return match_pattern(left, right)
156
+ return left == right
157
+ if op == "!=":
158
+ if allow_pattern:
159
+ return not match_pattern(left, right)
160
+ return left != right
161
+ return False
162
+
163
+
164
+ def match_pattern(value: str, pattern: str) -> bool:
165
+ """Match a value against a glob-style pattern.
166
+
167
+ Converts glob pattern to regex for matching.
168
+ """
169
+ # Use fnmatch for glob-style matching
170
+ return fnmatch.fnmatch(value, pattern)
171
+
172
+
173
+ def compare_numeric(op: str, left: int, right: int) -> bool:
174
+ """Compare two numbers."""
175
+ if op == "-eq":
176
+ return left == right
177
+ if op == "-ne":
178
+ return left != right
179
+ if op == "-lt":
180
+ return left < right
181
+ if op == "-le":
182
+ return left <= right
183
+ if op == "-gt":
184
+ return left > right
185
+ if op == "-ge":
186
+ return left >= right
187
+ return False
188
+
189
+
190
+ def parse_numeric(value: str) -> int:
191
+ """Parse a bash numeric value.
192
+
193
+ Supports:
194
+ - Decimal: 42, -42
195
+ - Octal: 0777, -0123
196
+ - Hex: 0xff, 0xFF, -0xff
197
+ - Base-N: 64#a, 2#1010
198
+ - Strings are coerced to 0
199
+ """
200
+ value = value.strip()
201
+ if not value:
202
+ return 0
203
+
204
+ # Handle negative numbers
205
+ negative = False
206
+ if value.startswith("-"):
207
+ negative = True
208
+ value = value[1:]
209
+ elif value.startswith("+"):
210
+ value = value[1:]
211
+
212
+ result = 0
213
+
214
+ # Base-N syntax: base#value
215
+ base_match = re.match(r'^(\d+)#([a-zA-Z0-9@_]+)$', value)
216
+ if base_match:
217
+ base = int(base_match.group(1))
218
+ if 2 <= base <= 64:
219
+ result = parse_base_n(base_match.group(2), base)
220
+ else:
221
+ result = 0
222
+ # Hex: 0x or 0X
223
+ elif re.match(r'^0[xX][0-9a-fA-F]+$', value):
224
+ result = int(value, 16)
225
+ # Octal: starts with 0 followed by octal digits
226
+ elif re.match(r'^0[0-7]+$', value):
227
+ result = int(value, 8)
228
+ # Decimal
229
+ else:
230
+ try:
231
+ result = int(value)
232
+ except ValueError:
233
+ result = 0
234
+
235
+ return -result if negative else result
236
+
237
+
238
+ def parse_base_n(digits: str, base: int) -> int:
239
+ """Parse a number in base N (2-64).
240
+
241
+ Digit values: 0-9=0-9, a-z=10-35, A-Z=36-61, @=62, _=63
242
+ """
243
+ result = 0
244
+ for char in digits:
245
+ if '0' <= char <= '9':
246
+ digit_value = ord(char) - ord('0')
247
+ elif 'a' <= char <= 'z':
248
+ digit_value = ord(char) - ord('a') + 10
249
+ elif 'A' <= char <= 'Z':
250
+ digit_value = ord(char) - ord('A') + 36
251
+ elif char == '@':
252
+ digit_value = 62
253
+ elif char == '_':
254
+ digit_value = 63
255
+ else:
256
+ return 0
257
+
258
+ if digit_value >= base:
259
+ return 0
260
+
261
+ result = result * base + digit_value
262
+
263
+ return result
264
+
265
+
266
+ async def evaluate_file_test(ctx: "InterpreterContext", op: str, path: str) -> bool:
267
+ """Evaluate a file test operator."""
268
+ full_path = ctx.fs.resolve_path(ctx.state.cwd, path)
269
+
270
+ try:
271
+ exists = await ctx.fs.exists(full_path)
272
+
273
+ if op == "-e":
274
+ return exists
275
+
276
+ if not exists:
277
+ return False
278
+
279
+ if op == "-d":
280
+ return await ctx.fs.is_directory(full_path)
281
+
282
+ if op == "-f":
283
+ return not await ctx.fs.is_directory(full_path)
284
+
285
+ if op == "-s":
286
+ content = await ctx.fs.read_file(full_path)
287
+ return len(content) > 0
288
+
289
+ # For r/w/x, we assume true if file exists (VFS doesn't track permissions)
290
+ if op in ("-r", "-w", "-x"):
291
+ return exists
292
+
293
+ # Symlink tests
294
+ if op in ("-h", "-L"):
295
+ # VFS doesn't track symlinks, assume false
296
+ return False
297
+
298
+ # Special file tests (pipe, socket, block, char)
299
+ if op in ("-p", "-S", "-b", "-c"):
300
+ return False
301
+
302
+ # Terminal test
303
+ if op == "-t":
304
+ return False
305
+
306
+ # Owner tests
307
+ if op in ("-O", "-G"):
308
+ return exists
309
+
310
+ # Permission bit tests
311
+ if op in ("-u", "-g", "-k"):
312
+ return False
313
+
314
+ # File modified since last read
315
+ if op == "-N":
316
+ return False
317
+
318
+ return False
319
+ except Exception:
320
+ return False
321
+
322
+
323
+ async def evaluate_binary_file_test(
324
+ ctx: "InterpreterContext", op: str, left: str, right: str
325
+ ) -> bool:
326
+ """Evaluate binary file test operators (-nt, -ot, -ef)."""
327
+ # These require file modification times which VFS may not track
328
+ # For now, return False
329
+ left_path = ctx.fs.resolve_path(ctx.state.cwd, left)
330
+ right_path = ctx.fs.resolve_path(ctx.state.cwd, right)
331
+
332
+ try:
333
+ left_exists = await ctx.fs.exists(left_path)
334
+ right_exists = await ctx.fs.exists(right_path)
335
+
336
+ if op == "-nt": # newer than
337
+ # If left exists and right doesn't, left is newer
338
+ if left_exists and not right_exists:
339
+ return True
340
+ return False
341
+
342
+ if op == "-ot": # older than
343
+ # If right exists and left doesn't, left is older
344
+ if right_exists and not left_exists:
345
+ return True
346
+ return False
347
+
348
+ if op == "-ef": # same file
349
+ # Check if both point to same file (VFS: check paths)
350
+ return left_path == right_path and left_exists
351
+
352
+ return False
353
+ except Exception:
354
+ return False
355
+
356
+
357
+ def evaluate_variable_test(ctx: "InterpreterContext", name: str) -> bool:
358
+ """Test if a variable is set (-v)."""
359
+ # Handle array element syntax: arr[idx]
360
+ if "[" in name and name.endswith("]"):
361
+ base = name[:name.index("[")]
362
+ idx = name[name.index("[") + 1:-1]
363
+ # Check if array element exists
364
+ key = f"{base}_{idx}"
365
+ return key in ctx.state.env
366
+
367
+ return name in ctx.state.env
368
+
369
+
370
+ def evaluate_shell_option(ctx: "InterpreterContext", option: str) -> bool:
371
+ """Test if a shell option is enabled (-o)."""
372
+ option_map = {
373
+ "errexit": lambda: ctx.state.options.errexit,
374
+ "nounset": lambda: ctx.state.options.nounset,
375
+ "pipefail": lambda: ctx.state.options.pipefail,
376
+ "xtrace": lambda: ctx.state.options.xtrace,
377
+ "verbose": lambda: ctx.state.options.verbose,
378
+ "e": lambda: ctx.state.options.errexit,
379
+ "u": lambda: ctx.state.options.nounset,
380
+ "x": lambda: ctx.state.options.xtrace,
381
+ "v": lambda: ctx.state.options.verbose,
382
+ }
383
+
384
+ getter = option_map.get(option)
385
+ if getter:
386
+ return getter()
387
+ return False