srsh 0.6.1.pre.HOTFIX → 0.7.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/bin/srsh +424 -244
- data/lib/srsh/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 405e071c7030f9eb0a2af1212dfd1357b047ec0c6aa37a7df490300b28d4a033
|
|
4
|
+
data.tar.gz: dd50fbc336a12486d5479e547a964e7129d4d0a1f51fe680ec394e9cf6d02792
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: fc2058fd9a0d13b6e91d2a4200acfab1c414040212c1d51c8755be0bb3d81279f7e86cfeeab14e13422f6454f463109fcdd1d1e0989191c68078b1ae10107930
|
|
7
|
+
data.tar.gz: 6057d29d402120f7dae36741aaacbbfa4cda2c09d5181c944fdc5bdaf03357c5c4cfecf6a1ad6fe4aca22b0044de4500cb3c0feceb4a3f0f2c476250bb731e98
|
data/bin/srsh
CHANGED
|
@@ -7,7 +7,7 @@ require 'rbconfig'
|
|
|
7
7
|
require 'io/console'
|
|
8
8
|
|
|
9
9
|
# ---------------- Version ----------------
|
|
10
|
-
SRSH_VERSION = "0.
|
|
10
|
+
SRSH_VERSION = "0.7.0"
|
|
11
11
|
|
|
12
12
|
$0 = "srsh-#{SRSH_VERSION}"
|
|
13
13
|
ENV['SHELL'] = "srsh-#{SRSH_VERSION}"
|
|
@@ -19,12 +19,17 @@ $child_pids = []
|
|
|
19
19
|
$aliases = {}
|
|
20
20
|
$last_render_rows = 0
|
|
21
21
|
|
|
22
|
+
$last_status = 0
|
|
23
|
+
$rsh_functions = {}
|
|
24
|
+
$rsh_positional = {}
|
|
25
|
+
$rsh_script_mode = false
|
|
26
|
+
|
|
22
27
|
Signal.trap("INT", "IGNORE")
|
|
23
28
|
|
|
24
29
|
# ---------------- History ----------------
|
|
25
30
|
HISTORY_FILE = File.join(Dir.home, ".srsh_history")
|
|
26
31
|
HISTORY = if File.exist?(HISTORY_FILE)
|
|
27
|
-
|
|
32
|
+
File.readlines(HISTORY_FILE, chomp: true)
|
|
28
33
|
else
|
|
29
34
|
[]
|
|
30
35
|
end
|
|
@@ -42,12 +47,12 @@ end
|
|
|
42
47
|
RC_FILE = File.join(Dir.home, ".srshrc")
|
|
43
48
|
begin
|
|
44
49
|
unless File.exist?(RC_FILE)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
File.write(RC_FILE, <<~RC)
|
|
51
|
+
# ~/.srshrc — srsh configuration
|
|
52
|
+
# This file was created automatically by srsh v#{SRSH_VERSION}.
|
|
53
|
+
# You can keep personal notes or planned settings here.
|
|
54
|
+
# (Currently not sourced by srsh runtime.)
|
|
55
|
+
RC
|
|
51
56
|
end
|
|
52
57
|
rescue
|
|
53
58
|
end
|
|
@@ -65,8 +70,15 @@ def rainbow_codes
|
|
|
65
70
|
[31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
|
|
66
71
|
end
|
|
67
72
|
|
|
73
|
+
# variable expansion: $VAR and $1, $2, $0 (script/function args)
|
|
68
74
|
def expand_vars(str)
|
|
69
|
-
str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/)
|
|
75
|
+
s = str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) do
|
|
76
|
+
ENV[$1] || ""
|
|
77
|
+
end
|
|
78
|
+
s.gsub(/\$(\d+)/) do
|
|
79
|
+
idx = $1.to_i
|
|
80
|
+
($rsh_positional && $rsh_positional[idx]) || ""
|
|
81
|
+
end
|
|
70
82
|
end
|
|
71
83
|
|
|
72
84
|
def parse_redirection(cmd)
|
|
@@ -118,241 +130,241 @@ end
|
|
|
118
130
|
|
|
119
131
|
def strip_ansi(str)
|
|
120
132
|
str.to_s.gsub(/\e\[[0-9;]*m/, '')
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# ---------------- Aliases ----------------
|
|
124
|
-
def expand_aliases(cmd, seen = [])
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# ---------------- System Info ----------------
|
|
140
|
-
def current_time
|
|
141
|
-
|
|
142
|
-
end
|
|
143
|
-
|
|
144
|
-
def detect_distro
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def os_type
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
# ---------------- Quotes ----------------
|
|
169
|
-
QUOTES = [
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
$current_quote = QUOTES.sample
|
|
205
|
-
|
|
206
|
-
def dynamic_quote
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
end
|
|
211
|
-
|
|
212
|
-
# ---------------- CPU / RAM / Storage ----------------
|
|
213
|
-
def read_cpu_times
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
def calculate_cpu_usage(prev, current)
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def cpu_cores_and_freq
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
def cpu_info
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
end
|
|
302
|
-
|
|
303
|
-
def ram_info
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
end
|
|
354
|
-
|
|
355
|
-
def storage_info
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# ---------------- Aliases ----------------
|
|
136
|
+
def expand_aliases(cmd, seen = [])
|
|
137
|
+
return cmd if cmd.nil? || cmd.strip.empty?
|
|
138
|
+
first_word, rest = cmd.strip.split(' ', 2)
|
|
139
|
+
return cmd if seen.include?(first_word)
|
|
140
|
+
seen << first_word
|
|
141
|
+
|
|
142
|
+
if $aliases.key?(first_word)
|
|
143
|
+
replacement = $aliases[first_word]
|
|
144
|
+
expanded = expand_aliases(replacement, seen)
|
|
145
|
+
rest ? "#{expanded} #{rest}" : expanded
|
|
146
|
+
else
|
|
147
|
+
cmd
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# ---------------- System Info ----------------
|
|
152
|
+
def current_time
|
|
153
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def detect_distro
|
|
157
|
+
if File.exist?('/etc/os-release')
|
|
158
|
+
line = File.read('/etc/os-release').lines.find { |l|
|
|
159
|
+
l.start_with?('PRETTY_NAME="') || l.start_with?('PRETTY_NAME=')
|
|
160
|
+
}
|
|
161
|
+
return line.split('=').last.strip.delete('"') if line
|
|
162
|
+
end
|
|
163
|
+
"#{RbConfig::CONFIG['host_os']}"
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def os_type
|
|
167
|
+
host = RbConfig::CONFIG['host_os'].to_s
|
|
168
|
+
case host
|
|
169
|
+
when /linux/i
|
|
170
|
+
:linux
|
|
171
|
+
when /darwin/i
|
|
172
|
+
:mac
|
|
173
|
+
when /bsd/i
|
|
174
|
+
:bsd
|
|
175
|
+
else
|
|
176
|
+
:other
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# ---------------- Quotes ----------------
|
|
181
|
+
QUOTES = [
|
|
182
|
+
"Keep calm and code on.",
|
|
183
|
+
"Did you try turning it off and on again?",
|
|
184
|
+
"There’s no place like 127.0.0.1.",
|
|
185
|
+
"To iterate is human, to recurse divine.",
|
|
186
|
+
"sudo rm -rf / – Just kidding, don’t do that!",
|
|
187
|
+
"The shell is mightier than the sword.",
|
|
188
|
+
"A journey of a thousand commits begins with a single push.",
|
|
189
|
+
"In case of fire: git commit, git push, leave building.",
|
|
190
|
+
"Debugging is like being the detective in a crime movie where you are also the murderer.",
|
|
191
|
+
"Unix is user-friendly. It's just selective about who its friends are.",
|
|
192
|
+
"Old sysadmins never die, they just become daemons.",
|
|
193
|
+
"Listen you flatpaker! – Totally Terry Davis",
|
|
194
|
+
"How is #{detect_distro}? 🤔",
|
|
195
|
+
"Life is short, but your command history is eternal.",
|
|
196
|
+
"If at first you don’t succeed, git commit and push anyway.",
|
|
197
|
+
"rm -rf: the ultimate trust exercise.",
|
|
198
|
+
"Coding is like magic, but with more coffee.",
|
|
199
|
+
"There’s no bug, only undocumented features.",
|
|
200
|
+
"Keep your friends close and your aliases closer.",
|
|
201
|
+
"Why wait for the future when you can Ctrl+Z it?",
|
|
202
|
+
"A watched process never completes.",
|
|
203
|
+
"When in doubt, make it a function.",
|
|
204
|
+
"Some call it procrastination, we call it debugging curiosity.",
|
|
205
|
+
"Life is like a terminal; some commands just don’t execute.",
|
|
206
|
+
"Good code is like a good joke; it needs no explanation.",
|
|
207
|
+
"sudo: because sometimes responsibility is overrated.",
|
|
208
|
+
"Pipes make the world go round.",
|
|
209
|
+
"In bash we trust, in Ruby we wonder.",
|
|
210
|
+
"A system without errors is like a day without coffee.",
|
|
211
|
+
"Keep your loops tight and your sleeps short.",
|
|
212
|
+
"Stack traces are just life giving you directions.",
|
|
213
|
+
"Your mom called, she wants her semicolons back."
|
|
214
|
+
]
|
|
215
|
+
|
|
216
|
+
$current_quote = QUOTES.sample
|
|
217
|
+
|
|
218
|
+
def dynamic_quote
|
|
219
|
+
chars = $current_quote.chars
|
|
220
|
+
rainbow = rainbow_codes.cycle
|
|
221
|
+
chars.map { |c| color(c, rainbow.next) }.join
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# ---------------- CPU / RAM / Storage ----------------
|
|
225
|
+
def read_cpu_times
|
|
226
|
+
return [] unless File.exist?('/proc/stat')
|
|
227
|
+
cpu_line = File.readlines('/proc/stat').find { |line| line.start_with?('cpu ') }
|
|
228
|
+
return [] unless cpu_line
|
|
229
|
+
cpu_line.split[1..-1].map(&:to_i)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
def calculate_cpu_usage(prev, current)
|
|
233
|
+
return 0.0 if prev.empty? || current.empty?
|
|
234
|
+
prev_idle = prev[3] + (prev[4] || 0)
|
|
235
|
+
idle = current[3] + (current[4] || 0)
|
|
236
|
+
prev_non_idle = prev[0] + prev[1] + prev[2] +
|
|
237
|
+
(prev[5] || 0) + (prev[6] || 0) + (prev[7] || 0)
|
|
238
|
+
non_idle = current[0] + current[1] + current[2] +
|
|
239
|
+
(current[5] || 0) + (current[6] || 0) + (current[7] || 0)
|
|
240
|
+
prev_total = prev_idle + prev_non_idle
|
|
241
|
+
total = idle + non_idle
|
|
242
|
+
totald = total - prev_total
|
|
243
|
+
idled = idle - prev_idle
|
|
244
|
+
return 0.0 if totald <= 0
|
|
245
|
+
((totald - idled).to_f / totald) * 100
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def cpu_cores_and_freq
|
|
249
|
+
return [0, []] unless File.exist?('/proc/cpuinfo')
|
|
250
|
+
cores = 0
|
|
251
|
+
freqs = []
|
|
252
|
+
File.foreach('/proc/cpuinfo') do |line|
|
|
253
|
+
cores += 1 if line =~ /^processor\s*:\s*\d+/
|
|
254
|
+
if line =~ /^cpu MHz\s*:\s*([\d.]+)/
|
|
255
|
+
freqs << $1.to_f
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
[cores, freqs.first(cores)]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def cpu_info
|
|
262
|
+
usage = 0.0
|
|
263
|
+
cores = 0
|
|
264
|
+
freq_display = "N/A"
|
|
265
|
+
|
|
266
|
+
case os_type
|
|
267
|
+
when :linux
|
|
268
|
+
prev = read_cpu_times
|
|
269
|
+
sleep 0.05
|
|
270
|
+
current = read_cpu_times
|
|
271
|
+
usage = calculate_cpu_usage(prev, current).round(1)
|
|
272
|
+
cores, freqs = cpu_cores_and_freq
|
|
273
|
+
freq_display = freqs.empty? ? "N/A" : freqs.map { |f| "#{f.round(0)}MHz" }.join(', ')
|
|
274
|
+
else
|
|
275
|
+
cores = begin
|
|
276
|
+
`sysctl -n hw.ncpu 2>/dev/null`.to_i
|
|
277
|
+
rescue
|
|
278
|
+
0
|
|
279
|
+
end
|
|
280
|
+
|
|
281
|
+
raw_freq_hz = begin
|
|
282
|
+
`sysctl -n hw.cpufrequency 2>/dev/null`.to_i
|
|
283
|
+
rescue
|
|
284
|
+
0
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
freq_display =
|
|
288
|
+
if raw_freq_hz > 0
|
|
289
|
+
mhz = (raw_freq_hz.to_f / 1_000_000.0).round(0)
|
|
290
|
+
"#{mhz.to_i}MHz"
|
|
291
|
+
else
|
|
292
|
+
"N/A"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
usage = begin
|
|
296
|
+
ps_output = `ps -A -o %cpu 2>/dev/null`
|
|
297
|
+
lines = ps_output.lines
|
|
298
|
+
values = lines[1..-1] || []
|
|
299
|
+
sum = values.map { |l| l.to_f }.inject(0.0, :+)
|
|
300
|
+
if cores > 0
|
|
301
|
+
(sum / cores).round(1)
|
|
302
|
+
else
|
|
303
|
+
sum.round(1)
|
|
304
|
+
end
|
|
305
|
+
rescue
|
|
306
|
+
0.0
|
|
307
|
+
end
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
"#{color("CPU Usage:",36)} #{color("#{usage}%",33)} | " \
|
|
311
|
+
"#{color("Cores:",36)} #{color(cores.to_s,32)} | " \
|
|
312
|
+
"#{color("Freqs:",36)} #{color(freq_display,35)}"
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def ram_info
|
|
316
|
+
case os_type
|
|
317
|
+
when :linux
|
|
318
|
+
if File.exist?('/proc/meminfo')
|
|
319
|
+
meminfo = {}
|
|
320
|
+
File.read('/proc/meminfo').each_line do |line|
|
|
321
|
+
key, val = line.split(':')
|
|
322
|
+
meminfo[key.strip] = val.strip.split.first.to_i * 1024 if key && val
|
|
323
|
+
end
|
|
324
|
+
total = meminfo['MemTotal'] || 0
|
|
325
|
+
free = (meminfo['MemFree'] || 0) + (meminfo['Buffers'] || 0) + (meminfo['Cached'] || 0)
|
|
326
|
+
used = total - free
|
|
327
|
+
"#{color("RAM Usage:",36)} #{color(human_bytes(used),33)} / #{color(human_bytes(total),32)}"
|
|
328
|
+
else
|
|
329
|
+
"#{color("RAM Usage:",36)} Info not available"
|
|
330
|
+
end
|
|
331
|
+
else
|
|
332
|
+
begin
|
|
333
|
+
if os_type == :mac
|
|
334
|
+
total = `sysctl -n hw.memsize 2>/dev/null`.to_i
|
|
335
|
+
return "#{color("RAM Usage:",36)} Info not available" if total <= 0
|
|
336
|
+
|
|
337
|
+
vm = `vm_stat 2>/dev/null`
|
|
338
|
+
page_size = vm[/page size of (\d+) bytes/, 1].to_i
|
|
339
|
+
page_size = 4096 if page_size <= 0
|
|
340
|
+
|
|
341
|
+
stats = {}
|
|
342
|
+
vm.each_line do |line|
|
|
343
|
+
if line =~ /^(.+):\s+(\d+)\./
|
|
344
|
+
stats[$1] = $2.to_i
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
used_pages = 0
|
|
349
|
+
%w[Pages active Pages wired down Pages occupied by compressor].each do |k|
|
|
350
|
+
used_pages += stats[k].to_i
|
|
351
|
+
end
|
|
352
|
+
used = used_pages * page_size
|
|
353
|
+
|
|
354
|
+
"#{color("RAM Usage:",36)} #{color(human_bytes(used),33)} / #{color(human_bytes(total),32)}"
|
|
355
|
+
else
|
|
356
|
+
total = `sysctl -n hw.physmem 2>/dev/null`.to_i
|
|
357
|
+
total = `sysctl -n hw.realmem 2>/dev/null`.to_i if total <= 0
|
|
358
|
+
return "#{color("RAM Usage:",36)} Info not available" if total <= 0
|
|
359
|
+
"#{color("RAM Usage:",36)} #{color("Unknown",33)} / #{color(human_bytes(total),32)}"
|
|
360
|
+
end
|
|
361
|
+
rescue
|
|
362
|
+
"#{color("RAM Usage:",36)} Info not available"
|
|
363
|
+
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def storage_info
|
|
356
368
|
begin
|
|
357
369
|
require 'sys/filesystem'
|
|
358
370
|
stat = Sys::Filesystem.stat(Dir.pwd)
|
|
@@ -382,6 +394,7 @@ def builtin_help
|
|
|
382
394
|
puts color(sprintf("%-15s", "systemfetch"), "1;36") + "Display system information"
|
|
383
395
|
puts color(sprintf("%-15s", "hist"), "1;36") + "Show shell history"
|
|
384
396
|
puts color(sprintf("%-15s", "clearhist"), "1;36") + "Clear saved history (memory + file)"
|
|
397
|
+
puts color(sprintf("%-15s", "put"), "1;36") + "Print text (like echo)"
|
|
385
398
|
puts color(sprintf("%-15s", "help"), "1;36") + "Show this help message"
|
|
386
399
|
puts color('=' * 60, "1;35")
|
|
387
400
|
end
|
|
@@ -560,6 +573,147 @@ def builtin_ls(path = ".")
|
|
|
560
573
|
print_columns_colored(labels)
|
|
561
574
|
end
|
|
562
575
|
|
|
576
|
+
# ---------------- rsh scripting helpers ----------------
|
|
577
|
+
|
|
578
|
+
# Evaluate rsh condition expressions, Ruby-style with $VARS
|
|
579
|
+
def eval_rsh_expr(expr)
|
|
580
|
+
return false if expr.nil? || expr.strip.empty?
|
|
581
|
+
s = expr.to_s
|
|
582
|
+
|
|
583
|
+
s = s.gsub(/\$([A-Za-z_][A-Za-z0-9_]*)/) do
|
|
584
|
+
(ENV[$1] || "").inspect
|
|
585
|
+
end
|
|
586
|
+
|
|
587
|
+
s = s.gsub(/\$(\d+)/) do
|
|
588
|
+
idx = $1.to_i
|
|
589
|
+
val = ($rsh_positional && $rsh_positional[idx]) || ""
|
|
590
|
+
val.inspect
|
|
591
|
+
end
|
|
592
|
+
|
|
593
|
+
begin
|
|
594
|
+
!!eval(s)
|
|
595
|
+
rescue
|
|
596
|
+
false
|
|
597
|
+
end
|
|
598
|
+
end
|
|
599
|
+
|
|
600
|
+
def rsh_find_if_bounds(lines, start_idx)
|
|
601
|
+
depth = 1
|
|
602
|
+
else_idx = nil
|
|
603
|
+
i = start_idx + 1
|
|
604
|
+
while i < lines.length
|
|
605
|
+
line = lines[i].to_s.strip
|
|
606
|
+
if line.start_with?("if ")
|
|
607
|
+
depth += 1
|
|
608
|
+
elsif line.start_with?("while ")
|
|
609
|
+
depth += 1
|
|
610
|
+
elsif line.start_with?("fn ")
|
|
611
|
+
depth += 1
|
|
612
|
+
elsif line == "end"
|
|
613
|
+
depth -= 1
|
|
614
|
+
return [else_idx, i] if depth == 0
|
|
615
|
+
elsif line == "else" && depth == 1
|
|
616
|
+
else_idx = i
|
|
617
|
+
end
|
|
618
|
+
i += 1
|
|
619
|
+
end
|
|
620
|
+
raise "Unmatched 'if' in rsh script"
|
|
621
|
+
end
|
|
622
|
+
|
|
623
|
+
def rsh_find_block_end(lines, start_idx)
|
|
624
|
+
depth = 1
|
|
625
|
+
i = start_idx + 1
|
|
626
|
+
while i < lines.length
|
|
627
|
+
line = lines[i].to_s.strip
|
|
628
|
+
if line.start_with?("if ") || line.start_with?("while ") || line.start_with?("fn ")
|
|
629
|
+
depth += 1
|
|
630
|
+
elsif line == "end"
|
|
631
|
+
depth -= 1
|
|
632
|
+
return i if depth == 0
|
|
633
|
+
end
|
|
634
|
+
i += 1
|
|
635
|
+
end
|
|
636
|
+
raise "Unmatched block in rsh script"
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def run_rsh_block(lines, start_idx, end_idx)
|
|
640
|
+
i = start_idx
|
|
641
|
+
while i < end_idx
|
|
642
|
+
raw = lines[i]
|
|
643
|
+
i += 1
|
|
644
|
+
next if raw.nil?
|
|
645
|
+
line = raw.strip
|
|
646
|
+
next if line.empty? || line.start_with?("#")
|
|
647
|
+
|
|
648
|
+
if line.start_with?("if ")
|
|
649
|
+
cond_expr = line[3..-1].strip
|
|
650
|
+
else_idx, end_idx_2 = rsh_find_if_bounds(lines, i - 1)
|
|
651
|
+
if eval_rsh_expr(cond_expr)
|
|
652
|
+
body_end = else_idx || end_idx_2
|
|
653
|
+
run_rsh_block(lines, i, body_end)
|
|
654
|
+
elsif else_idx
|
|
655
|
+
run_rsh_block(lines, else_idx + 1, end_idx_2)
|
|
656
|
+
end
|
|
657
|
+
i = end_idx_2 + 1
|
|
658
|
+
next
|
|
659
|
+
elsif line.start_with?("while ")
|
|
660
|
+
cond_expr = line[6..-1].strip
|
|
661
|
+
block_end = rsh_find_block_end(lines, i - 1)
|
|
662
|
+
while eval_rsh_expr(cond_expr)
|
|
663
|
+
run_rsh_block(lines, i, block_end)
|
|
664
|
+
end
|
|
665
|
+
i = block_end + 1
|
|
666
|
+
next
|
|
667
|
+
elsif line.start_with?("fn ")
|
|
668
|
+
parts = line.split
|
|
669
|
+
name = parts[1]
|
|
670
|
+
argnames = parts[2..-1] || []
|
|
671
|
+
block_end = rsh_find_block_end(lines, i - 1)
|
|
672
|
+
$rsh_functions[name] = {
|
|
673
|
+
args: argnames,
|
|
674
|
+
body: lines[i...block_end]
|
|
675
|
+
}
|
|
676
|
+
i = block_end + 1
|
|
677
|
+
next
|
|
678
|
+
else
|
|
679
|
+
run_input_line(line)
|
|
680
|
+
end
|
|
681
|
+
end
|
|
682
|
+
end
|
|
683
|
+
|
|
684
|
+
def rsh_run_script(script_path, argv)
|
|
685
|
+
$rsh_script_mode = true
|
|
686
|
+
$rsh_positional = {}
|
|
687
|
+
$rsh_positional[0] = File.basename(script_path)
|
|
688
|
+
argv.each_with_index do |val, idx|
|
|
689
|
+
$rsh_positional[idx + 1] = val
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
lines = File.readlines(script_path, chomp: true)
|
|
693
|
+
if lines[0] && lines[0].start_with?("#!")
|
|
694
|
+
lines = lines[1..-1] || []
|
|
695
|
+
end
|
|
696
|
+
run_rsh_block(lines, 0, lines.length)
|
|
697
|
+
end
|
|
698
|
+
|
|
699
|
+
def rsh_call_function(name, argv)
|
|
700
|
+
fn = $rsh_functions[name]
|
|
701
|
+
return unless fn
|
|
702
|
+
|
|
703
|
+
saved_positional = $rsh_positional
|
|
704
|
+
$rsh_positional = {}
|
|
705
|
+
$rsh_positional[0] = name
|
|
706
|
+
fn[:args].each_with_index do |argname, idx|
|
|
707
|
+
val = argv[idx] || ""
|
|
708
|
+
ENV[argname] = val
|
|
709
|
+
$rsh_positional[idx + 1] = val
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
run_rsh_block(fn[:body], 0, fn[:body].length)
|
|
713
|
+
ensure
|
|
714
|
+
$rsh_positional = saved_positional
|
|
715
|
+
end
|
|
716
|
+
|
|
563
717
|
# ---------------- External Execution Helper ----------------
|
|
564
718
|
def exec_external(args, stdin_file, stdout_file, append)
|
|
565
719
|
command_path = args[0]
|
|
@@ -601,6 +755,7 @@ def exec_external(args, stdin_file, stdout_file, append)
|
|
|
601
755
|
$child_pids << pid
|
|
602
756
|
begin
|
|
603
757
|
Process.wait(pid)
|
|
758
|
+
$last_status = $?.exitstatus || 0
|
|
604
759
|
rescue Interrupt
|
|
605
760
|
ensure
|
|
606
761
|
$child_pids.delete(pid)
|
|
@@ -616,6 +771,12 @@ def run_command(cmd)
|
|
|
616
771
|
args = Shellwords.shellsplit(cmd) rescue []
|
|
617
772
|
return if args.empty?
|
|
618
773
|
|
|
774
|
+
# rsh functions
|
|
775
|
+
if $rsh_functions.key?(args[0])
|
|
776
|
+
rsh_call_function(args[0], args[1..-1] || [])
|
|
777
|
+
return
|
|
778
|
+
end
|
|
779
|
+
|
|
619
780
|
case args[0]
|
|
620
781
|
when 'ls'
|
|
621
782
|
if args.length == 1
|
|
@@ -677,6 +838,10 @@ def run_command(cmd)
|
|
|
677
838
|
when 'clearhist'
|
|
678
839
|
builtin_clearhist
|
|
679
840
|
return
|
|
841
|
+
when 'put'
|
|
842
|
+
msg = args[1..-1].join(' ')
|
|
843
|
+
puts msg
|
|
844
|
+
return
|
|
680
845
|
end
|
|
681
846
|
|
|
682
847
|
exec_external(args, stdin_file, stdout_file, append)
|
|
@@ -935,6 +1100,10 @@ def read_line_with_ghost(prompt_str)
|
|
|
935
1100
|
else
|
|
936
1101
|
# ignore when line not empty
|
|
937
1102
|
end
|
|
1103
|
+
when "\u0001" # Ctrl-A - move to beginning of line
|
|
1104
|
+
cursor = 0
|
|
1105
|
+
last_tab_prefix = nil
|
|
1106
|
+
tab_cycle = 0
|
|
938
1107
|
when "\u007F", "\b" # Backspace
|
|
939
1108
|
if cursor > 0
|
|
940
1109
|
buffer.slice!(cursor - 1)
|
|
@@ -1022,6 +1191,17 @@ def print_welcome
|
|
|
1022
1191
|
puts
|
|
1023
1192
|
end
|
|
1024
1193
|
|
|
1194
|
+
# ---------------- Script vs interactive entry ----------------
|
|
1195
|
+
if ARGV[0]
|
|
1196
|
+
script_path = ARGV.shift
|
|
1197
|
+
begin
|
|
1198
|
+
rsh_run_script(script_path, ARGV)
|
|
1199
|
+
rescue => e
|
|
1200
|
+
STDERR.puts "rsh script error: #{e.class}: #{e.message}"
|
|
1201
|
+
end
|
|
1202
|
+
exit 0
|
|
1203
|
+
end
|
|
1204
|
+
|
|
1025
1205
|
print_welcome
|
|
1026
1206
|
|
|
1027
1207
|
# ---------------- Main Loop ----------------
|
data/lib/srsh/version.rb
CHANGED