srsh 0.6.0 → 0.6.1.pre.HOTFIX
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 +427 -265
- 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: bfa425d4d7aed693a0000a3e8c1720289ad8347e08a1a0cb7e22c3f4aa9a206a
|
|
4
|
+
data.tar.gz: af8b9efb9c83c9583791eb37933bcc93a4f594d82b8d79e8e8f24de110b61c01
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 7bc729a3439ffcfc7e90c13f50e8c0a9535b5b8bac9ae88227b8111ae002584cbcb0e91ef7b36736b0f68afa84d0e863871811f63989cd3101d5a94161a987fc
|
|
7
|
+
data.tar.gz: 211b5716b2734470f319007a75c213c80d6c7601081f7c7e5e076529b56f53d566fe3589230e60b1ed0ebeb335d206556c017796f9574d8c7aa1b8c42e69f26d
|
data/bin/srsh
CHANGED
|
@@ -7,7 +7,7 @@ require 'rbconfig'
|
|
|
7
7
|
require 'io/console'
|
|
8
8
|
|
|
9
9
|
# ---------------- Version ----------------
|
|
10
|
-
SRSH_VERSION = "0.6.
|
|
10
|
+
SRSH_VERSION = "0.6.1"
|
|
11
11
|
|
|
12
12
|
$0 = "srsh-#{SRSH_VERSION}"
|
|
13
13
|
ENV['SHELL'] = "srsh-#{SRSH_VERSION}"
|
|
@@ -15,15 +15,16 @@ print "\033]0;srsh-#{SRSH_VERSION}\007"
|
|
|
15
15
|
|
|
16
16
|
Dir.chdir(ENV['HOME']) if ENV['HOME']
|
|
17
17
|
|
|
18
|
-
$child_pids
|
|
19
|
-
$aliases
|
|
18
|
+
$child_pids = []
|
|
19
|
+
$aliases = {}
|
|
20
|
+
$last_render_rows = 0
|
|
20
21
|
|
|
21
22
|
Signal.trap("INT", "IGNORE")
|
|
22
23
|
|
|
23
24
|
# ---------------- History ----------------
|
|
24
25
|
HISTORY_FILE = File.join(Dir.home, ".srsh_history")
|
|
25
26
|
HISTORY = if File.exist?(HISTORY_FILE)
|
|
26
|
-
File.readlines(HISTORY_FILE, chomp: true)
|
|
27
|
+
File.readlines(HISTORY_FILE, chomp: true)
|
|
27
28
|
else
|
|
28
29
|
[]
|
|
29
30
|
end
|
|
@@ -41,12 +42,12 @@ end
|
|
|
41
42
|
RC_FILE = File.join(Dir.home, ".srshrc")
|
|
42
43
|
begin
|
|
43
44
|
unless File.exist?(RC_FILE)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
File.write(RC_FILE, <<~RC)
|
|
46
|
+
# ~/.srshrc — srsh configuration
|
|
47
|
+
# This file was created automatically by srsh v#{SRSH_VERSION}.
|
|
48
|
+
# You can keep personal notes or planned settings here.
|
|
49
|
+
# (Currently not sourced by srsh runtime.)
|
|
50
|
+
RC
|
|
50
51
|
end
|
|
51
52
|
rescue
|
|
52
53
|
end
|
|
@@ -57,11 +58,11 @@ def color(text, code)
|
|
|
57
58
|
end
|
|
58
59
|
|
|
59
60
|
def random_color
|
|
60
|
-
[31,32,33,34,35,36,37].sample
|
|
61
|
+
[31, 32, 33, 34, 35, 36, 37].sample
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
def rainbow_codes
|
|
64
|
-
[31,33,32,36,34,35,91,93,92,96,94,95]
|
|
65
|
+
[31, 33, 32, 36, 34, 35, 91, 93, 92, 96, 94, 95]
|
|
65
66
|
end
|
|
66
67
|
|
|
67
68
|
def expand_vars(str)
|
|
@@ -69,21 +70,21 @@ def expand_vars(str)
|
|
|
69
70
|
end
|
|
70
71
|
|
|
71
72
|
def parse_redirection(cmd)
|
|
72
|
-
stdin_file
|
|
73
|
+
stdin_file = nil
|
|
73
74
|
stdout_file = nil
|
|
74
|
-
append
|
|
75
|
+
append = false
|
|
75
76
|
|
|
76
77
|
if cmd =~ /(.*)>>\s*(\S+)/
|
|
77
|
-
cmd
|
|
78
|
+
cmd = $1.strip
|
|
78
79
|
stdout_file = $2.strip
|
|
79
|
-
append
|
|
80
|
+
append = true
|
|
80
81
|
elsif cmd =~ /(.*)>\s*(\S+)/
|
|
81
|
-
cmd
|
|
82
|
+
cmd = $1.strip
|
|
82
83
|
stdout_file = $2.strip
|
|
83
84
|
end
|
|
84
85
|
|
|
85
86
|
if cmd =~ /(.*)<\s*(\S+)/
|
|
86
|
-
cmd
|
|
87
|
+
cmd = $1.strip
|
|
87
88
|
stdin_file = $2.strip
|
|
88
89
|
end
|
|
89
90
|
|
|
@@ -91,12 +92,12 @@ def parse_redirection(cmd)
|
|
|
91
92
|
end
|
|
92
93
|
|
|
93
94
|
def human_bytes(bytes)
|
|
94
|
-
units = ['B','KB','MB','GB','TB']
|
|
95
|
-
size
|
|
96
|
-
unit
|
|
95
|
+
units = ['B', 'KB', 'MB', 'GB', 'TB']
|
|
96
|
+
size = bytes.to_f
|
|
97
|
+
unit = units.shift
|
|
97
98
|
while size > 1024 && !units.empty?
|
|
98
99
|
size /= 1024
|
|
99
|
-
unit
|
|
100
|
+
unit = units.shift
|
|
100
101
|
end
|
|
101
102
|
"#{format('%.2f', size)} #{unit}"
|
|
102
103
|
end
|
|
@@ -117,207 +118,344 @@ end
|
|
|
117
118
|
|
|
118
119
|
def strip_ansi(str)
|
|
119
120
|
str.to_s.gsub(/\e\[[0-9;]*m/, '')
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
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
|
|
138
|
+
|
|
139
|
+
# ---------------- System Info ----------------
|
|
140
|
+
def current_time
|
|
141
|
+
Time.now.strftime("%Y-%m-%d %H:%M:%S %Z")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def detect_distro
|
|
145
|
+
if File.exist?('/etc/os-release')
|
|
146
|
+
line = File.read('/etc/os-release').lines.find { |l|
|
|
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
|
|
153
|
+
|
|
154
|
+
def os_type
|
|
155
|
+
host = RbConfig::CONFIG['host_os'].to_s
|
|
156
|
+
case host
|
|
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
|
|
167
|
+
|
|
168
|
+
# ---------------- Quotes ----------------
|
|
169
|
+
QUOTES = [
|
|
170
|
+
"Keep calm and code on.",
|
|
171
|
+
"Did you try turning it off and on again?",
|
|
172
|
+
"There’s no place like 127.0.0.1.",
|
|
173
|
+
"To iterate is human, to recurse divine.",
|
|
174
|
+
"sudo rm -rf / – Just kidding, don’t do that!",
|
|
175
|
+
"The shell is mightier than the sword.",
|
|
176
|
+
"A journey of a thousand commits begins with a single push.",
|
|
177
|
+
"In case of fire: git commit, git push, leave building.",
|
|
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
|
|
211
|
+
|
|
212
|
+
# ---------------- CPU / RAM / Storage ----------------
|
|
213
|
+
def read_cpu_times
|
|
214
|
+
return [] unless File.exist?('/proc/stat')
|
|
215
|
+
cpu_line = File.readlines('/proc/stat').find { |line| line.start_with?('cpu ') }
|
|
216
|
+
return [] unless cpu_line
|
|
217
|
+
cpu_line.split[1..-1].map(&:to_i)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def calculate_cpu_usage(prev, current)
|
|
221
|
+
return 0.0 if prev.empty? || current.empty?
|
|
222
|
+
prev_idle = prev[3] + (prev[4] || 0)
|
|
223
|
+
idle = current[3] + (current[4] || 0)
|
|
224
|
+
prev_non_idle = prev[0] + prev[1] + prev[2] +
|
|
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
|
|
235
|
+
|
|
236
|
+
def cpu_cores_and_freq
|
|
237
|
+
return [0, []] unless File.exist?('/proc/cpuinfo')
|
|
238
|
+
cores = 0
|
|
239
|
+
freqs = []
|
|
240
|
+
File.foreach('/proc/cpuinfo') do |line|
|
|
241
|
+
cores += 1 if line =~ /^processor\s*:\s*\d+/
|
|
242
|
+
if line =~ /^cpu MHz\s*:\s*([\d.]+)/
|
|
243
|
+
freqs << $1.to_f
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
[cores, freqs.first(cores)]
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
def cpu_info
|
|
250
|
+
usage = 0.0
|
|
251
|
+
cores = 0
|
|
252
|
+
freq_display = "N/A"
|
|
253
|
+
|
|
254
|
+
case os_type
|
|
255
|
+
when :linux
|
|
256
|
+
prev = read_cpu_times
|
|
257
|
+
sleep 0.05
|
|
258
|
+
current = read_cpu_times
|
|
259
|
+
usage = calculate_cpu_usage(prev, current).round(1)
|
|
260
|
+
cores, freqs = cpu_cores_and_freq
|
|
261
|
+
freq_display = freqs.empty? ? "N/A" : freqs.map { |f| "#{f.round(0)}MHz" }.join(', ')
|
|
262
|
+
else
|
|
263
|
+
cores = begin
|
|
264
|
+
`sysctl -n hw.ncpu 2>/dev/null`.to_i
|
|
265
|
+
rescue
|
|
266
|
+
0
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
raw_freq_hz = begin
|
|
270
|
+
`sysctl -n hw.cpufrequency 2>/dev/null`.to_i
|
|
271
|
+
rescue
|
|
272
|
+
0
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
freq_display =
|
|
276
|
+
if raw_freq_hz > 0
|
|
277
|
+
mhz = (raw_freq_hz.to_f / 1_000_000.0).round(0)
|
|
278
|
+
"#{mhz.to_i}MHz"
|
|
279
|
+
else
|
|
280
|
+
"N/A"
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
usage = begin
|
|
284
|
+
ps_output = `ps -A -o %cpu 2>/dev/null`
|
|
285
|
+
lines = ps_output.lines
|
|
286
|
+
values = lines[1..-1] || []
|
|
287
|
+
sum = values.map { |l| l.to_f }.inject(0.0, :+)
|
|
288
|
+
if cores > 0
|
|
289
|
+
(sum / cores).round(1)
|
|
290
|
+
else
|
|
291
|
+
sum.round(1)
|
|
292
|
+
end
|
|
293
|
+
rescue
|
|
294
|
+
0.0
|
|
295
|
+
end
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
"#{color("CPU Usage:",36)} #{color("#{usage}%",33)} | " \
|
|
299
|
+
"#{color("Cores:",36)} #{color(cores.to_s,32)} | " \
|
|
300
|
+
"#{color("Freqs:",36)} #{color(freq_display,35)}"
|
|
301
|
+
end
|
|
302
|
+
|
|
303
|
+
def ram_info
|
|
304
|
+
case os_type
|
|
305
|
+
when :linux
|
|
306
|
+
if File.exist?('/proc/meminfo')
|
|
307
|
+
meminfo = {}
|
|
308
|
+
File.read('/proc/meminfo').each_line do |line|
|
|
309
|
+
key, val = line.split(':')
|
|
310
|
+
meminfo[key.strip] = val.strip.split.first.to_i * 1024 if key && val
|
|
311
|
+
end
|
|
312
|
+
total = meminfo['MemTotal'] || 0
|
|
313
|
+
free = (meminfo['MemFree'] || 0) + (meminfo['Buffers'] || 0) + (meminfo['Cached'] || 0)
|
|
314
|
+
used = total - free
|
|
315
|
+
"#{color("RAM Usage:",36)} #{color(human_bytes(used),33)} / #{color(human_bytes(total),32)}"
|
|
316
|
+
else
|
|
317
|
+
"#{color("RAM Usage:",36)} Info not available"
|
|
318
|
+
end
|
|
319
|
+
else
|
|
320
|
+
begin
|
|
321
|
+
if os_type == :mac
|
|
322
|
+
total = `sysctl -n hw.memsize 2>/dev/null`.to_i
|
|
323
|
+
return "#{color("RAM Usage:",36)} Info not available" if total <= 0
|
|
324
|
+
|
|
325
|
+
vm = `vm_stat 2>/dev/null`
|
|
326
|
+
page_size = vm[/page size of (\d+) bytes/, 1].to_i
|
|
327
|
+
page_size = 4096 if page_size <= 0
|
|
328
|
+
|
|
329
|
+
stats = {}
|
|
330
|
+
vm.each_line do |line|
|
|
331
|
+
if line =~ /^(.+):\s+(\d+)\./
|
|
332
|
+
stats[$1] = $2.to_i
|
|
333
|
+
end
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
used_pages = 0
|
|
337
|
+
%w[Pages active Pages wired down Pages occupied by compressor].each do |k|
|
|
338
|
+
used_pages += stats[k].to_i
|
|
339
|
+
end
|
|
340
|
+
used = used_pages * page_size
|
|
341
|
+
|
|
342
|
+
"#{color("RAM Usage:",36)} #{color(human_bytes(used),33)} / #{color(human_bytes(total),32)}"
|
|
343
|
+
else
|
|
344
|
+
total = `sysctl -n hw.physmem 2>/dev/null`.to_i
|
|
345
|
+
total = `sysctl -n hw.realmem 2>/dev/null`.to_i if total <= 0
|
|
346
|
+
return "#{color("RAM Usage:",36)} Info not available" if total <= 0
|
|
347
|
+
"#{color("RAM Usage:",36)} #{color("Unknown",33)} / #{color(human_bytes(total),32)}"
|
|
348
|
+
end
|
|
349
|
+
rescue
|
|
350
|
+
"#{color("RAM Usage:",36)} Info not available"
|
|
351
|
+
end
|
|
352
|
+
end
|
|
353
|
+
end
|
|
354
|
+
|
|
355
|
+
def storage_info
|
|
356
|
+
begin
|
|
357
|
+
require 'sys/filesystem'
|
|
358
|
+
stat = Sys::Filesystem.stat(Dir.pwd)
|
|
359
|
+
total = stat.bytes_total
|
|
360
|
+
free = stat.bytes_available
|
|
361
|
+
used = total - free
|
|
362
|
+
"#{color("Storage Usage (#{Dir.pwd}):",36)} #{color(human_bytes(used),33)} / #{color(human_bytes(total),32)}"
|
|
363
|
+
rescue LoadError
|
|
364
|
+
"#{color("Install 'sys-filesystem' gem for storage info:",31)} #{color('gem install sys-filesystem',33)}"
|
|
365
|
+
rescue
|
|
366
|
+
"#{color("Storage Usage:",36)} Info not available"
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# ---------------- Builtin helpers ----------------
|
|
371
|
+
def builtin_help
|
|
372
|
+
puts color('=' * 60, "1;35")
|
|
373
|
+
puts color("srsh #{SRSH_VERSION} - Builtin Commands", "1;33")
|
|
374
|
+
puts color(sprintf("%-15s%-45s", "Command", "Description"), "1;36")
|
|
375
|
+
puts color('-' * 60, "1;34")
|
|
376
|
+
puts color(sprintf("%-15s", "cd"), "1;36") + "Change directory"
|
|
377
|
+
puts color(sprintf("%-15s", "pwd"), "1;36") + "Print working directory"
|
|
378
|
+
puts color(sprintf("%-15s", "exit / quit"), "1;36") + "Exit the shell"
|
|
379
|
+
puts color(sprintf("%-15s", "alias"), "1;36") + "Create or list aliases"
|
|
380
|
+
puts color(sprintf("%-15s", "unalias"), "1;36") + "Remove alias"
|
|
381
|
+
puts color(sprintf("%-15s", "jobs"), "1;36") + "Show background jobs (tracked pids)"
|
|
382
|
+
puts color(sprintf("%-15s", "systemfetch"), "1;36") + "Display system information"
|
|
383
|
+
puts color(sprintf("%-15s", "hist"), "1;36") + "Show shell history"
|
|
384
|
+
puts color(sprintf("%-15s", "clearhist"), "1;36") + "Clear saved history (memory + file)"
|
|
385
|
+
puts color(sprintf("%-15s", "help"), "1;36") + "Show this help message"
|
|
386
|
+
puts color('=' * 60, "1;35")
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
def builtin_systemfetch
|
|
390
|
+
user = ENV['USER'] || Etc.getlogin || Etc.getpwuid.name rescue ENV['USER'] || Etc.getlogin
|
|
391
|
+
host = Socket.gethostname
|
|
392
|
+
os = detect_distro
|
|
393
|
+
ruby_ver = RUBY_VERSION
|
|
394
|
+
|
|
395
|
+
cpu_percent = begin
|
|
396
|
+
case os_type
|
|
397
|
+
when :linux
|
|
398
|
+
prev = read_cpu_times
|
|
399
|
+
sleep 0.05
|
|
400
|
+
cur = read_cpu_times
|
|
401
|
+
calculate_cpu_usage(prev, cur).round(1)
|
|
402
|
+
else
|
|
403
|
+
cores = `sysctl -n hw.ncpu 2>/dev/null`.to_i rescue 0
|
|
404
|
+
ps_output = `ps -A -o %cpu 2>/dev/null`
|
|
405
|
+
lines = ps_output.lines
|
|
406
|
+
values = lines[1..-1] || []
|
|
407
|
+
sum = values.map { |l| l.to_f }.inject(0.0, :+)
|
|
408
|
+
if cores > 0
|
|
409
|
+
(sum / cores).round(1)
|
|
410
|
+
else
|
|
411
|
+
sum.round(1)
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
rescue
|
|
415
|
+
0.0
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
mem_percent = begin
|
|
419
|
+
case os_type
|
|
420
|
+
when :linux
|
|
421
|
+
if File.exist?('/proc/meminfo')
|
|
422
|
+
meminfo = {}
|
|
423
|
+
File.read('/proc/meminfo').each_line do |line|
|
|
424
|
+
k, v = line.split(':')
|
|
425
|
+
meminfo[k.strip] = v.strip.split.first.to_i * 1024 if k && v
|
|
426
|
+
end
|
|
427
|
+
total = meminfo['MemTotal'] || 1
|
|
428
|
+
free = (meminfo['MemAvailable'] || meminfo['MemFree'] || 0)
|
|
429
|
+
used = total - free
|
|
430
|
+
(used.to_f / total.to_f * 100).round(1)
|
|
431
|
+
else
|
|
432
|
+
0.0
|
|
433
|
+
end
|
|
434
|
+
when :mac
|
|
435
|
+
total = `sysctl -n hw.memsize 2>/dev/null`.to_i
|
|
436
|
+
if total <= 0
|
|
437
|
+
0.0
|
|
438
|
+
else
|
|
439
|
+
vm = `vm_stat 2>/dev/null`
|
|
440
|
+
page_size = vm[/page size of (\d+) bytes/, 1].to_i
|
|
441
|
+
page_size = 4096 if page_size <= 0
|
|
442
|
+
|
|
443
|
+
stats = {}
|
|
444
|
+
vm.each_line do |line|
|
|
445
|
+
if line =~ /^(.+):\s+(\d+)\./
|
|
446
|
+
stats[$1] = $2.to_i
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
used_pages = 0
|
|
451
|
+
%w[Pages active Pages wired down Pages occupied by compressor].each do |k|
|
|
452
|
+
used_pages += stats[k].to_i
|
|
453
|
+
end
|
|
454
|
+
used = used_pages * page_size
|
|
455
|
+
((used.to_f / total.to_f) * 100).round(1)
|
|
456
|
+
end
|
|
457
|
+
else
|
|
458
|
+
0.0
|
|
321
459
|
end
|
|
322
460
|
rescue
|
|
323
461
|
0.0
|
|
@@ -373,19 +511,19 @@ end
|
|
|
373
511
|
def print_columns_colored(labels)
|
|
374
512
|
return if labels.nil? || labels.empty?
|
|
375
513
|
|
|
376
|
-
width
|
|
514
|
+
width = terminal_width
|
|
377
515
|
visible_lengths = labels.map { |s| strip_ansi(s).length }
|
|
378
|
-
max_len
|
|
379
|
-
col_width
|
|
380
|
-
cols
|
|
381
|
-
rows
|
|
516
|
+
max_len = visible_lengths.max || 0
|
|
517
|
+
col_width = [max_len + 2, 4].max
|
|
518
|
+
cols = [width / col_width, 1].max
|
|
519
|
+
rows = (labels.length.to_f / cols).ceil
|
|
382
520
|
|
|
383
521
|
rows.times do |r|
|
|
384
522
|
line = ""
|
|
385
523
|
cols.times do |c|
|
|
386
524
|
idx = c * rows + r
|
|
387
525
|
break if idx >= labels.length
|
|
388
|
-
label
|
|
526
|
+
label = labels[idx]
|
|
389
527
|
visible = strip_ansi(label).length
|
|
390
528
|
padding = col_width - visible
|
|
391
529
|
line << label << (" " * padding)
|
|
@@ -554,7 +692,7 @@ def run_input_line(input)
|
|
|
554
692
|
end
|
|
555
693
|
|
|
556
694
|
# ---------------- Prompt ----------------
|
|
557
|
-
hostname
|
|
695
|
+
hostname = Socket.gethostname
|
|
558
696
|
prompt_color = random_color
|
|
559
697
|
|
|
560
698
|
def prompt(hostname, prompt_color)
|
|
@@ -577,15 +715,15 @@ end
|
|
|
577
715
|
def tab_completions_for(prefix, first_word, at_first_word)
|
|
578
716
|
prefix ||= ""
|
|
579
717
|
|
|
580
|
-
dir
|
|
718
|
+
dir = "."
|
|
581
719
|
base = prefix
|
|
582
720
|
|
|
583
721
|
if prefix.include?('/')
|
|
584
722
|
if prefix.end_with?('/')
|
|
585
|
-
dir
|
|
723
|
+
dir = prefix.chomp('/')
|
|
586
724
|
base = ""
|
|
587
725
|
else
|
|
588
|
-
dir
|
|
726
|
+
dir = File.dirname(prefix)
|
|
589
727
|
base = File.basename(prefix)
|
|
590
728
|
end
|
|
591
729
|
dir = "." if dir.nil? || dir.empty?
|
|
@@ -646,7 +784,7 @@ def longest_common_prefix(strings)
|
|
|
646
784
|
end
|
|
647
785
|
|
|
648
786
|
def render_line(prompt_str, buffer, cursor, show_ghost = true)
|
|
649
|
-
buffer
|
|
787
|
+
buffer = buffer || ""
|
|
650
788
|
cursor = [[cursor, 0].max, buffer.length].min
|
|
651
789
|
|
|
652
790
|
ghost_tail = ""
|
|
@@ -655,8 +793,27 @@ def render_line(prompt_str, buffer, cursor, show_ghost = true)
|
|
|
655
793
|
ghost_tail = suggestion ? suggestion[buffer.length..-1].to_s : ""
|
|
656
794
|
end
|
|
657
795
|
|
|
796
|
+
width = terminal_width
|
|
797
|
+
prompt_vis = strip_ansi(prompt_str).length
|
|
798
|
+
total_vis = prompt_vis + buffer.length + ghost_tail.length
|
|
799
|
+
rows = [(total_vis.to_f / width).ceil, 1].max
|
|
800
|
+
|
|
801
|
+
# Clear previous render block (only what we drew last time)
|
|
802
|
+
if $last_render_rows && $last_render_rows > 0
|
|
803
|
+
STDOUT.print("\r")
|
|
804
|
+
($last_render_rows - 1).times do
|
|
805
|
+
STDOUT.print("\e[1A\r") # move up a line, to column 0
|
|
806
|
+
end
|
|
807
|
+
$last_render_rows.times do |i|
|
|
808
|
+
STDOUT.print("\e[0K") # clear this line
|
|
809
|
+
STDOUT.print("\n") if i < $last_render_rows - 1
|
|
810
|
+
end
|
|
811
|
+
($last_render_rows - 1).times do
|
|
812
|
+
STDOUT.print("\e[1A\r") # move back up to first line of block
|
|
813
|
+
end
|
|
814
|
+
end
|
|
815
|
+
|
|
658
816
|
STDOUT.print("\r")
|
|
659
|
-
STDOUT.print("\e[0K")
|
|
660
817
|
STDOUT.print(prompt_str)
|
|
661
818
|
STDOUT.print(buffer)
|
|
662
819
|
STDOUT.print(color(ghost_tail, "2")) unless ghost_tail.empty?
|
|
@@ -664,17 +821,19 @@ def render_line(prompt_str, buffer, cursor, show_ghost = true)
|
|
|
664
821
|
move_left = ghost_tail.length + (buffer.length - cursor)
|
|
665
822
|
STDOUT.print("\e[#{move_left}D") if move_left > 0
|
|
666
823
|
STDOUT.flush
|
|
824
|
+
|
|
825
|
+
$last_render_rows = rows
|
|
667
826
|
end
|
|
668
827
|
|
|
669
828
|
# --------- NEAT MULTI-COLUMN TAB LIST (bash-style) ----------
|
|
670
829
|
def print_tab_list(comps)
|
|
671
830
|
return if comps.empty?
|
|
672
831
|
|
|
673
|
-
width
|
|
674
|
-
max_len
|
|
832
|
+
width = terminal_width
|
|
833
|
+
max_len = comps.map { |s| s.length }.max || 0
|
|
675
834
|
col_width = [max_len + 2, 4].max
|
|
676
|
-
cols
|
|
677
|
-
rows
|
|
835
|
+
cols = [width / col_width, 1].max
|
|
836
|
+
rows = (comps.length.to_f / cols).ceil
|
|
678
837
|
|
|
679
838
|
STDOUT.print("\r\n")
|
|
680
839
|
rows.times do |r|
|
|
@@ -682,9 +841,9 @@ def print_tab_list(comps)
|
|
|
682
841
|
cols.times do |c|
|
|
683
842
|
idx = c * rows + r
|
|
684
843
|
break if idx >= comps.length
|
|
685
|
-
item
|
|
844
|
+
item = comps[idx]
|
|
686
845
|
padding = col_width - item.length
|
|
687
|
-
line
|
|
846
|
+
line << item << (" " * padding)
|
|
688
847
|
end
|
|
689
848
|
STDOUT.print("\r")
|
|
690
849
|
STDOUT.print(line.rstrip)
|
|
@@ -695,24 +854,24 @@ def print_tab_list(comps)
|
|
|
695
854
|
end
|
|
696
855
|
|
|
697
856
|
def handle_tab_completion(prompt_str, buffer, cursor, last_tab_prefix, tab_cycle)
|
|
698
|
-
buffer
|
|
857
|
+
buffer = buffer || ""
|
|
699
858
|
cursor = [[cursor, 0].max, buffer.length].min
|
|
700
859
|
|
|
701
860
|
wstart = buffer.rindex(/[ \t]/, cursor - 1) || -1
|
|
702
861
|
wstart += 1
|
|
703
862
|
prefix = buffer[wstart...cursor] || ""
|
|
704
863
|
|
|
705
|
-
before_word
|
|
864
|
+
before_word = buffer[0...wstart]
|
|
706
865
|
at_first_word = before_word.strip.empty?
|
|
707
|
-
first_word
|
|
866
|
+
first_word = buffer.strip.split(/\s+/, 2)[0] || ""
|
|
708
867
|
|
|
709
868
|
comps = tab_completions_for(prefix, first_word, at_first_word)
|
|
710
869
|
return [buffer, cursor, nil, 0, false] if comps.empty?
|
|
711
870
|
|
|
712
871
|
if comps.size == 1
|
|
713
872
|
new_word = comps.first
|
|
714
|
-
buffer
|
|
715
|
-
cursor
|
|
873
|
+
buffer = buffer[0...wstart] + new_word + buffer[cursor..-1].to_s
|
|
874
|
+
cursor = wstart + new_word.length
|
|
716
875
|
return [buffer, cursor, nil, 0, true]
|
|
717
876
|
end
|
|
718
877
|
|
|
@@ -725,25 +884,25 @@ def handle_tab_completion(prompt_str, buffer, cursor, last_tab_prefix, tab_cycle
|
|
|
725
884
|
STDOUT.print("\a")
|
|
726
885
|
end
|
|
727
886
|
last_tab_prefix = prefix
|
|
728
|
-
tab_cycle
|
|
887
|
+
tab_cycle = 1
|
|
729
888
|
return [buffer, cursor, last_tab_prefix, tab_cycle, false]
|
|
730
889
|
else
|
|
731
|
-
# Second tab on same prefix: show list
|
|
890
|
+
# Second tab on same prefix: show list
|
|
732
891
|
render_line(prompt_str, buffer, cursor, false)
|
|
733
892
|
print_tab_list(comps)
|
|
734
893
|
last_tab_prefix = prefix
|
|
735
|
-
tab_cycle
|
|
894
|
+
tab_cycle += 1
|
|
736
895
|
return [buffer, cursor, last_tab_prefix, tab_cycle, true]
|
|
737
896
|
end
|
|
738
897
|
end
|
|
739
898
|
|
|
740
899
|
def read_line_with_ghost(prompt_str)
|
|
741
|
-
buffer
|
|
742
|
-
cursor
|
|
743
|
-
hist_index
|
|
900
|
+
buffer = ""
|
|
901
|
+
cursor = 0
|
|
902
|
+
hist_index = HISTORY.length
|
|
744
903
|
saved_line_for_history = ""
|
|
745
|
-
last_tab_prefix
|
|
746
|
-
tab_cycle
|
|
904
|
+
last_tab_prefix = nil
|
|
905
|
+
tab_cycle = 0
|
|
747
906
|
|
|
748
907
|
render_line(prompt_str, buffer, cursor)
|
|
749
908
|
|
|
@@ -782,10 +941,13 @@ def read_line_with_ghost(prompt_str)
|
|
|
782
941
|
cursor -= 1
|
|
783
942
|
end
|
|
784
943
|
last_tab_prefix = nil
|
|
785
|
-
tab_cycle
|
|
944
|
+
tab_cycle = 0
|
|
786
945
|
when "\t" # Tab completion
|
|
787
|
-
buffer, cursor, last_tab_prefix, tab_cycle,
|
|
946
|
+
buffer, cursor, last_tab_prefix, tab_cycle, printed =
|
|
788
947
|
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
|
+
$last_render_rows = 1 if printed
|
|
789
951
|
when "\e" # Escape sequences (arrows, home/end)
|
|
790
952
|
seq1 = io.getch
|
|
791
953
|
seq2 = io.getch
|
|
@@ -797,18 +959,18 @@ def read_line_with_ghost(prompt_str)
|
|
|
797
959
|
end
|
|
798
960
|
if hist_index > 0
|
|
799
961
|
hist_index -= 1
|
|
800
|
-
buffer
|
|
801
|
-
cursor
|
|
962
|
+
buffer = HISTORY[hist_index] || ""
|
|
963
|
+
cursor = buffer.length
|
|
802
964
|
end
|
|
803
965
|
when "B" # Down
|
|
804
966
|
if hist_index < HISTORY.length - 1
|
|
805
967
|
hist_index += 1
|
|
806
|
-
buffer
|
|
807
|
-
cursor
|
|
968
|
+
buffer = HISTORY[hist_index] || ""
|
|
969
|
+
cursor = buffer.length
|
|
808
970
|
elsif hist_index == HISTORY.length - 1
|
|
809
971
|
hist_index = HISTORY.length
|
|
810
|
-
buffer
|
|
811
|
-
cursor
|
|
972
|
+
buffer = saved_line_for_history || ""
|
|
973
|
+
cursor = buffer.length
|
|
812
974
|
end
|
|
813
975
|
when "C" # Right
|
|
814
976
|
if cursor < buffer.length
|
|
@@ -829,14 +991,14 @@ def read_line_with_ghost(prompt_str)
|
|
|
829
991
|
end
|
|
830
992
|
end
|
|
831
993
|
last_tab_prefix = nil
|
|
832
|
-
tab_cycle
|
|
994
|
+
tab_cycle = 0
|
|
833
995
|
else
|
|
834
996
|
if ch.ord >= 32 && ch.ord != 127
|
|
835
997
|
buffer.insert(cursor, ch)
|
|
836
998
|
cursor += 1
|
|
837
|
-
hist_index
|
|
999
|
+
hist_index = HISTORY.length
|
|
838
1000
|
last_tab_prefix = nil
|
|
839
|
-
tab_cycle
|
|
1001
|
+
tab_cycle = 0
|
|
840
1002
|
end
|
|
841
1003
|
end
|
|
842
1004
|
|
data/lib/srsh/version.rb
CHANGED