nub 0.0.53 → 0.0.54
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 +11 -13
- data/lib/nub/commander.rb +43 -58
- 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: 00de24079eb9ea5d62262d9453e4bb8745a004fec49d0c300bd8b01a430e4589
         | 
| 4 | 
            +
              data.tar.gz: f1c7b1a86cb4637a80b48814193305b2c7a27fb50a46b7ac35ff22cb952e1b52
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 3b86079c3a02cf90b74d9db8ff3240f9d261c4ab5671af4d068e76686a45ac50682265d69bb04eb82aaee3e9c49739afe76462a19a3a08c139a03edbde992585
         | 
| 7 | 
            +
              data.tar.gz: 2093bb7ee390f121324b39ab66beb74f8a8fd5c7baa45ff9671a26459e785f937203e03d80c278d744b4340db2fe27e242d9322d893e112f7198c15c596902b1
         | 
    
        data/README.md
    CHANGED
    
    | @@ -38,25 +38,23 @@ have their own help to display their usage and available options. | |
| 38 38 |  | 
| 39 39 | 
             
            ### Commands <a name="commands"></a>
         | 
| 40 40 | 
             
            Commands are defined via configuration as key words that trigger different branches of functionality
         | 
| 41 | 
            -
            for the application. Each command may have zero or more  | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 41 | 
            +
            for the application. Each command may have zero or more sub commands, each of which follow the same 
         | 
| 42 | 
            +
            rules in a recursive fashion. Each command may have zero or more options that modify how this 
         | 
| 43 | 
            +
            behavior is invoked. Whenever more than one command is used in the command line expression the 
         | 
| 44 | 
            +
            expression is interpreted as being a ***chained command expression***. Chained command expressions 
         | 
| 45 | 
            +
            are executed left to right, such that you can execute the ***clean*** command then the ***build*** 
         | 
| 46 | 
            +
            command or more in a single command line expression. Each command in a chained command expression 
         | 
| 47 | 
            +
            may have its own specific options (those coming after the command but before the next command) or if 
         | 
| 48 | 
            +
            options are omitted the required options from the next command will be used. The chained command 
         | 
| 49 | 
            +
            options syntax allows one to have a cleaner multi-command line expression with reusable options. 
         | 
| 50 | 
            +
            Options are said to apply in a chained command syntax when they are of the same type and position in 
         | 
| 51 | 
            +
            the positional case or same type and name in the named case.
         | 
| 51 52 |  | 
| 52 53 | 
             
            ***Global*** options are options that are added with the ***add_global*** function and will show up
         | 
| 53 54 | 
             
            set in the command results using the ***:global*** symbol. Global positional options must be given
         | 
| 54 55 | 
             
            before any other commands but global named options may appear anywhere in the command line
         | 
| 55 56 | 
             
            expression.
         | 
| 56 57 |  | 
| 57 | 
            -
            ***Shared*** options are options that are added with the command ***add_shared*** function. They
         | 
| 58 | 
            -
            should be added before any commands are added. They are added to each command as an explicit option.
         | 
| 59 | 
            -
             | 
| 60 58 | 
             
            ***Commander.new*** must be run from the app's executable file for it to pick up the app's filename
         | 
| 61 59 | 
             
            properly.
         | 
| 62 60 |  | 
    
        data/lib/nub/commander.rb
    CHANGED
    
    | @@ -34,7 +34,6 @@ class Option | |
| 34 34 | 
             
              attr_reader(:type)
         | 
| 35 35 | 
             
              attr_accessor(:allowed)
         | 
| 36 36 | 
             
              attr_accessor(:required)
         | 
| 37 | 
            -
              attr_accessor(:shared)
         | 
| 38 37 |  | 
| 39 38 | 
             
              # Create a new option instance
         | 
| 40 39 | 
             
              # @param key [String] option short hand, long hand and hint e.g. -s|--skip=COMPONENTS
         | 
| @@ -47,13 +46,12 @@ class Option | |
| 47 46 | 
             
                @long = nil
         | 
| 48 47 | 
             
                @short = nil
         | 
| 49 48 | 
             
                @desc = desc
         | 
| 50 | 
            -
                @shared = false
         | 
| 51 49 | 
             
                @allowed = allowed || []
         | 
| 52 50 | 
             
                @required = required || false
         | 
| 53 51 |  | 
| 54 52 | 
             
                # Parse the key into its components (short hand, long hand, and hint)
         | 
| 55 53 | 
             
                #https://bneijt.nl/pr/ruby-regular-expressions/
         | 
| 56 | 
            -
                # Valid forms to look for with chars [a-zA-Z0-9-_=|] | 
| 54 | 
            +
                # Valid forms to look for with chars [a-zA-Z0-9-_=|]
         | 
| 57 55 | 
             
                # --help, --help=HINT, -h|--help, -h|--help=HINT
         | 
| 58 56 | 
             
                Log.die("invalid option key #{key}") if key && (key.count('=') > 1 or key.count('|') > 1 or !key[/[^\w\-=|]/].nil? or
         | 
| 59 57 | 
             
                  key[/(^--[a-zA-Z0-9\-_]+$)|(^--[a-zA-Z\-_]+=\w+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+$)|(^-[a-zA-Z]\|--[a-zA-Z0-9\-_]+=\w+$)/].nil?)
         | 
| @@ -89,7 +87,7 @@ class Commander | |
| 89 87 | 
             
              attr_reader(:banner)
         | 
| 90 88 | 
             
              attr_accessor(:cmds)
         | 
| 91 89 |  | 
| 92 | 
            -
              Command = Struct.new(:name, :desc, : | 
| 90 | 
            +
              Command = Struct.new(:name, :desc, :nodes, :help)
         | 
| 93 91 |  | 
| 94 92 | 
             
              # Initialize the commands for your application
         | 
| 95 93 | 
             
              # @param app [String] application name e.g. reduce
         | 
| @@ -114,9 +112,6 @@ class Commander | |
| 114 112 | 
             
                # Configuration - ordered list of commands
         | 
| 115 113 | 
             
                @config = []
         | 
| 116 114 |  | 
| 117 | 
            -
                # List of options that will be added to all commands
         | 
| 118 | 
            -
                @shared = []
         | 
| 119 | 
            -
             | 
| 120 115 | 
             
                # Configure default global options
         | 
| 121 116 | 
             
                add_global(Option.new('-h|--help', 'Print command/options help'))
         | 
| 122 117 | 
             
              end
         | 
| @@ -134,46 +129,39 @@ class Commander | |
| 134 129 | 
             
              # Add a command to the command list
         | 
| 135 130 | 
             
              # @param cmd [String] name of the command
         | 
| 136 131 | 
             
              # @param desc [String] description of the command
         | 
| 137 | 
            -
              # @param  | 
| 138 | 
            -
              def add(cmd, desc,  | 
| 132 | 
            +
              # @param nodes [List] list of command nodes (i.e. options or commands)
         | 
| 133 | 
            +
              def add(cmd, desc, nodes:[])
         | 
| 139 134 | 
             
                Log.die("'global' is a reserved command name") if cmd == 'global'
         | 
| 140 | 
            -
                Log.die("'shared' is a reserved command name") if cmd == 'shared'
         | 
| 141 135 | 
             
                Log.die("'#{cmd}' already exists") if @config.any?{|x| x.name == cmd}
         | 
| 142 | 
            -
                Log.die("'help' is a reserved option name") if  | 
| 136 | 
            +
                Log.die("'help' is a reserved option name") if nodes.any?{|x| x.class == Option && !x.key.nil? && x.key.include?('help')}
         | 
| 137 | 
            +
                Log.die("command names must be pure lowercase letters") if cmd =~ /[^a-z]/
         | 
| 143 138 |  | 
| 144 | 
            -
                #  | 
| 145 | 
            -
                 | 
| 139 | 
            +
                # Validate sub command key words
         | 
| 140 | 
            +
                validate_sub_cmd = ->(sub_cmd){
         | 
| 141 | 
            +
                  Log.die("'global' is a reserved command name") if sub_cmd.name == 'global'
         | 
| 142 | 
            +
                  Log.die("'help' is a reserved option name") if sub_cmd.nodes.any?{|x| x.class == Option && !x.key.nil? && x.key.include?('help')}
         | 
| 143 | 
            +
                  Log.die("command names must be pure lowercase letters") if sub_cmd.name =~ /[^a-z]/
         | 
| 144 | 
            +
                  sub_cmd.nodes.select{|x| x.class != Option}.each{|x| validate_sub_cmd.(x)}
         | 
| 145 | 
            +
                }
         | 
| 146 | 
            +
                nodes.select{|x| x.class != Option}.each{|x| validate_sub_cmd.(x)}
         | 
| 146 147 |  | 
| 147 | 
            -
                 | 
| 148 | 
            -
                @config << cmd
         | 
| 148 | 
            +
                @config << add_cmd(cmd, desc, nodes)
         | 
| 149 149 | 
             
              end
         | 
| 150 150 |  | 
| 151 151 | 
             
              # Add global options (any option coming before all commands)
         | 
| 152 152 | 
             
              # @param option/s [Array/Option] array or single option/s
         | 
| 153 153 | 
             
              def add_global(options)
         | 
| 154 | 
            -
                options = [options] if options.class  | 
| 154 | 
            +
                options = [options] if options.class != Array
         | 
| 155 | 
            +
                Log.die("only options are allowed as globals") if options.any?{|x| x.class != Option}
         | 
| 155 156 |  | 
| 156 157 | 
             
                # Aggregate global options
         | 
| 157 158 | 
             
                if (global = @config.find{|x| x.name == 'global'})
         | 
| 158 | 
            -
                  global. | 
| 159 | 
            +
                  global.nodes.each{|x| options << x}
         | 
| 159 160 | 
             
                  @config.reject!{|x| x.name == 'global'}
         | 
| 160 161 | 
             
                end
         | 
| 161 162 | 
             
                @config << add_cmd('global', 'Global options:', options)
         | 
| 162 163 | 
             
              end
         | 
| 163 164 |  | 
| 164 | 
            -
              # Add shared option (options that are added to all commands)
         | 
| 165 | 
            -
              # @param option/s [Array/Option] array or single option/s
         | 
| 166 | 
            -
              def add_shared(options)
         | 
| 167 | 
            -
                options = [options] if options.class == Option
         | 
| 168 | 
            -
                options.each{|x|
         | 
| 169 | 
            -
                  Log.die("duplicate shared option '#{x.desc}' given") if @shared
         | 
| 170 | 
            -
                    .any?{|y| y.key == x.key && y.desc == x.desc && y.type == x.type}
         | 
| 171 | 
            -
                  x.shared = true
         | 
| 172 | 
            -
                  x.required = true
         | 
| 173 | 
            -
                  @shared << x
         | 
| 174 | 
            -
                }
         | 
| 175 | 
            -
              end
         | 
| 176 | 
            -
             | 
| 177 165 | 
             
              # Returns banner string
         | 
| 178 166 | 
             
              # @return [String] the app's banner
         | 
| 179 167 | 
             
              def banner
         | 
| @@ -206,7 +194,7 @@ class Commander | |
| 206 194 |  | 
| 207 195 | 
             
                # Set help if nothing was given
         | 
| 208 196 | 
             
                ARGV.clear and ARGV << '-h' if ARGV.empty?
         | 
| 209 | 
            -
             | 
| 197 | 
            +
             | 
| 210 198 | 
             
                # Process command options
         | 
| 211 199 | 
             
                #---------------------------------------------------------------------------
         | 
| 212 200 | 
             
                order_globals!
         | 
| @@ -221,7 +209,7 @@ class Commander | |
| 221 209 | 
             
                    # Collect command options from args to compare against
         | 
| 222 210 | 
             
                    opts = ARGV.take_while{|x| !cmd_names.include?(x) }
         | 
| 223 211 | 
             
                    ARGV.shift(opts.size)
         | 
| 224 | 
            -
             | 
| 212 | 
            +
             | 
| 225 213 | 
             
                    # Handle help upfront before anything else
         | 
| 226 214 | 
             
                    if opts.any?{|x| m = match_named(x, cmd); m.hit? && m.sym == :help }
         | 
| 227 215 | 
             
                      !puts(help) and exit if cmd.name == 'global'
         | 
| @@ -229,8 +217,8 @@ class Commander | |
| 229 217 | 
             
                    end
         | 
| 230 218 |  | 
| 231 219 | 
             
                    # Check that all required options were given
         | 
| 232 | 
            -
                    cmd_pos_opts = cmd. | 
| 233 | 
            -
                    cmd_named_opts = cmd. | 
| 220 | 
            +
                    cmd_pos_opts = cmd.nodes.select{|x| x.key.nil? }
         | 
| 221 | 
            +
                    cmd_named_opts = cmd.nodes.select{|x| !x.key.nil? }
         | 
| 234 222 |  | 
| 235 223 | 
             
                    !puts("Error: positional option required!".colorize(:red)) && !puts(cmd.help) and
         | 
| 236 224 | 
             
                      exit if opts.select{|x| !x.start_with?('-')}.size < cmd_pos_opts.select{|x| x.required}.size
         | 
| @@ -278,18 +266,12 @@ class Commander | |
| 278 266 | 
             
                      # --------------------------------------------------------------------
         | 
| 279 267 | 
             
                      !puts("Error: unknown named option '#{opt}' given!".colorize(:red)) && !puts(cmd.help) and exit if !sym
         | 
| 280 268 | 
             
                      @cmds[cmd.name.to_sym][sym] = value
         | 
| 281 | 
            -
                      if cmd_opt.shared
         | 
| 282 | 
            -
                        sym = "shared#{pos}".to_sym if cmd_opt.key.nil?
         | 
| 283 | 
            -
                        @cmds[:shared] = {} if !@cmds.key?(:shared)
         | 
| 284 | 
            -
                        @cmds[:shared][sym] = value
         | 
| 285 | 
            -
                      end
         | 
| 286 269 | 
             
                    }
         | 
| 287 270 | 
             
                  end
         | 
| 288 271 | 
             
                }
         | 
| 289 272 |  | 
| 290 | 
            -
                # Ensure specials (global | 
| 273 | 
            +
                # Ensure specials (global) are always set
         | 
| 291 274 | 
             
                @cmds[:global] = {} if !@cmds[:global]
         | 
| 292 | 
            -
                @cmds[:shared] = {} if !@cmds[:shared]
         | 
| 293 275 |  | 
| 294 276 | 
             
                # Ensure all options were consumed
         | 
| 295 277 | 
             
                Log.die("invalid options #{ARGV}") if ARGV.any?
         | 
| @@ -353,13 +335,13 @@ class Commander | |
| 353 335 | 
             
                args = ARGV[0..-1]
         | 
| 354 336 | 
             
                results = {}
         | 
| 355 337 | 
             
                cmd_names = @config.map{|x| x.name }
         | 
| 356 | 
            -
             | 
| 338 | 
            +
             | 
| 357 339 | 
             
                chained = []
         | 
| 358 340 | 
             
                while args.any? do
         | 
| 359 341 | 
             
                  if !(cmd = @config.find{|x| x.name == args.first}).nil?
         | 
| 360 342 | 
             
                    results[args.shift] = []                            # Add the command to the results
         | 
| 361 343 | 
             
                    cmd_names.reject!{|x| x == cmd.name}                # Remove command from possible commands
         | 
| 362 | 
            -
                    cmd_required = cmd. | 
| 344 | 
            +
                    cmd_required = cmd.nodes.select{|x| x.required}
         | 
| 363 345 |  | 
| 364 346 | 
             
                    # Collect command options from args to compare against
         | 
| 365 347 | 
             
                    opts = args.take_while{|x| !cmd_names.include?(x)}
         | 
| @@ -369,20 +351,22 @@ class Commander | |
| 369 351 | 
             
                    results[cmd.name].concat(opts) and next if cmd.name == 'global'
         | 
| 370 352 |  | 
| 371 353 | 
             
                    # Chained case is when no options are given but some are required
         | 
| 372 | 
            -
                    if opts.size == 0 && cmd. | 
| 354 | 
            +
                    if opts.size == 0 && cmd.nodes.any?{|x| x.required}
         | 
| 373 355 | 
             
                      chained << cmd
         | 
| 374 356 | 
             
                    else
         | 
| 357 | 
            +
                      # Add cmd with options
         | 
| 375 358 | 
             
                      results[cmd.name].concat(opts)
         | 
| 376 359 |  | 
| 360 | 
            +
                      # Check chained cmds against current cmd
         | 
| 377 361 | 
             
                      chained.each{|x|
         | 
| 378 | 
            -
                        other_required = x. | 
| 379 | 
            -
                        !puts("Error: chained commands must  | 
| 380 | 
            -
                          exit if cmd_required.size  | 
| 381 | 
            -
                         | 
| 362 | 
            +
                        other_required = x.nodes.select{|x| x.required}
         | 
| 363 | 
            +
                        !puts("Error: chained commands must satisfy required options!".colorize(:red)) && !puts(x.help) and
         | 
| 364 | 
            +
                          exit if cmd_required.size < other_required.size
         | 
| 365 | 
            +
                        other_required.each_with_index{|y,i|
         | 
| 382 366 | 
             
                          !puts("Error: chained command options are not type consistent!".colorize(:red)) && !puts(x.help) and
         | 
| 383 | 
            -
                            exit if y.type !=  | 
| 367 | 
            +
                            exit if y.type != cmd_required[i].type || y.key != cmd_required[i].key
         | 
| 384 368 | 
             
                        }
         | 
| 385 | 
            -
                        results[x.name].concat(opts)
         | 
| 369 | 
            +
                        results[x.name].concat(opts.take(other_required.size))
         | 
| 386 370 | 
             
                      }
         | 
| 387 371 | 
             
                    end
         | 
| 388 372 | 
             
                  end
         | 
| @@ -398,7 +382,7 @@ class Commander | |
| 398 382 | 
             
              # @return [OptionMatch]] struct with some helper functions
         | 
| 399 383 | 
             
              def match_named(opt, cmd)
         | 
| 400 384 | 
             
                match = OptionMatch.new(opt)
         | 
| 401 | 
            -
                cmd_named_opts = cmd. | 
| 385 | 
            +
                cmd_named_opts = cmd.nodes.select{|x| !x.key.nil? }
         | 
| 402 386 |  | 
| 403 387 | 
             
                if opt.start_with?('-')
         | 
| 404 388 | 
             
                  short = opt[@short_regex, 1]
         | 
| @@ -450,25 +434,26 @@ class Commander | |
| 450 434 | 
             
              # Add a command to the command list
         | 
| 451 435 | 
             
              # @param cmd [String] name of the command
         | 
| 452 436 | 
             
              # @param desc [String] description of the command
         | 
| 453 | 
            -
              # @param  | 
| 437 | 
            +
              # @param nodes [List] list of command nodes (i.e. options or commands)
         | 
| 454 438 | 
             
              # @return [Command] new command
         | 
| 455 | 
            -
              def add_cmd(cmd, desc,  | 
| 456 | 
            -
                 | 
| 439 | 
            +
              def add_cmd(cmd, desc, nodes)
         | 
| 440 | 
            +
                #nodes.select{|x| x.class != Option}.each{|x| validate_sub_cmd.(x)}
         | 
| 457 441 |  | 
| 458 442 | 
             
                # Build help for command
         | 
| 443 | 
            +
                #---------------------------------------------------------------------------
         | 
| 459 444 | 
             
                app = @app || @app_default
         | 
| 460 445 | 
             
                help = "#{desc}\n"
         | 
| 461 446 | 
             
                help += "\nUsage: ./#{app} #{cmd} [options]\n" if cmd != 'global'
         | 
| 462 447 | 
             
                help = "#{banner}\n#{help}" if @app && cmd != 'global'
         | 
| 463 448 |  | 
| 464 449 | 
             
                # Add help option if not global command
         | 
| 465 | 
            -
                 | 
| 450 | 
            +
                nodes << @config.find{|x| x.name == 'global'}.nodes.find{|x| x.long == '--help'} if cmd != 'global'
         | 
| 466 451 |  | 
| 467 452 | 
             
                # Add positional options first
         | 
| 468 | 
            -
                sorted_options =  | 
| 469 | 
            -
                sorted_options +=  | 
| 453 | 
            +
                sorted_options = nodes.select{|x| x.key.nil?}
         | 
| 454 | 
            +
                sorted_options += nodes.select{|x| !x.key.nil?}.sort{|x,y| x.key <=> y.key}
         | 
| 470 455 | 
             
                positional_index = -1
         | 
| 471 | 
            -
                sorted_options.each{|x| | 
| 456 | 
            +
                sorted_options.each{|x|
         | 
| 472 457 | 
             
                  required = x.required ? ", Required" : ""
         | 
| 473 458 | 
             
                  allowed = x.allowed.empty? ? "" : " (#{x.allowed * ','})"
         | 
| 474 459 | 
             
                  positional_index += 1 if x.key.nil?
         | 
    
        metadata
    CHANGED
    
    | @@ -1,14 +1,14 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: nub
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 0.0. | 
| 4 | 
            +
              version: 0.0.54
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Patrick Crummett
         | 
| 8 8 | 
             
            autorequire: 
         | 
| 9 9 | 
             
            bindir: bin
         | 
| 10 10 | 
             
            cert_chain: []
         | 
| 11 | 
            -
            date: 2018- | 
| 11 | 
            +
            date: 2018-05-02 00:00:00.000000000 Z
         | 
| 12 12 | 
             
            dependencies:
         | 
| 13 13 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 14 14 | 
             
              name: colorize
         |