ruby-shell 3.4.5 → 3.4.7

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 (5) hide show
  1. checksums.yaml +4 -4
  2. data/PLUGIN_GUIDE.md +107 -0
  3. data/README.md +1 -0
  4. data/bin/rsh +107 -38
  5. metadata +5 -6
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a0c1e45454167bc454071a1419673d2ca195980970208c54b2745870709e024
4
- data.tar.gz: 4981d0f400409d059e703df8d6daa9eab3be19e6a4e0da3f96de7533aac66c6c
3
+ metadata.gz: 70cf831b842cb78920de4f541751dc71b1ec87d4d2619eb813d726aa9abe844d
4
+ data.tar.gz: 3a1baee44aa63c583f62485d6df477b5aeca1f04801ce2dd6c9b92a35795e27e
5
5
  SHA512:
6
- metadata.gz: 6632764744a12eb79fda32cf92677e44e0ded8cab07e9b41cad11cfb911df0f678c63d9f8448390c8556fca89d11bb83a37864050f75c716c9589e05c88d4e67
7
- data.tar.gz: fe1dee534ac7e91a7ad925ba6845acc49e9ca897763486973589d5f8997a6b78771ae1155f20ef8bf42d982a3fe4f37255a33a8a1499223cec77fa3b7319b935
6
+ metadata.gz: 96e26e3cb1788d7aa04eea7d8aabf034ac59f904fcc808151c3297d6a896794330be4e92c372c71f96376605e9943cd734c8871373fe6c4bf07e5fb3a0bbd891
7
+ data.tar.gz: ef2907de7d2758f996beb911677430b1e1e674b388ebfa726d2dbead4e8b9670dc03baa7009b534ca268064ae4feaf8c1c6dc474552c1f6a6e60a86068cbd147
data/PLUGIN_GUIDE.md CHANGED
@@ -661,6 +661,113 @@ Planned for v4.0:
661
661
 
662
662
  ---
663
663
 
664
+ ## Included Plugins
665
+
666
+ rsh comes with several useful plugins out of the box. Enable them with `:plugins "enable", "pluginname"`.
667
+
668
+ ### venv - Virtual Environment Indicators
669
+
670
+ Shows active Python venv, Ruby rbenv/rvm, Node nvm, and Conda environments in your prompt.
671
+
672
+ **Features:**
673
+ - Detects Python virtualenv: `(py:myenv)`
674
+ - Shows Ruby rbenv/rvm versions: `(rb:3.2.0)`
675
+ - Displays Node version: `(node:18.0.0)`
676
+ - Shows Conda environments: `(conda:base)`
677
+ - Color-coded indicators (green for Python, red for Ruby, yellow for Node, cyan for Conda)
678
+
679
+ **Usage:** Simply enable the plugin, indicators appear automatically when in an active environment.
680
+
681
+ ### extract - Universal Archive Handler
682
+
683
+ Extract any archive format with a single command.
684
+
685
+ **Features:**
686
+ - Handles: `.tar.gz`, `.tar.bz2`, `.tar.xz`, `.zip`, `.rar`, `.7z`, `.gz`, `.bz2`, `.xz`, `.Z`, `.deb`, `.rpm`
687
+ - Auto-detects format from extension
688
+ - Success/error feedback with color
689
+
690
+ **Commands:**
691
+ - `extract file.tar.gz` - Extract any supported archive
692
+
693
+ **Example:**
694
+ ```ruby
695
+ extract project.tar.gz
696
+ extract backup.zip
697
+ extract archive.7z
698
+ ```
699
+
700
+ ### docker - Container Integration
701
+
702
+ Quick Docker container management with prompt indicators.
703
+
704
+ **Features:**
705
+ - Shows running container count in prompt: `[docker:3]`
706
+ - Quick container management commands
707
+ - Docker Compose shortcuts
708
+
709
+ **Commands:**
710
+ - `dps` - List running containers (docker ps)
711
+ - `dpsa` - List all containers (docker ps -a)
712
+ - `dex <container>` - Execute bash in container
713
+ - `dlogs <container> [lines]` - Show container logs (default 50 lines)
714
+ - `dstop` - Stop all running containers
715
+ - `dclean` - Remove all stopped containers
716
+ - `dcup` - Docker compose up -d
717
+ - `dcdown` - Docker compose down
718
+ - `dcrestart` - Docker compose restart
719
+
720
+ **Example:**
721
+ ```ruby
722
+ dps # List running containers
723
+ dex web_1 # Enter bash in web_1 container
724
+ dlogs nginx 100 # Show last 100 log lines
725
+ ```
726
+
727
+ ### clipboard - Cross-Platform Clipboard
728
+
729
+ Copy and paste text to/from system clipboard.
730
+
731
+ **Features:**
732
+ - Auto-detects clipboard tool (xclip, xsel, wl-copy/paste, pbcopy/paste, clip.exe)
733
+ - Works on Linux (X11/Wayland), macOS, and Windows WSL
734
+ - Copy last command, arbitrary text, or file contents
735
+
736
+ **Commands:**
737
+ - `clip [text]` - Copy text to clipboard (copies last command if no args)
738
+ - `clipp` - Paste from clipboard and display
739
+ - `clipf <file>` - Copy file contents to clipboard
740
+
741
+ **Example:**
742
+ ```ruby
743
+ clip # Copy last command
744
+ clip some text here # Copy this text
745
+ clipf config.yml # Copy file contents
746
+ clipp # Show clipboard contents
747
+ ```
748
+
749
+ ### git_prompt - Git Branch Indicator
750
+
751
+ Shows current git branch in prompt when in a git repository.
752
+
753
+ **Features:**
754
+ - Displays current branch: `[main]`
755
+ - Only shows when in a git repo
756
+ - Yellow color coding
757
+
758
+ **Usage:** Enable and branch appears automatically in prompt.
759
+
760
+ ### command_logger - Command Logging
761
+
762
+ Logs all commands to a file for audit/review.
763
+
764
+ **Features:**
765
+ - Logs commands with timestamp
766
+ - Customizable log file location
767
+ - Useful for command history analysis
768
+
769
+ ---
770
+
664
771
  ## Example Plugin Templates
665
772
 
666
773
  ### Minimal Plugin
data/README.md CHANGED
@@ -136,6 +136,7 @@ Special commands:
136
136
  * `:nick` lists all command nicks, `:gnick` lists general nicks
137
137
  * `:nick -name` delete a command nick, `:gnick -name` delete a general nick
138
138
  * `:history` will list the command history, while `:rmhistory` will delete the history
139
+ * `:rehash` rebuilds the executable cache (useful after installing new commands)
139
140
  * `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
140
141
  * `:defun func(args) = code` defines Ruby functions callable as shell commands (persistent!)
141
142
  * `:defun` lists all user-defined functions, `:defun -func` removes functions
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.5" # Dynamic directory colors in prompt via LS_COLORS integration
11
+ @version = "3.4.7" # Added :rehash command to manually rebuild executable cache
12
12
 
13
13
  # MODULES, CLASSES AND EXTENSIONS
14
14
  class String # Add coloring to strings (with escaping for Readline)
@@ -154,7 +154,7 @@ begin # Initialization
154
154
  @slow_command_threshold = 0 # Threshold for slow command alerts (0 = disabled)
155
155
  @plugin_dir = Dir.home + '/.rsh/plugins' # Plugins directory
156
156
  @plugins = [] # Loaded plugin instances
157
- @plugin_disabled = [] # List of disabled plugin names
157
+ @plugin_enabled = [] # List of enabled plugin names (whitelist)
158
158
  @plugin_commands = {} # Commands added by plugins
159
159
  @validation_rules = [] # Custom validation rules
160
160
  @completion_weights = {} # Completion learning weights
@@ -352,7 +352,7 @@ def getstr # A custom Readline-like function
352
352
  # The actual printing og the command line
353
353
  @c.row(@row0)
354
354
  @c.clear_line
355
- print @prompt
355
+ print @display_prompt
356
356
  @c.clear_screen_down
357
357
  row, @pos0 = @c.pos
358
358
  #@history[0] = "" if @history[0].nil?
@@ -476,11 +476,13 @@ def getstr # A custom Readline-like function
476
476
  @c.clear_line_after
477
477
  when 'WBACK' # Delete one word to the left (Ctrl-W)
478
478
  unless @pos == @pos0
479
- until @history[0][@pos - 1] == " " or @pos == 0
479
+ # Skip over any trailing spaces first
480
+ while @pos > 0 && @history[0][@pos - 1] == " "
480
481
  @pos -= 1
481
482
  @history[0][@pos] = ""
482
483
  end
483
- if @history[0][@pos - 1] == " "
484
+ # Delete the word (until we hit a space or start)
485
+ while @pos > 0 && @history[0][@pos - 1] != " "
484
486
  @pos -= 1
485
487
  @history[0][@pos] = ""
486
488
  end
@@ -622,7 +624,7 @@ def tab(type)
622
624
  @c_col = @pos0 + @pos
623
625
  @c.clear_line
624
626
  line_display = cmd_check(@history[0]).to_s
625
- print @prompt + line_display
627
+ print @display_prompt + line_display
626
628
  @c.col(@c_col)
627
629
  return
628
630
  end
@@ -645,14 +647,22 @@ def tab(type)
645
647
  @tabarray = @recordings.keys
646
648
  when "plugin_args"
647
649
  # Completions for :plugins command
648
- @tabarray = %w[reload info]
649
- # Add plugin names for enable/disable/info
650
+ @tabarray = %w[reload]
651
+ # Add help for all available plugins
652
+ if Dir.exist?(@plugin_dir)
653
+ available = Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }
654
+ @tabarray += available.map { |p| "help #{p}" }
655
+ @tabarray += available.map { |p| "info #{p}" }
656
+ end
657
+ # Add plugin names for enable/disable
650
658
  if @plugins.any?
651
659
  @tabarray += @plugins.map { |p| "disable #{p[:name]}" }
652
- @tabarray += @plugins.map { |p| "info #{p[:name]}" }
653
660
  end
654
- if @plugin_disabled.any?
655
- @tabarray += @plugin_disabled.map { |p| "enable #{p}" }
661
+ # Show "enable" for disabled plugins (available but not enabled)
662
+ if Dir.exist?(@plugin_dir)
663
+ available = Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }
664
+ disabled = available - @plugin_enabled
665
+ @tabarray += disabled.map { |p| "enable #{p}" } if disabled.any?
656
666
  end
657
667
  when "switch"
658
668
  cmdswitch = @pretab.split(/[|, ]/).last.to_s.strip
@@ -839,7 +849,7 @@ def tab(type)
839
849
  # Escape regex special characters in @tabstr for pattern matching
840
850
  escaped_tabstr = Regexp.escape(@tabstr)
841
851
  tabline = display_choice.sub(/(.*)#{escaped_tabstr}(.*)/, '\1'.c(choice_color) + @tabstr.u.c(choice_color) + '\2'.c(choice_color))
842
- print @prompt + line1 + tabline + line2 # Print the commandline
852
+ print @display_prompt + line1 + tabline + line2 # Print the commandline
843
853
  @pos = @pretab.length.to_i + tabchoice.length.to_i # Set the position on that commandline
844
854
  @c_col = @pos0 + @pos # The cursor position must include the prompt as well
845
855
  @c.col(@c_col) # Set the cursor position
@@ -1389,8 +1399,8 @@ def rshrc # Write user configuration to .rshrc (portable between machines)
1389
1399
  conf += "@completion_learning = #{@completion_learning}\n" unless @completion_learning
1390
1400
  conf.sub!(/^@completion_show_metadata.*(\n|$)/, "")
1391
1401
  conf += "@completion_show_metadata = #{@completion_show_metadata}\n" if @completion_show_metadata
1392
- conf.sub!(/^@plugin_disabled.*(\n|$)/, "")
1393
- conf += "@plugin_disabled = #{@plugin_disabled}\n" unless @plugin_disabled.empty?
1402
+ conf.sub!(/^@plugin_enabled.*(\n|$)/, "")
1403
+ conf += "@plugin_enabled = #{@plugin_enabled}\n" unless @plugin_enabled.empty?
1394
1404
  conf.sub!(/^@validation_rules.*(\n|$)/, "")
1395
1405
  conf += "@validation_rules = #{@validation_rules}\n" unless @validation_rules.empty?
1396
1406
 
@@ -1592,6 +1602,14 @@ def rmhistory # Delete history
1592
1602
  @history = []
1593
1603
  puts "History deleted."
1594
1604
  end
1605
+ def rehash # Force rebuild of executable cache
1606
+ @exe_cache = nil
1607
+ @exe_cache_path = nil
1608
+ @exe_cache_time = 0
1609
+ @exe_cache_paths = ""
1610
+ cache_executables
1611
+ puts "Executable cache rebuilt (#{@exe.length} commands cached)."
1612
+ end
1595
1613
  def nick(nick_str = nil) # Define a new nick like this: `:nick "ls = ls --color"`
1596
1614
  if nick_str.nil? || nick_str.empty?
1597
1615
  # List all nicks
@@ -2250,8 +2268,8 @@ def load_plugins # Load all plugins from plugin directory
2250
2268
  plugin_files.each do |plugin_file|
2251
2269
  plugin_name = File.basename(plugin_file, '.rb')
2252
2270
 
2253
- # Skip if disabled
2254
- next if @plugin_disabled.include?(plugin_name)
2271
+ # Only load if explicitly enabled (whitelist)
2272
+ next unless @plugin_enabled.include?(plugin_name)
2255
2273
 
2256
2274
  begin
2257
2275
  # Load the plugin file
@@ -2321,22 +2339,26 @@ def call_plugin_hook(hook_name, *args) # Call a lifecycle hook for all plugins
2321
2339
  end
2322
2340
  def plugins(*args) # Plugin management command
2323
2341
  if args.empty?
2324
- # List all plugins
2325
- if @plugins.empty?
2326
- puts "\nNo plugins loaded"
2342
+ # List all available plugins
2343
+ available_plugins = Dir.exist?(@plugin_dir) ? Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }.sort : []
2344
+
2345
+ if available_plugins.empty?
2346
+ puts "\nNo plugins found"
2327
2347
  puts "Place .rb files in #{@plugin_dir}"
2328
2348
  return
2329
2349
  end
2330
2350
 
2331
- puts "\n Loaded Plugins:".c(@c_prompt).b
2332
- @plugins.each do |plugin|
2333
- status = @plugin_disabled.include?(plugin[:name]) ? '[disabled]'.c(196) : '[enabled]'.c(@c_path)
2334
- puts " #{plugin[:name].ljust(20)} #{status} (#{plugin[:class]})"
2335
- end
2336
-
2337
- unless @plugin_disabled.empty?
2338
- puts "\n Disabled Plugins:".c(@c_stamp)
2339
- @plugin_disabled.each { |name| puts " #{name}" }
2351
+ puts "\n Available Plugins:".c(@c_prompt).b
2352
+ available_plugins.each do |name|
2353
+ if @plugin_enabled.include?(name)
2354
+ status = '[enabled]'.c(@c_path)
2355
+ plugin = @plugins.find { |p| p[:name] == name }
2356
+ class_info = plugin ? " (#{plugin[:class]})" : ""
2357
+ else
2358
+ status = '[disabled]'.c(196)
2359
+ class_info = ""
2360
+ end
2361
+ puts " #{name.ljust(20)} #{status}#{class_info}"
2340
2362
  end
2341
2363
  puts
2342
2364
  elsif args[0] == 'reload'
@@ -2348,15 +2370,15 @@ def plugins(*args) # Plugin management command
2348
2370
  elsif args[0] == 'enable' && args[1]
2349
2371
  # Enable a plugin
2350
2372
  plugin_name = args[1]
2351
- @plugin_disabled.delete(plugin_name)
2373
+ unless @plugin_enabled.include?(plugin_name)
2374
+ @plugin_enabled << plugin_name
2375
+ end
2352
2376
  puts "Plugin '#{plugin_name}' enabled. Use :plugins reload to load it"
2353
2377
  rshrc
2354
2378
  elsif args[0] == 'disable' && args[1]
2355
2379
  # Disable a plugin
2356
2380
  plugin_name = args[1]
2357
- unless @plugin_disabled.include?(plugin_name)
2358
- @plugin_disabled << plugin_name
2359
- end
2381
+ @plugin_enabled.delete(plugin_name)
2360
2382
  @plugins.reject! { |p| p[:name] == plugin_name }
2361
2383
  puts "Plugin '#{plugin_name}' disabled"
2362
2384
  rshrc
@@ -2382,6 +2404,51 @@ def plugins(*args) # Plugin management command
2382
2404
  else
2383
2405
  puts "Plugin '#{plugin_name}' not found"
2384
2406
  end
2407
+ elsif args[0] == 'help' && args[1]
2408
+ # Show plugin help
2409
+ plugin_name = args[1]
2410
+ plugin_file = "#{@plugin_dir}/#{plugin_name}.rb"
2411
+
2412
+ if File.exist?(plugin_file)
2413
+ content = File.read(plugin_file)
2414
+
2415
+ # Extract help block (lines starting with # at top of file)
2416
+ help_lines = []
2417
+ in_help = true
2418
+ content.each_line do |line|
2419
+ if in_help && line =~ /^#\s*(.*)$/
2420
+ help_text = $1
2421
+ next if help_text =~ /^-+$/ # Skip separator lines
2422
+ help_lines << help_text
2423
+ elsif line.strip.empty?
2424
+ next
2425
+ else
2426
+ in_help = false
2427
+ break
2428
+ end
2429
+ end
2430
+
2431
+ if help_lines.any?
2432
+ puts "\n#{help_lines.join("\n")}\n"
2433
+ else
2434
+ puts "No help available for plugin '#{plugin_name}'"
2435
+ end
2436
+
2437
+ # Extract and show available commands
2438
+ commands = []
2439
+ content.scan(/def (\w+)\(/) { |match| commands << match[0] unless match[0] == 'initialize' }
2440
+
2441
+ if commands.any?
2442
+ puts "\nCommands: #{commands.join(', ')}".c(@c_nick)
2443
+ puts
2444
+ end
2445
+ else
2446
+ puts "Plugin '#{plugin_name}' not found"
2447
+ puts "Available plugins:"
2448
+ Dir.glob(@plugin_dir + '/*.rb').each do |file|
2449
+ puts " #{File.basename(file, '.rb')}"
2450
+ end
2451
+ end
2385
2452
  else
2386
2453
  puts "Usage:"
2387
2454
  puts " :plugins List all plugins"
@@ -2389,6 +2456,7 @@ def plugins(*args) # Plugin management command
2389
2456
  puts " :plugins enable NAME Enable a plugin"
2390
2457
  puts " :plugins disable NAME Disable a plugin"
2391
2458
  puts " :plugins info NAME Show plugin details"
2459
+ puts " :plugins help NAME Show plugin help/usage"
2392
2460
  end
2393
2461
  end
2394
2462
  def validate_command(cmd) # Syntax validation before execution
@@ -2999,7 +3067,7 @@ def load_rshrc_safe
2999
3067
  @session_autosave = 0 unless @session_autosave.is_a?(Integer)
3000
3068
  @auto_correct = false unless [@auto_correct].any? { |v| v == true || v == false }
3001
3069
  @slow_command_threshold = 0 unless @slow_command_threshold.is_a?(Integer)
3002
- @plugin_disabled = [] unless @plugin_disabled.is_a?(Array)
3070
+ @plugin_enabled = [] unless @plugin_enabled.is_a?(Array)
3003
3071
  @plugins = [] unless @plugins.is_a?(Array)
3004
3072
  @plugin_commands = {} unless @plugin_commands.is_a?(Hash)
3005
3073
  @validation_rules = [] unless @validation_rules.is_a?(Array)
@@ -3154,7 +3222,7 @@ def load_defaults
3154
3222
  @session_autosave ||= 0
3155
3223
  @auto_correct ||= false
3156
3224
  @slow_command_threshold ||= 0
3157
- @plugin_disabled ||= []
3225
+ @plugin_enabled ||= []
3158
3226
  @plugins ||= []
3159
3227
  @plugin_commands ||= {}
3160
3228
  @validation_rules ||= []
@@ -3293,9 +3361,10 @@ loop do
3293
3361
  load_plugins
3294
3362
  @plugins_loaded = true
3295
3363
  end
3296
- # Append plugin prompt additions (after plugins loaded)
3364
+ # Build display prompt with plugin additions (don't modify @prompt)
3297
3365
  plugin_prompts = call_plugin_hook(:on_prompt)
3298
- @prompt += plugin_prompts.join if plugin_prompts.any?
3366
+ @display_prompt = @prompt.dup
3367
+ @display_prompt += plugin_prompts.join if plugin_prompts.any?
3299
3368
  # Auto-save session if enabled and interval elapsed
3300
3369
  if @session_autosave && @session_autosave > 0
3301
3370
  current_time = Time.now.to_i
@@ -3342,7 +3411,7 @@ loop do
3342
3411
  # Move cursor to end of line and print the full command before clearing
3343
3412
  @c.row(@row0)
3344
3413
  @c.clear_line
3345
- print @prompt + cmd_check(@cmd)
3414
+ print @display_prompt + cmd_check(@cmd)
3346
3415
  print "\n"; @c.clear_screen_down
3347
3416
  if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
3348
3417
  t = Time.now
@@ -3397,7 +3466,7 @@ loop do
3397
3466
  save_session load_session list_sessions delete_session rmsession
3398
3467
  validate completion_stats completion_reset
3399
3468
  record replay
3400
- history rmhistory jobs fg bg dirs help info version]
3469
+ history rmhistory rehash jobs fg bg dirs help info version]
3401
3470
 
3402
3471
  # Try to call as rsh method
3403
3472
  if known_commands.include?(cmd_name)
metadata CHANGED
@@ -1,21 +1,20 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-shell
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.5
4
+ version: 3.4.7
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-26 00:00:00.000000000 Z
11
+ date: 2025-10-29 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.5: FULL LS_COLORS COMPLIANCE - Prompt and command line now use LS_COLORS with
16
- pattern-based directory coloring. Configure @dir_colors like RTFM''s @topmatch for
17
- visual project distinction. Plus v3.4.0: Completion learning, context-aware ranking,
18
- persistent patterns across sessions!'
15
+ v3.4.7: Added :rehash command to manually rebuild executable cache (like zsh''s
16
+ rehash builtin). Plus v3.4.6: Plugin help system, 4 new plugins, plugins disabled
17
+ by default!'
19
18
  email: g@isene.com
20
19
  executables:
21
20
  - rsh