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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/bin/rsh +262 -102
  3. metadata +5 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c2eadccdac83c394da85a64a138d36cad24de2603aeb2867bce52f4aa00d0568
4
- data.tar.gz: 0b54ea26c9f82db1f1c29fffbc6e69d19c0c4ad3e5d94d490ba33aaf268be3c5
3
+ metadata.gz: c1a7a1c2414582ba14f71073684dc8046d52ba9bc45bf9dcc8e333ba4a99cdfe
4
+ data.tar.gz: 7a41cf6ed38b56e4e706e42712a59adfd0ac35fb4b60329a37bfa30d40b77abc
5
5
  SHA512:
6
- metadata.gz: 8448bde19ed12fb02b3b28a7295f928ce643caa43ce7045f8693a733729bd8dea2b23c01cbae44fcf3d39f412238a5aa5a17281e9d55cf64f5ec2bf5c56fe38d
7
- data.tar.gz: bd903733344c8aa9b0d42a2dfbcaf63339861bc7db5a289425881fea0a8c10fcbfc705a0840000ef165a99a843ebdd39c155263fe393fbd09ce8885dad6513c0
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.4.9" # Improved :calc with safer eval, Math sandbox, better error messages
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.3 - the Ruby SHell.
179
-
180
- rsh does not attempt to compete with the grand old shells like bash and zsh.
181
- It serves the specific needs and wants of its author. If you like it, then feel free
182
- to ask for more or different features here: https://github.com/isene/rsh.
183
-
184
- Core Features:
185
- * Aliases (called nicks in rsh) - both for commands and general nicks
186
- * Syntax highlighting for nicks, bookmarks, commands, switches and valid dirs/files
187
- * Tab completions for nicks, system commands, command switches and dirs/files
188
- * Smart context-aware tab completion for git, apt, docker, systemctl, cargo, npm, gem, bundle
189
- * History with editing, search and repeat (use `!` or UP arrow)
190
- * Auto-suggestions from history (press RIGHT arrow to accept)
191
- * Ruby functions callable as shell commands (persistent across sessions)
192
- * AI integration: Use @ for text responses and @@ for command suggestions
193
-
194
- NEW in v3.0:
195
- * Command analytics - :stats shows usage patterns and performance metrics
196
- * Enhanced bookmarks with tags - :bm "name path #tag1,tag2" then just type name to jump
197
- * Session management - :save_session and :load_session preserve your entire shell state
198
- * Smart typo detection - "Did you mean...?" suggestions for misspelled commands
199
- * Switch caching - Faster TAB completion for command options
200
- * Option value completion - TAB complete values like --format=json
201
- * Syntax validation - Pre-execution warnings for dangerous or malformed commands
202
- * Unified command syntax - :nick, :gnick, :bm all work the same way (list/create/delete)
203
-
204
- NEW in v3.4:
205
- * Completion learning - Shell learns which TAB completions you use and ranks them higher
206
- * Context-aware - Separate learning for each command (git, ls, docker, etc.)
207
- * :completion_stats - View learned patterns with visual bar charts
208
- * Persistent - Learning data saves to .rshrc across sessions
209
-
210
- v3.3 Features:
211
- * Quote-less syntax - No more quotes! Use :nick la = ls -la
212
- * Parametrized nicks - :nick gp = git push origin {{branch}}, then: gp branch=main
213
- * Ctrl-G multi-line edit - Press Ctrl-G to edit command in $EDITOR
214
- * Custom validation - :validate rm -rf / = block prevents dangerous commands
215
- * Shell script support - for/while/if loops work with full bash syntax
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 = {"ls"=>"ls --color -F"}
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
- # The actual printing og the command line
353
- @c.row(@row0)
354
- @c.clear_line
355
- print @display_prompt
356
- @c.clear_screen_down
357
- row, @pos0 = @c.pos
358
- #@history[0] = "" if @history[0].nil?
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
- c_col = @pos0 + @pos
370
- c_row = @row0 + c_col/(@maxcol)
371
- c_col == 0 ? @c.row(c_row + 1) : @c.row(c_row)
372
- if c_col.modulo(@maxcol) == 0
373
- @c.col(c_col)
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.col(c_col.modulo(@maxcol))
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
- @c_col = @pos0 + @pos
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
- print @display_prompt + line_display
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 @display_prompt + line1 + tabline + line2 # Print the commandline
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
- @c_col = @pos0 + @pos # The cursor position must include the prompt as well
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 = (@exe + @nick.keys).select do |c|
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, completion_limit, completion_show_metadata"
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
- puts @info
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
- puts "History:"
1599
- @history.each_with_index {|h,i| puts i.to_s + "; " + h if i < 50}
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 local variable or method `(\w+)'/
2664
+ # Extract the undefined name (sandbox format)
2665
+ if e.message =~ /undefined method `(\w+)'/
2542
2666
  undefined = $1
2543
- puts "Error: Unknown function or variable '#{undefined}'"
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 @display_prompt + cmd_check(@cmd)
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.9
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-30 00:00:00.000000000 Z
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.4.9: Improved :calc with Math sandbox for safer evaluation, better error messages
16
- (division by zero, unknown functions, syntax errors). Suggested by havenwood from
17
- #ruby IRC!'
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