srsh 0.7.1 → 1.0.0
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 +18 -14
- data/README.md +351 -134
- data/bin/srsh +53 -1473
- 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 +392 -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 +1 -2
- data/lib/srsh.rb +15 -0
- metadata +59 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5b4ebcca8a9c5952dd9608cd8fe5dd311aae5445224896c4e190db2c7981a4dc
|
|
4
|
+
data.tar.gz: 17f76ba91a316ebd35dfdfaf5c260baf778348a73e4f930c8c61fb0de2222cc3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37f82634b0c88623c50d1aa3eee65d2b219ed317c87ea6e9abe42c904a9989aec5a0fd360715a33879a5dfc44ecf020611c5880b0a024868bb4f6627cabf95d0
|
|
7
|
+
data.tar.gz: 787a567cfed45fdd641874c8a0e9f43b919645ff5c882c5be0aac6bde2899a6cb4c89f2f5f032bfe0b1c2a274df4df66800542834937fe8d12dec0f3932db835
|
data/LICENSE
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
-
|
|
1
|
+
MIT License
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
warranty. In no event will the authors be held liable for any damages
|
|
5
|
-
arising from the use of this software.
|
|
3
|
+
Copyright (c) 2026 RobertFlexx
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
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
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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,235 +1,452 @@
|
|
|
1
|
-
|
|
2
|
-
(Fixed Control-C, fixed some bugs, added new features (check via help command)
|
|
3
|
-
THIS IS A BETA RELEASE, IT MAY NOT WORK.
|
|
1
|
+
# srsh, Simple Ruby Shell
|
|
4
2
|
|
|
5
|
-
|
|
3
|
+
`srsh` is a Unix shell written in Ruby. Its scripting language is **RSH**.
|
|
6
4
|
|
|
7
|
-
|
|
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.
|
|
8
6
|
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
```rsh
|
|
8
|
+
printf 'c\na\nb\n' | sort
|
|
11
9
|
|
|
12
|
-
|
|
10
|
+
files := glob("src/**/*.rb")
|
|
11
|
+
|> reject(::p => contains(p, "/vendor/"))
|
|
12
|
+
|> sort
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
? files |> len > 20 => = "that's a lot of Ruby"
|
|
15
|
+
```
|
|
16
|
+
|
|
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.0**. It is the unreleased 1.0 line; the older 1.1/1.2 numbers were development snapshots and were a dumb way to version something that had not shipped yet.
|
|
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.
|
|
15
24
|
|
|
16
|
-
|
|
25
|
+
RSH is meant to cover that gap without turning the shell into a foreign environment:
|
|
17
26
|
|
|
18
|
-
|
|
27
|
+
```rsh
|
|
28
|
+
branch := $(git branch --show-current)
|
|
19
29
|
|
|
20
|
-
|
|
21
|
-
|
|
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"
|
|
22
34
|
```
|
|
23
35
|
|
|
24
|
-
|
|
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
|
|
25
39
|
|
|
26
|
-
|
|
27
|
-
|
|
40
|
+
Ruby 3.2+ is required.
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
make test
|
|
44
|
+
./bin/srsh
|
|
28
45
|
```
|
|
29
46
|
|
|
30
|
-
|
|
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:
|
|
31
48
|
|
|
32
|
-
```
|
|
33
|
-
|
|
49
|
+
```sh
|
|
50
|
+
make native
|
|
51
|
+
./bin/srsh
|
|
34
52
|
```
|
|
35
53
|
|
|
36
|
-
|
|
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:
|
|
37
57
|
|
|
38
|
-
|
|
58
|
+
```sh
|
|
59
|
+
make PREFIX="$HOME/.local" install
|
|
60
|
+
```
|
|
39
61
|
|
|
40
|
-
|
|
41
|
-
* A POSIX-ish terminal (Linux, *BSD, macOS Terminal, iTerm2, etc).
|
|
42
|
-
* `./rsh` needs the executable bit set:
|
|
62
|
+
## The syntax has a reason
|
|
43
63
|
|
|
44
|
-
|
|
45
|
-
chmod +x rsh
|
|
46
|
-
```
|
|
64
|
+
RSH has some odd-looking syntax, but the goal is not to win a weird-language contest.
|
|
47
65
|
|
|
48
|
-
|
|
66
|
+
There are a few boundaries you hit constantly in shell programming, and RSH gives each one a visible shape:
|
|
49
67
|
|
|
50
|
-
|
|
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
|
+
```
|
|
51
77
|
|
|
52
|
-
|
|
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.
|
|
53
79
|
|
|
54
|
-
|
|
80
|
+
Readable forms exist too:
|
|
55
81
|
|
|
56
|
-
```
|
|
57
|
-
|
|
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
|
|
58
92
|
```
|
|
59
93
|
|
|
60
|
-
|
|
94
|
+
The short forms lower to the same AST:
|
|
61
95
|
|
|
62
|
-
|
|
63
|
-
*
|
|
96
|
+
```rsh
|
|
97
|
+
:: twice(x) => x * 2
|
|
98
|
+
? ready => emit "go"
|
|
99
|
+
@ jobs -> job => = job.name
|
|
100
|
+
```
|
|
64
101
|
|
|
65
|
-
|
|
66
|
-
* `systemfetch` – prints system info with nice bars.
|
|
67
|
-
* `hist` – view shell history.
|
|
68
|
-
* `clearhist` – clear history (memory + file).
|
|
69
|
-
* `alias` / `unalias` – manage aliases.
|
|
70
|
-
* Enjoy:
|
|
102
|
+
Use whichever one fits the size of the job.
|
|
71
103
|
|
|
72
|
-
|
|
73
|
-
* **Smart Tab completion**:
|
|
104
|
+
## The REPL is actually an RSH REPL
|
|
74
105
|
|
|
75
|
-
|
|
76
|
-
* `cd` → only directories.
|
|
77
|
-
* `cat` → only files.
|
|
106
|
+
Bindings, expressions and blocks work directly at the shell prompt:
|
|
78
107
|
|
|
79
|
-
|
|
108
|
+
```text
|
|
109
|
+
> x := 21
|
|
110
|
+
> = x * 2
|
|
111
|
+
42
|
|
80
112
|
|
|
81
|
-
|
|
113
|
+
> fn triple(n)
|
|
114
|
+
... return n * 3
|
|
115
|
+
... end
|
|
116
|
+
> = triple(10)
|
|
117
|
+
30
|
|
118
|
+
```
|
|
82
119
|
|
|
83
|
-
|
|
120
|
+
Multiline list/map/call expressions also continue properly:
|
|
84
121
|
|
|
85
|
-
|
|
86
|
-
|
|
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
|
+
```
|
|
87
177
|
|
|
88
|
-
|
|
89
|
-
> To avoid conflict, using the name `srsh` for the installed command is usually safer.
|
|
178
|
+
The usual functional operations are built in:
|
|
90
179
|
|
|
91
|
-
|
|
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
|
+
```
|
|
92
185
|
|
|
93
|
-
|
|
186
|
+
Named functions are first-class too:
|
|
94
187
|
|
|
95
|
-
```
|
|
96
|
-
|
|
188
|
+
```rsh
|
|
189
|
+
fn square(x) => x * x
|
|
190
|
+
= 1 .. 10 |> map(square) |> sum
|
|
97
191
|
```
|
|
98
192
|
|
|
99
|
-
|
|
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
|
|
100
201
|
|
|
101
|
-
|
|
202
|
+
proto Counter(name, start := 0) with Named
|
|
203
|
+
slot name := name
|
|
204
|
+
slot value := start
|
|
102
205
|
|
|
103
|
-
|
|
104
|
-
|
|
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()
|
|
105
215
|
```
|
|
106
216
|
|
|
107
|
-
|
|
217
|
+
Object slot updates are synchronized, so objects can be an explicit shared-state tool between tasks.
|
|
218
|
+
|
|
219
|
+
## Concurrency
|
|
108
220
|
|
|
109
|
-
|
|
110
|
-
|
|
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)
|
|
111
230
|
```
|
|
112
231
|
|
|
113
|
-
|
|
232
|
+
You can spawn a lambda directly:
|
|
233
|
+
|
|
234
|
+
```rsh
|
|
235
|
+
job := &:: => expensive_io()
|
|
236
|
+
= job.await(2.0)
|
|
237
|
+
```
|
|
114
238
|
|
|
115
|
-
|
|
239
|
+
Shared mutation is explicit:
|
|
116
240
|
|
|
117
|
-
```
|
|
118
|
-
|
|
241
|
+
```rsh
|
|
242
|
+
hits := atom(0)
|
|
243
|
+
workers := 0 ..< 32 |> map(::i => &:: => hits.swap(::n => n + 1))
|
|
244
|
+
await_all(workers)
|
|
245
|
+
= hits.get()
|
|
119
246
|
```
|
|
120
247
|
|
|
121
|
-
|
|
248
|
+
There are channels too:
|
|
122
249
|
|
|
123
|
-
```
|
|
124
|
-
|
|
250
|
+
```rsh
|
|
251
|
+
queue := chan(8)
|
|
252
|
+
queue.send("hello")
|
|
253
|
+
= queue.recv(1)
|
|
254
|
+
queue.close()
|
|
125
255
|
```
|
|
126
256
|
|
|
127
|
-
|
|
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.
|
|
128
258
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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)
|
|
133
272
|
```
|
|
134
273
|
|
|
135
|
-
|
|
274
|
+
Or load a file as a namespace:
|
|
136
275
|
|
|
137
|
-
|
|
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.
|
|
138
282
|
|
|
139
|
-
|
|
283
|
+
Cleanup is LIFO and tied to the current execution scope:
|
|
140
284
|
|
|
141
|
-
|
|
285
|
+
```rsh
|
|
286
|
+
fn build()
|
|
287
|
+
tmp := "/tmp/my-build.lock"
|
|
288
|
+
writefile(tmp, "busy")
|
|
289
|
+
defer rmfile(tmp)
|
|
142
290
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
291
|
+
# return, error, whatever; the cleanup still runs
|
|
292
|
+
return compile()
|
|
293
|
+
end
|
|
146
294
|
```
|
|
147
295
|
|
|
148
|
-
|
|
296
|
+
There is a block form when cleanup takes more than one statement:
|
|
149
297
|
|
|
150
|
-
```
|
|
151
|
-
|
|
298
|
+
```rsh
|
|
299
|
+
defer
|
|
300
|
+
close_stuff()
|
|
301
|
+
emit "cleaned up"
|
|
302
|
+
end
|
|
152
303
|
```
|
|
153
304
|
|
|
154
|
-
|
|
305
|
+
## Structured process values
|
|
155
306
|
|
|
156
|
-
|
|
307
|
+
Shell strings are convenient. They are not always what you want for untrusted filenames or arguments.
|
|
157
308
|
|
|
158
|
-
```
|
|
159
|
-
|
|
309
|
+
```rsh
|
|
310
|
+
result := cmd("git", "rev-parse", "--verify", ref).check()
|
|
311
|
+
sha := result.out.trim()
|
|
160
312
|
```
|
|
161
313
|
|
|
162
|
-
|
|
314
|
+
`cmd()` keeps argv as argv. It does not feed your data back through shell parsing.
|
|
163
315
|
|
|
164
|
-
|
|
316
|
+
Available methods include `.argv()`, `.run()`, `.result()`, `.capture()`, `.check()` and `.task()`.
|
|
165
317
|
|
|
166
|
-
|
|
318
|
+
## C interop: `bridge`
|
|
167
319
|
|
|
168
|
-
|
|
320
|
+
This is the native boundary:
|
|
169
321
|
|
|
170
|
-
```
|
|
171
|
-
|
|
322
|
+
```rsh
|
|
323
|
+
bridge c from "@self"
|
|
324
|
+
strlen(cstr) -> usize
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
= c.strlen("hello")
|
|
172
328
|
```
|
|
173
329
|
|
|
174
|
-
|
|
330
|
+
Or load a specific shared library:
|
|
175
331
|
|
|
176
|
-
|
|
332
|
+
```rsh
|
|
333
|
+
bridge math from "libm.so.6"
|
|
334
|
+
cos(f64) -> f64
|
|
335
|
+
pow(f64, f64) -> f64
|
|
336
|
+
end
|
|
177
337
|
|
|
178
|
-
|
|
179
|
-
export PATH="$HOME/RSH:$PATH"
|
|
338
|
+
= math.cos(0.0)
|
|
180
339
|
```
|
|
181
340
|
|
|
182
|
-
|
|
341
|
+
For output buffers/pointer APIs:
|
|
183
342
|
|
|
184
|
-
```
|
|
185
|
-
.
|
|
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
|
|
186
361
|
```
|
|
187
362
|
|
|
188
|
-
|
|
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
|
+
```
|
|
189
379
|
|
|
190
|
-
|
|
380
|
+
For value pipelines:
|
|
191
381
|
|
|
192
|
-
```
|
|
193
|
-
|
|
382
|
+
```rsh
|
|
383
|
+
result := attempt(:: => risky())
|
|
384
|
+
? !result.ok => = result.error.message
|
|
194
385
|
```
|
|
195
386
|
|
|
196
|
-
|
|
387
|
+
For shell-side stricter behavior:
|
|
197
388
|
|
|
198
|
-
```
|
|
199
|
-
|
|
389
|
+
```rsh
|
|
390
|
+
option strict yes
|
|
391
|
+
option noclobber yes
|
|
200
392
|
```
|
|
201
393
|
|
|
202
|
-
|
|
394
|
+
`strict` currently means `nounset + pipefail`; it does **not** try to clone all of Bash's `set -e` edge cases.
|
|
203
395
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
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)
|
|
208
409
|
```
|
|
209
410
|
|
|
210
|
-
|
|
411
|
+
The reflective toolbox includes `code()`, `run()`, `eval()`, `sourceof()`, `valid()`, `locals()`, `fns()`, `protos()` and `traits()`.
|
|
412
|
+
|
|
413
|
+
## Ruby plugins
|
|
211
414
|
|
|
212
|
-
|
|
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.
|
|
213
416
|
|
|
214
|
-
|
|
417
|
+
RSH plugins are supported too if you want to stay entirely in the language.
|
|
215
418
|
|
|
216
|
-
|
|
419
|
+
## Process identity
|
|
217
420
|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
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
|
+
```
|
|
223
445
|
|
|
224
|
-
|
|
225
|
-
echo "$PATH"
|
|
226
|
-
```
|
|
227
|
-
* If things feel off, run it directly from the repo with:
|
|
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.
|
|
228
447
|
|
|
229
|
-
|
|
230
|
-
./rsh
|
|
231
|
-
```
|
|
448
|
+
## Current status
|
|
232
449
|
|
|
233
|
-
|
|
450
|
+
The project is still unreleased. `1.0.0` means “the 1.0 tree,” not “independently audited and incapable of bugs.” A shell has too much surface area for that kind of claim.
|
|
234
451
|
|
|
235
|
-
|
|
452
|
+
Run the tests, beat on it, and report the ugly cases. Those are usually more useful than another feature bullet.
|