diff --git a/README.md b/README.md index dccef30..78b2afb 100644 --- a/README.md +++ b/README.md @@ -311,4 +311,23 @@ end Commands -------- -Slop no longer has built in support for git-style subcommands. +You can implement git-style subcommands by passing `subcommands: true` to +`parse`: + +```ruby +argv = ["-n", "my-ns", "run", "-q"] +global_result = Slop.parse(argv, subcommands: true) do |o| + o.string "-n", "--namespace", "a namespace" +end + +argv = global_result.arguments +subcommand = argv.shift +if subcommand == "run" + result = Slop.parse(argv) do |o| + o.bool "-q", "--quiet", "suppress output" + end + + puts global_result[:namespace] #=> "my-ns" + puts result[:quiet] #=> true +end +``` diff --git a/lib/slop/parser.rb b/lib/slop/parser.rb index 448ddf0..4da6b80 100644 --- a/lib/slop/parser.rb +++ b/lib/slop/parser.rb @@ -37,50 +37,28 @@ def reset # Returns a Slop::Result. def parse(strings) reset # reset before every parse + strings = strings.dup - # ignore everything after "--" - strings, ignored_args = partition(strings) + while (arg = strings.shift) + possible_value = strings.first unless strings.first == '--' + break if arg == "--" - pairs = strings.each_cons(2).to_a - # this ensures we still support the last string being a flag, - # otherwise it'll only be used as an argument. - pairs << [strings.last, nil] + opt_name, explicit_value = arg.split("=", 2) - @arguments = strings.dup - - pairs.each_with_index do |pair, idx| - flag, arg = pair - break if !flag - - # support `foo=bar` - orig_flag = flag.dup - if match = flag.match(/([^=]+)=(.*)/) - flag, arg = match.captures - end - - if opt = try_process(flag, arg) - # since the option was parsed, we remove it from our - # arguments (plus the arg if necessary) - # delete argument first while we can find its index. - if opt.expects_argument? - - # if we consumed the argument, remove the next pair - if consume_next_argument?(orig_flag) - pairs.delete_at(idx + 1) - end - - arguments.each_with_index do |argument, i| - if argument == orig_flag && !orig_flag.include?("=") - arguments.delete_at(i + 1) - end - end + if (opt = try_process(opt_name, explicit_value || possible_value)) + # Skip the next argument if we consumed it as the value for this arg. + if opt.expects_argument? && consume_next_argument?(arg) + strings.shift end - arguments.delete(orig_flag) + else + # If it wasn't used as an arg, add it to the arguments. + add_argument(arg) + # If we're expecting subcommands, this argument was the subcommand, + # and any subsequent flags/opts are _its_ property, not ours. + break if subcommands? end end - @arguments += ignored_args - if !suppress_errors? unused_options.each do |o| if o.config[:required] @@ -89,12 +67,17 @@ def parse(strings) end end end + arguments.concat(strings) Result.new(self).tap do |result| used_options.each { |o| o.finish(result) } end end + def add_argument(string) + arguments << string + end + # Returns an Array of Option instances that were used. def used_options options.select { |o| o.count > 0 } @@ -154,6 +137,10 @@ def try_process_grouped_flags(flag, arg) try_process(last, arg) # send the argument to the last flag end + def subcommands? + config[:subcommands] + end + def suppress_errors? config[:suppress_errors] end @@ -161,15 +148,5 @@ def suppress_errors? def matching_option(flag) options.find { |o| o.flags.include?(flag) } end - - def partition(strings) - if strings.include?("--") - partition_idx = strings.index("--") - return [[], strings[1..-1]] if partition_idx.zero? - [strings[0..partition_idx-1], strings[partition_idx+1..-1]] - else - [strings, []] - end - end end end diff --git a/test/parser_test.rb b/test/parser_test.rb index 02c1f00..d3e7c44 100644 --- a/test/parser_test.rb +++ b/test/parser_test.rb @@ -13,6 +13,18 @@ @result = @parser.parse %w(foo -v --name lee argument) end + describe "in subcommands mode" do + before do + @parser = Slop::Parser.new(@options, subcommands: true) + end + + it "stops parsing after the first argument" do + @parser.parse %w(-n name cmd -v) + assert_equal [@name], @parser.used_options + assert_equal ["cmd", "-v"], @parser.arguments + end + end + it "ignores everything after --" do @parser.parse %w(-v -- -v --name lee) assert_equal [@verbose], @parser.used_options