srsh 0.6.2 → 0.7.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/bin/srsh +660 -213
- 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: 0c0910382ad05f07b93041cfbecb7d3db98f24e23df1ecd5d23243aab35a7f63
|
|
4
|
+
data.tar.gz: c39ce2184076112bb950035ea2f09939bef9f4105b2c207e6ec73ee152fc3c89
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ee7bd7e5ae91582ed612171eda2f3440d16d8d07988b465fd61470e094d80b0f766cfc6f248d2980f0371a0db228593a326934fe7f6c4367fb8e9b009213e26f
|
|
7
|
+
data.tar.gz: 6195a9d026471e57b1b7b8f87970362261302d0e7f8b936a030ecc02bce7de2c56d3420d2e7cee0b7810f60acc32f5a171bd45acd1181ffc7d2469389f37ebd7
|
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.1"
|
|
11
11
|
|
|
12
12
|
$0 = "srsh-#{SRSH_VERSION}"
|
|
13
13
|
ENV['SHELL'] = "srsh-#{SRSH_VERSION}"
|
|
@@ -15,228 +15,276 @@ print "\033]0;srsh-#{SRSH_VERSION}\007"
|
|
|
15
15
|
|
|
16
16
|
Dir.chdir(ENV['HOME']) if ENV['HOME']
|
|
17
17
|
|
|
18
|
+
# ---------------- Globals ----------------
|
|
18
19
|
$child_pids = []
|
|
19
20
|
$aliases = {}
|
|
20
21
|
$last_render_rows = 0
|
|
21
22
|
|
|
23
|
+
$last_status = 0
|
|
24
|
+
$rsh_functions = {}
|
|
25
|
+
$rsh_positional = {}
|
|
26
|
+
$rsh_script_mode = false
|
|
27
|
+
|
|
22
28
|
Signal.trap("INT", "IGNORE")
|
|
23
29
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
else
|
|
29
|
-
[]
|
|
30
|
-
end
|
|
30
|
+
# Control-flow exceptions for the scripting engine
|
|
31
|
+
class RshBreak < StandardError; end
|
|
32
|
+
class RshContinue < StandardError; end
|
|
33
|
+
class RshReturn < StandardError; end
|
|
31
34
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
# ---------------- History ----------------
|
|
36
|
+
HISTORY_FILE = File.join(Dir.home, ".srsh_history")
|
|
37
|
+
HISTORY = if File.exist?(HISTORY_FILE)
|
|
38
|
+
File.readlines(HISTORY_FILE, chomp: true)
|
|
39
|
+
else
|
|
40
|
+
[]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
at_exit do
|
|
44
|
+
begin
|
|
45
|
+
File.open(HISTORY_FILE, "w") do |f|
|
|
46
|
+
HISTORY.each { |line| f.puts line }
|
|
47
|
+
end
|
|
48
|
+
rescue
|
|
49
|
+
end
|
|
36
50
|
end
|
|
37
|
-
rescue
|
|
38
|
-
end
|
|
39
|
-
end
|
|
40
51
|
|
|
41
|
-
# ---------------- RC file (create if missing) ----------------
|
|
42
|
-
RC_FILE = File.join(Dir.home, ".srshrc")
|
|
43
|
-
begin
|
|
44
|
-
|
|
45
|
-
|
|
52
|
+
# ---------------- RC file (create if missing) ----------------
|
|
53
|
+
RC_FILE = File.join(Dir.home, ".srshrc")
|
|
54
|
+
begin
|
|
55
|
+
unless File.exist?(RC_FILE)
|
|
56
|
+
File.write(RC_FILE, <<~RC)
|
|
46
57
|
# ~/.srshrc — srsh configuration
|
|
47
58
|
# This file was created automatically by srsh v#{SRSH_VERSION}.
|
|
48
59
|
# You can keep personal notes or planned settings here.
|
|
49
60
|
# (Currently not sourced by srsh runtime.)
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
rescue
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
# ---------------- Utilities ----------------
|
|
56
|
-
def color(text, code)
|
|
57
|
-
"\e[#{code}m#{text}\e[0m"
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def random_color
|
|
61
|
-
[31, 32, 33, 34, 35, 36, 37].sample
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def rainbow_codes
|
|
65
|
-
[31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def expand_vars(str)
|
|
69
|
-
str.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { ENV[$1] || "" }
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def parse_redirection(cmd)
|
|
73
|
-
stdin_file = nil
|
|
74
|
-
stdout_file = nil
|
|
75
|
-
append = false
|
|
76
|
-
|
|
77
|
-
if cmd =~ /(.*)>>\s*(\S+)/
|
|
78
|
-
cmd = $1.strip
|
|
79
|
-
stdout_file = $2.strip
|
|
80
|
-
append = true
|
|
81
|
-
elsif cmd =~ /(.*)>\s*(\S+)/
|
|
82
|
-
cmd = $1.strip
|
|
83
|
-
stdout_file = $2.strip
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
if cmd =~ /(.*)<\s*(\S+)/
|
|
87
|
-
cmd = $1.strip
|
|
88
|
-
stdin_file = $2.strip
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
[cmd, stdin_file, stdout_file, append]
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
def human_bytes(bytes)
|
|
95
|
-
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
96
|
-
size = bytes.to_f
|
|
97
|
-
unit = units.shift
|
|
98
|
-
while size > 1024 && !units.empty?
|
|
99
|
-
size /= 1024
|
|
100
|
-
unit = units.shift
|
|
101
|
-
end
|
|
102
|
-
"#{format('%.2f', size)} #{unit}"
|
|
103
|
-
end
|
|
104
|
-
|
|
105
|
-
def nice_bar(p, w = 30, code = 32)
|
|
106
|
-
p = [[p, 0.0].max, 1.0].min
|
|
107
|
-
f = (p * w).round
|
|
108
|
-
b = "█" * f + "░" * (w - f)
|
|
109
|
-
pct = (p * 100).to_i
|
|
110
|
-
"#{color("[#{b}]", code)} #{color(sprintf("%3d%%", pct), 37)}"
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def terminal_width
|
|
114
|
-
IO.console.winsize[1]
|
|
115
|
-
rescue
|
|
116
|
-
80
|
|
117
|
-
end
|
|
118
|
-
|
|
119
|
-
def strip_ansi(str)
|
|
120
|
-
str.to_s.gsub(/\e\[[0-9;]*m/, '')
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
# ---------------- Aliases ----------------
|
|
124
|
-
def expand_aliases(cmd, seen = [])
|
|
125
|
-
return cmd if cmd.nil? || cmd.strip.empty?
|
|
126
|
-
first_word, rest = cmd.strip.split(' ', 2)
|
|
127
|
-
return cmd if seen.include?(first_word)
|
|
128
|
-
seen << first_word
|
|
129
|
-
|
|
130
|
-
if $aliases.key?(first_word)
|
|
131
|
-
replacement = $aliases[first_word]
|
|
132
|
-
expanded = expand_aliases(replacement, seen)
|
|
133
|
-
rest ? "#{expanded} #{rest}" : expanded
|
|
134
|
-
else
|
|
135
|
-
cmd
|
|
136
|
-
end
|
|
137
|
-
end
|
|
61
|
+
RC
|
|
62
|
+
end
|
|
63
|
+
rescue
|
|
64
|
+
end
|
|
138
65
|
|
|
139
|
-
# ----------------
|
|
140
|
-
def
|
|
141
|
-
|
|
142
|
-
end
|
|
66
|
+
# ---------------- Utilities ----------------
|
|
67
|
+
def color(text, code)
|
|
68
|
+
"\e[#{code}m#{text}\e[0m"
|
|
69
|
+
end
|
|
143
70
|
|
|
144
|
-
def
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
l.start_with?('PRETTY_NAME="') || l.start_with?('PRETTY_NAME=')
|
|
148
|
-
}
|
|
149
|
-
return line.split('=').last.strip.delete('"') if line
|
|
150
|
-
end
|
|
151
|
-
"#{RbConfig::CONFIG['host_os']}"
|
|
152
|
-
end
|
|
71
|
+
def random_color
|
|
72
|
+
[31, 32, 33, 34, 35, 36, 37].sample
|
|
73
|
+
end
|
|
153
74
|
|
|
154
|
-
def
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
when /linux/i
|
|
158
|
-
:linux
|
|
159
|
-
when /darwin/i
|
|
160
|
-
:mac
|
|
161
|
-
when /bsd/i
|
|
162
|
-
:bsd
|
|
163
|
-
else
|
|
164
|
-
:other
|
|
165
|
-
end
|
|
166
|
-
end
|
|
75
|
+
def rainbow_codes
|
|
76
|
+
[31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
|
|
77
|
+
end
|
|
167
78
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
"Debugging is like being the detective in a crime movie where you are also the murderer.",
|
|
179
|
-
"Unix is user-friendly. It's just selective about who its friends are.",
|
|
180
|
-
"Old sysadmins never die, they just become daemons.",
|
|
181
|
-
"Listen you flatpaker! – Totally Terry Davis",
|
|
182
|
-
"How is #{detect_distro}? 🤔",
|
|
183
|
-
"Life is short, but your command history is eternal.",
|
|
184
|
-
"If at first you don’t succeed, git commit and push anyway.",
|
|
185
|
-
"rm -rf: the ultimate trust exercise.",
|
|
186
|
-
"Coding is like magic, but with more coffee.",
|
|
187
|
-
"There’s no bug, only undocumented features.",
|
|
188
|
-
"Keep your friends close and your aliases closer.",
|
|
189
|
-
"Why wait for the future when you can Ctrl+Z it?",
|
|
190
|
-
"A watched process never completes.",
|
|
191
|
-
"When in doubt, make it a function.",
|
|
192
|
-
"Some call it procrastination, we call it debugging curiosity.",
|
|
193
|
-
"Life is like a terminal; some commands just don’t execute.",
|
|
194
|
-
"Good code is like a good joke; it needs no explanation.",
|
|
195
|
-
"sudo: because sometimes responsibility is overrated.",
|
|
196
|
-
"Pipes make the world go round.",
|
|
197
|
-
"In bash we trust, in Ruby we wonder.",
|
|
198
|
-
"A system without errors is like a day without coffee.",
|
|
199
|
-
"Keep your loops tight and your sleeps short.",
|
|
200
|
-
"Stack traces are just life giving you directions.",
|
|
201
|
-
"Your mom called, she wants her semicolons back."
|
|
202
|
-
]
|
|
203
|
-
|
|
204
|
-
$current_quote = QUOTES.sample
|
|
205
|
-
|
|
206
|
-
def dynamic_quote
|
|
207
|
-
chars = $current_quote.chars
|
|
208
|
-
rainbow = rainbow_codes.cycle
|
|
209
|
-
chars.map { |c| color(c, rainbow.next) }.join
|
|
210
|
-
end
|
|
79
|
+
def human_bytes(bytes)
|
|
80
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
81
|
+
size = bytes.to_f
|
|
82
|
+
unit = units.shift
|
|
83
|
+
while size > 1024 && !units.empty?
|
|
84
|
+
size /= 1024
|
|
85
|
+
unit = units.shift
|
|
86
|
+
end
|
|
87
|
+
"#{format('%.2f', size)} #{unit}"
|
|
88
|
+
end
|
|
211
89
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
end
|
|
90
|
+
def nice_bar(p, w = 30, code = 32)
|
|
91
|
+
p = [[p, 0.0].max, 1.0].min
|
|
92
|
+
f = (p * w).round
|
|
93
|
+
b = "█" * f + "░" * (w - f)
|
|
94
|
+
pct = (p * 100).to_i
|
|
95
|
+
"#{color("[#{b}]", code)} #{color(sprintf("%3d%%", pct), 37)}"
|
|
96
|
+
end
|
|
219
97
|
|
|
220
|
-
def
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
(prev[5] || 0) + (prev[6] || 0) + (prev[7] || 0)
|
|
226
|
-
non_idle = current[0] + current[1] + current[2] +
|
|
227
|
-
(current[5] || 0) + (current[6] || 0) + (current[7] || 0)
|
|
228
|
-
prev_total = prev_idle + prev_non_idle
|
|
229
|
-
total = idle + non_idle
|
|
230
|
-
totald = total - prev_total
|
|
231
|
-
idled = idle - prev_idle
|
|
232
|
-
return 0.0 if totald <= 0
|
|
233
|
-
((totald - idled).to_f / totald) * 100
|
|
234
|
-
end
|
|
98
|
+
def terminal_width
|
|
99
|
+
IO.console.winsize[1]
|
|
100
|
+
rescue
|
|
101
|
+
80
|
|
102
|
+
end
|
|
235
103
|
|
|
236
|
-
def
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
104
|
+
def strip_ansi(str)
|
|
105
|
+
str.to_s.gsub(/\e\[[0-9;]*m/, '')
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Simple $(...) command substitution (no nesting)
|
|
109
|
+
def expand_command_substitutions(str)
|
|
110
|
+
return "" if str.nil?
|
|
111
|
+
s = str.to_s.dup
|
|
112
|
+
|
|
113
|
+
s.gsub(/\$\(([^()]*)\)/) do
|
|
114
|
+
inner = $1.to_s.strip
|
|
115
|
+
next "" if inner.empty?
|
|
116
|
+
begin
|
|
117
|
+
out = `#{inner} 2>/dev/null`
|
|
118
|
+
out.to_s.strip
|
|
119
|
+
rescue
|
|
120
|
+
""
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# variable expansion: $VAR, $1, $2, $0, $?
|
|
126
|
+
def expand_vars(str)
|
|
127
|
+
return "" if str.nil?
|
|
128
|
+
|
|
129
|
+
# First handle $(...) substitution
|
|
130
|
+
s = expand_command_substitutions(str.to_s)
|
|
131
|
+
|
|
132
|
+
# $VARNAME from ENV
|
|
133
|
+
s = s.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) do
|
|
134
|
+
ENV[$1] || ""
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# $1, $2, $0 from positional table
|
|
138
|
+
s = s.gsub(/\$(\d+)/) do
|
|
139
|
+
idx = $1.to_i
|
|
140
|
+
($rsh_positional && $rsh_positional[idx]) || ""
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# $? -> last exit status
|
|
144
|
+
s = s.gsub(/\$\?/) { $last_status.to_s }
|
|
145
|
+
|
|
146
|
+
s
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def parse_redirection(cmd)
|
|
150
|
+
stdin_file = nil
|
|
151
|
+
stdout_file = nil
|
|
152
|
+
append = false
|
|
153
|
+
|
|
154
|
+
if cmd =~ /(.*)>>\s*(\S+)/
|
|
155
|
+
cmd = $1.strip
|
|
156
|
+
stdout_file = $2.strip
|
|
157
|
+
append = true
|
|
158
|
+
elsif cmd =~ /(.*)>\s*(\S+)/
|
|
159
|
+
cmd = $1.strip
|
|
160
|
+
stdout_file = $2.strip
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
if cmd =~ /(.*)<\s*(\S+)/
|
|
164
|
+
cmd = $1.strip
|
|
165
|
+
stdin_file = $2.strip
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
[cmd, stdin_file, stdout_file, append]
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# ---------------- Aliases ----------------
|
|
172
|
+
def expand_aliases(cmd, seen = [])
|
|
173
|
+
return cmd if cmd.nil? || cmd.strip.empty?
|
|
174
|
+
first_word, rest = cmd.strip.split(' ', 2)
|
|
175
|
+
return cmd if seen.include?(first_word)
|
|
176
|
+
seen << first_word
|
|
177
|
+
|
|
178
|
+
if $aliases.key?(first_word)
|
|
179
|
+
replacement = $aliases[first_word]
|
|
180
|
+
expanded = expand_aliases(replacement, seen)
|
|
181
|
+
rest ? "#{expanded} #{rest}" : expanded
|
|
182
|
+
else
|
|
183
|
+
cmd
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
# ---------------- System Info ----------------
|
|
188
|
+
def current_time
|
|
189
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def detect_distro
|
|
193
|
+
if File.exist?('/etc/os-release')
|
|
194
|
+
line = File.read('/etc/os-release').lines.find { |l|
|
|
195
|
+
l.start_with?('PRETTY_NAME="') || l.start_with?('PRETTY_NAME=')
|
|
196
|
+
}
|
|
197
|
+
return line.split('=').last.strip.delete('"') if line
|
|
198
|
+
end
|
|
199
|
+
"#{RbConfig::CONFIG['host_os']}"
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def os_type
|
|
203
|
+
host = RbConfig::CONFIG['host_os'].to_s
|
|
204
|
+
case host
|
|
205
|
+
when /linux/i
|
|
206
|
+
:linux
|
|
207
|
+
when /darwin/i
|
|
208
|
+
:mac
|
|
209
|
+
when /bsd/i
|
|
210
|
+
:bsd
|
|
211
|
+
else
|
|
212
|
+
:other
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
# ---------------- Quotes ----------------
|
|
217
|
+
QUOTES = [
|
|
218
|
+
"Keep calm and code on.",
|
|
219
|
+
"Did you try turning it off and on again?",
|
|
220
|
+
"There’s no place like 127.0.0.1.",
|
|
221
|
+
"To iterate is human, to recurse divine.",
|
|
222
|
+
"sudo rm -rf / – Just kidding, don’t do that!",
|
|
223
|
+
"The shell is mightier than the sword.",
|
|
224
|
+
"A journey of a thousand commits begins with a single push.",
|
|
225
|
+
"In case of fire: git commit, git push, leave building.",
|
|
226
|
+
"Debugging is like being the detective in a crime movie where you are also the murderer.",
|
|
227
|
+
"Unix is user-friendly. It's just selective about who its friends are.",
|
|
228
|
+
"Old sysadmins never die, they just become daemons.",
|
|
229
|
+
"Listen you flatpaker! – Totally Terry Davis",
|
|
230
|
+
"How is #{detect_distro}? 🤔",
|
|
231
|
+
"Life is short, but your command history is eternal.",
|
|
232
|
+
"If at first you don’t succeed, git commit and push anyway.",
|
|
233
|
+
"rm -rf: the ultimate trust exercise.",
|
|
234
|
+
"Coding is like magic, but with more coffee.",
|
|
235
|
+
"There’s no bug, only undocumented features.",
|
|
236
|
+
"Keep your friends close and your aliases closer.",
|
|
237
|
+
"Why wait for the future when you can Ctrl+Z it?",
|
|
238
|
+
"A watched process never completes.",
|
|
239
|
+
"When in doubt, make it a function.",
|
|
240
|
+
"Some call it procrastination, we call it debugging curiosity.",
|
|
241
|
+
"Life is like a terminal; some commands just don’t execute.",
|
|
242
|
+
"Good code is like a good joke; it needs no explanation.",
|
|
243
|
+
"sudo: because sometimes responsibility is overrated.",
|
|
244
|
+
"Pipes make the world go round.",
|
|
245
|
+
"In bash we trust, in Ruby we wonder.",
|
|
246
|
+
"A system without errors is like a day without coffee.",
|
|
247
|
+
"Keep your loops tight and your sleeps short.",
|
|
248
|
+
"Stack traces are just life giving you directions.",
|
|
249
|
+
"Your mom called, she wants her semicolons back."
|
|
250
|
+
]
|
|
251
|
+
|
|
252
|
+
$current_quote = QUOTES.sample
|
|
253
|
+
|
|
254
|
+
def dynamic_quote
|
|
255
|
+
chars = $current_quote.chars
|
|
256
|
+
rainbow = rainbow_codes.cycle
|
|
257
|
+
chars.map { |c| color(c, rainbow.next) }.join
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
# ---------------- CPU / RAM / Storage ----------------
|
|
261
|
+
def read_cpu_times
|
|
262
|
+
return [] unless File.exist?('/proc/stat')
|
|
263
|
+
cpu_line = File.readlines('/proc/stat').find { |line| line.start_with?('cpu ') }
|
|
264
|
+
return [] unless cpu_line
|
|
265
|
+
cpu_line.split[1..-1].map(&:to_i)
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def calculate_cpu_usage(prev, current)
|
|
269
|
+
return 0.0 if prev.empty? || current.empty?
|
|
270
|
+
prev_idle = prev[3] + (prev[4] || 0)
|
|
271
|
+
idle = current[3] + (current[4] || 0)
|
|
272
|
+
prev_non_idle = prev[0] + prev[1] + prev[2] +
|
|
273
|
+
(prev[5] || 0) + (prev[6] || 0) + (prev[7] || 0)
|
|
274
|
+
non_idle = current[0] + current[1] + current[2] +
|
|
275
|
+
(current[5] || 0) + (current[6] || 0) + (current[7] || 0)
|
|
276
|
+
prev_total = prev_idle + prev_non_idle
|
|
277
|
+
total = idle + non_idle
|
|
278
|
+
totald = total - prev_total
|
|
279
|
+
idled = idle - prev_idle
|
|
280
|
+
return 0.0 if totald <= 0
|
|
281
|
+
((totald - idled).to_f / totald) * 100
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
def cpu_cores_and_freq
|
|
285
|
+
return [0, []] unless File.exist?('/proc/cpuinfo')
|
|
286
|
+
cores = 0
|
|
287
|
+
freqs = []
|
|
240
288
|
File.foreach('/proc/cpuinfo') do |line|
|
|
241
289
|
cores += 1 if line =~ /^processor\s*:\s*\d+/
|
|
242
290
|
if line =~ /^cpu MHz\s*:\s*([\d.]+)/
|
|
@@ -382,6 +430,15 @@ def builtin_help
|
|
|
382
430
|
puts color(sprintf("%-15s", "systemfetch"), "1;36") + "Display system information"
|
|
383
431
|
puts color(sprintf("%-15s", "hist"), "1;36") + "Show shell history"
|
|
384
432
|
puts color(sprintf("%-15s", "clearhist"), "1;36") + "Clear saved history (memory + file)"
|
|
433
|
+
puts color(sprintf("%-15s", "put"), "1;36") + "Print text (like echo)"
|
|
434
|
+
puts color(sprintf("%-15s", "set"), "1;36") + "Set or list variables"
|
|
435
|
+
puts color(sprintf("%-15s", "unset"), "1;36") + "Unset a variable"
|
|
436
|
+
puts color(sprintf("%-15s", "read"), "1;36") + "Read a line into a variable"
|
|
437
|
+
puts color(sprintf("%-15s", "sleep"), "1;36") + "Sleep for N seconds"
|
|
438
|
+
puts color(sprintf("%-15s", "true / false"), "1;36")+ "Always succeed / fail"
|
|
439
|
+
puts color(sprintf("%-15s", "source / ."), "1;36") + "Run another rsh script"
|
|
440
|
+
puts color(sprintf("%-15s", "break / continue"), "1;36") + "Loop control (in scripts)"
|
|
441
|
+
puts color(sprintf("%-15s", "return"), "1;36") + "Return from a function"
|
|
385
442
|
puts color(sprintf("%-15s", "help"), "1;36") + "Show this help message"
|
|
386
443
|
puts color('=' * 60, "1;35")
|
|
387
444
|
end
|
|
@@ -560,6 +617,187 @@ def builtin_ls(path = ".")
|
|
|
560
617
|
print_columns_colored(labels)
|
|
561
618
|
end
|
|
562
619
|
|
|
620
|
+
# ---------------- rsh scripting helpers ----------------
|
|
621
|
+
|
|
622
|
+
# Evaluate rsh condition expressions, Ruby-style with $VARS
|
|
623
|
+
def eval_rsh_expr(expr)
|
|
624
|
+
return false if expr.nil? || expr.strip.empty?
|
|
625
|
+
s = expr.to_s
|
|
626
|
+
|
|
627
|
+
s = s.gsub(/\$([A-Za-z_][A-Za-z0-9_]*)/) do
|
|
628
|
+
(ENV[$1] || "").inspect
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
s = s.gsub(/\$(\d+)/) do
|
|
632
|
+
idx = $1.to_i
|
|
633
|
+
val = ($rsh_positional && $rsh_positional[idx]) || ""
|
|
634
|
+
val.inspect
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
begin
|
|
638
|
+
!!eval(s)
|
|
639
|
+
rescue
|
|
640
|
+
false
|
|
641
|
+
end
|
|
642
|
+
end
|
|
643
|
+
|
|
644
|
+
def rsh_find_if_bounds(lines, start_idx)
|
|
645
|
+
depth = 1
|
|
646
|
+
else_idx = nil
|
|
647
|
+
i = start_idx + 1
|
|
648
|
+
while i < lines.length
|
|
649
|
+
line = strip_rsh_comment(lines[i].to_s).strip
|
|
650
|
+
if line.start_with?("if ")
|
|
651
|
+
depth += 1
|
|
652
|
+
elsif line.start_with?("while ")
|
|
653
|
+
depth += 1
|
|
654
|
+
elsif line.start_with?("fn ")
|
|
655
|
+
depth += 1
|
|
656
|
+
elsif line == "end"
|
|
657
|
+
depth -= 1
|
|
658
|
+
return [else_idx, i] if depth == 0
|
|
659
|
+
elsif line == "else" && depth == 1
|
|
660
|
+
else_idx = i
|
|
661
|
+
end
|
|
662
|
+
i += 1
|
|
663
|
+
end
|
|
664
|
+
raise "Unmatched 'if' in rsh script"
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
def rsh_find_block_end(lines, start_idx)
|
|
668
|
+
depth = 1
|
|
669
|
+
i = start_idx + 1
|
|
670
|
+
while i < lines.length
|
|
671
|
+
line = strip_rsh_comment(lines[i].to_s).strip
|
|
672
|
+
if line.start_with?("if ") || line.start_with?("while ") || line.start_with?("fn ")
|
|
673
|
+
depth += 1
|
|
674
|
+
elsif line == "end"
|
|
675
|
+
depth -= 1
|
|
676
|
+
return i if depth == 0
|
|
677
|
+
end
|
|
678
|
+
i += 1
|
|
679
|
+
end
|
|
680
|
+
raise "Unmatched block in rsh script"
|
|
681
|
+
end
|
|
682
|
+
|
|
683
|
+
def strip_rsh_comment(line)
|
|
684
|
+
in_single = false
|
|
685
|
+
in_double = false
|
|
686
|
+
escaped = false
|
|
687
|
+
i = 0
|
|
688
|
+
|
|
689
|
+
while i < line.length
|
|
690
|
+
ch = line[i]
|
|
691
|
+
if escaped
|
|
692
|
+
escaped = false
|
|
693
|
+
elsif ch == '\\'
|
|
694
|
+
escaped = true
|
|
695
|
+
elsif ch == "'" && !in_double
|
|
696
|
+
in_single = !in_single
|
|
697
|
+
elsif ch == '"' && !in_single
|
|
698
|
+
in_double = !in_double
|
|
699
|
+
elsif ch == '#' && !in_single && !in_double
|
|
700
|
+
return line[0...i]
|
|
701
|
+
end
|
|
702
|
+
i += 1
|
|
703
|
+
end
|
|
704
|
+
|
|
705
|
+
line
|
|
706
|
+
end
|
|
707
|
+
|
|
708
|
+
def run_rsh_block(lines, start_idx, end_idx)
|
|
709
|
+
i = start_idx
|
|
710
|
+
while i < end_idx
|
|
711
|
+
raw = lines[i]
|
|
712
|
+
i += 1
|
|
713
|
+
next if raw.nil?
|
|
714
|
+
|
|
715
|
+
line = strip_rsh_comment(raw).strip
|
|
716
|
+
next if line.empty?
|
|
717
|
+
|
|
718
|
+
if line.start_with?("if ")
|
|
719
|
+
cond_expr = line[3..-1].strip
|
|
720
|
+
else_idx, end_idx_2 = rsh_find_if_bounds(lines, i - 1)
|
|
721
|
+
if eval_rsh_expr(cond_expr)
|
|
722
|
+
body_end = else_idx || end_idx_2
|
|
723
|
+
run_rsh_block(lines, i, body_end)
|
|
724
|
+
elsif else_idx
|
|
725
|
+
run_rsh_block(lines, else_idx + 1, end_idx_2)
|
|
726
|
+
end
|
|
727
|
+
i = end_idx_2 + 1
|
|
728
|
+
next
|
|
729
|
+
|
|
730
|
+
elsif line.start_with?("while ")
|
|
731
|
+
cond_expr = line[6..-1].strip
|
|
732
|
+
block_end = rsh_find_block_end(lines, i - 1)
|
|
733
|
+
while eval_rsh_expr(cond_expr)
|
|
734
|
+
begin
|
|
735
|
+
run_rsh_block(lines, i, block_end)
|
|
736
|
+
rescue RshBreak
|
|
737
|
+
break
|
|
738
|
+
rescue RshContinue
|
|
739
|
+
next
|
|
740
|
+
end
|
|
741
|
+
end
|
|
742
|
+
i = block_end + 1
|
|
743
|
+
next
|
|
744
|
+
|
|
745
|
+
elsif line.start_with?("fn ")
|
|
746
|
+
parts = line.split
|
|
747
|
+
name = parts[1]
|
|
748
|
+
argnames = parts[2..-1] || []
|
|
749
|
+
block_end = rsh_find_block_end(lines, i - 1)
|
|
750
|
+
$rsh_functions[name] = {
|
|
751
|
+
args: argnames,
|
|
752
|
+
body: lines[i...block_end]
|
|
753
|
+
}
|
|
754
|
+
i = block_end + 1
|
|
755
|
+
next
|
|
756
|
+
|
|
757
|
+
else
|
|
758
|
+
run_input_line(line)
|
|
759
|
+
end
|
|
760
|
+
end
|
|
761
|
+
end
|
|
762
|
+
|
|
763
|
+
def rsh_run_script(script_path, argv)
|
|
764
|
+
$rsh_script_mode = true
|
|
765
|
+
$rsh_positional = {}
|
|
766
|
+
$rsh_positional[0] = File.basename(script_path)
|
|
767
|
+
argv.each_with_index do |val, idx|
|
|
768
|
+
$rsh_positional[idx + 1] = val
|
|
769
|
+
end
|
|
770
|
+
|
|
771
|
+
lines = File.readlines(script_path, chomp: true)
|
|
772
|
+
if lines[0] && lines[0].start_with?("#!")
|
|
773
|
+
lines = lines[1..-1] || []
|
|
774
|
+
end
|
|
775
|
+
run_rsh_block(lines, 0, lines.length)
|
|
776
|
+
end
|
|
777
|
+
|
|
778
|
+
def rsh_call_function(name, argv)
|
|
779
|
+
fn = $rsh_functions[name]
|
|
780
|
+
return unless fn
|
|
781
|
+
|
|
782
|
+
saved_positional = $rsh_positional
|
|
783
|
+
$rsh_positional = {}
|
|
784
|
+
$rsh_positional[0] = name
|
|
785
|
+
|
|
786
|
+
fn[:args].each_with_index do |argname, idx|
|
|
787
|
+
val = argv[idx] || ""
|
|
788
|
+
ENV[argname] = val
|
|
789
|
+
$rsh_positional[idx + 1] = val
|
|
790
|
+
end
|
|
791
|
+
|
|
792
|
+
begin
|
|
793
|
+
run_rsh_block(fn[:body], 0, fn[:body].length)
|
|
794
|
+
rescue RshReturn
|
|
795
|
+
# swallow return
|
|
796
|
+
ensure
|
|
797
|
+
$rsh_positional = saved_positional
|
|
798
|
+
end
|
|
799
|
+
end
|
|
800
|
+
|
|
563
801
|
# ---------------- External Execution Helper ----------------
|
|
564
802
|
def exec_external(args, stdin_file, stdout_file, append)
|
|
565
803
|
command_path = args[0]
|
|
@@ -601,6 +839,7 @@ def exec_external(args, stdin_file, stdout_file, append)
|
|
|
601
839
|
$child_pids << pid
|
|
602
840
|
begin
|
|
603
841
|
Process.wait(pid)
|
|
842
|
+
$last_status = $?.exitstatus || 0
|
|
604
843
|
rescue Interrupt
|
|
605
844
|
ensure
|
|
606
845
|
$child_pids.delete(pid)
|
|
@@ -609,37 +848,79 @@ end
|
|
|
609
848
|
|
|
610
849
|
# ---------------- Command Execution ----------------
|
|
611
850
|
def run_command(cmd)
|
|
612
|
-
cmd = cmd.to_s
|
|
613
|
-
|
|
614
|
-
|
|
851
|
+
cmd = cmd.to_s.strip
|
|
852
|
+
return if cmd.empty?
|
|
853
|
+
|
|
854
|
+
cmd = expand_aliases(cmd)
|
|
855
|
+
cmd = expand_vars(cmd)
|
|
856
|
+
|
|
857
|
+
# ---------------- Assignments ----------------
|
|
858
|
+
# Expression-style: VAR = Ruby_expression
|
|
859
|
+
if (m = cmd.match(/\A([A-Za-z_][A-Za-z0-9_]*)\s+=\s+(.+)\z/))
|
|
860
|
+
var = m[1]
|
|
861
|
+
rhs = m[2]
|
|
862
|
+
begin
|
|
863
|
+
value = eval(rhs)
|
|
864
|
+
ENV[var] = value.is_a?(String) ? value : value.to_s
|
|
865
|
+
rescue Exception
|
|
866
|
+
ENV[var] = rhs
|
|
867
|
+
end
|
|
868
|
+
$last_status = 0
|
|
869
|
+
return
|
|
870
|
+
end
|
|
871
|
+
|
|
872
|
+
# Simple shell-style: VAR=value (no spaces)
|
|
873
|
+
if (m = cmd.match(/\A([A-Za-z_][A-Za-z0-9_]*)=(.*)\z/))
|
|
874
|
+
var = m[1]
|
|
875
|
+
val = m[2] || ""
|
|
876
|
+
ENV[var] = val
|
|
877
|
+
$last_status = 0
|
|
878
|
+
return
|
|
879
|
+
end
|
|
880
|
+
|
|
881
|
+
# ---------------- Redirections + args ----------------
|
|
615
882
|
cmd, stdin_file, stdout_file, append = parse_redirection(cmd)
|
|
616
883
|
args = Shellwords.shellsplit(cmd) rescue []
|
|
617
884
|
return if args.empty?
|
|
618
885
|
|
|
886
|
+
# rsh functions
|
|
887
|
+
if $rsh_functions.key?(args[0])
|
|
888
|
+
rsh_call_function(args[0], args[1..-1] || [])
|
|
889
|
+
$last_status = 0
|
|
890
|
+
return
|
|
891
|
+
end
|
|
892
|
+
|
|
619
893
|
case args[0]
|
|
620
894
|
when 'ls'
|
|
621
895
|
if args.length == 1
|
|
622
896
|
builtin_ls(".")
|
|
623
|
-
return
|
|
624
897
|
elsif args.length == 2 && !args[1].start_with?("-")
|
|
625
898
|
builtin_ls(args[1])
|
|
899
|
+
else
|
|
900
|
+
exec_external(args, stdin_file, stdout_file, append)
|
|
626
901
|
return
|
|
627
902
|
end
|
|
628
|
-
|
|
903
|
+
$last_status = 0
|
|
629
904
|
return
|
|
905
|
+
|
|
630
906
|
when 'cd'
|
|
631
907
|
path = args[1] ? File.expand_path(args[1]) : ENV['HOME']
|
|
632
908
|
if !File.exist?(path)
|
|
633
909
|
puts color("cd: no such file or directory: #{args[1]}", 31)
|
|
910
|
+
$last_status = 1
|
|
634
911
|
elsif !File.directory?(path)
|
|
635
912
|
puts color("cd: not a directory: #{args[1]}", 31)
|
|
913
|
+
$last_status = 1
|
|
636
914
|
else
|
|
637
915
|
Dir.chdir(path)
|
|
916
|
+
$last_status = 0
|
|
638
917
|
end
|
|
639
918
|
return
|
|
919
|
+
|
|
640
920
|
when 'exit','quit'
|
|
641
921
|
$child_pids.each { |pid| Process.kill("TERM", pid) rescue nil }
|
|
642
922
|
exit 0
|
|
923
|
+
|
|
643
924
|
when 'alias'
|
|
644
925
|
if args[1].nil?
|
|
645
926
|
$aliases.each { |k,v| puts "#{k}='#{v}'" }
|
|
@@ -651,42 +932,188 @@ def run_command(cmd)
|
|
|
651
932
|
puts color("Invalid alias format", 31)
|
|
652
933
|
end
|
|
653
934
|
end
|
|
935
|
+
$last_status = 0
|
|
654
936
|
return
|
|
937
|
+
|
|
655
938
|
when 'unalias'
|
|
656
939
|
if args[1]
|
|
657
940
|
$aliases.delete(args[1])
|
|
941
|
+
$last_status = 0
|
|
658
942
|
else
|
|
659
943
|
puts color("unalias: usage: unalias name", 31)
|
|
944
|
+
$last_status = 1
|
|
660
945
|
end
|
|
661
946
|
return
|
|
947
|
+
|
|
662
948
|
when 'help'
|
|
663
949
|
builtin_help
|
|
950
|
+
$last_status = 0
|
|
664
951
|
return
|
|
952
|
+
|
|
665
953
|
when 'systemfetch'
|
|
666
954
|
builtin_systemfetch
|
|
955
|
+
$last_status = 0
|
|
667
956
|
return
|
|
957
|
+
|
|
668
958
|
when 'jobs'
|
|
669
959
|
builtin_jobs
|
|
960
|
+
$last_status = 0
|
|
670
961
|
return
|
|
962
|
+
|
|
671
963
|
when 'pwd'
|
|
672
964
|
puts color(Dir.pwd, 36)
|
|
965
|
+
$last_status = 0
|
|
673
966
|
return
|
|
967
|
+
|
|
674
968
|
when 'hist'
|
|
675
969
|
builtin_hist
|
|
970
|
+
$last_status = 0
|
|
676
971
|
return
|
|
972
|
+
|
|
677
973
|
when 'clearhist'
|
|
678
974
|
builtin_clearhist
|
|
975
|
+
$last_status = 0
|
|
976
|
+
return
|
|
977
|
+
|
|
978
|
+
when 'put'
|
|
979
|
+
msg = args[1..-1].join(' ')
|
|
980
|
+
puts msg
|
|
981
|
+
$last_status = 0
|
|
982
|
+
return
|
|
983
|
+
|
|
984
|
+
# -------- New scripting builtins --------
|
|
985
|
+
when 'break'
|
|
986
|
+
raise RshBreak
|
|
987
|
+
|
|
988
|
+
when 'continue'
|
|
989
|
+
raise RshContinue
|
|
990
|
+
|
|
991
|
+
when 'return'
|
|
992
|
+
raise RshReturn
|
|
993
|
+
|
|
994
|
+
when 'set'
|
|
995
|
+
if args.length == 1
|
|
996
|
+
ENV.keys.sort.each do |k|
|
|
997
|
+
puts "#{k}=#{ENV[k]}"
|
|
998
|
+
end
|
|
999
|
+
else
|
|
1000
|
+
var = args[1]
|
|
1001
|
+
val = args[2..-1].join(' ')
|
|
1002
|
+
ENV[var] = val
|
|
1003
|
+
end
|
|
1004
|
+
$last_status = 0
|
|
1005
|
+
return
|
|
1006
|
+
|
|
1007
|
+
when 'unset'
|
|
1008
|
+
if args[1]
|
|
1009
|
+
ENV.delete(args[1])
|
|
1010
|
+
$last_status = 0
|
|
1011
|
+
else
|
|
1012
|
+
puts color("unset: usage: unset VAR", 31)
|
|
1013
|
+
$last_status = 1
|
|
1014
|
+
end
|
|
1015
|
+
return
|
|
1016
|
+
|
|
1017
|
+
when 'read'
|
|
1018
|
+
var = args[1]
|
|
1019
|
+
unless var
|
|
1020
|
+
puts color("read: usage: read VAR", 31)
|
|
1021
|
+
$last_status = 1
|
|
1022
|
+
return
|
|
1023
|
+
end
|
|
1024
|
+
line = STDIN.gets
|
|
1025
|
+
ENV[var] = (line ? line.chomp : "")
|
|
1026
|
+
$last_status = 0
|
|
1027
|
+
return
|
|
1028
|
+
|
|
1029
|
+
when 'true'
|
|
1030
|
+
$last_status = 0
|
|
1031
|
+
return
|
|
1032
|
+
|
|
1033
|
+
when 'false'
|
|
1034
|
+
$last_status = 1
|
|
1035
|
+
return
|
|
1036
|
+
|
|
1037
|
+
when 'sleep'
|
|
1038
|
+
secs = (args[1] || "1").to_f
|
|
1039
|
+
begin
|
|
1040
|
+
Kernel.sleep(secs)
|
|
1041
|
+
$last_status = 0
|
|
1042
|
+
rescue
|
|
1043
|
+
$last_status = 1
|
|
1044
|
+
end
|
|
1045
|
+
return
|
|
1046
|
+
|
|
1047
|
+
when 'source', '.'
|
|
1048
|
+
file = args[1]
|
|
1049
|
+
if file.nil?
|
|
1050
|
+
puts color("source: usage: source FILE", 31)
|
|
1051
|
+
$last_status = 1
|
|
1052
|
+
return
|
|
1053
|
+
end
|
|
1054
|
+
begin
|
|
1055
|
+
rsh_run_script(file, args[2..-1] || [])
|
|
1056
|
+
$last_status = 0
|
|
1057
|
+
rescue => e
|
|
1058
|
+
STDERR.puts "source error: #{e.class}: #{e.message}"
|
|
1059
|
+
$last_status = 1
|
|
1060
|
+
end
|
|
679
1061
|
return
|
|
680
1062
|
end
|
|
681
1063
|
|
|
1064
|
+
# Fallback to external command
|
|
682
1065
|
exec_external(args, stdin_file, stdout_file, append)
|
|
683
1066
|
end
|
|
684
1067
|
|
|
685
1068
|
# ---------------- Chained Commands ----------------
|
|
1069
|
+
def split_commands(input)
|
|
1070
|
+
return [] if input.nil?
|
|
1071
|
+
|
|
1072
|
+
cmds = []
|
|
1073
|
+
buf = +""
|
|
1074
|
+
in_single = false
|
|
1075
|
+
in_double = false
|
|
1076
|
+
escaped = false
|
|
1077
|
+
i = 0
|
|
1078
|
+
|
|
1079
|
+
while i < input.length
|
|
1080
|
+
ch = input[i]
|
|
1081
|
+
|
|
1082
|
+
if escaped
|
|
1083
|
+
buf << ch
|
|
1084
|
+
escaped = false
|
|
1085
|
+
elsif ch == '\\'
|
|
1086
|
+
escaped = true
|
|
1087
|
+
buf << ch
|
|
1088
|
+
elsif ch == "'" && !in_double
|
|
1089
|
+
in_single = !in_single
|
|
1090
|
+
buf << ch
|
|
1091
|
+
elsif ch == '"' && !in_single
|
|
1092
|
+
in_double = !in_double
|
|
1093
|
+
buf << ch
|
|
1094
|
+
elsif !in_single && !in_double && ch == ';'
|
|
1095
|
+
cmd = buf.strip
|
|
1096
|
+
cmds << cmd unless cmd.empty?
|
|
1097
|
+
buf = +""
|
|
1098
|
+
elsif !in_single && !in_double && ch == '&' && input[i + 1] == '&'
|
|
1099
|
+
cmd = buf.strip
|
|
1100
|
+
cmds << cmd unless cmd.empty?
|
|
1101
|
+
buf = +""
|
|
1102
|
+
i += 1
|
|
1103
|
+
else
|
|
1104
|
+
buf << ch
|
|
1105
|
+
end
|
|
1106
|
+
|
|
1107
|
+
i += 1
|
|
1108
|
+
end
|
|
1109
|
+
|
|
1110
|
+
cmd = buf.strip
|
|
1111
|
+
cmds << cmd unless cmd.empty?
|
|
1112
|
+
cmds
|
|
1113
|
+
end
|
|
1114
|
+
|
|
686
1115
|
def run_input_line(input)
|
|
687
|
-
|
|
688
|
-
commands.each do |cmd|
|
|
689
|
-
next if cmd.empty?
|
|
1116
|
+
split_commands(input).each do |cmd|
|
|
690
1117
|
run_command(cmd)
|
|
691
1118
|
end
|
|
692
1119
|
end
|
|
@@ -919,12 +1346,14 @@ def read_line_with_ghost(prompt_str)
|
|
|
919
1346
|
STDOUT.print("\r\n")
|
|
920
1347
|
STDOUT.flush
|
|
921
1348
|
break
|
|
1349
|
+
|
|
922
1350
|
when "\u0003" # Ctrl-C
|
|
923
1351
|
STDOUT.print("^C\r\n")
|
|
924
1352
|
STDOUT.flush
|
|
925
1353
|
status = :interrupt
|
|
926
1354
|
buffer = ""
|
|
927
1355
|
break
|
|
1356
|
+
|
|
928
1357
|
when "\u0004" # Ctrl-D
|
|
929
1358
|
if buffer.empty?
|
|
930
1359
|
status = :eof
|
|
@@ -935,6 +1364,12 @@ def read_line_with_ghost(prompt_str)
|
|
|
935
1364
|
else
|
|
936
1365
|
# ignore when line not empty
|
|
937
1366
|
end
|
|
1367
|
+
|
|
1368
|
+
when "\u0001" # Ctrl-A - move to beginning of line
|
|
1369
|
+
cursor = 0
|
|
1370
|
+
last_tab_prefix = nil
|
|
1371
|
+
tab_cycle = 0
|
|
1372
|
+
|
|
938
1373
|
when "\u007F", "\b" # Backspace
|
|
939
1374
|
if cursor > 0
|
|
940
1375
|
buffer.slice!(cursor - 1)
|
|
@@ -942,12 +1377,12 @@ def read_line_with_ghost(prompt_str)
|
|
|
942
1377
|
end
|
|
943
1378
|
last_tab_prefix = nil
|
|
944
1379
|
tab_cycle = 0
|
|
1380
|
+
|
|
945
1381
|
when "\t" # Tab completion
|
|
946
1382
|
buffer, cursor, last_tab_prefix, tab_cycle, printed =
|
|
947
1383
|
handle_tab_completion(prompt_str, buffer, cursor, last_tab_prefix, tab_cycle)
|
|
948
|
-
# After showing completion list, reset render rows so the next prompt
|
|
949
|
-
# redraw only clears the current input line, not the completion block.
|
|
950
1384
|
$last_render_rows = 1 if printed
|
|
1385
|
+
|
|
951
1386
|
when "\e" # Escape sequences (arrows, home/end)
|
|
952
1387
|
seq1 = io.getch
|
|
953
1388
|
seq2 = io.getch
|
|
@@ -992,6 +1427,7 @@ def read_line_with_ghost(prompt_str)
|
|
|
992
1427
|
end
|
|
993
1428
|
last_tab_prefix = nil
|
|
994
1429
|
tab_cycle = 0
|
|
1430
|
+
|
|
995
1431
|
else
|
|
996
1432
|
if ch.ord >= 32 && ch.ord != 127
|
|
997
1433
|
buffer.insert(cursor, ch)
|
|
@@ -1022,6 +1458,17 @@ def print_welcome
|
|
|
1022
1458
|
puts
|
|
1023
1459
|
end
|
|
1024
1460
|
|
|
1461
|
+
# ---------------- Script vs interactive entry ----------------
|
|
1462
|
+
if ARGV[0]
|
|
1463
|
+
script_path = ARGV.shift
|
|
1464
|
+
begin
|
|
1465
|
+
rsh_run_script(script_path, ARGV)
|
|
1466
|
+
rescue => e
|
|
1467
|
+
STDERR.puts "rsh script error: #{e.class}: #{e.message}"
|
|
1468
|
+
end
|
|
1469
|
+
exit 0
|
|
1470
|
+
end
|
|
1471
|
+
|
|
1025
1472
|
print_welcome
|
|
1026
1473
|
|
|
1027
1474
|
# ---------------- Main Loop ----------------
|
data/lib/srsh/version.rb
CHANGED