ruby-shell 3.4.9 → 3.6.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/rsh +262 -102
- metadata +5 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c1a7a1c2414582ba14f71073684dc8046d52ba9bc45bf9dcc8e333ba4a99cdfe
|
|
4
|
+
data.tar.gz: 7a41cf6ed38b56e4e706e42712a59adfd0ac35fb4b60329a37bfa30d40b77abc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d6c26b7a0654f7f1dc37a55f42b6f5721ab7493838344e9bedbcb65fd023025b89b2448eae56a7dc5e907aa71ede64644b4636779d89f2ef991c1dfedf22728b
|
|
7
|
+
data.tar.gz: 03cd96dfeadcc4f6b3ee39db49ada250e1c4f301616269bb99f5940f672548c2d16f35631044fc137f483db57833c0111684443ac2aa39a1f7a4c4b1131934ba
|
data/bin/rsh
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
# Web_site: http://isene.com/
|
|
9
9
|
# Github: https://github.com/isene/rsh
|
|
10
10
|
# License: Public domain
|
|
11
|
-
@version = "3.
|
|
11
|
+
@version = "3.6.0" # Multi-line prompt support: Complete readline refactor with rcurses-inspired ANSI handling
|
|
12
12
|
|
|
13
13
|
# MODULES, CLASSES AND EXTENSIONS
|
|
14
14
|
class String # Add coloring to strings (with escaping for Readline)
|
|
@@ -137,8 +137,18 @@ begin # Initialization
|
|
|
137
137
|
"cargo" => %w[build run test check clean doc new init add search publish install update],
|
|
138
138
|
"npm" => %w[install uninstall update run build test start init publish],
|
|
139
139
|
"gem" => %w[install uninstall update list search build push],
|
|
140
|
-
"bundle" => %w[install update exec check config]
|
|
140
|
+
"bundle" => %w[install update exec check config],
|
|
141
|
+
"kubectl" => %w[get describe create delete apply edit logs exec port-forward scale rollout top explain diff patch],
|
|
142
|
+
"terraform" => %w[init plan apply destroy import output show],
|
|
143
|
+
"aws" => %w[s3 ec2 lambda cloudformation iam rds dynamodb sns sqs],
|
|
144
|
+
"brew" => %w[install uninstall update upgrade search info list],
|
|
145
|
+
"yarn" => %w[add remove install build test start init],
|
|
146
|
+
"make" => %w[clean install build test all],
|
|
147
|
+
"ansible" => %w[playbook galaxy vault inventory],
|
|
148
|
+
"poetry" => %w[add remove install update build publish],
|
|
149
|
+
"pipenv" => %w[install uninstall update shell run]
|
|
141
150
|
}
|
|
151
|
+
@show_tips = true # Show startup tips (configurable)
|
|
142
152
|
# New v3.0 features initialization
|
|
143
153
|
@switch_cache = {} # Cache for command switches from --help
|
|
144
154
|
@switch_cache_time = {} # Timestamp for cache expiry
|
|
@@ -175,67 +185,44 @@ end
|
|
|
175
185
|
# HELP TEXT
|
|
176
186
|
@info = <<~INFO
|
|
177
187
|
|
|
178
|
-
Hello #{@user}, welcome to rsh v3.
|
|
179
|
-
|
|
180
|
-
rsh
|
|
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
|
-
v3.2 Features:
|
|
218
|
-
* Plugin system - Extensible architecture for custom commands, completions, and hooks
|
|
219
|
-
* Auto-correct typos - :config auto_correct on (with confirmation prompt)
|
|
220
|
-
* Command timing alerts - :config slow_command_threshold 5 warns on slow commands
|
|
221
|
-
* Inline calculator - :calc 2 + 2, :calc "Math::PI", full Ruby Math library
|
|
222
|
-
* Enhanced history - !!, !-2, !5:7 for repeat last, nth-to-last, and chaining
|
|
223
|
-
* Stats visualization - :stats --graph for colorful ASCII bar charts
|
|
224
|
-
|
|
225
|
-
v3.1 Features:
|
|
226
|
-
* Multiple named sessions - :save_session "project" and :load_session "project"
|
|
227
|
-
* Stats export - :stats --csv or :stats --json for data analysis
|
|
228
|
-
* Session auto-save - Set @session_autosave = 300 in .rshrc for 5-min auto-save
|
|
229
|
-
* Bookmark import/export - :bm --export file.json and :bm --import file.json
|
|
230
|
-
* Bookmark statistics - :bm --stats shows usage patterns and tag distribution
|
|
231
|
-
* Color themes - :theme solarized|dracula|gruvbox|nord|monokai
|
|
232
|
-
* Config management - :config shows/sets history_dedup, session_autosave, etc.
|
|
233
|
-
* Environment management - :env for listing/setting/exporting environment variables
|
|
234
|
-
|
|
235
|
-
Config file (.rshrc) updates on exit (Ctrl-d) or not (Ctrl-e).
|
|
236
|
-
All colors are themeable in .rshrc (see github link for possibilities).
|
|
237
|
-
|
|
238
|
-
Use `:help` for complete command reference.
|
|
188
|
+
Hello #{@user}, welcome to rsh v3.6 - the Ruby SHell.
|
|
189
|
+
|
|
190
|
+
rsh is a feature-rich shell written in pure Ruby, designed for productivity and customization.
|
|
191
|
+
|
|
192
|
+
KEY FEATURES:
|
|
193
|
+
• Nicks (aliases) with {{parameters}} - :nick gp = git push origin {{branch}}
|
|
194
|
+
• Completion learning - Shell learns and adapts to your patterns
|
|
195
|
+
• Plugin system - Extend with custom commands and hooks
|
|
196
|
+
• Command recording/replay - Automate workflows
|
|
197
|
+
• Sessions & bookmarks - Save and restore your workspace
|
|
198
|
+
• Multi-line prompts - Full support for prompts with newlines
|
|
199
|
+
• Stats & analytics - :stats shows usage patterns and performance
|
|
200
|
+
• Validation rules - :validate rm -rf / = block for safety
|
|
201
|
+
• 6 color themes - :theme solarized|dracula|gruvbox|nord|monokai|default
|
|
202
|
+
• Auto-correct - Smart typo detection with suggestions
|
|
203
|
+
• Shell scripts - Full bash support (for, while, if loops)
|
|
204
|
+
• AI integration - @ for questions, @@ for command suggestions
|
|
205
|
+
• Ruby functions - :defun for custom shell commands
|
|
206
|
+
• 18 tool completions - git, docker, kubectl, terraform, aws, etc.
|
|
207
|
+
|
|
208
|
+
QUICK START:
|
|
209
|
+
• :help - Command reference
|
|
210
|
+
• :info --features - Feature list
|
|
211
|
+
• :nick - Create alias
|
|
212
|
+
• :bm - Bookmark directory
|
|
213
|
+
• :stats - View analytics
|
|
214
|
+
• :theme - Change colors
|
|
215
|
+
• :plugins - Extend functionality
|
|
216
|
+
|
|
217
|
+
CUSTOMIZATION:
|
|
218
|
+
• Config file: ~/.rshrc (edit with :config or manually)
|
|
219
|
+
• Colors: All themeable (see :help)
|
|
220
|
+
• Exit: Ctrl-D (save) or Ctrl-E (don't save)
|
|
221
|
+
|
|
222
|
+
MORE INFO:
|
|
223
|
+
• GitHub: https://github.com/isene/rsh
|
|
224
|
+
• Docs: Type :help for quick reference
|
|
225
|
+
• Version: Type :version for current/latest
|
|
239
226
|
|
|
240
227
|
INFO
|
|
241
228
|
|
|
@@ -277,7 +264,9 @@ def firstrun
|
|
|
277
264
|
@completion_show_descriptions = false # Show help text inline
|
|
278
265
|
@completion_fuzzy = true # Enable fuzzy matching
|
|
279
266
|
|
|
280
|
-
@nick = {
|
|
267
|
+
@nick = {
|
|
268
|
+
"ls" => "ls --color -F"
|
|
269
|
+
}
|
|
281
270
|
RSHRC
|
|
282
271
|
File.write(Dir.home+'/.rshrc', rc)
|
|
283
272
|
end
|
|
@@ -344,19 +333,73 @@ def getstr # A custom Readline-like function
|
|
|
344
333
|
@pos = @ai_suggestion.length
|
|
345
334
|
@ai_suggestion = nil
|
|
346
335
|
end
|
|
336
|
+
|
|
337
|
+
# Print prompt ONCE at start (optimization + multi-line support)
|
|
347
338
|
@row0, p = @c.pos
|
|
339
|
+
|
|
340
|
+
# Clear any previous prompt
|
|
341
|
+
@c.row(@row0)
|
|
342
|
+
@c.clear_screen_down
|
|
343
|
+
|
|
344
|
+
print @display_prompt
|
|
345
|
+
|
|
346
|
+
# Calculate positions and save prompt parts
|
|
347
|
+
if @display_prompt.include?("\n")
|
|
348
|
+
# Multi-line prompt
|
|
349
|
+
newline_count = @display_prompt.count("\n")
|
|
350
|
+
@row0 += newline_count
|
|
351
|
+
|
|
352
|
+
# Extract last line and reapply color codes (rcurses pattern)
|
|
353
|
+
# Detect ANY color codes at start of full prompt
|
|
354
|
+
color_codes = ""
|
|
355
|
+
if @display_prompt =~ /^(\001\e\[[0-9;]+m\002)/
|
|
356
|
+
color_codes = $1
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
# Split and get last line
|
|
360
|
+
lines = @display_prompt.split("\n")
|
|
361
|
+
@prompt_last_line = lines.last
|
|
362
|
+
|
|
363
|
+
# If original had colors and last line doesn't, reapply them
|
|
364
|
+
if !color_codes.empty? && !@prompt_last_line.start_with?("\001")
|
|
365
|
+
@prompt_last_line = color_codes + @prompt_last_line + "\001\e[0m\002"
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# Calculate visible length (strip ANSI for counting)
|
|
369
|
+
@pos0 = @prompt_last_line.gsub(/\001?\e\[[0-9;]*m\002?/, '').length
|
|
370
|
+
@cmd_row = @row0
|
|
371
|
+
@is_multiline = true
|
|
372
|
+
else
|
|
373
|
+
# Single-line prompt
|
|
374
|
+
row, @pos0 = @c.pos
|
|
375
|
+
@prompt_last_line = @display_prompt
|
|
376
|
+
@cmd_row = @row0
|
|
377
|
+
@is_multiline = false
|
|
378
|
+
end
|
|
379
|
+
|
|
348
380
|
while chr != "ENTER" # Keep going with readline until user presses ENTER
|
|
349
381
|
@ci = nil
|
|
350
382
|
lift = false
|
|
351
383
|
right = false
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
@c.
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
384
|
+
|
|
385
|
+
# Update command line display (not the prompt!)
|
|
386
|
+
@c.row(@cmd_row)
|
|
387
|
+
|
|
388
|
+
# Reprint the prompt line (preserves colors)
|
|
389
|
+
if @is_multiline
|
|
390
|
+
@c.col(1)
|
|
391
|
+
print @prompt_last_line
|
|
392
|
+
@c.clear_line_after
|
|
393
|
+
else
|
|
394
|
+
@c.col(1)
|
|
395
|
+
print @prompt_last_line
|
|
396
|
+
@c.clear_line_after
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
# Print command text with syntax highlighting
|
|
359
400
|
print cmd_check(@history[0])
|
|
401
|
+
|
|
402
|
+
# Print history suggestion if available
|
|
360
403
|
@ci = @history[1..].find_index {|e| e =~ /^#{Regexp.escape(@history[0].to_s)}./}
|
|
361
404
|
unless @ci == nil
|
|
362
405
|
@ci += 1
|
|
@@ -366,15 +409,17 @@ def getstr # A custom Readline-like function
|
|
|
366
409
|
print @ciprompt.c(@c_stamp)
|
|
367
410
|
right = true
|
|
368
411
|
end
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
@
|
|
374
|
-
@c.row(@c.rowget - 1)
|
|
412
|
+
|
|
413
|
+
# Position cursor (different logic for single vs multi-line)
|
|
414
|
+
if @is_multiline
|
|
415
|
+
# Multi-line: @pos0 is string length (0-indexed), need +1 for terminal
|
|
416
|
+
cursor_col = @pos0 + @pos + 1
|
|
375
417
|
else
|
|
376
|
-
@c.
|
|
418
|
+
# Single-line: @pos0 already from @c.pos (1-indexed)
|
|
419
|
+
cursor_col = @pos0 + @pos
|
|
377
420
|
end
|
|
421
|
+
@c.row(@cmd_row)
|
|
422
|
+
@c.col(cursor_col)
|
|
378
423
|
chr = getchr
|
|
379
424
|
puts "DEBUG: Got char: '#{chr}' (length: #{chr.length})" if ENV['RSH_DEBUG']
|
|
380
425
|
case chr
|
|
@@ -404,8 +449,21 @@ def getstr # A custom Readline-like function
|
|
|
404
449
|
exit
|
|
405
450
|
when 'C-L' # Clear screen and set position to top of the screen
|
|
406
451
|
@c.row(1)
|
|
407
|
-
@row0 = 1
|
|
408
452
|
@c.clear_screen_down
|
|
453
|
+
# Reprint prompt after clear
|
|
454
|
+
@row0 = 1
|
|
455
|
+
print @display_prompt
|
|
456
|
+
# Recalculate positions
|
|
457
|
+
if @display_prompt.include?("\n")
|
|
458
|
+
newline_count = @display_prompt.count("\n")
|
|
459
|
+
@row0 += newline_count
|
|
460
|
+
last_line = @display_prompt.split("\n").last
|
|
461
|
+
@pos0 = last_line.gsub(/\001?\e\[[0-9;]*m\002?/, '').length
|
|
462
|
+
@cmd_row = @row0
|
|
463
|
+
else
|
|
464
|
+
row, @pos0 = @c.pos
|
|
465
|
+
@cmd_row = @row0
|
|
466
|
+
end
|
|
409
467
|
when 'UP' # Go up in history
|
|
410
468
|
if @stk == 0 and @history[0].length > 0
|
|
411
469
|
@tabsearch = @history[0]
|
|
@@ -424,8 +482,6 @@ def getstr # A custom Readline-like function
|
|
|
424
482
|
end
|
|
425
483
|
lift = false
|
|
426
484
|
end
|
|
427
|
-
@c.row(@row0)
|
|
428
|
-
@c.clear_screen_down
|
|
429
485
|
when 'DOWN' # Go down in history
|
|
430
486
|
if lift
|
|
431
487
|
@history.unshift("")
|
|
@@ -445,8 +501,6 @@ def getstr # A custom Readline-like function
|
|
|
445
501
|
@pos = @history[0].length
|
|
446
502
|
end
|
|
447
503
|
lift = false
|
|
448
|
-
@c.row(@row0)
|
|
449
|
-
@c.clear_screen_down
|
|
450
504
|
when 'RIGHT' # Move right on the readline
|
|
451
505
|
if right
|
|
452
506
|
if lift
|
|
@@ -621,10 +675,17 @@ def tab(type)
|
|
|
621
675
|
completed = @tabstr + "/"
|
|
622
676
|
@history[0] = @pretab + completed + @postab
|
|
623
677
|
@pos = @pretab.length + completed.length
|
|
624
|
-
|
|
678
|
+
# Consistent cursor calc
|
|
679
|
+
if @is_multiline
|
|
680
|
+
@c_col = @pos0 + @pos + 1
|
|
681
|
+
else
|
|
682
|
+
@c_col = @pos0 + @pos
|
|
683
|
+
end
|
|
625
684
|
@c.clear_line
|
|
626
685
|
line_display = cmd_check(@history[0]).to_s
|
|
627
|
-
|
|
686
|
+
# Use last line only for multi-line prompts
|
|
687
|
+
prompt_display = @is_multiline ? @display_prompt.split("\n").last : @display_prompt
|
|
688
|
+
print prompt_display + line_display
|
|
628
689
|
@c.col(@c_col)
|
|
629
690
|
return
|
|
630
691
|
end
|
|
@@ -839,6 +900,9 @@ def tab(type)
|
|
|
839
900
|
@newhist0 = @pretab + tabchoice + @postab # Remember now the new value to be given to @history[0]
|
|
840
901
|
line1 = cmd_check(@pretab).to_s # Syntax highlight before @tabstr
|
|
841
902
|
line2 = cmd_check(@postab).to_s # Syntax highlight after @tabstr
|
|
903
|
+
|
|
904
|
+
# For multi-line prompts, only show last line during tab completion
|
|
905
|
+
prompt_for_tab = @display_prompt.include?("\n") ? @display_prompt.split("\n").last : @display_prompt
|
|
842
906
|
# Color and underline the current tabchoice on the commandline with file type color:
|
|
843
907
|
display_choice = tabchoice.dup
|
|
844
908
|
clean_choice = tabchoice.gsub(/['"]/, '').chomp('/')
|
|
@@ -849,9 +913,14 @@ def tab(type)
|
|
|
849
913
|
# Escape regex special characters in @tabstr for pattern matching
|
|
850
914
|
escaped_tabstr = Regexp.escape(@tabstr)
|
|
851
915
|
tabline = display_choice.sub(/(.*)#{escaped_tabstr}(.*)/, '\1'.c(choice_color) + @tabstr.u.c(choice_color) + '\2'.c(choice_color))
|
|
852
|
-
print
|
|
916
|
+
print prompt_for_tab + line1 + tabline + line2 # Print the commandline (use last line only for multi-line)
|
|
853
917
|
@pos = @pretab.length.to_i + tabchoice.length.to_i # Set the position on that commandline
|
|
854
|
-
|
|
918
|
+
# Consistent cursor positioning
|
|
919
|
+
if @is_multiline
|
|
920
|
+
@c_col = @pos0 + @pos + 1 # Multi-line: string length needs +1
|
|
921
|
+
else
|
|
922
|
+
@c_col = @pos0 + @pos # Single-line: already 1-indexed
|
|
923
|
+
end
|
|
855
924
|
@c.col(@c_col) # Set the cursor position
|
|
856
925
|
nextline # Then start showing the completion items
|
|
857
926
|
tabline = @tabarray[i] # Get the next matching tabline
|
|
@@ -1063,11 +1132,25 @@ def suggest_command(cmd) # Smart command suggestions for typos
|
|
|
1063
1132
|
return nil if cmd.nil? || cmd.empty?
|
|
1064
1133
|
return nil if @exe.include?(cmd) || @nick.include?(cmd)
|
|
1065
1134
|
|
|
1135
|
+
max_dist = [cmd.length / 3, 2].max
|
|
1136
|
+
|
|
1137
|
+
# Pre-filter by length and first letter (huge optimization for large @exe)
|
|
1138
|
+
length_range = (cmd.length - max_dist)..(cmd.length + max_dist)
|
|
1139
|
+
first_letter = cmd[0]
|
|
1140
|
+
|
|
1141
|
+
# Filter: similar length AND (same first letter OR within 1 char distance)
|
|
1142
|
+
pool = (@exe + @nick.keys).select do |c|
|
|
1143
|
+
c.length >= 2 &&
|
|
1144
|
+
length_range.include?(c.length) &&
|
|
1145
|
+
(c[0] == first_letter || (c[0].ord - first_letter.ord).abs <= 1)
|
|
1146
|
+
end
|
|
1147
|
+
|
|
1148
|
+
# Hard limit to prevent any hang
|
|
1149
|
+
pool = pool.first(100)
|
|
1150
|
+
|
|
1066
1151
|
# Find commands with small edit distance
|
|
1067
|
-
candidates =
|
|
1068
|
-
next false if c.length < 2
|
|
1152
|
+
candidates = pool.select do |c|
|
|
1069
1153
|
dist = levenshtein_distance(cmd, c)
|
|
1070
|
-
max_dist = [cmd.length / 3, 2].max
|
|
1071
1154
|
dist <= max_dist && dist > 0
|
|
1072
1155
|
end
|
|
1073
1156
|
|
|
@@ -1115,6 +1198,7 @@ def config(*args) # Configure rsh settings
|
|
|
1115
1198
|
puts " auto_correct: #{@auto_correct ? 'on' : 'off'}"
|
|
1116
1199
|
puts " slow_command_threshold: #{@slow_command_threshold}s #{@slow_command_threshold > 0 ? '(enabled)' : '(disabled)'}"
|
|
1117
1200
|
puts " completion_learning: #{@completion_learning ? 'on' : 'off'}"
|
|
1201
|
+
puts " show_tips: #{@show_tips ? 'on' : 'off'}"
|
|
1118
1202
|
puts " completion_limit: #{@completion_limit}"
|
|
1119
1203
|
puts " completion_fuzzy: #{@completion_fuzzy}"
|
|
1120
1204
|
puts " completion_case_sensitive: #{@completion_case_sensitive}"
|
|
@@ -1155,9 +1239,13 @@ def config(*args) # Configure rsh settings
|
|
|
1155
1239
|
@completion_show_metadata = %w[on true yes 1].include?(value.to_s.downcase)
|
|
1156
1240
|
puts "Completion metadata display #{@completion_show_metadata ? 'enabled' : 'disabled'}"
|
|
1157
1241
|
rshrc
|
|
1242
|
+
when 'show_tips'
|
|
1243
|
+
@show_tips = %w[on true yes 1].include?(value.to_s.downcase)
|
|
1244
|
+
puts "Startup tips #{@show_tips ? 'enabled' : 'disabled'}"
|
|
1245
|
+
rshrc
|
|
1158
1246
|
else
|
|
1159
1247
|
puts "Unknown setting '#{setting}'"
|
|
1160
|
-
puts "Available: history_dedup, session_autosave, auto_correct, slow_command_threshold, completion_learning,
|
|
1248
|
+
puts "Available: history_dedup, session_autosave, auto_correct, slow_command_threshold, completion_learning, show_tips, completion_limit"
|
|
1161
1249
|
end
|
|
1162
1250
|
end
|
|
1163
1251
|
def env(*args) # Environment variable management
|
|
@@ -1588,15 +1676,38 @@ def help
|
|
|
1588
1676
|
end
|
|
1589
1677
|
puts
|
|
1590
1678
|
end
|
|
1591
|
-
def info
|
|
1592
|
-
|
|
1679
|
+
def info(*args)
|
|
1680
|
+
option = args[0]
|
|
1681
|
+
|
|
1682
|
+
if option == '--quick'
|
|
1683
|
+
puts "\nrsh v#{@version} - Ruby SHell"
|
|
1684
|
+
puts "Type :help for commands, :info for full intro"
|
|
1685
|
+
elsif option == '--features'
|
|
1686
|
+
puts "\nKey Features:"
|
|
1687
|
+
puts "• Nicks (aliases) with {{parameters}}"
|
|
1688
|
+
puts "• Completion learning (adapts to you)"
|
|
1689
|
+
puts "• Command recording & replay"
|
|
1690
|
+
puts "• Plugin system"
|
|
1691
|
+
puts "• Stats & analytics"
|
|
1692
|
+
puts "• 6 color themes"
|
|
1693
|
+
puts "• AI integration"
|
|
1694
|
+
puts "Type :help for details"
|
|
1695
|
+
else
|
|
1696
|
+
puts @info
|
|
1697
|
+
end
|
|
1593
1698
|
end
|
|
1594
1699
|
def version
|
|
1595
1700
|
puts "rsh version = #{@version} (latest RubyGems version is #{Gem.latest_version_for("ruby-shell").version} - https://github.com/isene/rsh)"
|
|
1596
1701
|
end
|
|
1597
|
-
def history # Show most recent history (up to 50 entries)
|
|
1598
|
-
|
|
1599
|
-
|
|
1702
|
+
def history(*args) # Show most recent history (up to 50 entries)
|
|
1703
|
+
if args[0] == '--export'
|
|
1704
|
+
filename = args[1] || 'history.txt'
|
|
1705
|
+
File.write(filename, @history.join("\n"))
|
|
1706
|
+
puts "History exported to #{filename} (#{@history.length} commands)"
|
|
1707
|
+
else
|
|
1708
|
+
puts "History:"
|
|
1709
|
+
@history.each_with_index {|h,i| puts i.to_s + "; " + h if i < 50}
|
|
1710
|
+
end
|
|
1600
1711
|
end
|
|
1601
1712
|
def rmhistory # Delete history
|
|
1602
1713
|
@history = []
|
|
@@ -1620,6 +1731,19 @@ def nick(nick_str = nil) # Define a new nick like this: `:nick "ls = ls --color
|
|
|
1620
1731
|
@nick.sort.each {|key, value| puts " #{key.c(@c_nick)} = #{value}"}
|
|
1621
1732
|
end
|
|
1622
1733
|
puts
|
|
1734
|
+
elsif nick_str =~ /^--export\s*(.*)/
|
|
1735
|
+
filename = $1.strip.empty? ? 'nicks.json' : $1.strip
|
|
1736
|
+
require 'json' unless defined?(JSON)
|
|
1737
|
+
File.write(filename, JSON.pretty_generate(@nick))
|
|
1738
|
+
puts "Nicks exported to #{filename}"
|
|
1739
|
+
elsif nick_str =~ /^--import\s+(.*)/
|
|
1740
|
+
filename = $1.strip
|
|
1741
|
+
return puts "File '#{filename}' not found" unless File.exist?(filename)
|
|
1742
|
+
require 'json' unless defined?(JSON)
|
|
1743
|
+
imported = JSON.parse(File.read(filename))
|
|
1744
|
+
imported.each { |k, v| @nick[k] = v }
|
|
1745
|
+
puts "Imported #{imported.length} nicks"
|
|
1746
|
+
rshrc
|
|
1623
1747
|
elsif nick_str.match(/^\s*-/)
|
|
1624
1748
|
source = nick_str.sub(/^\s*-/, '')
|
|
1625
1749
|
if @nick.delete(source)
|
|
@@ -2537,10 +2661,10 @@ def calc(*args) # Inline calculator using Ruby's Math library
|
|
|
2537
2661
|
rescue ZeroDivisionError
|
|
2538
2662
|
puts "Error: Division by zero"
|
|
2539
2663
|
rescue NameError => e
|
|
2540
|
-
# Extract the undefined name
|
|
2541
|
-
if e.message =~ /undefined
|
|
2664
|
+
# Extract the undefined name (sandbox format)
|
|
2665
|
+
if e.message =~ /undefined method `(\w+)'/
|
|
2542
2666
|
undefined = $1
|
|
2543
|
-
puts "Error: Unknown function
|
|
2667
|
+
puts "Error: Unknown function '#{undefined}'"
|
|
2544
2668
|
puts "Available Math functions: sqrt, sin, cos, tan, log, exp, abs, ceil, floor, round"
|
|
2545
2669
|
puts "Constants: PI, E"
|
|
2546
2670
|
else
|
|
@@ -2564,6 +2688,7 @@ def validate(rule_str = nil) # Custom validation rule management
|
|
|
2564
2688
|
puts "\nNo validation rules defined"
|
|
2565
2689
|
puts "Usage: :validate pattern = action"
|
|
2566
2690
|
puts "Actions: block, confirm, warn, log"
|
|
2691
|
+
puts "Or try: :validate --templates"
|
|
2567
2692
|
return
|
|
2568
2693
|
end
|
|
2569
2694
|
puts "\n Validation Rules:".c(@c_prompt).b
|
|
@@ -2571,6 +2696,21 @@ def validate(rule_str = nil) # Custom validation rule management
|
|
|
2571
2696
|
puts " #{i+1}. #{rule[:pattern].inspect} → #{rule[:action]}"
|
|
2572
2697
|
end
|
|
2573
2698
|
puts
|
|
2699
|
+
elsif rule_str == '--templates'
|
|
2700
|
+
puts "\n Common Validation Rule Templates:".c(@c_prompt).b
|
|
2701
|
+
puts " Safety:"
|
|
2702
|
+
puts " :validate rm -rf / = block"
|
|
2703
|
+
puts " :validate DROP TABLE = block"
|
|
2704
|
+
puts " Confirmation:"
|
|
2705
|
+
puts " :validate git push --force = confirm"
|
|
2706
|
+
puts " :validate npm publish = confirm"
|
|
2707
|
+
puts " Warnings:"
|
|
2708
|
+
puts " :validate sudo = warn"
|
|
2709
|
+
puts " :validate chmod 777 = warn"
|
|
2710
|
+
puts " Logging:"
|
|
2711
|
+
puts " :validate npm install = log"
|
|
2712
|
+
puts " :validate pip install = log"
|
|
2713
|
+
puts
|
|
2574
2714
|
elsif rule_str =~ /^-(\d+)$/
|
|
2575
2715
|
# Delete rule by index
|
|
2576
2716
|
index = $1.to_i - 1
|
|
@@ -3395,6 +3535,20 @@ loop do
|
|
|
3395
3535
|
unless @plugins_loaded
|
|
3396
3536
|
load_plugins
|
|
3397
3537
|
@plugins_loaded = true
|
|
3538
|
+
# Show startup tip (30% chance, if enabled)
|
|
3539
|
+
if @show_tips && rand < 0.3
|
|
3540
|
+
tips = [
|
|
3541
|
+
"Tip: Use :nick gp={{branch}} for parametrized aliases!",
|
|
3542
|
+
"Tip: Press Ctrl-G to edit commands in $EDITOR",
|
|
3543
|
+
"Tip: :stats --graph shows visual analytics",
|
|
3544
|
+
"Tip: :record your workflows and :replay them later",
|
|
3545
|
+
"Tip: :validate rm -rf / = block for safety rules",
|
|
3546
|
+
"Tip: :completion_stats shows what you use most",
|
|
3547
|
+
"Tip: Type :help for quick reference",
|
|
3548
|
+
"Tip: Disable tips with: :config show_tips off"
|
|
3549
|
+
]
|
|
3550
|
+
puts tips.sample.c(244)
|
|
3551
|
+
end
|
|
3398
3552
|
end
|
|
3399
3553
|
# Build display prompt with plugin additions (don't modify @prompt)
|
|
3400
3554
|
plugin_prompts = call_plugin_hook(:on_prompt)
|
|
@@ -3444,9 +3598,11 @@ loop do
|
|
|
3444
3598
|
@cmd = hi if hi
|
|
3445
3599
|
end
|
|
3446
3600
|
# Move cursor to end of line and print the full command before clearing
|
|
3447
|
-
@c.row(@row0)
|
|
3601
|
+
@c.row(@cmd_row || @row0) # Use cmd_row if set (multi-line)
|
|
3448
3602
|
@c.clear_line
|
|
3449
|
-
print
|
|
3603
|
+
# For multi-line, only print last line; for single-line, print full prompt
|
|
3604
|
+
prompt_to_print = (@display_prompt.include?("\n") && @prompt_last_line) ? @prompt_last_line : @display_prompt
|
|
3605
|
+
print prompt_to_print + cmd_check(@cmd)
|
|
3450
3606
|
print "\n"; @c.clear_screen_down
|
|
3451
3607
|
if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
|
|
3452
3608
|
t = Time.now
|
|
@@ -3622,11 +3778,12 @@ loop do
|
|
|
3622
3778
|
puts "Bookmark '#{@cmd}' points to non-existent directory: #{bm_dir}".c(196)
|
|
3623
3779
|
end
|
|
3624
3780
|
else
|
|
3625
|
-
puts "#{Time.now.strftime("%H:%M:%S")}: #{@cmd}".c(@c_stamp)
|
|
3626
3781
|
if @cmd == "f" # fzf integration (https://github.com/junegunn/fzf)
|
|
3782
|
+
puts "#{Time.now.strftime("%H:%M:%S")}: #{@cmd}".c(@c_stamp)
|
|
3627
3783
|
res = `fzf`.chomp
|
|
3628
3784
|
Dir.chdir(File.dirname(res))
|
|
3629
3785
|
elsif File.exist?(@cmd) and not File.executable?(@cmd) and not @cmd.include?(" ")
|
|
3786
|
+
puts "#{Time.now.strftime("%H:%M:%S")}: #{@cmd}".c(@c_stamp)
|
|
3630
3787
|
# Only auto-open files if it's a single filename (no spaces = no command with args)
|
|
3631
3788
|
if File.read(@cmd).force_encoding("UTF-8").valid_encoding?
|
|
3632
3789
|
system("#{ENV['EDITOR']} #{@cmd}") # Try open with user's editor
|
|
@@ -3644,6 +3801,9 @@ loop do
|
|
|
3644
3801
|
# Apply auto-correct if enabled (before validation)
|
|
3645
3802
|
@cmd = apply_auto_correct(@cmd)
|
|
3646
3803
|
|
|
3804
|
+
# Print timestamp AFTER auto-correct (shows what actually executes)
|
|
3805
|
+
puts "#{Time.now.strftime("%H:%M:%S")}: #{@cmd}".c(@c_stamp)
|
|
3806
|
+
|
|
3647
3807
|
# Validate command after auto-correction
|
|
3648
3808
|
warnings = validate_command(@cmd)
|
|
3649
3809
|
if warnings && !warnings.empty?
|
metadata
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ruby-shell
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Geir Isene
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2025-10-
|
|
11
|
+
date: 2025-10-31 00:00:00.000000000 Z
|
|
12
12
|
dependencies: []
|
|
13
13
|
description: 'A shell written in Ruby with extensive tab completions, aliases/nicks,
|
|
14
14
|
history, syntax highlighting, theming, auto-cd, auto-opening files and more. UPDATE
|
|
15
|
-
v3.
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
v3.6.0: MULTI-LINE PROMPT SUPPORT - Complete readline refactor with rcurses-inspired
|
|
16
|
+
ANSI handling. Define prompts with newlines! Plus nick/history export, 9 new completions,
|
|
17
|
+
validation templates, startup tips, and critical performance fixes for huge PATHs!'
|
|
18
18
|
email: g@isene.com
|
|
19
19
|
executables:
|
|
20
20
|
- rsh
|