ruby-shell 3.4.0 → 3.4.2
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 +94 -91
- data/bin/rsh +70 -8
- metadata +1 -1
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 77bd4af1f0ec04f25fb48fc4bde5b1b18a50b02e046698c9cb660dafa6caa59c
         | 
| 4 | 
            +
              data.tar.gz: 807220a097167fb1338c52ce8868d3fcf0a022d8cec7963cf23654daafd72e8d
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 6e84bedcc95db51816bcfd6c5ed560914898a3e661767bcd810c6dab22d8aec7fffb8904877a606e3cf9cdc1129307fd50c1db848f429165aa935d8a57f5b46d
         | 
| 7 | 
            +
              data.tar.gz: 0d939b9668cd56581466acf659cc99b75652522ee5f05d5d6fdcf7eb70930da766a818dcbeef6d81597ce24e4dc9f720a6364bc5792ccad05231ba216e9fdcb2
         | 
    
        data/README.md
    CHANGED
    
    | @@ -19,97 +19,100 @@ Or simply `gem install ruby-shell`. | |
| 19 19 |  | 
| 20 20 | 
             
            # Features
         | 
| 21 21 |  | 
| 22 | 
            -
            ##  | 
| 23 | 
            -
             | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
             | 
| 29 | 
            -
             | 
| 30 | 
            -
             | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 37 | 
            -
             | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
            * ** | 
| 46 | 
            -
            * ** | 
| 47 | 
            -
            * ** | 
| 48 | 
            -
            * ** | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
            * ** | 
| 52 | 
            -
            * ** | 
| 53 | 
            -
            * ** | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
            * ** | 
| 59 | 
            -
            * ** | 
| 60 | 
            -
            * ** | 
| 61 | 
            -
            * ** | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
            * ** | 
| 65 | 
            -
            * ** | 
| 66 | 
            -
            * ** | 
| 67 | 
            -
            * ** | 
| 68 | 
            -
             | 
| 69 | 
            -
             | 
| 70 | 
            -
             | 
| 71 | 
            -
            *  | 
| 72 | 
            -
            *  | 
| 73 | 
            -
            *  | 
| 74 | 
            -
             | 
| 75 | 
            -
             | 
| 76 | 
            -
            *  | 
| 77 | 
            -
            *  | 
| 78 | 
            -
            *  | 
| 79 | 
            -
            *  | 
| 80 | 
            -
            *  | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 86 | 
            -
             | 
| 87 | 
            -
             | 
| 88 | 
            -
             | 
| 89 | 
            -
             | 
| 90 | 
            -
             | 
| 91 | 
            -
             | 
| 92 | 
            -
             | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
             | 
| 96 | 
            -
             | 
| 97 | 
            -
             | 
| 98 | 
            -
             | 
| 99 | 
            -
             | 
| 100 | 
            -
             | 
| 101 | 
            -
             | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 | 
            -
             | 
| 105 | 
            -
             | 
| 106 | 
            -
             | 
| 107 | 
            -
             | 
| 108 | 
            -
             | 
| 22 | 
            +
            ## Key Features
         | 
| 23 | 
            +
             | 
| 24 | 
            +
            ### Aliases (Nicks)
         | 
| 25 | 
            +
            **rsh uses "nicks" for aliases** - both simple command shortcuts and powerful parametrized templates:
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            ```bash
         | 
| 28 | 
            +
            # Simple aliases
         | 
| 29 | 
            +
            :nick la = ls -la
         | 
| 30 | 
            +
            :nick gs = git status
         | 
| 31 | 
            +
             | 
| 32 | 
            +
            # Parametrized nicks (templates with {{placeholders}})
         | 
| 33 | 
            +
            :nick gp = git push origin {{branch}}
         | 
| 34 | 
            +
            gp branch=main              # Executes: git push origin main
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            :nick deploy = ssh {{user}}@{{host}} 'systemctl restart {{app}}'
         | 
| 37 | 
            +
            deploy user=admin host=prod app=api
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            # List and manage
         | 
| 40 | 
            +
            :nick                       # List all nicks
         | 
| 41 | 
            +
            :nick -la                   # Delete a nick
         | 
| 42 | 
            +
            ```
         | 
| 43 | 
            +
             | 
| 44 | 
            +
            ### Intelligence & Learning
         | 
| 45 | 
            +
            * **Completion Learning**: Shell learns which TAB completions you use and ranks them higher
         | 
| 46 | 
            +
            * **Smart Suggestions**: "Did you mean...?" for typos
         | 
| 47 | 
            +
            * **Auto-correct**: Optional auto-fix with confirmation
         | 
| 48 | 
            +
            * **Command Analytics**: `:stats` shows usage patterns and performance
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            ### Productivity
         | 
| 51 | 
            +
            * **Command Recording**: `:record start name` → run commands → `:record stop` → `:replay name`
         | 
| 52 | 
            +
            * **Sessions**: Save/load entire shell state with bookmarks, history, and functions
         | 
| 53 | 
            +
            * **Bookmarks**: Tag directories and jump instantly
         | 
| 54 | 
            +
            * **Multi-line Editing**: Press Ctrl-G to edit in $EDITOR
         | 
| 55 | 
            +
            * **Shell Scripts**: Full bash support for for/while/if loops
         | 
| 56 | 
            +
             | 
| 57 | 
            +
            ### Extensibility
         | 
| 58 | 
            +
            * **Plugin System**: Add custom commands, completions, and hooks
         | 
| 59 | 
            +
            * **Ruby Functions**: Define callable functions - `:defun hello(name) = puts "Hello, #{name}!"`
         | 
| 60 | 
            +
            * **Validation Rules**: `:validate rm -rf / = block` prevents dangerous commands
         | 
| 61 | 
            +
            * **6 Color Themes**: solarized, dracula, gruvbox, nord, monokai, default
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            ### Integrations
         | 
| 64 | 
            +
            * **AI Support**: @ for questions, @@ for command suggestions (Ollama or OpenAI)
         | 
| 65 | 
            +
            * **RTFM**: Launch file manager with `r`
         | 
| 66 | 
            +
            * **fzf**: Fuzzy finder with `f`
         | 
| 67 | 
            +
            * **XRPN**: Calculator with `= expression`
         | 
| 68 | 
            +
             | 
| 69 | 
            +
            ### Tab Completion
         | 
| 70 | 
            +
            * Smart context-aware completion for git, apt, docker, systemctl, cargo, npm, gem
         | 
| 71 | 
            +
            * Command switches from --help
         | 
| 72 | 
            +
            * Option values (--format=json, --level=debug)
         | 
| 73 | 
            +
            * Learns your patterns and adapts
         | 
| 74 | 
            +
             | 
| 75 | 
            +
            ### Core Shell
         | 
| 76 | 
            +
            * Syntax highlighting for nicks, commands, paths, bookmarks
         | 
| 77 | 
            +
            * History with search, edit, and repeat (!, !!, !-2, !5:7)
         | 
| 78 | 
            +
            * Job control (background jobs, suspend, resume)
         | 
| 79 | 
            +
            * Config file (.rshrc) updates on exit
         | 
| 80 | 
            +
            * All colors themeable
         | 
| 81 | 
            +
             | 
| 82 | 
            +
            ---
         | 
| 83 | 
            +
             | 
| 84 | 
            +
            ## Quick Start
         | 
| 85 | 
            +
             | 
| 86 | 
            +
            ```bash
         | 
| 87 | 
            +
            # Install
         | 
| 88 | 
            +
            gem install ruby-shell
         | 
| 89 | 
            +
             | 
| 90 | 
            +
            # Run
         | 
| 91 | 
            +
            rsh
         | 
| 92 | 
            +
             | 
| 93 | 
            +
            # Create an alias
         | 
| 94 | 
            +
            :nick ll = ls -l
         | 
| 95 | 
            +
            ll
         | 
| 96 | 
            +
             | 
| 97 | 
            +
            # Create parametrized alias
         | 
| 98 | 
            +
            :nick gp = git push origin {{branch}}
         | 
| 99 | 
            +
            gp branch=main
         | 
| 100 | 
            +
             | 
| 101 | 
            +
            # Get help
         | 
| 102 | 
            +
            :help
         | 
| 103 | 
            +
            :info
         | 
| 104 | 
            +
             | 
| 105 | 
            +
            # See version and changelog
         | 
| 106 | 
            +
            :version
         | 
| 107 | 
            +
            ```
         | 
| 108 | 
            +
             | 
| 109 | 
            +
            ---
         | 
| 110 | 
            +
             | 
| 111 | 
            +
            ## Latest Features (v3.4)
         | 
| 109 112 | 
             
            * **Define Ruby functions as shell commands**: `:defun 'weather(*args) = system("curl -s wttr.in/#{args[0] || \"oslo\"}")'`
         | 
| 110 113 | 
             
            * **Call like any shell command**: `weather london`
         | 
| 111 114 | 
             
            * **Full Ruby power**: Access to Ruby stdlib, file operations, JSON parsing, web requests, etc.
         | 
| 112 | 
            -
            * **Function management**: `:defun | 
| 115 | 
            +
            * **Function management**: `:defun` to list, `:defun -name` to remove
         | 
| 113 116 | 
             
            * **Syntax highlighting**: Ruby functions highlighted in bold
         | 
| 114 117 |  | 
| 115 118 | 
             
            ## Advanced Shell Features
         | 
| @@ -135,7 +138,7 @@ Special commands: | |
| 135 138 | 
             
            * `:history` will list the command history, while `:rmhistory` will delete the history
         | 
| 136 139 | 
             
            * `:jobs` will list background jobs, `:fg [job_id]` brings jobs to foreground, `:bg [job_id]` resumes stopped jobs
         | 
| 137 140 | 
             
            * `:defun func(args) = code` defines Ruby functions callable as shell commands (persistent!)
         | 
| 138 | 
            -
            * `:defun | 
| 141 | 
            +
            * `:defun` lists all user-defined functions, `:defun -func` removes functions
         | 
| 139 142 | 
             
            * `:stats` shows command execution statistics, `:stats --graph` for visual charts, `:stats --clear` to reset
         | 
| 140 143 | 
             
            * `:bm name` or `:bookmark name` bookmark current directory, `:bm name path #tags` with tags
         | 
| 141 144 | 
             
            * `:bm` lists all bookmarks, just type bookmark name to jump (e.g., `work`)
         | 
| @@ -306,8 +309,8 @@ weather london | |
| 306 309 |  | 
| 307 310 | 
             
            ### Function Management
         | 
| 308 311 | 
             
            ```bash
         | 
| 309 | 
            -
            :defun | 
| 310 | 
            -
            :defun  | 
| 312 | 
            +
            :defun            # List all defined functions
         | 
| 313 | 
            +
            :defun -myls      # Remove a function
         | 
| 311 314 | 
             
            ```
         | 
| 312 315 |  | 
| 313 316 | 
             
            Ruby functions have access to:
         | 
    
        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.2" # Improved defun syntax: :defun lists, :defun -name removes, enhanced :help
         | 
| 12 12 |  | 
| 13 13 | 
             
            # MODULES, CLASSES AND EXTENSIONS
         | 
| 14 14 | 
             
            class String # Add coloring to strings (with escaping for Readline)
         | 
| @@ -155,6 +155,8 @@ begin # Initialization | |
| 155 155 | 
             
              @completion_learning = true             # Enable completion learning (default: on)
         | 
| 156 156 | 
             
              @recording = {active: false, name: nil, commands: []} # Command recording state
         | 
| 157 157 | 
             
              @recordings = {}                        # Saved recordings
         | 
| 158 | 
            +
              @command_cache = {}                     # Cache for expensive shell command outputs
         | 
| 159 | 
            +
              @last_prompt_dir = nil                  # Track directory for .rshrc reload optimization
         | 
| 158 160 | 
             
              # Built-in rsh commands are called with : prefix, so no need for separate tracking
         | 
| 159 161 | 
             
              Dir.mkdir(Dir.home + '/.rsh') unless Dir.exist?(Dir.home + '/.rsh')
         | 
| 160 162 | 
             
              Dir.mkdir(@session_dir) unless Dir.exist?(@session_dir)
         | 
| @@ -1157,6 +1159,15 @@ def rshrc # Write updates to .rshrc | |
| 1157 1159 | 
             
              conf += "@completion_learning = #{@completion_learning}\n" unless @completion_learning
         | 
| 1158 1160 | 
             
              conf.sub!(/^@recordings.*(\n|$)/, "")
         | 
| 1159 1161 | 
             
              conf += "@recordings = #{@recordings}\n" unless @recordings.empty?
         | 
| 1162 | 
            +
              # Persist executable cache for faster startup
         | 
| 1163 | 
            +
              if @exe && @exe.length > 100
         | 
| 1164 | 
            +
                conf.sub!(/^@exe_cache.*(\n|$)/, "")
         | 
| 1165 | 
            +
                conf += "@exe_cache = #{@exe.inspect}\n"
         | 
| 1166 | 
            +
                conf.sub!(/^@exe_cache_path.*(\n|$)/, "")
         | 
| 1167 | 
            +
                conf += "@exe_cache_path = #{ENV['PATH'].inspect}\n"
         | 
| 1168 | 
            +
                conf.sub!(/^@exe_cache_time.*(\n|$)/, "")
         | 
| 1169 | 
            +
                conf += "@exe_cache_time = #{Time.now.to_i}\n"
         | 
| 1170 | 
            +
              end
         | 
| 1160 1171 | 
             
              # Only write @cmd_completions if user has customized it
         | 
| 1161 1172 | 
             
              unless conf =~ /^@cmd_completions\s*=/
         | 
| 1162 1173 | 
             
                # Don't write default completions to avoid cluttering .rshrc
         | 
| @@ -1199,10 +1210,13 @@ def help | |
| 1199 1210 | 
             
              col1 << "Shift-TAB History search"
         | 
| 1200 1211 | 
             
              col1 << ""
         | 
| 1201 1212 | 
             
              col1 << "CORE COMMANDS:".c(@c_prompt).b
         | 
| 1202 | 
            -
              col1 << ":nick a = b       | 
| 1203 | 
            -
              col1 << ":nick  | 
| 1213 | 
            +
              col1 << ":nick a = b      Create alias"
         | 
| 1214 | 
            +
              col1 << ":nick            List all"
         | 
| 1215 | 
            +
              col1 << ":nick -a         Delete"
         | 
| 1216 | 
            +
              col1 << ":defun f()=x     Create function"
         | 
| 1217 | 
            +
              col1 << ":defun           List all"
         | 
| 1218 | 
            +
              col1 << ":defun -f        Delete"
         | 
| 1204 1219 | 
             
              col1 << ":bm name         Bookmark"
         | 
| 1205 | 
            -
              col1 << ":defun f()=x     Function"
         | 
| 1206 1220 | 
             
              col1 << ":stats           Analytics"
         | 
| 1207 1221 | 
             
              col1 << ":validate p=a    Safety rules"
         | 
| 1208 1222 | 
             
              col1 << ":calc expr       Calculator"
         | 
| @@ -1404,10 +1418,23 @@ def bg(job_id = nil) | |
| 1404 1418 | 
             
                puts "Job #{job_id} no longer exists"
         | 
| 1405 1419 | 
             
              end
         | 
| 1406 1420 | 
             
            end
         | 
| 1407 | 
            -
            def defun(func_def)  # Define a Ruby function like: `:defun  | 
| 1408 | 
            -
              if func_def. | 
| 1421 | 
            +
            def defun(func_def = nil)  # Define a Ruby function like: `:defun myls(*args) = Dir.glob('*').each {|f| puts f}`
         | 
| 1422 | 
            +
              if func_def.nil? || func_def.strip.empty?
         | 
| 1423 | 
            +
                # List all defined functions
         | 
| 1424 | 
            +
                puts "User-defined Ruby functions:"
         | 
| 1425 | 
            +
                all_methods = singleton_class.instance_methods(false)
         | 
| 1426 | 
            +
                excluded = [:defun, :defun?, :execute_conditional, :expand_braces]
         | 
| 1427 | 
            +
                methods = all_methods - excluded
         | 
| 1428 | 
            +
                if methods.empty?
         | 
| 1429 | 
            +
                  puts "  (none defined)"
         | 
| 1430 | 
            +
                else
         | 
| 1431 | 
            +
                  methods.each do |method|
         | 
| 1432 | 
            +
                    puts "  #{method}"
         | 
| 1433 | 
            +
                  end
         | 
| 1434 | 
            +
                end
         | 
| 1435 | 
            +
              elsif func_def.match(/^\s*-/)
         | 
| 1409 1436 | 
             
                # Remove function
         | 
| 1410 | 
            -
                func_name = func_def.sub(/^\s*-/, '')
         | 
| 1437 | 
            +
                func_name = func_def.sub(/^\s*-/, '').strip
         | 
| 1411 1438 | 
             
                if self.respond_to?(func_name)
         | 
| 1412 1439 | 
             
                  singleton_class.remove_method(func_name.to_sym)
         | 
| 1413 1440 | 
             
                  @defuns.delete(func_name)
         | 
| @@ -2862,10 +2889,40 @@ def load_defaults | |
| 2862 2889 | 
             
              puts "Loaded with default configuration."
         | 
| 2863 2890 | 
             
            end
         | 
| 2864 2891 |  | 
| 2892 | 
            +
            def cached_command(cmd, ttl = 300) # Cache expensive command outputs
         | 
| 2893 | 
            +
              key = cmd.hash
         | 
| 2894 | 
            +
              now = Time.now.to_i
         | 
| 2895 | 
            +
             | 
| 2896 | 
            +
              if @command_cache[key] && (now - @command_cache[key][:time]) < ttl
         | 
| 2897 | 
            +
                return @command_cache[key][:result]
         | 
| 2898 | 
            +
              end
         | 
| 2899 | 
            +
             | 
| 2900 | 
            +
              result = `#{cmd}`.chomp
         | 
| 2901 | 
            +
              @command_cache[key] = {result: result, time: now}
         | 
| 2902 | 
            +
             | 
| 2903 | 
            +
              # Limit cache size
         | 
| 2904 | 
            +
              if @command_cache.length > 50
         | 
| 2905 | 
            +
                # Remove oldest entry
         | 
| 2906 | 
            +
                oldest = @command_cache.min_by { |k, v| v[:time] }
         | 
| 2907 | 
            +
                @command_cache.delete(oldest[0])
         | 
| 2908 | 
            +
              end
         | 
| 2909 | 
            +
             | 
| 2910 | 
            +
              result
         | 
| 2911 | 
            +
            end
         | 
| 2865 2912 | 
             
            def cache_executables
         | 
| 2866 2913 | 
             
              current_path = ENV["PATH"]
         | 
| 2867 2914 | 
             
              current_time = Time.now.to_i
         | 
| 2868 2915 |  | 
| 2916 | 
            +
              # Use persisted cache if valid (from .rshrc)
         | 
| 2917 | 
            +
              if @exe_cache && @exe_cache_path == current_path
         | 
| 2918 | 
            +
                cache_age = current_time - (@exe_cache_time || 0)
         | 
| 2919 | 
            +
                if cache_age < 3600  # 1 hour
         | 
| 2920 | 
            +
                  @exe = @exe_cache
         | 
| 2921 | 
            +
                  @exe_cache_paths = current_path
         | 
| 2922 | 
            +
                  return
         | 
| 2923 | 
            +
                end
         | 
| 2924 | 
            +
              end
         | 
| 2925 | 
            +
             | 
| 2869 2926 | 
             
              # Only rebuild cache if PATH changed or cache is older than 60 seconds
         | 
| 2870 2927 | 
             
              return if @exe_cache_paths == current_path && (current_time - @exe_cache_time) < 60
         | 
| 2871 2928 |  | 
| @@ -2940,7 +2997,12 @@ loop do | |
| 2940 2997 | 
             
              begin
         | 
| 2941 2998 | 
             
                @user = Etc.getpwuid(Process.euid).name # For use in @prompt
         | 
| 2942 2999 | 
             
                @node = Etc.uname[:nodename]            # For use in @prompt
         | 
| 2943 | 
            -
                 | 
| 3000 | 
            +
                # Only reload .rshrc if directory changed (optimization)
         | 
| 3001 | 
            +
                current_dir = Dir.pwd
         | 
| 3002 | 
            +
                if @last_prompt_dir != current_dir
         | 
| 3003 | 
            +
                  h = @history; f = @cmd_frequency; s = @cmd_stats; b = @bookmarks; d = @defuns; load_rshrc_safe; @history = h; @cmd_frequency = f; @cmd_stats = s; @bookmarks = b; @defuns = d
         | 
| 3004 | 
            +
                  @last_prompt_dir = current_dir
         | 
| 3005 | 
            +
                end
         | 
| 2944 3006 | 
             
                @prompt.gsub!(/#{Dir.home}/, '~') # Simplify path in prompt
         | 
| 2945 3007 | 
             
                system("printf \"\033]0;rsh: #{Dir.pwd}\007\"")   # Set Window title to path
         | 
| 2946 3008 | 
             
                @history[0] = "" unless @history[0]
         |