srsh 0.8.0 → 1.0.1
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.
- checksums.yaml +4 -4
- data/LICENSE +12 -3
- data/README.md +446 -8
- data/bin/srsh +71 -0
- data/docs/assets/slut.txt +4 -0
- data/docs/assets/srsh-mark.svg +12 -0
- data/docs/css/style.css +696 -0
- data/docs/index.html +703 -0
- data/docs/js/app.js +203 -0
- data/examples/bridge.rsh +8 -0
- data/examples/calculator.rsh +253 -0
- data/examples/defer.rsh +14 -0
- data/examples/hot.rsh +14 -0
- data/examples/meta.rsh +20 -0
- data/examples/modules/text.rsh +6 -0
- data/examples/modules.rsh +6 -0
- data/examples/paste.rsh +15 -0
- data/examples/plugin.rb +8 -0
- data/examples/power.rsh +65 -0
- data/examples/tour.rsh +38 -0
- data/ext/srsh_native/extconf.rb +3 -0
- data/ext/srsh_native/srsh_native.c +48 -0
- data/language-docs/LANGUAGE.md +670 -0
- data/language-docs/MIGRATION.md +44 -0
- data/language-docs/SECURITY.md +44 -0
- data/lib/srsh/app.rb +261 -0
- data/lib/srsh/builtins.rb +492 -0
- data/lib/srsh/editor.rb +530 -0
- data/lib/srsh/errors.rb +23 -0
- data/lib/srsh/history.rb +74 -0
- data/lib/srsh/language/evaluator.rb +1175 -0
- data/lib/srsh/language/lexer.rb +316 -0
- data/lib/srsh/language/parser.rb +997 -0
- data/lib/srsh/language/token.rb +5 -0
- data/lib/srsh/language/values.rb +390 -0
- data/lib/srsh/paths.rb +29 -0
- data/lib/srsh/plugins.rb +59 -0
- data/lib/srsh/process_identity.rb +38 -0
- data/lib/srsh/security.rb +38 -0
- data/lib/srsh/shell/executor.rb +1182 -0
- data/lib/srsh/shell/job.rb +101 -0
- data/lib/srsh/shell/lexer.rb +114 -0
- data/lib/srsh/shell/terminal.rb +26 -0
- data/lib/srsh/state.rb +136 -0
- data/lib/srsh/theme.rb +108 -0
- data/lib/srsh/version.rb +3 -0
- data/lib/srsh.rb +16 -4
- metadata +72 -15
- data/exe/srsh +0 -6
- data/lib/srsh/runner.rb +0 -2416
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ddbe1c3a93674d1cd10bd2c47cbea50a6971b081d10122a771b300f2c33cd6ba
|
|
4
|
+
data.tar.gz: 6667a7f67f97db531fff8389adc162d18747b4f782f0583b4cfd7443d7983efb
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 3927998d22cb5faf8e4448691071e9afe178aca4b6fd62fcb175ad78f0ffa2903c981a1a984037b57f109ad592a74e6bf35916b2a0c4bb82296dc1108fb3c79f
|
|
7
|
+
data.tar.gz: 1f0d80a3ceda4f22f56d01a2057063d06c01cb005e6a8f284f3573394d590c681cda56f25681295a20c4544c2a258839df36004d6144d5e649318240213eb814
|
data/LICENSE
CHANGED
|
@@ -1,12 +1,21 @@
|
|
|
1
1
|
MIT License
|
|
2
2
|
|
|
3
|
-
Copyright (c)
|
|
3
|
+
Copyright (c) 2026 RobertFlexx
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
|
7
7
|
in the Software without restriction, including without limitation the rights
|
|
8
8
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
9
|
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
CHANGED
|
@@ -1,14 +1,452 @@
|
|
|
1
|
-
# srsh
|
|
1
|
+
# srsh, Simple Ruby Shell
|
|
2
2
|
|
|
3
|
-
srsh is a
|
|
4
|
-
theme system, plugin support, and advanced line-editing features.
|
|
3
|
+
`srsh` is a Unix shell written in Ruby. Its scripting language is **RSH**.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
The basic idea is pretty simple: keep the parts of a normal shell that are already good, then stop making people switch languages when a script gets serious.
|
|
7
6
|
|
|
8
|
-
|
|
7
|
+
```rsh
|
|
8
|
+
printf 'c\na\nb\n' | sort
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
files := glob("src/**/*.rb")
|
|
11
|
+
|> reject(::p => contains(p, "/vendor/"))
|
|
12
|
+
|> sort
|
|
11
13
|
|
|
12
|
-
|
|
14
|
+
? files |> len > 20 => = "that's a lot of Ruby"
|
|
15
|
+
```
|
|
13
16
|
|
|
14
|
-
|
|
17
|
+
`|` is still a Unix process pipe. `|>` is an RSH value pipe. They look related because they *are* related, but they move different things.
|
|
18
|
+
|
|
19
|
+
This tree reports **1.0.1**. It keeps the 1.0 language and moves the supported runtime to Ruby 4.
|
|
20
|
+
|
|
21
|
+
## What SRSH is trying to be
|
|
22
|
+
|
|
23
|
+
Bash is fantastic at launching programs. It gets a lot less fun when you need nested data, workers, reusable modules, objects, error handling, higher-order functions, or a pile of state that has to remain understandable next month.
|
|
24
|
+
|
|
25
|
+
RSH is meant to cover that gap without turning the shell into a foreign environment:
|
|
26
|
+
|
|
27
|
+
```rsh
|
|
28
|
+
branch := $(git branch --show-current)
|
|
29
|
+
|
|
30
|
+
fn changed(path) => cmd("git", "diff", "--quiet", "--", path).result().status != 0
|
|
31
|
+
|
|
32
|
+
dirty := glob("src/**/*") |> filter(changed)
|
|
33
|
+
= "#{branch}: #{dirty |> len} changed files"
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
It is **not a Bash parser**. Existing Bash scripts still belong to Bash. The goal is that new automation can start as a shell one-liner and grow into a proper RSH program without a rewrite.
|
|
37
|
+
|
|
38
|
+
## Build and run
|
|
39
|
+
|
|
40
|
+
Ruby 4.0+ is required. The repository selects the current Ruby 4.0 patch release through `.ruby-version`.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
make test
|
|
44
|
+
./bin/srsh
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
The shell is fully usable as Ruby-only code. There is a tiny optional C extension for hot lexer work and Linux process naming:
|
|
48
|
+
|
|
49
|
+
```sh
|
|
50
|
+
make native
|
|
51
|
+
./bin/srsh
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
YJIT is enabled when the running Ruby supports it. Set `SRSH_YJIT=0` if you want it off.
|
|
55
|
+
|
|
56
|
+
Install somewhere else with:
|
|
57
|
+
|
|
58
|
+
```sh
|
|
59
|
+
make PREFIX="$HOME/.local" install
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## The syntax has a reason
|
|
63
|
+
|
|
64
|
+
RSH has some odd-looking syntax, but the goal is not to win a weird-language contest.
|
|
65
|
+
|
|
66
|
+
There are a few boundaries you hit constantly in shell programming, and RSH gives each one a visible shape:
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
command | command bytes/processes move between Unix programs
|
|
70
|
+
value |> function RSH values move through functions
|
|
71
|
+
$(command) process output crosses into the value layer
|
|
72
|
+
name := value local RSH binding
|
|
73
|
+
$NAME := value exported process environment
|
|
74
|
+
::x => expression tiny callable value
|
|
75
|
+
bridge ... from ... RSH crosses into a native C ABI
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That makes parsing less ambiguous around arbitrary Unix command names, and it lets hot scripts stay short without turning maintained code into punctuation soup.
|
|
79
|
+
|
|
80
|
+
Readable forms exist too:
|
|
81
|
+
|
|
82
|
+
```rsh
|
|
83
|
+
fn deploy(files, dry := no)
|
|
84
|
+
if dry
|
|
85
|
+
= "would deploy #{files |> len} files"
|
|
86
|
+
else
|
|
87
|
+
each files -> path
|
|
88
|
+
emit path
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The short forms lower to the same AST:
|
|
95
|
+
|
|
96
|
+
```rsh
|
|
97
|
+
:: twice(x) => x * 2
|
|
98
|
+
? ready => emit "go"
|
|
99
|
+
@ jobs -> job => = job.name
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use whichever one fits the size of the job.
|
|
103
|
+
|
|
104
|
+
## The REPL is actually an RSH REPL
|
|
105
|
+
|
|
106
|
+
Bindings, expressions and blocks work directly at the shell prompt:
|
|
107
|
+
|
|
108
|
+
```text
|
|
109
|
+
> x := 21
|
|
110
|
+
> = x * 2
|
|
111
|
+
42
|
|
112
|
+
|
|
113
|
+
> fn triple(n)
|
|
114
|
+
... return n * 3
|
|
115
|
+
... end
|
|
116
|
+
> = triple(10)
|
|
117
|
+
30
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Multiline list/map/call expressions also continue properly:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
> jobs := [
|
|
124
|
+
... fetch("a"),
|
|
125
|
+
... fetch("b")
|
|
126
|
+
... ]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
That same completeness logic is used for manually entered continuations. For an actual terminal paste, SRSH enables bracketed-paste mode and captures the whole clipboard payload before parsing any of it. Multi-line pastes get a small preview; press Enter once to run the whole program or Ctrl-C to throw it away. Embedded newlines never become a queue of accidental commands.
|
|
130
|
+
|
|
131
|
+
The editor keeps the old SRSH behavior too: prefix history prediction, Right Arrow to accept it, UTF-8 cursor movement, history navigation, and first-Tab/second-Tab completion.
|
|
132
|
+
|
|
133
|
+
## Shell side
|
|
134
|
+
|
|
135
|
+
Normal Unix muscle memory is supposed to work:
|
|
136
|
+
|
|
137
|
+
```sh
|
|
138
|
+
cat *.log | grep ERROR | sort -u
|
|
139
|
+
make -j8 && put "built"
|
|
140
|
+
long_job &
|
|
141
|
+
jobs
|
|
142
|
+
fg %1
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Implemented shell features include:
|
|
146
|
+
|
|
147
|
+
- `fork`/`exec` pipelines and process groups
|
|
148
|
+
- `|`, `&&`, `||`, `;`, background `&`
|
|
149
|
+
- `<`, `>`, `>>`, `2>`, `2>>`
|
|
150
|
+
- `jobs`, `fg`, `bg`
|
|
151
|
+
- command substitution with nested `$(...)`
|
|
152
|
+
- aliases
|
|
153
|
+
- `$VAR`, `${VAR}`, `$?`, `$!`, positional arguments
|
|
154
|
+
- pathname globbing (`*`, `?`, `[...]`, and recursive `**` where Ruby's glob supports it)
|
|
155
|
+
- `~` expansion
|
|
156
|
+
- command lookup caching
|
|
157
|
+
- `cd`, `pwd`, `ls`, `printf`, `export`, `read`, `source`, `which`/`type`
|
|
158
|
+
- `pushd`, `popd`, `dirs`, `umask`, `kill`
|
|
159
|
+
- `option pipefail`, `option nounset`, `option noclobber`
|
|
160
|
+
- `option strict yes` as a shortcut for `pipefail + nounset`
|
|
161
|
+
|
|
162
|
+
No-match globs stay literal, which is a much less surprising default for an interactive shell.
|
|
163
|
+
|
|
164
|
+
## Values and functional scripting
|
|
165
|
+
|
|
166
|
+
RSH has integers, floats, strings, booleans, `void`, lists, maps, ranges, lambdas, code values, tasks, prototypes, namespaces, native handles and structured commands.
|
|
167
|
+
|
|
168
|
+
```rsh
|
|
169
|
+
users := json(readfile("users.json"))
|
|
170
|
+
|
|
171
|
+
enabled := users
|
|
172
|
+
|> filter(::u => u?.enabled ?? no)
|
|
173
|
+
|> sort(::u => lower(u.name))
|
|
174
|
+
|
|
175
|
+
= enabled |> map(::u => u.name)
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The usual functional operations are built in:
|
|
179
|
+
|
|
180
|
+
```text
|
|
181
|
+
map filter reject fold find any all count sum
|
|
182
|
+
sort uniq flat zip enumerate take drop chunk group
|
|
183
|
+
each tap partial compose
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Named functions are first-class too:
|
|
187
|
+
|
|
188
|
+
```rsh
|
|
189
|
+
fn square(x) => x * x
|
|
190
|
+
= 1 .. 10 |> map(square) |> sum
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Objects without a ceremony tax
|
|
194
|
+
|
|
195
|
+
RSH uses prototypes plus traits. There is no class-inheritance maze to climb through just to hold some state.
|
|
196
|
+
|
|
197
|
+
```rsh
|
|
198
|
+
trait Named
|
|
199
|
+
fn label() => "#{self.name}: #{self.value}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
proto Counter(name, start := 0) with Named
|
|
203
|
+
slot name := name
|
|
204
|
+
slot value := start
|
|
205
|
+
|
|
206
|
+
fn inc(by := 1)
|
|
207
|
+
self.value += by
|
|
208
|
+
return self
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
hits := Counter("hits")
|
|
213
|
+
hits.inc(3)
|
|
214
|
+
= hits.label()
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Object slot updates are synchronized, so objects can be an explicit shared-state tool between tasks.
|
|
218
|
+
|
|
219
|
+
## Concurrency
|
|
220
|
+
|
|
221
|
+
Async RSH functions return task values immediately:
|
|
222
|
+
|
|
223
|
+
```rsh
|
|
224
|
+
task fetch(url)
|
|
225
|
+
return cmd("curl", "-fsS", url).check().out
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
jobs := urls |> map(fetch)
|
|
229
|
+
pages := await_all(jobs)
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
You can spawn a lambda directly:
|
|
233
|
+
|
|
234
|
+
```rsh
|
|
235
|
+
job := &:: => expensive_io()
|
|
236
|
+
= job.await(2.0)
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
Shared mutation is explicit:
|
|
240
|
+
|
|
241
|
+
```rsh
|
|
242
|
+
hits := atom(0)
|
|
243
|
+
workers := 0 ..< 32 |> map(::i => &:: => hits.swap(::n => n + 1))
|
|
244
|
+
await_all(workers)
|
|
245
|
+
= hits.get()
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
There are channels too:
|
|
249
|
+
|
|
250
|
+
```rsh
|
|
251
|
+
queue := chan(8)
|
|
252
|
+
queue.send("hello")
|
|
253
|
+
= queue.recv(1)
|
|
254
|
+
queue.close()
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
`parallel()` is a Ruby thread pool, which is great for I/O and subprocess waits. On CRuby the GVL still exists; SRSH does not pretend otherwise. `pmap()` uses Unix fork workers when you actually want CPU parallelism.
|
|
258
|
+
|
|
259
|
+
`race()` cancels losing task handles. A failing `await_all()` cancels still-running siblings before propagating the error.
|
|
260
|
+
|
|
261
|
+
## Modules, namespaces and cleanup
|
|
262
|
+
|
|
263
|
+
Namespaces can live in the same file:
|
|
264
|
+
|
|
265
|
+
```rsh
|
|
266
|
+
space mathx
|
|
267
|
+
bias := 10
|
|
268
|
+
fn bump(x) => x + bias
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
= mathx.bump(5)
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
Or load a file as a namespace:
|
|
275
|
+
|
|
276
|
+
```rsh
|
|
277
|
+
use "./lib/http.rsh" as http
|
|
278
|
+
= http.get("https://example.com")
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
Module paths are resolved relative to the importing script when possible, and import cycles are rejected.
|
|
282
|
+
|
|
283
|
+
Cleanup is LIFO and tied to the current execution scope:
|
|
284
|
+
|
|
285
|
+
```rsh
|
|
286
|
+
fn build()
|
|
287
|
+
tmp := "/tmp/my-build.lock"
|
|
288
|
+
writefile(tmp, "busy")
|
|
289
|
+
defer rmfile(tmp)
|
|
290
|
+
|
|
291
|
+
# return, error, whatever; the cleanup still runs
|
|
292
|
+
return compile()
|
|
293
|
+
end
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
There is a block form when cleanup takes more than one statement:
|
|
297
|
+
|
|
298
|
+
```rsh
|
|
299
|
+
defer
|
|
300
|
+
close_stuff()
|
|
301
|
+
emit "cleaned up"
|
|
302
|
+
end
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Structured process values
|
|
306
|
+
|
|
307
|
+
Shell strings are convenient. They are not always what you want for untrusted filenames or arguments.
|
|
308
|
+
|
|
309
|
+
```rsh
|
|
310
|
+
result := cmd("git", "rev-parse", "--verify", ref).check()
|
|
311
|
+
sha := result.out.trim()
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
`cmd()` keeps argv as argv. It does not feed your data back through shell parsing.
|
|
315
|
+
|
|
316
|
+
Available methods include `.argv()`, `.run()`, `.result()`, `.capture()`, `.check()` and `.task()`.
|
|
317
|
+
|
|
318
|
+
## C interop: `bridge`
|
|
319
|
+
|
|
320
|
+
This is the native boundary:
|
|
321
|
+
|
|
322
|
+
```rsh
|
|
323
|
+
bridge c from "@self"
|
|
324
|
+
strlen(cstr) -> usize
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
= c.strlen("hello")
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
Or load a specific shared library:
|
|
331
|
+
|
|
332
|
+
```rsh
|
|
333
|
+
bridge math from "libm.so.6"
|
|
334
|
+
cos(f64) -> f64
|
|
335
|
+
pow(f64, f64) -> f64
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
= math.cos(0.0)
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
For output buffers/pointer APIs:
|
|
342
|
+
|
|
343
|
+
```rsh
|
|
344
|
+
bridge libc from "libc.so.6"
|
|
345
|
+
gethostname(ptr, usize) -> i32
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
buf := cbuf(256)
|
|
349
|
+
libc.gethostname(buf, buf.size())
|
|
350
|
+
= buf.string()
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
Supported ABI names are:
|
|
354
|
+
|
|
355
|
+
```text
|
|
356
|
+
void bool
|
|
357
|
+
i8 u8 i16 u16 i32 u32 i64 u64
|
|
358
|
+
isize usize
|
|
359
|
+
f32 f64
|
|
360
|
+
cstr ptr
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
`@self` means the current process' symbol table. Bridges resolve their symbols once when declared, so calls do not repeatedly search the library.
|
|
364
|
+
|
|
365
|
+
This is real FFI. A wrong C signature can crash the SRSH process, because C does not care about your feelings. See `docs/SECURITY.md` before feeding native pointers to random libraries.
|
|
366
|
+
|
|
367
|
+
## Errors and strict scripts
|
|
368
|
+
|
|
369
|
+
```rsh
|
|
370
|
+
try
|
|
371
|
+
cfg := json(readfile("config.json"))
|
|
372
|
+
catch err
|
|
373
|
+
= "config failed: #{err.message}"
|
|
374
|
+
cfg := %[]
|
|
375
|
+
finally
|
|
376
|
+
audit("config attempted")
|
|
377
|
+
end
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
For value pipelines:
|
|
381
|
+
|
|
382
|
+
```rsh
|
|
383
|
+
result := attempt(:: => risky())
|
|
384
|
+
? !result.ok => = result.error.message
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
For shell-side stricter behavior:
|
|
388
|
+
|
|
389
|
+
```rsh
|
|
390
|
+
option strict yes
|
|
391
|
+
option noclobber yes
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
`strict` currently means `nounset + pipefail`; it does **not** try to clone all of Bash's `set -e` edge cases.
|
|
395
|
+
|
|
396
|
+
## Metascripting
|
|
397
|
+
|
|
398
|
+
RSH code can be a value instead of a string you hope parses later:
|
|
399
|
+
|
|
400
|
+
```rsh
|
|
401
|
+
code cleanup
|
|
402
|
+
@ glob("tmp/*.log") -> file
|
|
403
|
+
rm $file
|
|
404
|
+
.@
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
? dry => = sourceof(cleanup)
|
|
408
|
+
? !dry => run(cleanup)
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
The reflective toolbox includes `code()`, `run()`, `eval()`, `sourceof()`, `valid()`, `locals()`, `fns()`, `protos()` and `traits()`.
|
|
412
|
+
|
|
413
|
+
## Ruby plugins
|
|
414
|
+
|
|
415
|
+
Ruby is still part of the point. A Ruby plugin can register builtins, hooks and themes through the SRSH API. Plugins are trusted code and are permission-checked before auto-loading.
|
|
416
|
+
|
|
417
|
+
RSH plugins are supported too if you want to stay entirely in the language.
|
|
418
|
+
|
|
419
|
+
## Process identity
|
|
420
|
+
|
|
421
|
+
SRSH sets `$SHELL`, argv0, and the Linux process name to `srsh`. That keeps process-oriented tools from calling your active shell `ruby` just because Ruby is the engine.
|
|
422
|
+
|
|
423
|
+
The executable behind the process is still Ruby. That is intentional, not something SRSH tries to disguise.
|
|
424
|
+
|
|
425
|
+
## Repo layout
|
|
426
|
+
|
|
427
|
+
```text
|
|
428
|
+
bin/srsh
|
|
429
|
+
lib/srsh/
|
|
430
|
+
app.rb
|
|
431
|
+
editor.rb
|
|
432
|
+
builtins.rb
|
|
433
|
+
history.rb
|
|
434
|
+
plugins.rb
|
|
435
|
+
process_identity.rb
|
|
436
|
+
state.rb
|
|
437
|
+
theme.rb
|
|
438
|
+
language/
|
|
439
|
+
shell/
|
|
440
|
+
ext/srsh_native/ optional accelerator
|
|
441
|
+
examples/
|
|
442
|
+
docs/
|
|
443
|
+
test/
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The code is split by job, not by architectural cosplay. If a file gets big because the job is big, it gets split when that actually makes it easier to work on.
|
|
447
|
+
|
|
448
|
+
## Current status
|
|
449
|
+
|
|
450
|
+
The 1.0 line is released and still young. A shell has too much surface area for claims about being independently audited or incapable of bugs.
|
|
451
|
+
|
|
452
|
+
Run the tests, beat on it, and report the ugly cases. Those are usually more useful than another feature bullet.
|
data/bin/srsh
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
|
|
5
|
+
require 'srsh'
|
|
6
|
+
|
|
7
|
+
Srsh::ProcessIdentity.install!(executable: File.expand_path(__FILE__))
|
|
8
|
+
|
|
9
|
+
if defined?(RubyVM::YJIT) && RubyVM::YJIT.respond_to?(:enable) && ENV.fetch('SRSH_YJIT', '1') != '0'
|
|
10
|
+
RubyVM::YJIT.enable rescue nil
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
app = Srsh::App.new
|
|
14
|
+
args = ARGV.dup
|
|
15
|
+
app.disable_startup! if args.delete('--norc')
|
|
16
|
+
|
|
17
|
+
case args[0]
|
|
18
|
+
when '--version', '-V'
|
|
19
|
+
puts "srsh #{Srsh::VERSION} (Simple Ruby Shell; #{RUBY_ENGINE} #{RUBY_VERSION})"
|
|
20
|
+
exit 0
|
|
21
|
+
when '--help', '-h'
|
|
22
|
+
puts <<~TXT
|
|
23
|
+
usage: srsh [options] [script.rsh [args...]]
|
|
24
|
+
|
|
25
|
+
-c COMMAND run one shell command
|
|
26
|
+
-e EXPR evaluate and print one RSH expression
|
|
27
|
+
--check FILE parse an RSH script without running it
|
|
28
|
+
--norc skip ~/.srshrc and plugins for -c/interactive startup
|
|
29
|
+
-V, --version show version
|
|
30
|
+
-h, --help show this help
|
|
31
|
+
TXT
|
|
32
|
+
exit 0
|
|
33
|
+
when '--check'
|
|
34
|
+
file = args[1] or abort 'srsh: --check needs a file'
|
|
35
|
+
exit app.check_script(file)
|
|
36
|
+
when '-c'
|
|
37
|
+
command = args[1] or abort 'srsh: -c needs a command'
|
|
38
|
+
begin
|
|
39
|
+
exit app.run_command(command).to_i
|
|
40
|
+
rescue StandardError => e
|
|
41
|
+
warn "srsh: #{e.class}: #{e.message}"
|
|
42
|
+
exit 1
|
|
43
|
+
ensure
|
|
44
|
+
app.history.flush
|
|
45
|
+
end
|
|
46
|
+
when '-e'
|
|
47
|
+
expression = args[1] or abort 'srsh: -e needs an expression'
|
|
48
|
+
begin
|
|
49
|
+
exit app.run_expression(expression).to_i
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
warn "srsh: #{e.class}: #{e.message}"
|
|
52
|
+
exit 2
|
|
53
|
+
ensure
|
|
54
|
+
app.history.flush
|
|
55
|
+
end
|
|
56
|
+
else
|
|
57
|
+
if args[0]
|
|
58
|
+
script = args.shift
|
|
59
|
+
app.startup!
|
|
60
|
+
begin
|
|
61
|
+
exit app.run_script(script, args).to_i
|
|
62
|
+
rescue StandardError => e
|
|
63
|
+
warn "srsh: #{e.class}: #{e.message}"
|
|
64
|
+
exit 1
|
|
65
|
+
ensure
|
|
66
|
+
app.history.flush
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
exit app.interactive
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<svg width="128" height="128" viewBox="0 0 128 128" fill="none" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="srsh">
|
|
3
|
+
<rect x="10" y="18" width="108" height="92" rx="16" fill="#0c0f14"/>
|
|
4
|
+
<rect x="16" y="26" width="96" height="16" rx="8" fill="#141824"/>
|
|
5
|
+
<circle cx="28" cy="34" r="5" fill="#ff5f57"/>
|
|
6
|
+
<circle cx="44" cy="34" r="5" fill="#febc2e"/>
|
|
7
|
+
<circle cx="60" cy="34" r="5" fill="#28c840"/>
|
|
8
|
+
<path d="M28 60L44 68L28 76" stroke="#e6edf3" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
|
|
9
|
+
<path d="M54 78H86" stroke="#e6edf3" stroke-width="7" stroke-linecap="round"/>
|
|
10
|
+
<path d="M54 60H92" stroke="#7ee787" stroke-width="7" stroke-linecap="round" opacity="0.9"/>
|
|
11
|
+
<text x="64" y="106" text-anchor="middle" font-family="ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace" font-size="14" fill="#9aa4b2">srsh</text>
|
|
12
|
+
</svg>
|