Thanks to visit codestin.com
Credit goes to github.com

Skip to content

subcommand parser #41

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
183 changes: 113 additions & 70 deletions lib/optparse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -1149,6 +1149,7 @@ def initialize(banner = nil, width = 32, indent = ' ' * 4)
@default_argv = ARGV
@require_exact = false
@raise_unknown = true
@subparsers = nil
add_officious
yield self if block_given?
end
Expand All @@ -1171,6 +1172,12 @@ def self.terminate(arg = nil)
throw :terminate, arg
end

def subparser(name, *rest, &block)
parser = self.class.new(*rest)
(@subparsers ||= CompletingHash.new)[name] = [parser, block]
parser
end

@stack = [DefaultList]
def self.top() DefaultList end

Expand Down Expand Up @@ -1626,86 +1633,119 @@ def order(*argv, into: nil, &nonopt)
# Non-option arguments remain in +argv+.
#
def order!(argv = default_argv, into: nil, &nonopt)
setter = ->(name, val) {into[name.to_sym] = val} if into
setter = into.extend(SymSetter).method(:sym_set) if into
parse_in_order(argv, setter, &nonopt)
end

def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:
opt, arg, val, rest = nil
module SymSetter
def sym_set(name, val)
self[name.to_sym] = val
end
end

def parse_option(arg, argv, setter = nil)
case arg
when /\A--([^=]*)(?:=(.*))?/m
opt, rest = $1, $2
opt.tr!('_', '-')
begin
sw, = complete(:long, opt, true)
if require_exact && !sw.long.include?(arg)
throw :terminate, arg unless raise_unknown
raise InvalidOption, arg
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, rest)
end

when /\A-(.)((=).*|.+)?/m
eq, rest, opt = $3, $2, $1
has_arg, val = eq, rest
begin
sw, = search(:short, opt)
unless sw
begin
sw, = complete(:short, opt)
# short option matched.
val = arg.delete_prefix('-')
has_arg = true
rescue InvalidOption
raise if require_exact
# if no short options match, try completion with long
# options.
sw, = complete(:long, opt)
eq ||= !rest
end
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
else
raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
end
begin
argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
end

else
return false
end

true
end

def parse_in_order(argv = default_argv, setter = nil, raise_unknown: self.raise_unknown, &nonopt) # :nodoc:
opt, arg, val, rest, sub = nil
nonopt ||= proc {|a| throw :terminate, a}
argv.unshift(arg) if arg = catch(:terminate) {
while arg = argv.shift
case arg
# long option
when /\A--([^=]*)(?:=(.*))?/m
opt, rest = $1, $2
opt.tr!('_', '-')
begin
sw, = complete(:long, opt, true)
if require_exact && !sw.long.include?(arg)
throw :terminate, arg unless raise_unknown
raise InvalidOption, arg
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
next if parse_option(arg, argv, setter)

# sub-command
if (key, (sub, block) = @subparsers&.complete(arg))
block.call if block
if setter
into = setter.receiver.class.new.extend(SymSetter)
setter.call(key, into)
subsetter = into.method(:sym_set)
end
begin
opt, cb, val = sw.parse(rest, argv) {|*exc| raise(*exc)}
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, rest)
end

# short option
when /\A-(.)((=).*|.+)?/m
eq, rest, opt = $3, $2, $1
has_arg, val = eq, rest
begin
sw, = search(:short, opt)
unless sw
begin
sw, = complete(:short, opt)
# short option matched.
val = arg.delete_prefix('-')
has_arg = true
rescue InvalidOption
raise if require_exact
# if no short options match, try completion with long
# options.
sw, = complete(:long, opt)
eq ||= !rest
end
pp argv: argv
sub.parse_in_order(argv, subsetter, raise_unknown: true) do |a|
pp arg: arg, a: a, argv: argv
nonopt.call(a) unless parse_option(a, argv)
end
rescue ParseError
throw :terminate, arg unless raise_unknown
raise $!.set_option(arg, true)
end
begin
opt, cb, val = sw.parse(val, argv) {|*exc| raise(*exc) if eq}
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
else
raise InvalidOption, arg if has_arg and !eq and arg == "-#{opt}"
end
begin
argv.unshift(opt) if opt and (!rest or (opt = opt.sub(/\A-*/, '-')) != '-')
val = cb.call(val) if cb
setter.call(sw.switch_name, val) if setter
rescue ParseError
raise $!.set_option(arg, arg.length > 2)
rescue InvalidOption => e
e.recover(argv)
arg = argv.shift
retry if parse_option(arg, argv, setter)
raise
end
end

# non-option argument
else
catch(:prune) do
visit(:each_option) do |sw0|
sw = sw0
sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
end
nonopt.call(arg)
catch(:prune) do
visit(:each_option) do |sw0|
sw = sw0
sw.block.call(arg) if Switch === sw and sw.match_nonswitch?(arg)
end
nonopt.call(arg)
end
end

Expand All @@ -1716,7 +1756,7 @@ def parse_in_order(argv = default_argv, setter = nil, &nonopt) # :nodoc:

argv
end
private :parse_in_order
protected :parse_in_order

#
# Parses command line arguments +argv+ in permutation mode and returns
Expand All @@ -1735,6 +1775,9 @@ def permute(*argv, into: nil)
# Non-option arguments remain in +argv+.
#
def permute!(argv = default_argv, into: nil)
if @subparsers
raise "cannot parse in permutation mode with subparsers"
end
nonopts = []
order!(argv, into: into, &nonopts.method(:<<))
argv[0, 0] = nonopts
Expand All @@ -1758,7 +1801,7 @@ def parse(*argv, into: nil)
# Non-option arguments remain in +argv+.
#
def parse!(argv = default_argv, into: nil)
if ENV.include?('POSIXLY_CORRECT')
if @subparsers or ENV.include?('POSIXLY_CORRECT')
order!(argv, into: into)
else
permute!(argv, into: into)
Expand Down
23 changes: 23 additions & 0 deletions sample/optparse/subcommand.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#! /usr/bin/ruby
# contributed by Minero Aoki.

require 'optparse'

opts = {}
parser = OptionParser.new
parser.on('-i') { opts["i"] = true }
parser.on('-o') { opts["o"] = true }

parser.subparser('add') {opts[:add] = {}}
.on('-i') { opts[:add]["i"] = true }
parser.subparser('del') {opts[:del] = {}}.then do |sub|
sub.on('-i') { opts[:del]["i"] = true }
end
parser.subparser('list') {opts[:list] = {}}.then do |sub|
sub.on('-iN', Integer) {|i| opts[:list]["i"] = i }
end

h = {}
p parser.parse!(ARGV, into: h)
p h
p opts