ruby-shell 3.4.5 → 3.4.6

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 (4) hide show
  1. checksums.yaml +4 -4
  2. data/PLUGIN_GUIDE.md +107 -0
  3. data/bin/rsh +98 -37
  4. metadata +4 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1a0c1e45454167bc454071a1419673d2ca195980970208c54b2745870709e024
4
- data.tar.gz: 4981d0f400409d059e703df8d6daa9eab3be19e6a4e0da3f96de7533aac66c6c
3
+ metadata.gz: 8180bf48f336b9667d34fc01d5df5ddf7a3412ff5f35cfc443d10846a81f6686
4
+ data.tar.gz: acd063a28be3a5bfe7ccb9ec6623ddb7e061deba44806725c1b76781fcf45acb
5
5
  SHA512:
6
- metadata.gz: 6632764744a12eb79fda32cf92677e44e0ded8cab07e9b41cad11cfb911df0f678c63d9f8448390c8556fca89d11bb83a37864050f75c716c9589e05c88d4e67
7
- data.tar.gz: fe1dee534ac7e91a7ad925ba6845acc49e9ca897763486973589d5f8997a6b78771ae1155f20ef8bf42d982a3fe4f37255a33a8a1499223cec77fa3b7319b935
6
+ metadata.gz: 05d07658f8356edad6da19da615170e2423785fc4a3f9074cfb5b9ef3a7f602b3f109172cb6ba799f9b2bc0bbc7363a9c0ff29b4d9d27cfebb05b2883156aad6
7
+ data.tar.gz: 6e1d7c8d6c16c0ce6f2d7cf67a5229b3a31f3e0d096438fd59877c3647dd5f8b1530ff0b0c46cd95bd377d25663e79e7f0cbef8dc8599f61cb281a395c54bc94
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/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.6" # Plugin help system, 4 new plugins, plugins disabled by default, Ctrl-W fix
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
 
@@ -2250,8 +2260,8 @@ def load_plugins # Load all plugins from plugin directory
2250
2260
  plugin_files.each do |plugin_file|
2251
2261
  plugin_name = File.basename(plugin_file, '.rb')
2252
2262
 
2253
- # Skip if disabled
2254
- next if @plugin_disabled.include?(plugin_name)
2263
+ # Only load if explicitly enabled (whitelist)
2264
+ next unless @plugin_enabled.include?(plugin_name)
2255
2265
 
2256
2266
  begin
2257
2267
  # Load the plugin file
@@ -2321,22 +2331,26 @@ def call_plugin_hook(hook_name, *args) # Call a lifecycle hook for all plugins
2321
2331
  end
2322
2332
  def plugins(*args) # Plugin management command
2323
2333
  if args.empty?
2324
- # List all plugins
2325
- if @plugins.empty?
2326
- puts "\nNo plugins loaded"
2334
+ # List all available plugins
2335
+ available_plugins = Dir.exist?(@plugin_dir) ? Dir.glob(@plugin_dir + '/*.rb').map { |f| File.basename(f, '.rb') }.sort : []
2336
+
2337
+ if available_plugins.empty?
2338
+ puts "\nNo plugins found"
2327
2339
  puts "Place .rb files in #{@plugin_dir}"
2328
2340
  return
2329
2341
  end
2330
2342
 
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}" }
2343
+ puts "\n Available Plugins:".c(@c_prompt).b
2344
+ available_plugins.each do |name|
2345
+ if @plugin_enabled.include?(name)
2346
+ status = '[enabled]'.c(@c_path)
2347
+ plugin = @plugins.find { |p| p[:name] == name }
2348
+ class_info = plugin ? " (#{plugin[:class]})" : ""
2349
+ else
2350
+ status = '[disabled]'.c(196)
2351
+ class_info = ""
2352
+ end
2353
+ puts " #{name.ljust(20)} #{status}#{class_info}"
2340
2354
  end
2341
2355
  puts
2342
2356
  elsif args[0] == 'reload'
@@ -2348,15 +2362,15 @@ def plugins(*args) # Plugin management command
2348
2362
  elsif args[0] == 'enable' && args[1]
2349
2363
  # Enable a plugin
2350
2364
  plugin_name = args[1]
2351
- @plugin_disabled.delete(plugin_name)
2365
+ unless @plugin_enabled.include?(plugin_name)
2366
+ @plugin_enabled << plugin_name
2367
+ end
2352
2368
  puts "Plugin '#{plugin_name}' enabled. Use :plugins reload to load it"
2353
2369
  rshrc
2354
2370
  elsif args[0] == 'disable' && args[1]
2355
2371
  # Disable a plugin
2356
2372
  plugin_name = args[1]
2357
- unless @plugin_disabled.include?(plugin_name)
2358
- @plugin_disabled << plugin_name
2359
- end
2373
+ @plugin_enabled.delete(plugin_name)
2360
2374
  @plugins.reject! { |p| p[:name] == plugin_name }
2361
2375
  puts "Plugin '#{plugin_name}' disabled"
2362
2376
  rshrc
@@ -2382,6 +2396,51 @@ def plugins(*args) # Plugin management command
2382
2396
  else
2383
2397
  puts "Plugin '#{plugin_name}' not found"
2384
2398
  end
2399
+ elsif args[0] == 'help' && args[1]
2400
+ # Show plugin help
2401
+ plugin_name = args[1]
2402
+ plugin_file = "#{@plugin_dir}/#{plugin_name}.rb"
2403
+
2404
+ if File.exist?(plugin_file)
2405
+ content = File.read(plugin_file)
2406
+
2407
+ # Extract help block (lines starting with # at top of file)
2408
+ help_lines = []
2409
+ in_help = true
2410
+ content.each_line do |line|
2411
+ if in_help && line =~ /^#\s*(.*)$/
2412
+ help_text = $1
2413
+ next if help_text =~ /^-+$/ # Skip separator lines
2414
+ help_lines << help_text
2415
+ elsif line.strip.empty?
2416
+ next
2417
+ else
2418
+ in_help = false
2419
+ break
2420
+ end
2421
+ end
2422
+
2423
+ if help_lines.any?
2424
+ puts "\n#{help_lines.join("\n")}\n"
2425
+ else
2426
+ puts "No help available for plugin '#{plugin_name}'"
2427
+ end
2428
+
2429
+ # Extract and show available commands
2430
+ commands = []
2431
+ content.scan(/def (\w+)\(/) { |match| commands << match[0] unless match[0] == 'initialize' }
2432
+
2433
+ if commands.any?
2434
+ puts "\nCommands: #{commands.join(', ')}".c(@c_nick)
2435
+ puts
2436
+ end
2437
+ else
2438
+ puts "Plugin '#{plugin_name}' not found"
2439
+ puts "Available plugins:"
2440
+ Dir.glob(@plugin_dir + '/*.rb').each do |file|
2441
+ puts " #{File.basename(file, '.rb')}"
2442
+ end
2443
+ end
2385
2444
  else
2386
2445
  puts "Usage:"
2387
2446
  puts " :plugins List all plugins"
@@ -2389,6 +2448,7 @@ def plugins(*args) # Plugin management command
2389
2448
  puts " :plugins enable NAME Enable a plugin"
2390
2449
  puts " :plugins disable NAME Disable a plugin"
2391
2450
  puts " :plugins info NAME Show plugin details"
2451
+ puts " :plugins help NAME Show plugin help/usage"
2392
2452
  end
2393
2453
  end
2394
2454
  def validate_command(cmd) # Syntax validation before execution
@@ -2999,7 +3059,7 @@ def load_rshrc_safe
2999
3059
  @session_autosave = 0 unless @session_autosave.is_a?(Integer)
3000
3060
  @auto_correct = false unless [@auto_correct].any? { |v| v == true || v == false }
3001
3061
  @slow_command_threshold = 0 unless @slow_command_threshold.is_a?(Integer)
3002
- @plugin_disabled = [] unless @plugin_disabled.is_a?(Array)
3062
+ @plugin_enabled = [] unless @plugin_enabled.is_a?(Array)
3003
3063
  @plugins = [] unless @plugins.is_a?(Array)
3004
3064
  @plugin_commands = {} unless @plugin_commands.is_a?(Hash)
3005
3065
  @validation_rules = [] unless @validation_rules.is_a?(Array)
@@ -3154,7 +3214,7 @@ def load_defaults
3154
3214
  @session_autosave ||= 0
3155
3215
  @auto_correct ||= false
3156
3216
  @slow_command_threshold ||= 0
3157
- @plugin_disabled ||= []
3217
+ @plugin_enabled ||= []
3158
3218
  @plugins ||= []
3159
3219
  @plugin_commands ||= {}
3160
3220
  @validation_rules ||= []
@@ -3293,9 +3353,10 @@ loop do
3293
3353
  load_plugins
3294
3354
  @plugins_loaded = true
3295
3355
  end
3296
- # Append plugin prompt additions (after plugins loaded)
3356
+ # Build display prompt with plugin additions (don't modify @prompt)
3297
3357
  plugin_prompts = call_plugin_hook(:on_prompt)
3298
- @prompt += plugin_prompts.join if plugin_prompts.any?
3358
+ @display_prompt = @prompt.dup
3359
+ @display_prompt += plugin_prompts.join if plugin_prompts.any?
3299
3360
  # Auto-save session if enabled and interval elapsed
3300
3361
  if @session_autosave && @session_autosave > 0
3301
3362
  current_time = Time.now.to_i
@@ -3342,7 +3403,7 @@ loop do
3342
3403
  # Move cursor to end of line and print the full command before clearing
3343
3404
  @c.row(@row0)
3344
3405
  @c.clear_line
3345
- print @prompt + cmd_check(@cmd)
3406
+ print @display_prompt + cmd_check(@cmd)
3346
3407
  print "\n"; @c.clear_screen_down
3347
3408
  if @cmd == "r" # Integration with rtfm (https://github.com/isene/RTFM)
3348
3409
  t = Time.now
metadata CHANGED
@@ -1,7 +1,7 @@
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.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Geir Isene
@@ -12,10 +12,9 @@ date: 2025-10-26 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.6: PLUGIN SYSTEM ENHANCED - Plugin help system, 4 new plugins (venv/extract/docker/clipboard),
16
+ plugins disabled by default. Plus v3.4.5: Full LS_COLORS compliance with pattern-based
17
+ directory coloring (@dir_colors like RTFM''s @topmatch)!'
19
18
  email: g@isene.com
20
19
  executables:
21
20
  - rsh