ruby-shell 3.4.1 → 3.4.3
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/README.md +34 -4
- data/bin/rsh +255 -22
- metadata +2 -2
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: aa582c1417712c47e836881c478b375ec93f4def3b7f75b4ad9c486c3bfc36d3
         | 
| 4 | 
            +
              data.tar.gz: e6c40f3e4eb1277bdd72ec20413a3bee631abb102a818e71d7e18716b665ebf0
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 2c9adb770a67ec3c7ac67c42bf7a963b2cedd3c7a4aec8f1d0460210f3fb965286ec528df049426598ca1890df2e00cd174ec96d078e93796acde6ff7ff857e1
         | 
| 7 | 
            +
              data.tar.gz: ec2dc6f60a5daf6394d3fc57ad0a977b7e1c9e2e0b4e231c8967cc20052d007b68f2c3e85f226885d26da4f706ff86b6976827b1455bf35fcbbfa1b59fed0ac6
         | 
    
        data/README.md
    CHANGED
    
    | @@ -112,7 +112,7 @@ gp branch=main | |
| 112 112 | 
             
            * **Define Ruby functions as shell commands**: `:defun 'weather(*args) = system("curl -s wttr.in/#{args[0] || \"oslo\"}")'`
         | 
| 113 113 | 
             
            * **Call like any shell command**: `weather london`
         | 
| 114 114 | 
             
            * **Full Ruby power**: Access to Ruby stdlib, file operations, JSON parsing, web requests, etc.
         | 
| 115 | 
            -
            * **Function management**: `:defun | 
| 115 | 
            +
            * **Function management**: `:defun` to list, `:defun -name` to remove
         | 
| 116 116 | 
             
            * **Syntax highlighting**: Ruby functions highlighted in bold
         | 
| 117 117 |  | 
| 118 118 | 
             
            ## Advanced Shell Features
         | 
| @@ -138,7 +138,7 @@ Special commands: | |
| 138 138 | 
             
            * `:history` will list the command history, while `:rmhistory` will delete the history
         | 
| 139 139 | 
             
            * `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
         | 
| 140 140 | 
             
            * `:defun func(args) = code` defines Ruby functions callable as shell commands (persistent!)
         | 
| 141 | 
            -
            * `:defun | 
| 141 | 
            +
            * `:defun` lists all user-defined functions, `:defun -func` removes functions
         | 
| 142 142 | 
             
            * `:stats` shows command execution statistics, `:stats --graph` for visual charts, `:stats --clear` to reset
         | 
| 143 143 | 
             
            * `:bm name` or `:bookmark name` bookmark current directory, `:bm name path #tags` with tags
         | 
| 144 144 | 
             
            * `:bm` lists all bookmarks, just type bookmark name to jump (e.g., `work`)
         | 
| @@ -309,8 +309,8 @@ weather london | |
| 309 309 |  | 
| 310 310 | 
             
            ### Function Management
         | 
| 311 311 | 
             
            ```bash
         | 
| 312 | 
            -
            :defun | 
| 313 | 
            -
            :defun  | 
| 312 | 
            +
            :defun            # List all defined functions
         | 
| 313 | 
            +
            :defun -myls      # Remove a function
         | 
| 314 314 | 
             
            ```
         | 
| 315 315 |  | 
| 316 316 | 
             
            Ruby functions have access to:
         | 
| @@ -359,6 +359,36 @@ Create safety rules to block, confirm, warn, or log specific command patterns: | |
| 359 359 |  | 
| 360 360 | 
             
            ---
         | 
| 361 361 |  | 
| 362 | 
            +
            ## Environment Variables
         | 
| 363 | 
            +
             | 
| 364 | 
            +
            **Note:** rsh uses `:env` commands for environment management, not the standard `export` syntax.
         | 
| 365 | 
            +
             | 
| 366 | 
            +
            ```bash
         | 
| 367 | 
            +
            # List all environment variables (shows first 20)
         | 
| 368 | 
            +
            :env
         | 
| 369 | 
            +
             | 
| 370 | 
            +
            # View specific variable
         | 
| 371 | 
            +
            :env PATH
         | 
| 372 | 
            +
             | 
| 373 | 
            +
            # Set environment variable
         | 
| 374 | 
            +
            :env set PATH /opt/local/bin:/usr/bin:/bin
         | 
| 375 | 
            +
             | 
| 376 | 
            +
            # Unset environment variable
         | 
| 377 | 
            +
            :env unset MY_VAR
         | 
| 378 | 
            +
             | 
| 379 | 
            +
            # Export all variables to shell script
         | 
| 380 | 
            +
            :env export my_env.sh
         | 
| 381 | 
            +
            ```
         | 
| 382 | 
            +
             | 
| 383 | 
            +
            **Why not `export`?**
         | 
| 384 | 
            +
            - rsh uses colon commands (`:cmd`) for shell operations
         | 
| 385 | 
            +
            - Standard `export VAR=value` syntax spawns a subprocess that doesn't affect parent shell
         | 
| 386 | 
            +
            - Use `:env set VAR value` instead for persistent environment changes
         | 
| 387 | 
            +
             | 
| 388 | 
            +
            **Tip:** Add `:env set` commands to your `~/.rshrc` for variables you need on every startup.
         | 
| 389 | 
            +
             | 
| 390 | 
            +
            ---
         | 
| 391 | 
            +
             | 
| 362 392 | 
             
            ## Plugin System (v3.2.0+)
         | 
| 363 393 |  | 
| 364 394 | 
             
            rsh supports a powerful plugin system for extending functionality. Plugins are Ruby classes placed in `~/.rsh/plugins/` that can:
         | 
    
        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. | 
| 11 | 
            +
            @version    = "3.4.3" # Enhanced tab completion: LS_COLORS, visual indicators, smart context, auto-complete ..
         | 
| 12 12 |  | 
| 13 13 | 
             
            # MODULES, CLASSES AND EXTENSIONS
         | 
| 14 14 | 
             
            class String # Add coloring to strings (with escaping for Readline)
         | 
| @@ -97,6 +97,12 @@ begin # Initialization | |
| 97 97 | 
             
              @c_tabselect = 5                        # Color for selected tabcompleted item
         | 
| 98 98 | 
             
              @c_taboption = 244                      # Color for unselected tabcompleted item
         | 
| 99 99 | 
             
              @c_stamp     = 244                      # Color for time stamp/command
         | 
| 100 | 
            +
              # File type colors for tab completion
         | 
| 101 | 
            +
              @c_dir       = 33                       # Color for directories (blue)
         | 
| 102 | 
            +
              @c_exec      = 2                        # Color for executables (green)
         | 
| 103 | 
            +
              @c_image     = 13                       # Color for images (magenta)
         | 
| 104 | 
            +
              @c_archive   = 11                       # Color for archives (yellow)
         | 
| 105 | 
            +
              @c_file      = 7                        # Color for regular files (white/default)
         | 
| 100 106 | 
             
              # Prompt
         | 
| 101 107 | 
             
              @prompt      = "rsh > ".c(@c_prompt).b  # Very basic prompt if not defined in .rshrc
         | 
| 102 108 | 
             
              # Hash & array initializations
         | 
| @@ -153,6 +159,7 @@ begin # Initialization | |
| 153 159 | 
             
              @validation_rules = []                  # Custom validation rules
         | 
| 154 160 | 
             
              @completion_weights = {}                # Completion learning weights
         | 
| 155 161 | 
             
              @completion_learning = true             # Enable completion learning (default: on)
         | 
| 162 | 
            +
              @completion_show_metadata = false       # Show file metadata in completions (default: off)
         | 
| 156 163 | 
             
              @recording = {active: false, name: nil, commands: []} # Command recording state
         | 
| 157 164 | 
             
              @recordings = {}                        # Saved recordings
         | 
| 158 165 | 
             
              @command_cache = {}                     # Cache for expensive shell command outputs
         | 
| @@ -599,11 +606,27 @@ def tab(type) | |
| 599 606 | 
             
                    if @cmd_completions.key?(last_cmd) && cmd_parts.length == 1
         | 
| 600 607 | 
             
                      type = "cmd_subcommands"
         | 
| 601 608 | 
             
                      @current_cmd = last_cmd
         | 
| 609 | 
            +
                    # If we're completing after a command (not at start of line), show only files
         | 
| 610 | 
            +
                    elsif last_cmd
         | 
| 611 | 
            +
                      type = "files_dirs_only"
         | 
| 602 612 | 
             
                    end
         | 
| 603 613 | 
             
                  end
         | 
| 604 614 | 
             
                end
         | 
| 605 615 | 
             
              end
         | 
| 606 616 |  | 
| 617 | 
            +
              # Auto-complete . and .. for directory navigation commands
         | 
| 618 | 
            +
              if type == "dirs_only" && (@tabstr == ".." || @tabstr == ".")
         | 
| 619 | 
            +
                completed = @tabstr + "/"
         | 
| 620 | 
            +
                @history[0] = @pretab + completed + @postab
         | 
| 621 | 
            +
                @pos = @pretab.length + completed.length
         | 
| 622 | 
            +
                @c_col = @pos0 + @pos
         | 
| 623 | 
            +
                @c.clear_line
         | 
| 624 | 
            +
                line_display = cmd_check(@history[0]).to_s
         | 
| 625 | 
            +
                print @prompt + line_display
         | 
| 626 | 
            +
                @c.col(@c_col)
         | 
| 627 | 
            +
                return
         | 
| 628 | 
            +
              end
         | 
| 629 | 
            +
             | 
| 607 630 | 
             
              while chr != "ENTER"
         | 
| 608 631 | 
             
                case type
         | 
| 609 632 | 
             
                when "hist"         # Handle history completions ('UP' key)
         | 
| @@ -663,6 +686,24 @@ def tab(type) | |
| 663 686 | 
             
                  fdir = @tabstr + "*"
         | 
| 664 687 | 
             
                  files = Dir.glob(fdir).reject { |f| Dir.exist?(f) }
         | 
| 665 688 | 
             
                  @tabarray = files
         | 
| 689 | 
            +
                when "files_dirs_only"  # Show files and directories, but not commands
         | 
| 690 | 
            +
                  fdir = @tabstr + "*"
         | 
| 691 | 
            +
                  files = Dir.glob(fdir)
         | 
| 692 | 
            +
                  # Only show hidden files if tabstr starts with .
         | 
| 693 | 
            +
                  unless @tabstr.start_with?('.')
         | 
| 694 | 
            +
                    files.reject! { |f| File.basename(f).start_with?('.') }
         | 
| 695 | 
            +
                  end
         | 
| 696 | 
            +
                  files.map! do |e|
         | 
| 697 | 
            +
                    if e =~ /(?<!\\) /
         | 
| 698 | 
            +
                      e = e.sub(/(.*\/|^)(.*)/, '\1\'\2\'') unless e =~ /'/
         | 
| 699 | 
            +
                    end
         | 
| 700 | 
            +
                    Dir.exist?(e) ? e + "/" : e
         | 
| 701 | 
            +
                  end
         | 
| 702 | 
            +
                  # Separate directories and files for better ordering
         | 
| 703 | 
            +
                  dirs = files.select { |f| f.end_with?('/') }
         | 
| 704 | 
            +
                  files_only = files.reject { |f| f.end_with?('/') }
         | 
| 705 | 
            +
                  # Order: directories first, then files
         | 
| 706 | 
            +
                  @tabarray = dirs + files_only
         | 
| 666 707 | 
             
                when "commands_only" # Only show executable commands
         | 
| 667 708 | 
             
                  ex = @exe.dup
         | 
| 668 709 | 
             
                  ex.prepend(*@nick.keys, *@gnick.keys)
         | 
| @@ -788,33 +829,91 @@ def tab(type) | |
| 788 829 | 
             
                    @newhist0 = @pretab + tabchoice + @postab             # Remember now the new value to be given to @history[0]
         | 
| 789 830 | 
             
                    line1     = cmd_check(@pretab).to_s                   # Syntax highlight before @tabstr
         | 
| 790 831 | 
             
                    line2     = cmd_check(@postab).to_s                   # Syntax highlight after  @tabstr
         | 
| 791 | 
            -
                    # Color and underline the current tabchoice on the commandline:
         | 
| 792 | 
            -
                     | 
| 832 | 
            +
                    # Color and underline the current tabchoice on the commandline with file type color:
         | 
| 833 | 
            +
                    display_choice = tabchoice.dup
         | 
| 834 | 
            +
                    clean_choice = tabchoice.gsub(/['"]/, '').chomp('/')
         | 
| 835 | 
            +
                    if !tabchoice.end_with?('/') && File.exist?(clean_choice) && File.executable?(clean_choice) && !File.directory?(clean_choice)
         | 
| 836 | 
            +
                      display_choice += '*'
         | 
| 837 | 
            +
                    end
         | 
| 838 | 
            +
                    choice_color = get_file_color(tabchoice)
         | 
| 839 | 
            +
                    # Escape regex special characters in @tabstr for pattern matching
         | 
| 840 | 
            +
                    escaped_tabstr = Regexp.escape(@tabstr)
         | 
| 841 | 
            +
                    tabline   = display_choice.sub(/(.*)#{escaped_tabstr}(.*)/, '\1'.c(choice_color) + @tabstr.u.c(choice_color) + '\2'.c(choice_color))
         | 
| 793 842 | 
             
                    print @prompt + line1 + tabline + line2               # Print the commandline
         | 
| 794 843 | 
             
                    @pos   = @pretab.length.to_i + tabchoice.length.to_i  # Set the position on that commandline
         | 
| 795 844 | 
             
                    @c_col = @pos0 + @pos                                 # The cursor position must include the prompt as well
         | 
| 796 845 | 
             
                    @c.col(@c_col)                                        # Set the cursor position
         | 
| 797 846 | 
             
                    nextline                                              # Then start showing the completion items
         | 
| 798 847 | 
             
                    tabline  = @tabarray[i]                               # Get the next matching tabline
         | 
| 848 | 
            +
                    # Add executable indicator
         | 
| 849 | 
            +
                    display_item = tabline.dup
         | 
| 850 | 
            +
                    clean_item = tabline.gsub(/['"]/, '').chomp('/')
         | 
| 851 | 
            +
                    if !tabline.end_with?('/') && File.exist?(clean_item) && File.executable?(clean_item) && !File.directory?(clean_item)
         | 
| 852 | 
            +
                      display_item += '*'
         | 
| 853 | 
            +
                    end
         | 
| 854 | 
            +
                    # Get file type color, but make selected item bold for distinction
         | 
| 855 | 
            +
                    file_color = get_file_color(tabline)
         | 
| 799 856 | 
             
                    # Can't nest ANSI codes, they must each complete/conclude or they will mess eachother up
         | 
| 800 | 
            -
                     | 
| 801 | 
            -
             | 
| 802 | 
            -
                       | 
| 803 | 
            -
                       | 
| 857 | 
            +
                    escaped_tabstr = Regexp.escape(@tabstr)
         | 
| 858 | 
            +
                    if display_item.include?(@tabstr)
         | 
| 859 | 
            +
                      tabline1 = display_item.sub(/(.*?)#{escaped_tabstr}.*/, '\1').c(file_color).b # Bold + color for selected
         | 
| 860 | 
            +
                      tabline2 = display_item.sub(/.*?#{escaped_tabstr}(.*)/, '\1').c(file_color).b
         | 
| 861 | 
            +
                      print " " + tabline1 + @tabstr.c(file_color).u.b + tabline2       # Bold, color & underline @tabstr
         | 
| 804 862 | 
             
                    else
         | 
| 805 863 | 
             
                      # For fuzzy matches, just show the whole word highlighted
         | 
| 806 | 
            -
                      print " " +  | 
| 864 | 
            +
                      print " " + display_item.c(file_color).b
         | 
| 865 | 
            +
                    end
         | 
| 866 | 
            +
                    # Add metadata if enabled
         | 
| 867 | 
            +
                    if @completion_show_metadata && File.exist?(clean_item)
         | 
| 868 | 
            +
                      if File.directory?(clean_item)
         | 
| 869 | 
            +
                        begin
         | 
| 870 | 
            +
                          count = Dir.entries(clean_item).length - 2
         | 
| 871 | 
            +
                          print " [#{count}]".c(244)
         | 
| 872 | 
            +
                        rescue
         | 
| 873 | 
            +
                        end
         | 
| 874 | 
            +
                      else
         | 
| 875 | 
            +
                        size = File.size(clean_item)
         | 
| 876 | 
            +
                        size_str = size < 1024 ? "#{size}B" :
         | 
| 877 | 
            +
                                   size < 1024*1024 ? "#{(size/1024.0).round(1)}K" :
         | 
| 878 | 
            +
                                   "#{(size/(1024.0*1024)).round(1)}M"
         | 
| 879 | 
            +
                        print " [#{size_str}]".c(244)
         | 
| 880 | 
            +
                      end
         | 
| 807 881 | 
             
                    end
         | 
| 808 882 | 
             
                  else
         | 
| 809 883 | 
             
                    begin
         | 
| 810 884 | 
             
                      tabline = @tabarray[i+x]    # Next tabline, and next, etc (usually 4 times here)
         | 
| 811 | 
            -
                       | 
| 812 | 
            -
             | 
| 813 | 
            -
             | 
| 814 | 
            -
             | 
| 885 | 
            +
                      # Add executable indicator
         | 
| 886 | 
            +
                      display_item = tabline.dup
         | 
| 887 | 
            +
                      clean_item = tabline.gsub(/['"]/, '').chomp('/')
         | 
| 888 | 
            +
                      if !tabline.end_with?('/') && File.exist?(clean_item) && File.executable?(clean_item) && !File.directory?(clean_item)
         | 
| 889 | 
            +
                        display_item += '*'
         | 
| 890 | 
            +
                      end
         | 
| 891 | 
            +
                      # Get file type color for unselected items (no bold)
         | 
| 892 | 
            +
                      file_color = get_file_color(tabline)
         | 
| 893 | 
            +
                      escaped_tabstr = Regexp.escape(@tabstr)
         | 
| 894 | 
            +
                      if display_item.include?(@tabstr)
         | 
| 895 | 
            +
                        tabline1 = display_item.sub(/(.*?)#{escaped_tabstr}.*/, '\1').c(file_color) # Color before @tabstr
         | 
| 896 | 
            +
                        tabline2 = display_item.sub(/.*?#{escaped_tabstr}(.*)/, '\1').c(file_color) # Color after @tabstr
         | 
| 897 | 
            +
                        print " " + tabline1 + @tabstr.c(file_color).u + tabline2       # Print the whole line
         | 
| 815 898 | 
             
                      else
         | 
| 816 899 | 
             
                        # For fuzzy matches, just show the whole word
         | 
| 817 | 
            -
                        print " " +  | 
| 900 | 
            +
                        print " " + display_item.c(file_color)
         | 
| 901 | 
            +
                      end
         | 
| 902 | 
            +
                      # Add metadata if enabled
         | 
| 903 | 
            +
                      if @completion_show_metadata && File.exist?(clean_item)
         | 
| 904 | 
            +
                        if File.directory?(clean_item)
         | 
| 905 | 
            +
                          begin
         | 
| 906 | 
            +
                            count = Dir.entries(clean_item).length - 2
         | 
| 907 | 
            +
                            print " [#{count}]".c(244)
         | 
| 908 | 
            +
                          rescue
         | 
| 909 | 
            +
                          end
         | 
| 910 | 
            +
                        else
         | 
| 911 | 
            +
                          size = File.size(clean_item)
         | 
| 912 | 
            +
                          size_str = size < 1024 ? "#{size}B" :
         | 
| 913 | 
            +
                                     size < 1024*1024 ? "#{(size/1024.0).round(1)}K" :
         | 
| 914 | 
            +
                                     "#{(size/(1024.0*1024)).round(1)}M"
         | 
| 915 | 
            +
                          print " [#{size_str}]".c(244)
         | 
| 916 | 
            +
                        end
         | 
| 818 917 | 
             
                      end
         | 
| 819 918 | 
             
                    rescue => e
         | 
| 820 919 | 
             
                      # Log completion errors if debugging enabled
         | 
| @@ -839,7 +938,7 @@ def tab(type) | |
| 839 938 | 
             
                  if @tabstr == ""
         | 
| 840 939 | 
             
                    @history[0] = @pretab + @postab
         | 
| 841 940 | 
             
                    tabend
         | 
| 842 | 
            -
                    return | 
| 941 | 
            +
                    return
         | 
| 843 942 | 
             
                  end
         | 
| 844 943 | 
             
                  @tabstr.chop!
         | 
| 845 944 | 
             
                when 'WBACK' # Delete one word to the left (Ctrl-W)
         | 
| @@ -1032,9 +1131,13 @@ def config(*args) # Configure rsh settings | |
| 1032 1131 | 
             
                @completion_limit = value.to_i
         | 
| 1033 1132 | 
             
                puts "Completion limit set to #{value}"
         | 
| 1034 1133 | 
             
                rshrc
         | 
| 1134 | 
            +
              when 'completion_show_metadata'
         | 
| 1135 | 
            +
                @completion_show_metadata = %w[on true yes 1].include?(value.to_s.downcase)
         | 
| 1136 | 
            +
                puts "Completion metadata display #{@completion_show_metadata ? 'enabled' : 'disabled'}"
         | 
| 1137 | 
            +
                rshrc
         | 
| 1035 1138 | 
             
              else
         | 
| 1036 1139 | 
             
                puts "Unknown setting '#{setting}'"
         | 
| 1037 | 
            -
                puts "Available: history_dedup, session_autosave, auto_correct, slow_command_threshold, completion_learning, completion_limit"
         | 
| 1140 | 
            +
                puts "Available: history_dedup, session_autosave, auto_correct, slow_command_threshold, completion_learning, completion_limit, completion_show_metadata"
         | 
| 1038 1141 | 
             
              end
         | 
| 1039 1142 | 
             
            end
         | 
| 1040 1143 | 
             
            def env(*args) # Environment variable management
         | 
| @@ -1075,6 +1178,119 @@ def env(*args) # Environment variable management | |
| 1075 1178 | 
             
                end
         | 
| 1076 1179 | 
             
              end
         | 
| 1077 1180 | 
             
            end
         | 
| 1181 | 
            +
            def parse_ls_colors # Parse LS_COLORS into a hash for file type coloring
         | 
| 1182 | 
            +
              @ls_colors = {}
         | 
| 1183 | 
            +
             | 
| 1184 | 
            +
              # Map ANSI basic color codes (30-37, 90-97) to 256-color equivalents
         | 
| 1185 | 
            +
              ansi_to_256 = {
         | 
| 1186 | 
            +
                30 => 0,   # black
         | 
| 1187 | 
            +
                31 => 196, # red -> bright red
         | 
| 1188 | 
            +
                32 => 2,   # green
         | 
| 1189 | 
            +
                33 => 11,  # yellow -> bright yellow
         | 
| 1190 | 
            +
                34 => 33,  # blue -> nice blue
         | 
| 1191 | 
            +
                35 => 13,  # magenta -> nice magenta
         | 
| 1192 | 
            +
                36 => 14,  # cyan -> bright cyan
         | 
| 1193 | 
            +
                37 => 7,   # white
         | 
| 1194 | 
            +
                90 => 8,   # bright black (gray)
         | 
| 1195 | 
            +
                91 => 9,   # bright red
         | 
| 1196 | 
            +
                92 => 10,  # bright green
         | 
| 1197 | 
            +
                93 => 11,  # bright yellow
         | 
| 1198 | 
            +
                94 => 12,  # bright blue
         | 
| 1199 | 
            +
                95 => 13,  # bright magenta
         | 
| 1200 | 
            +
                96 => 14,  # bright cyan
         | 
| 1201 | 
            +
                97 => 15   # bright white
         | 
| 1202 | 
            +
              }
         | 
| 1203 | 
            +
             | 
| 1204 | 
            +
              if ENV['LS_COLORS']
         | 
| 1205 | 
            +
                ENV['LS_COLORS'].split(':').each do |entry|
         | 
| 1206 | 
            +
                  next if entry.empty?
         | 
| 1207 | 
            +
                  key, value = entry.split('=')
         | 
| 1208 | 
            +
                  next unless key && value
         | 
| 1209 | 
            +
             | 
| 1210 | 
            +
                  # LS_COLORS can use multiple formats:
         | 
| 1211 | 
            +
                  # - "38;5;111" or "38;5;111;1" = 256-color format (use color 111 directly)
         | 
| 1212 | 
            +
                  # - "01;34" = ANSI format (bold + basic color 34, needs conversion)
         | 
| 1213 | 
            +
                  # - "34" = simple ANSI basic color (needs conversion)
         | 
| 1214 | 
            +
             | 
| 1215 | 
            +
                  if value =~ /38;5;(\d+)/ # 256-color format (check this first!)
         | 
| 1216 | 
            +
                    @ls_colors[key] = $1.to_i
         | 
| 1217 | 
            +
                  elsif value =~ /(\d+);(\d+)/ # ANSI with attributes (e.g., "01;34")
         | 
| 1218 | 
            +
                    ansi_code = $2.to_i
         | 
| 1219 | 
            +
                    @ls_colors[key] = ansi_to_256[ansi_code] || ansi_code
         | 
| 1220 | 
            +
                  elsif value =~ /^(\d+)$/ # Simple ANSI color
         | 
| 1221 | 
            +
                    ansi_code = $1.to_i
         | 
| 1222 | 
            +
                    @ls_colors[key] = ansi_to_256[ansi_code] || ansi_code
         | 
| 1223 | 
            +
                  end
         | 
| 1224 | 
            +
                end
         | 
| 1225 | 
            +
              end
         | 
| 1226 | 
            +
             | 
| 1227 | 
            +
              # Always set defaults (even if LS_COLORS isn't available)
         | 
| 1228 | 
            +
              @ls_colors['di'] ||= 33   # directories = blue
         | 
| 1229 | 
            +
              @ls_colors['ex'] ||= 2    # executables = green
         | 
| 1230 | 
            +
              @ls_colors['ln'] ||= 14   # symlinks = cyan
         | 
| 1231 | 
            +
              @ls_colors['fi'] ||= 7    # regular files = white
         | 
| 1232 | 
            +
            end
         | 
| 1233 | 
            +
            def get_file_color(filename) # Get color for a file based on LS_COLORS
         | 
| 1234 | 
            +
              return 7 unless @ls_colors  # Default to white if not initialized
         | 
| 1235 | 
            +
             | 
| 1236 | 
            +
              # Check if it's a directory
         | 
| 1237 | 
            +
              if filename.end_with?('/')
         | 
| 1238 | 
            +
                return @ls_colors['di'] || 33
         | 
| 1239 | 
            +
              end
         | 
| 1240 | 
            +
             | 
| 1241 | 
            +
              # Remove quotes and trailing slash for checking
         | 
| 1242 | 
            +
              clean_name = filename.gsub(/['"]/, '').chomp('/')
         | 
| 1243 | 
            +
             | 
| 1244 | 
            +
              # Check if file exists and is executable
         | 
| 1245 | 
            +
              if File.exist?(clean_name) && File.executable?(clean_name) && !File.directory?(clean_name)
         | 
| 1246 | 
            +
                return @ls_colors['ex'] || 2
         | 
| 1247 | 
            +
              end
         | 
| 1248 | 
            +
             | 
| 1249 | 
            +
              # Check extension patterns (*.jpg, *.tar, etc.)
         | 
| 1250 | 
            +
              ext = File.extname(clean_name)
         | 
| 1251 | 
            +
              if ext && !ext.empty? && @ls_colors["*#{ext}"]
         | 
| 1252 | 
            +
                return @ls_colors["*#{ext}"]
         | 
| 1253 | 
            +
              end
         | 
| 1254 | 
            +
             | 
| 1255 | 
            +
              # Default to regular file color (if 0, use 7/white as 0 means "default" in LS_COLORS)
         | 
| 1256 | 
            +
              file_color = @ls_colors['fi'] || 7
         | 
| 1257 | 
            +
              file_color = 7 if file_color == 0  # 0 means "reset to default" in LS_COLORS
         | 
| 1258 | 
            +
              file_color
         | 
| 1259 | 
            +
            end
         | 
| 1260 | 
            +
            def format_tab_item(item, show_metadata: false) # Format tab item with color and optional metadata
         | 
| 1261 | 
            +
              color = get_file_color(item)
         | 
| 1262 | 
            +
              formatted = item
         | 
| 1263 | 
            +
             | 
| 1264 | 
            +
              # Add executable indicator
         | 
| 1265 | 
            +
              clean_name = item.gsub(/['"]/, '')
         | 
| 1266 | 
            +
              if !item.end_with?('/') && File.exist?(clean_name) && File.executable?(clean_name)
         | 
| 1267 | 
            +
                formatted += '*'
         | 
| 1268 | 
            +
              end
         | 
| 1269 | 
            +
             | 
| 1270 | 
            +
              # Add metadata if requested
         | 
| 1271 | 
            +
              if show_metadata && @completion_show_metadata
         | 
| 1272 | 
            +
                clean_name = clean_name.chomp('/')
         | 
| 1273 | 
            +
                if File.exist?(clean_name)
         | 
| 1274 | 
            +
                  if File.directory?(clean_name)
         | 
| 1275 | 
            +
                    begin
         | 
| 1276 | 
            +
                      count = Dir.entries(clean_name).length - 2  # Exclude . and ..
         | 
| 1277 | 
            +
                      meta = "[dir, #{count} items]"
         | 
| 1278 | 
            +
                    rescue
         | 
| 1279 | 
            +
                      meta = "[dir]"
         | 
| 1280 | 
            +
                    end
         | 
| 1281 | 
            +
                  else
         | 
| 1282 | 
            +
                    size = File.size(clean_name)
         | 
| 1283 | 
            +
                    size_str = size < 1024 ? "#{size}B" :
         | 
| 1284 | 
            +
                               size < 1024*1024 ? "#{(size/1024.0).round(1)}K" :
         | 
| 1285 | 
            +
                               "#{(size/(1024.0*1024)).round(1)}M"
         | 
| 1286 | 
            +
                    meta = "[#{size_str}]"
         | 
| 1287 | 
            +
                  end
         | 
| 1288 | 
            +
                  formatted = formatted.ljust(30) + " #{meta}".c(244)
         | 
| 1289 | 
            +
                end
         | 
| 1290 | 
            +
              end
         | 
| 1291 | 
            +
             | 
| 1292 | 
            +
              formatted.c(color)
         | 
| 1293 | 
            +
            end
         | 
| 1078 1294 | 
             
            def cmd_check(str) # Check if each element on the readline matches commands, nicks, paths; color them
         | 
| 1079 1295 | 
             
              return if str.nil?
         | 
| 1080 1296 |  | 
| @@ -1210,10 +1426,13 @@ def help | |
| 1210 1426 | 
             
              col1 << "Shift-TAB History search"
         | 
| 1211 1427 | 
             
              col1 << ""
         | 
| 1212 1428 | 
             
              col1 << "CORE COMMANDS:".c(@c_prompt).b
         | 
| 1213 | 
            -
              col1 << ":nick a = b       | 
| 1214 | 
            -
              col1 << ":nick  | 
| 1429 | 
            +
              col1 << ":nick a = b      Create alias"
         | 
| 1430 | 
            +
              col1 << ":nick            List all"
         | 
| 1431 | 
            +
              col1 << ":nick -a         Delete"
         | 
| 1432 | 
            +
              col1 << ":defun f()=x     Create function"
         | 
| 1433 | 
            +
              col1 << ":defun           List all"
         | 
| 1434 | 
            +
              col1 << ":defun -f        Delete"
         | 
| 1215 1435 | 
             
              col1 << ":bm name         Bookmark"
         | 
| 1216 | 
            -
              col1 << ":defun f()=x     Function"
         | 
| 1217 1436 | 
             
              col1 << ":stats           Analytics"
         | 
| 1218 1437 | 
             
              col1 << ":validate p=a    Safety rules"
         | 
| 1219 1438 | 
             
              col1 << ":calc expr       Calculator"
         | 
| @@ -1415,10 +1634,23 @@ def bg(job_id = nil) | |
| 1415 1634 | 
             
                puts "Job #{job_id} no longer exists"
         | 
| 1416 1635 | 
             
              end
         | 
| 1417 1636 | 
             
            end
         | 
| 1418 | 
            -
            def defun(func_def)  # Define a Ruby function like: `:defun  | 
| 1419 | 
            -
              if func_def. | 
| 1637 | 
            +
            def defun(func_def = nil)  # Define a Ruby function like: `:defun myls(*args) = Dir.glob('*').each {|f| puts f}`
         | 
| 1638 | 
            +
              if func_def.nil? || func_def.strip.empty?
         | 
| 1639 | 
            +
                # List all defined functions
         | 
| 1640 | 
            +
                puts "User-defined Ruby functions:"
         | 
| 1641 | 
            +
                all_methods = singleton_class.instance_methods(false)
         | 
| 1642 | 
            +
                excluded = [:defun, :defun?, :execute_conditional, :expand_braces]
         | 
| 1643 | 
            +
                methods = all_methods - excluded
         | 
| 1644 | 
            +
                if methods.empty?
         | 
| 1645 | 
            +
                  puts "  (none defined)"
         | 
| 1646 | 
            +
                else
         | 
| 1647 | 
            +
                  methods.each do |method|
         | 
| 1648 | 
            +
                    puts "  #{method}"
         | 
| 1649 | 
            +
                  end
         | 
| 1650 | 
            +
                end
         | 
| 1651 | 
            +
              elsif func_def.match(/^\s*-/)
         | 
| 1420 1652 | 
             
                # Remove function
         | 
| 1421 | 
            -
                func_name = func_def.sub(/^\s*-/, '')
         | 
| 1653 | 
            +
                func_name = func_def.sub(/^\s*-/, '').strip
         | 
| 1422 1654 | 
             
                if self.respond_to?(func_name)
         | 
| 1423 1655 | 
             
                  singleton_class.remove_method(func_name.to_sym)
         | 
| 1424 1656 | 
             
                  @defuns.delete(func_name)
         | 
| @@ -2960,11 +3192,12 @@ begin # Load .rshrc and populate @history | |
| 2960 3192 | 
             
              ENV["PATH"]  ? ENV["PATH"] += ":" : ENV["PATH"] = ""
         | 
| 2961 3193 | 
             
              ENV["PATH"] += "/home/#{@user}/bin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
         | 
| 2962 3194 | 
             
              if @lscolors and File.exist?(@lscolors)
         | 
| 2963 | 
            -
                ls = File.read(@lscolors) | 
| 3195 | 
            +
                ls = File.read(@lscolors)
         | 
| 2964 3196 | 
             
                ls.sub!(/export.*/, '')
         | 
| 2965 3197 | 
             
                ls.sub!(/^LS_COLORS=/, 'ENV["LS_COLORS"]=')
         | 
| 2966 3198 | 
             
                eval(ls)
         | 
| 2967 3199 | 
             
              end
         | 
| 3200 | 
            +
              parse_ls_colors             # Parse LS_COLORS for tab completion coloring
         | 
| 2968 3201 | 
             
              @c = Cursor               # Initiate @c as Cursor
         | 
| 2969 3202 | 
             
              @c.save                   # Get max row & col
         | 
| 2970 3203 | 
             
              @c.row(8000)
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: ruby-shell
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 3.4. | 
| 4 | 
            +
              version: 3.4.3
         | 
| 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-25 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
         |