puppet-debugger 0.4.4 → 0.5.1

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.
@@ -38,17 +38,9 @@ module PuppetDebugger
38
38
  @functions
39
39
  end
40
40
 
41
- # returns an array of module loaders that we may need to use in the future
42
- # in order to parse all types of code (ie. functions) For now this is not
43
- # being used.
44
- def resolve_paths(loaders)
45
- mod_resolver = loaders.instance_variable_get(:@module_resolver)
46
- all_mods = mod_resolver.instance_variable_get(:@all_module_loaders)
47
- end
48
-
49
41
  # gather all the lib dirs
50
- def lib_dirs
51
- dirs = modules_paths.map do |mod_dir|
42
+ def lib_dirs(module_dirs = modules_paths)
43
+ dirs = module_dirs.map do |mod_dir|
52
44
  Dir["#{mod_dir}/*/lib"].entries
53
45
  end.flatten
54
46
  dirs + [puppet_repl_lib_dir]
@@ -56,16 +48,11 @@ module PuppetDebugger
56
48
 
57
49
  # load all the lib dirs so puppet can find the functions
58
50
  # at this time, this function is not being used
59
- def load_lib_dirs
60
- lib_dirs.each do |lib|
51
+ def load_lib_dirs(module_dirs = modules_paths)
52
+ lib_dirs(module_dirs).each do |lib|
61
53
  $LOAD_PATH << lib
62
54
  end
63
55
  end
64
-
65
- # def functions
66
- # @functions = []
67
- # @functions << compiler.loaders.static_loader.loaded.keys.find_all {|l| l.type == :function}
68
- # end
69
56
  end
70
57
  end
71
58
  end
@@ -85,6 +85,7 @@ module PuppetDebugger
85
85
  set_node(nil)
86
86
  set_facts(nil)
87
87
  set_environment(nil)
88
+ set_compiler(nil)
88
89
  set_log_level(log_level)
89
90
  end
90
91
 
@@ -0,0 +1,34 @@
1
+ module PuppetDebugger
2
+ module Support
3
+ module Loader
4
+
5
+ def create_loader(environment)
6
+ Puppet::Pops::Loaders.new(environment)
7
+ end
8
+
9
+ def data_types
10
+ loader.implementation_registry.
11
+ instance_variable_get(:'@implementations_per_type_name').
12
+ keys.find_all { |t| t !~ /::/ }
13
+ end
14
+
15
+ def loaders
16
+ @loaders ||= create_loader(puppet_environment)
17
+ end
18
+
19
+ # returns an array of module loaders that we may need to use in the future
20
+ # in order to parse all types of code (ie. functions) For now this is not
21
+ # being used.
22
+ def resolve_paths(loaders)
23
+ mod_resolver = loaders.instance_variable_get(:@module_resolver)
24
+ all_mods = mod_resolver.instance_variable_get(:@all_module_loaders)
25
+ end
26
+
27
+ # def functions
28
+ # @functions = []
29
+ # @functions << compiler.loaders.static_loader.loaded.keys.find_all {|l| l.type == :function}
30
+ # end
31
+
32
+ end
33
+ end
34
+ end
@@ -27,6 +27,27 @@ module PuppetDebugger
27
27
  node_obj
28
28
  end
29
29
 
30
+ def create_real_node(environment)
31
+ node = nil
32
+ unless Puppet[:node_name_fact].empty?
33
+ # Collect our facts.
34
+ unless facts = Puppet::Node::Facts.indirection.find(Puppet[:node_name_value])
35
+ raise "Could not find facts for #{Puppet[:node_name_value]}"
36
+ end
37
+ Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]]
38
+ facts.name = Puppet[:node_name_value]
39
+ end
40
+ Puppet.override({ current_environment: environment }, 'For puppet debugger') do
41
+ # Find our Node
42
+ unless node = Puppet::Node.indirection.find(Puppet[:node_name_value])
43
+ raise "Could not find node #{Puppet[:node_name_value]}"
44
+ end
45
+ # Merge in the facts.
46
+ node.merge(facts.values) if facts
47
+ end
48
+ node
49
+ end
50
+
30
51
  def set_remote_node_name(name)
31
52
  @remote_node_name = name
32
53
  end
@@ -2,25 +2,26 @@
2
2
  module PuppetDebugger
3
3
  module Support
4
4
  module Scope
5
+
6
+ # @param [Puppet::Pops::Scope] - Scope object or nil
5
7
  def set_scope(value)
6
8
  @scope = value
7
9
  end
8
10
 
9
- # @return [Scope] puppet scope object
11
+ # @return [Puppet::Pops::Scope] - returns a puppet scope object
10
12
  def scope
11
- @scope ||= create_scope unless @scope
12
- @scope
13
+ @scope ||= create_scope
13
14
  end
14
15
 
16
+ # @return [Puppet::Pops::Scope] - returns a puppet scope object
15
17
  def create_scope
16
18
  do_initialize
17
19
  begin
18
- @compiler = create_compiler(node) # creates a new compiler for each scope
19
- scope = Puppet::Parser::Scope.new(@compiler)
20
+ # creates a new compiler for each scope
21
+ scope = Puppet::Parser::Scope.new(compiler)
20
22
  # creates a node class
21
23
  scope.source = Puppet::Resource::Type.new(:node, node.name)
22
- scope.parent = @compiler.topscope
23
- load_lib_dirs
24
+ scope.parent = compiler.topscope
24
25
  # compiling will load all the facts into the scope
25
26
  # without this step facts will not get resolved
26
27
  scope.compiler.compile # this will load everything into the scope
@@ -31,7 +32,7 @@ module PuppetDebugger
31
32
  scope
32
33
  end
33
34
 
34
- # returns a hash of varaibles that are currently in scope
35
+ # @return [Hash] - returns a hash of variables that are currently in scope
35
36
  def scope_vars
36
37
  vars = scope.to_hash.delete_if { |key, _value| node.facts.values.key?(key.to_sym) }
37
38
  vars['facts'] = 'removed by the puppet-debugger'
@@ -4,14 +4,14 @@ require 'optparse'
4
4
  require 'puppet/util/command_line'
5
5
 
6
6
  class Puppet::Application::Debugger < Puppet::Application
7
- attr_reader :use_facterdb, :use_stdin
7
+ attr_reader :use_stdin
8
8
 
9
9
  option('--execute EXECUTE', '-e') do |arg|
10
10
  options[:code] = arg
11
11
  end
12
12
 
13
13
  option('--facterdb-filter FILTER') do |arg|
14
- @use_facterdb = true unless options[:node_name]
14
+ options[:use_facterdb] = true unless options[:node_name]
15
15
  ENV['DEBUGGER_FACTERDB_FILTER'] = arg if arg
16
16
  end
17
17
 
@@ -21,7 +21,7 @@ class Puppet::Application::Debugger < Puppet::Application
21
21
  @use_stdin = true
22
22
  end
23
23
 
24
- option('--no-facterdb') { |_arg| @use_facterdb = false }
24
+ option('--no-facterdb') { |_arg| options[:use_facterdb] = false }
25
25
 
26
26
  option('--log-level LEVEL', '-l') do |arg|
27
27
  Puppet::Util::Log.level = arg.to_sym
@@ -38,7 +38,7 @@ class Puppet::Application::Debugger < Puppet::Application
38
38
  option('--run-once', '-r') { |_arg| options[:run_once] = true }
39
39
 
40
40
  option('--node-name CERTNAME', '-n') do |arg|
41
- @use_facterdb = false
41
+ options[:use_facterdb] = false
42
42
  options[:node_name] = arg
43
43
  end
44
44
 
@@ -172,20 +172,9 @@ Copyright (c) 2016 NWOps
172
172
  HELP
173
173
  end
174
174
 
175
- def app_defaults
176
- Puppet::Settings.app_defaults_for_run_mode(self.class.run_mode).merge(
177
- name: name
178
- )
179
- end
180
-
181
- def initialize_app_defaults
182
- Puppet.settings.initialize_app_defaults(app_defaults)
183
- end
184
-
185
175
  def initialize(command_line = Puppet::Util::CommandLine.new)
186
176
  @command_line = CommandLineArgs.new(command_line.subcommand_name, command_line.args.dup)
187
- @options = {}
188
- @use_facterdb = true
177
+ @options = { use_facterdb: true, play: nil, run_once: false, node_name: nil, quiet: false, help: false, scope: nil }
189
178
  @use_stdin = false
190
179
  begin
191
180
  require 'puppet-debugger'
@@ -201,7 +190,7 @@ Copyright (c) 2016 NWOps
201
190
  # if this is a file we don't play back since its part of the environment
202
191
  # if just the code we put in a file and use the play feature of the debugger
203
192
  # we could do the same thing with the passed in manifest file but that might be too much code to show
204
- manifest = nil
193
+
205
194
  if options[:code]
206
195
  code_input = options.delete(:code)
207
196
  file = Tempfile.new(['puppet_repl_input', '.pp'])
@@ -222,7 +211,7 @@ Copyright (c) 2016 NWOps
222
211
  Puppet.warning("Only one file can be used per run. Skipping #{command_line.args.join(', ')}") unless command_line.args.empty?
223
212
  options[:play] = file
224
213
  end
225
- if !use_facterdb && options[:node_name].nil?
214
+ if !options[:use_facterdb] && options[:node_name].nil?
226
215
  debug_environment = create_environment(nil)
227
216
  Puppet.notice('Gathering node facts...')
228
217
  node = create_node(debug_environment)
@@ -250,7 +239,6 @@ Copyright (c) 2016 NWOps
250
239
  Puppet[:node_name_value] = facts.values[Puppet[:node_name_fact]]
251
240
  facts.name = Puppet[:node_name_value]
252
241
  end
253
-
254
242
  Puppet.override({ current_environment: environment }, 'For puppet debugger') do
255
243
  # Find our Node
256
244
  unless node = Puppet::Node.indirection.find(Puppet[:node_name_value])
data/lib/trollop.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  # lib/trollop.rb -- trollop command-line processing library
2
3
  # Copyright (c) 2008-2014 William Morgan.
3
4
  # Copyright (c) 2014 Red Hat, Inc.
@@ -8,854 +9,854 @@ require 'date'
8
9
  module Trollop
9
10
  # note: this is duplicated in gemspec
10
11
  # please change over there too
11
- VERSION = "2.1.2"
12
+ VERSION = '2.1.2'
12
13
 
13
- ## Thrown by Parser in the event of a commandline error. Not needed if
14
- ## you're using the Trollop::options entry.
15
- class CommandlineError < StandardError
16
- attr_reader :error_code
14
+ ## Thrown by Parser in the event of a commandline error. Not needed if
15
+ ## you're using the Trollop::options entry.
16
+ class CommandlineError < StandardError
17
+ attr_reader :error_code
17
18
 
18
- def initialize(msg, error_code = nil)
19
- super(msg)
20
- @error_code = error_code
19
+ def initialize(msg, error_code = nil)
20
+ super(msg)
21
+ @error_code = error_code
22
+ end
21
23
  end
22
- end
23
-
24
- ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
25
- ## automatically by Trollop#options.
26
- class HelpNeeded < StandardError
27
- end
28
-
29
- ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
30
- ## automatically by Trollop#options.
31
- class VersionNeeded < StandardError
32
- end
33
-
34
- ## Regex for floating point numbers
35
- FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
36
-
37
- ## Regex for parameters
38
- PARAM_RE = /^-(-|\.$|[^\d\.])/
39
-
40
- ## The commandline parser. In typical usage, the methods in this class
41
- ## will be handled internally by Trollop::options. In this case, only the
42
- ## #opt, #banner and #version, #depends, and #conflicts methods will
43
- ## typically be called.
44
- ##
45
- ## If you want to instantiate this class yourself (for more complicated
46
- ## argument-parsing logic), call #parse to actually produce the output hash,
47
- ## and consider calling it from within
48
- ## Trollop::with_standard_exception_handling.
49
- class Parser
50
- ## The set of values that indicate a flag option when passed as the
51
- ## +:type+ parameter of #opt.
52
- FLAG_TYPES = [:flag, :bool, :boolean]
53
-
54
- ## The set of values that indicate a single-parameter (normal) option when
55
- ## passed as the +:type+ parameter of #opt.
56
- ##
57
- ## A value of +io+ corresponds to a readable IO resource, including
58
- ## a filename, URI, or the strings 'stdin' or '-'.
59
- SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date]
60
-
61
- ## The set of values that indicate a multiple-parameter option (i.e., that
62
- ## takes multiple space-separated values on the commandline) when passed as
63
- ## the +:type+ parameter of #opt.
64
- MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates]
65
-
66
- ## The complete set of legal values for the +:type+ parameter of #opt.
67
- TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
68
-
69
- INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
70
-
71
- ## The values from the commandline that were not interpreted by #parse.
72
- attr_reader :leftovers
73
-
74
- ## The complete configuration hashes for each option. (Mainly useful
75
- ## for testing.)
76
- attr_reader :specs
77
-
78
- ## A flag that determines whether or not to raise an error if the parser is passed one or more
79
- ## options that were not registered ahead of time. If 'true', then the parser will simply
80
- ## ignore options that it does not recognize.
81
- attr_accessor :ignore_invalid_options
82
-
83
- ## Initializes the parser, and instance-evaluates any block given.
84
- def initialize(*a, &b)
85
- @version = nil
86
- @leftovers = []
87
- @specs = {}
88
- @long = {}
89
- @short = {}
90
- @order = []
91
- @constraints = []
92
- @stop_words = []
93
- @stop_on_unknown = false
94
- @educate_on_error = false
95
-
96
- # instance_eval(&b) if b # can't take arguments
97
- cloaker(&b).bind(self).call(*a) if b
24
+
25
+ ## Thrown by Parser if the user passes in '-h' or '--help'. Handled
26
+ ## automatically by Trollop#options.
27
+ class HelpNeeded < StandardError
98
28
  end
99
29
 
100
- ## Define an option. +name+ is the option name, a unique identifier
101
- ## for the option that you will use internally, which should be a
102
- ## symbol or a string. +desc+ is a string description which will be
103
- ## displayed in help messages.
104
- ##
105
- ## Takes the following optional arguments:
106
- ##
107
- ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
108
- ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. Use :none: to not have a short value.
109
- ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
110
- ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
111
- ## [+:required+] If set to +true+, the argument must be provided on the commandline.
112
- ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
113
- ##
114
- ## Note that there are two types of argument multiplicity: an argument
115
- ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
116
- ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
117
- ##
118
- ## Arguments that take multiple values should have a +:type+ parameter
119
- ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
120
- ## value of an array of the correct type (e.g. [String]). The
121
- ## value of this argument will be an array of the parameters on the
122
- ## commandline.
123
- ##
124
- ## Arguments that can occur multiple times should be marked with
125
- ## +:multi+ => +true+. The value of this argument will also be an array.
126
- ## In contrast with regular non-multi options, if not specified on
127
- ## the commandline, the default value will be [], not nil.
128
- ##
129
- ## These two attributes can be combined (e.g. +:type+ => +:strings+,
130
- ## +:multi+ => +true+), in which case the value of the argument will be
131
- ## an array of arrays.
132
- ##
133
- ## There's one ambiguous case to be aware of: when +:multi+: is true and a
134
- ## +:default+ is set to an array (of something), it's ambiguous whether this
135
- ## is a multi-value argument as well as a multi-occurrence argument.
136
- ## In thise case, Trollop assumes that it's not a multi-value argument.
137
- ## If you want a multi-value, multi-occurrence argument with a default
138
- ## value, you must specify +:type+ as well.
139
-
140
- def opt(name, desc = "", opts = {}, &b)
141
- raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
142
-
143
- ## fill in :type
144
- opts[:type] = # normalize
145
- case opts[:type]
146
- when :boolean, :bool then :flag
147
- when :integer then :int
148
- when :integers then :ints
149
- when :double then :float
150
- when :doubles then :floats
151
- when Class
152
- case opts[:type].name
153
- when 'TrueClass',
154
- 'FalseClass' then :flag
155
- when 'String' then :string
156
- when 'Integer' then :int
157
- when 'Float' then :float
158
- when 'IO' then :io
159
- when 'Date' then :date
160
- else
161
- raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
162
- end
163
- when nil then nil
164
- else
165
- raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
166
- opts[:type]
167
- end
30
+ ## Thrown by Parser if the user passes in '-v' or '--version'. Handled
31
+ ## automatically by Trollop#options.
32
+ class VersionNeeded < StandardError
33
+ end
168
34
 
169
- ## for options with :multi => true, an array default doesn't imply
170
- ## a multi-valued argument. for that you have to specify a :type
171
- ## as well. (this is how we disambiguate an ambiguous situation;
172
- ## see the docs for Parser#opt for details.)
173
- disambiguated_default = if opts[:multi] && opts[:default].kind_of?(Array) && !opts[:type]
174
- opts[:default].first
175
- else
176
- opts[:default]
35
+ ## Regex for floating point numbers
36
+ FLOAT_RE = /^-?((\d+(\.\d+)?)|(\.\d+))([eE][-+]?[\d]+)?$/
37
+
38
+ ## Regex for parameters
39
+ PARAM_RE = /^-(-|\.$|[^\d\.])/
40
+
41
+ ## The commandline parser. In typical usage, the methods in this class
42
+ ## will be handled internally by Trollop::options. In this case, only the
43
+ ## #opt, #banner and #version, #depends, and #conflicts methods will
44
+ ## typically be called.
45
+ ##
46
+ ## If you want to instantiate this class yourself (for more complicated
47
+ ## argument-parsing logic), call #parse to actually produce the output hash,
48
+ ## and consider calling it from within
49
+ ## Trollop::with_standard_exception_handling.
50
+ class Parser
51
+ ## The set of values that indicate a flag option when passed as the
52
+ ## +:type+ parameter of #opt.
53
+ FLAG_TYPES = [:flag, :bool, :boolean].freeze
54
+
55
+ ## The set of values that indicate a single-parameter (normal) option when
56
+ ## passed as the +:type+ parameter of #opt.
57
+ ##
58
+ ## A value of +io+ corresponds to a readable IO resource, including
59
+ ## a filename, URI, or the strings 'stdin' or '-'.
60
+ SINGLE_ARG_TYPES = [:int, :integer, :string, :double, :float, :io, :date].freeze
61
+
62
+ ## The set of values that indicate a multiple-parameter option (i.e., that
63
+ ## takes multiple space-separated values on the commandline) when passed as
64
+ ## the +:type+ parameter of #opt.
65
+ MULTI_ARG_TYPES = [:ints, :integers, :strings, :doubles, :floats, :ios, :dates].freeze
66
+
67
+ ## The complete set of legal values for the +:type+ parameter of #opt.
68
+ TYPES = FLAG_TYPES + SINGLE_ARG_TYPES + MULTI_ARG_TYPES
69
+
70
+ INVALID_SHORT_ARG_REGEX = /[\d-]/ #:nodoc:
71
+
72
+ ## The values from the commandline that were not interpreted by #parse.
73
+ attr_reader :leftovers
74
+
75
+ ## The complete configuration hashes for each option. (Mainly useful
76
+ ## for testing.)
77
+ attr_reader :specs
78
+
79
+ ## A flag that determines whether or not to raise an error if the parser is passed one or more
80
+ ## options that were not registered ahead of time. If 'true', then the parser will simply
81
+ ## ignore options that it does not recognize.
82
+ attr_accessor :ignore_invalid_options
83
+
84
+ ## Initializes the parser, and instance-evaluates any block given.
85
+ def initialize(*a, &b)
86
+ @version = nil
87
+ @leftovers = []
88
+ @specs = {}
89
+ @long = {}
90
+ @short = {}
91
+ @order = []
92
+ @constraints = []
93
+ @stop_words = []
94
+ @stop_on_unknown = false
95
+ @educate_on_error = false
96
+
97
+ # instance_eval(&b) if b # can't take arguments
98
+ cloaker(&b).bind(self).call(*a) if b
177
99
  end
178
100
 
179
- type_from_default =
180
- case disambiguated_default
181
- when Integer then :int
182
- when Numeric then :float
183
- when TrueClass,
184
- FalseClass then :flag
185
- when String then :string
186
- when IO then :io
187
- when Date then :date
188
- when Array
189
- if opts[:default].empty?
190
- if opts[:type]
191
- raise ArgumentError, "multiple argument type must be plural" unless MULTI_ARG_TYPES.include?(opts[:type])
192
- nil
101
+ ## Define an option. +name+ is the option name, a unique identifier
102
+ ## for the option that you will use internally, which should be a
103
+ ## symbol or a string. +desc+ is a string description which will be
104
+ ## displayed in help messages.
105
+ ##
106
+ ## Takes the following optional arguments:
107
+ ##
108
+ ## [+:long+] Specify the long form of the argument, i.e. the form with two dashes. If unspecified, will be automatically derived based on the argument name by turning the +name+ option into a string, and replacing any _'s by -'s.
109
+ ## [+:short+] Specify the short form of the argument, i.e. the form with one dash. If unspecified, will be automatically derived from +name+. Use :none: to not have a short value.
110
+ ## [+:type+] Require that the argument take a parameter or parameters of type +type+. For a single parameter, the value can be a member of +SINGLE_ARG_TYPES+, or a corresponding Ruby class (e.g. +Integer+ for +:int+). For multiple-argument parameters, the value can be any member of +MULTI_ARG_TYPES+ constant. If unset, the default argument type is +:flag+, meaning that the argument does not take a parameter. The specification of +:type+ is not necessary if a +:default+ is given.
111
+ ## [+:default+] Set the default value for an argument. Without a default value, the hash returned by #parse (and thus Trollop::options) will have a +nil+ value for this key unless the argument is given on the commandline. The argument type is derived automatically from the class of the default value given, so specifying a +:type+ is not necessary if a +:default+ is given. (But see below for an important caveat when +:multi+: is specified too.) If the argument is a flag, and the default is set to +true+, then if it is specified on the the commandline the value will be +false+.
112
+ ## [+:required+] If set to +true+, the argument must be provided on the commandline.
113
+ ## [+:multi+] If set to +true+, allows multiple occurrences of the option on the commandline. Otherwise, only a single instance of the option is allowed. (Note that this is different from taking multiple parameters. See below.)
114
+ ##
115
+ ## Note that there are two types of argument multiplicity: an argument
116
+ ## can take multiple values, e.g. "--arg 1 2 3". An argument can also
117
+ ## be allowed to occur multiple times, e.g. "--arg 1 --arg 2".
118
+ ##
119
+ ## Arguments that take multiple values should have a +:type+ parameter
120
+ ## drawn from +MULTI_ARG_TYPES+ (e.g. +:strings+), or a +:default:+
121
+ ## value of an array of the correct type (e.g. [String]). The
122
+ ## value of this argument will be an array of the parameters on the
123
+ ## commandline.
124
+ ##
125
+ ## Arguments that can occur multiple times should be marked with
126
+ ## +:multi+ => +true+. The value of this argument will also be an array.
127
+ ## In contrast with regular non-multi options, if not specified on
128
+ ## the commandline, the default value will be [], not nil.
129
+ ##
130
+ ## These two attributes can be combined (e.g. +:type+ => +:strings+,
131
+ ## +:multi+ => +true+), in which case the value of the argument will be
132
+ ## an array of arrays.
133
+ ##
134
+ ## There's one ambiguous case to be aware of: when +:multi+: is true and a
135
+ ## +:default+ is set to an array (of something), it's ambiguous whether this
136
+ ## is a multi-value argument as well as a multi-occurrence argument.
137
+ ## In thise case, Trollop assumes that it's not a multi-value argument.
138
+ ## If you want a multi-value, multi-occurrence argument with a default
139
+ ## value, you must specify +:type+ as well.
140
+
141
+ def opt(name, desc = '', opts = {}, &b)
142
+ raise ArgumentError, "you already have an argument named '#{name}'" if @specs.member? name
143
+
144
+ ## fill in :type
145
+ opts[:type] = # normalize
146
+ case opts[:type]
147
+ when :boolean, :bool then :flag
148
+ when :integer then :int
149
+ when :integers then :ints
150
+ when :double then :float
151
+ when :doubles then :floats
152
+ when Class
153
+ case opts[:type].name
154
+ when 'TrueClass',
155
+ 'FalseClass' then :flag
156
+ when 'String' then :string
157
+ when 'Integer' then :int
158
+ when 'Float' then :float
159
+ when 'IO' then :io
160
+ when 'Date' then :date
193
161
  else
194
- raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
162
+ raise ArgumentError, "unsupported argument type '#{opts[:type].class.name}'"
195
163
  end
164
+ when nil then nil
196
165
  else
197
- case opts[:default][0] # the first element determines the types
198
- when Integer then :ints
199
- when Numeric then :floats
200
- when String then :strings
201
- when IO then :ios
202
- when Date then :dates
166
+ raise ArgumentError, "unsupported argument type '#{opts[:type]}'" unless TYPES.include?(opts[:type])
167
+ opts[:type]
168
+ end
169
+
170
+ ## for options with :multi => true, an array default doesn't imply
171
+ ## a multi-valued argument. for that you have to specify a :type
172
+ ## as well. (this is how we disambiguate an ambiguous situation;
173
+ ## see the docs for Parser#opt for details.)
174
+ disambiguated_default = if opts[:multi] && opts[:default].is_a?(Array) && !opts[:type]
175
+ opts[:default].first
176
+ else
177
+ opts[:default]
178
+ end
179
+
180
+ type_from_default =
181
+ case disambiguated_default
182
+ when Integer then :int
183
+ when Numeric then :float
184
+ when TrueClass,
185
+ FalseClass then :flag
186
+ when String then :string
187
+ when IO then :io
188
+ when Date then :date
189
+ when Array
190
+ if opts[:default].empty?
191
+ if opts[:type]
192
+ raise ArgumentError, 'multiple argument type must be plural' unless MULTI_ARG_TYPES.include?(opts[:type])
193
+ nil
194
+ else
195
+ raise ArgumentError, "multiple argument type cannot be deduced from an empty array for '#{opts[:default][0].class.name}'"
196
+ end
203
197
  else
204
- raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
198
+ case opts[:default][0] # the first element determines the types
199
+ when Integer then :ints
200
+ when Numeric then :floats
201
+ when String then :strings
202
+ when IO then :ios
203
+ when Date then :dates
204
+ else
205
+ raise ArgumentError, "unsupported multiple argument type '#{opts[:default][0].class.name}'"
206
+ end
205
207
  end
208
+ when nil then nil
209
+ else
210
+ raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
206
211
  end
207
- when nil then nil
208
- else
209
- raise ArgumentError, "unsupported argument type '#{opts[:default].class.name}'"
212
+
213
+ raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
214
+
215
+ opts[:type] = opts[:type] || type_from_default || :flag
216
+
217
+ ## fill in :long
218
+ opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.tr('_', '-')
219
+ opts[:long] = case opts[:long]
220
+ when /^--([^-].*)$/ then Regexp.last_match(1)
221
+ when /^[^-]/ then opts[:long]
222
+ else raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
223
+ end
224
+ raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
225
+
226
+ ## fill in :short
227
+ opts[:short] = opts[:short].to_s if opts[:short] && opts[:short] != :none
228
+ opts[:short] = case opts[:short]
229
+ when /^-(.)$/ then Regexp.last_match(1)
230
+ when nil, :none, /^.$/ then opts[:short]
231
+ else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
232
+ end
233
+
234
+ if opts[:short]
235
+ raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
236
+ raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
210
237
  end
211
238
 
212
- raise ArgumentError, ":type specification and default type don't match (default type is #{type_from_default})" if opts[:type] && type_from_default && opts[:type] != type_from_default
239
+ ## fill in :default for flags
240
+ opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
213
241
 
214
- opts[:type] = opts[:type] || type_from_default || :flag
242
+ ## autobox :default for :multi (multi-occurrence) arguments
243
+ opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].is_a?(Array)
215
244
 
216
- ## fill in :long
217
- opts[:long] = opts[:long] ? opts[:long].to_s : name.to_s.gsub("_", "-")
218
- opts[:long] = case opts[:long]
219
- when /^--([^-].*)$/ then $1
220
- when /^[^-]/ then opts[:long]
221
- else raise ArgumentError, "invalid long option name #{opts[:long].inspect}"
245
+ ## fill in :multi
246
+ opts[:multi] ||= false
247
+ opts[:callback] ||= b if block_given?
248
+ opts[:desc] ||= desc
249
+ @long[opts[:long]] = name
250
+ @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
251
+ @specs[name] = opts
252
+ @order << [:opt, name]
222
253
  end
223
- raise ArgumentError, "long option name #{opts[:long].inspect} is already taken; please specify a (different) :long" if @long[opts[:long]]
224
-
225
- ## fill in :short
226
- opts[:short] = opts[:short].to_s if opts[:short] && opts[:short] != :none
227
- opts[:short] = case opts[:short]
228
- when /^-(.)$/ then $1
229
- when nil, :none, /^.$/ then opts[:short]
230
- else raise ArgumentError, "invalid short option name '#{opts[:short].inspect}'"
254
+
255
+ ## Sets the version string. If set, the user can request the version
256
+ ## on the commandline. Should probably be of the form "<program name>
257
+ ## <version number>".
258
+ def version(s = nil)
259
+ s ? @version = s : @version
231
260
  end
232
261
 
233
- if opts[:short]
234
- raise ArgumentError, "short option name #{opts[:short].inspect} is already taken; please specify a (different) :short" if @short[opts[:short]]
235
- raise ArgumentError, "a short option name can't be a number or a dash" if opts[:short] =~ INVALID_SHORT_ARG_REGEX
262
+ ## Sets the usage string. If set the message will be printed as the
263
+ ## first line in the help (educate) output and ending in two new
264
+ ## lines.
265
+ def usage(s = nil)
266
+ s ? @usage = s : @usage
236
267
  end
237
268
 
238
- ## fill in :default for flags
239
- opts[:default] = false if opts[:type] == :flag && opts[:default].nil?
269
+ ## Adds a synopsis (command summary description) right below the
270
+ ## usage line, or as the first line if usage isn't specified.
271
+ def synopsis(s = nil)
272
+ s ? @synopsis = s : @synopsis
273
+ end
240
274
 
241
- ## autobox :default for :multi (multi-occurrence) arguments
242
- opts[:default] = [opts[:default]] if opts[:default] && opts[:multi] && !opts[:default].kind_of?(Array)
275
+ ## Adds text to the help display. Can be interspersed with calls to
276
+ ## #opt to build a multi-section help page.
277
+ def banner(s)
278
+ @order << [:text, s]
279
+ end
280
+ alias text banner
281
+
282
+ ## Marks two (or more!) options as requiring each other. Only handles
283
+ ## undirected (i.e., mutual) dependencies. Directed dependencies are
284
+ ## better modeled with Trollop::die.
285
+ def depends(*syms)
286
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
287
+ @constraints << [:depends, syms]
288
+ end
243
289
 
244
- ## fill in :multi
245
- opts[:multi] ||= false
246
- opts[:callback] ||= b if block_given?
247
- opts[:desc] ||= desc
248
- @long[opts[:long]] = name
249
- @short[opts[:short]] = name if opts[:short] && opts[:short] != :none
250
- @specs[name] = opts
251
- @order << [:opt, name]
252
- end
290
+ ## Marks two (or more!) options as conflicting.
291
+ def conflicts(*syms)
292
+ syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
293
+ @constraints << [:conflicts, syms]
294
+ end
253
295
 
254
- ## Sets the version string. If set, the user can request the version
255
- ## on the commandline. Should probably be of the form "<program name>
256
- ## <version number>".
257
- def version(s = nil)
258
- s ? @version = s : @version
259
- end
296
+ ## Defines a set of words which cause parsing to terminate when
297
+ ## encountered, such that any options to the left of the word are
298
+ ## parsed as usual, and options to the right of the word are left
299
+ ## intact.
300
+ ##
301
+ ## A typical use case would be for subcommand support, where these
302
+ ## would be set to the list of subcommands. A subsequent Trollop
303
+ ## invocation would then be used to parse subcommand options, after
304
+ ## shifting the subcommand off of ARGV.
305
+ def stop_on(*words)
306
+ @stop_words = [*words].flatten
307
+ end
260
308
 
261
- ## Sets the usage string. If set the message will be printed as the
262
- ## first line in the help (educate) output and ending in two new
263
- ## lines.
264
- def usage(s = nil)
265
- s ? @usage = s : @usage
266
- end
309
+ ## Similar to #stop_on, but stops on any unknown word when encountered
310
+ ## (unless it is a parameter for an argument). This is useful for
311
+ ## cases where you don't know the set of subcommands ahead of time,
312
+ ## i.e., without first parsing the global options.
313
+ def stop_on_unknown
314
+ @stop_on_unknown = true
315
+ end
267
316
 
268
- ## Adds a synopsis (command summary description) right below the
269
- ## usage line, or as the first line if usage isn't specified.
270
- def synopsis(s = nil)
271
- s ? @synopsis = s : @synopsis
272
- end
317
+ ## Instead of displaying "Try --help for help." on an error
318
+ ## display the usage (via educate)
319
+ def educate_on_error
320
+ @educate_on_error = true
321
+ end
273
322
 
274
- ## Adds text to the help display. Can be interspersed with calls to
275
- ## #opt to build a multi-section help page.
276
- def banner(s)
277
- @order << [:text, s]
278
- end
279
- alias_method :text, :banner
280
-
281
- ## Marks two (or more!) options as requiring each other. Only handles
282
- ## undirected (i.e., mutual) dependencies. Directed dependencies are
283
- ## better modeled with Trollop::die.
284
- def depends(*syms)
285
- syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
286
- @constraints << [:depends, syms]
287
- end
323
+ ## Parses the commandline. Typically called by Trollop::options,
324
+ ## but you can call it directly if you need more control.
325
+ ##
326
+ ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
327
+ def parse(cmdline = ARGV)
328
+ vals = {}
329
+ required = {}
330
+
331
+ opt :version, 'Print version and exit' if @version && !(@specs[:version] || @long['version'])
332
+ opt :help, 'Show this message' unless @specs[:help] || @long['help']
333
+
334
+ @specs.each do |sym, opts|
335
+ required[sym] = true if opts[:required]
336
+ vals[sym] = opts[:default]
337
+ vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
338
+ end
288
339
 
289
- ## Marks two (or more!) options as conflicting.
290
- def conflicts(*syms)
291
- syms.each { |sym| raise ArgumentError, "unknown option '#{sym}'" unless @specs[sym] }
292
- @constraints << [:conflicts, syms]
293
- end
340
+ resolve_default_short_options!
294
341
 
295
- ## Defines a set of words which cause parsing to terminate when
296
- ## encountered, such that any options to the left of the word are
297
- ## parsed as usual, and options to the right of the word are left
298
- ## intact.
299
- ##
300
- ## A typical use case would be for subcommand support, where these
301
- ## would be set to the list of subcommands. A subsequent Trollop
302
- ## invocation would then be used to parse subcommand options, after
303
- ## shifting the subcommand off of ARGV.
304
- def stop_on(*words)
305
- @stop_words = [*words].flatten
306
- end
342
+ ## resolve symbols
343
+ given_args = {}
344
+ @leftovers = each_arg cmdline do |arg, params|
345
+ ## handle --no- forms
346
+ arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
347
+ ["--#{Regexp.last_match(1)}", true]
348
+ else
349
+ [arg, false]
350
+ end
307
351
 
308
- ## Similar to #stop_on, but stops on any unknown word when encountered
309
- ## (unless it is a parameter for an argument). This is useful for
310
- ## cases where you don't know the set of subcommands ahead of time,
311
- ## i.e., without first parsing the global options.
312
- def stop_on_unknown
313
- @stop_on_unknown = true
314
- end
352
+ sym = case arg
353
+ when /^-([^-])$/ then @short[Regexp.last_match(1)]
354
+ when /^--([^-]\S*)$/ then @long[Regexp.last_match(1)] || @long["no-#{Regexp.last_match(1)}"]
355
+ else raise CommandlineError, "invalid argument syntax: '#{arg}'"
356
+ end
315
357
 
316
- ## Instead of displaying "Try --help for help." on an error
317
- ## display the usage (via educate)
318
- def educate_on_error
319
- @educate_on_error = true
320
- end
358
+ sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
321
359
 
322
- ## Parses the commandline. Typically called by Trollop::options,
323
- ## but you can call it directly if you need more control.
324
- ##
325
- ## throws CommandlineError, HelpNeeded, and VersionNeeded exceptions.
326
- def parse(cmdline = ARGV)
327
- vals = {}
328
- required = {}
329
-
330
- opt :version, "Print version and exit" if @version && ! (@specs[:version] || @long["version"])
331
- opt :help, "Show this message" unless @specs[:help] || @long["help"]
332
-
333
- @specs.each do |sym, opts|
334
- required[sym] = true if opts[:required]
335
- vals[sym] = opts[:default]
336
- vals[sym] = [] if opts[:multi] && !opts[:default] # multi arguments default to [], not nil
337
- end
360
+ next 0 if ignore_invalid_options && !sym
361
+ raise CommandlineError, "unknown argument '#{arg}'" unless sym
338
362
 
339
- resolve_default_short_options!
363
+ if given_args.include?(sym) && !@specs[sym][:multi]
364
+ raise CommandlineError, "option '#{arg}' specified multiple times"
365
+ end
340
366
 
341
- ## resolve symbols
342
- given_args = {}
343
- @leftovers = each_arg cmdline do |arg, params|
344
- ## handle --no- forms
345
- arg, negative_given = if arg =~ /^--no-([^-]\S*)$/
346
- ["--#{$1}", true]
347
- else
348
- [arg, false]
349
- end
367
+ given_args[sym] ||= {}
368
+ given_args[sym][:arg] = arg
369
+ given_args[sym][:negative_given] = negative_given
370
+ given_args[sym][:params] ||= []
371
+
372
+ # The block returns the number of parameters taken.
373
+ num_params_taken = 0
374
+
375
+ unless params.nil?
376
+ if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
377
+ given_args[sym][:params] << params[0, 1] # take the first parameter
378
+ num_params_taken = 1
379
+ elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
380
+ given_args[sym][:params] << params # take all the parameters
381
+ num_params_taken = params.size
382
+ end
383
+ end
350
384
 
351
- sym = case arg
352
- when /^-([^-])$/ then @short[$1]
353
- when /^--([^-]\S*)$/ then @long[$1] || @long["no-#{$1}"]
354
- else raise CommandlineError, "invalid argument syntax: '#{arg}'"
385
+ num_params_taken
355
386
  end
356
387
 
357
- sym = nil if arg =~ /--no-/ # explicitly invalidate --no-no- arguments
388
+ ## check for version and help args
389
+ raise VersionNeeded if given_args.include? :version
390
+ raise HelpNeeded if given_args.include? :help
358
391
 
359
- next 0 if ignore_invalid_options && !sym
360
- raise CommandlineError, "unknown argument '#{arg}'" unless sym
392
+ ## check constraint satisfaction
393
+ @constraints.each do |type, syms|
394
+ constraint_sym = syms.find { |sym| given_args[sym] }
395
+ next unless constraint_sym
361
396
 
362
- if given_args.include?(sym) && !@specs[sym][:multi]
363
- raise CommandlineError, "option '#{arg}' specified multiple times"
364
- end
365
-
366
- given_args[sym] ||= {}
367
- given_args[sym][:arg] = arg
368
- given_args[sym][:negative_given] = negative_given
369
- given_args[sym][:params] ||= []
370
-
371
- # The block returns the number of parameters taken.
372
- num_params_taken = 0
373
-
374
- unless params.nil?
375
- if SINGLE_ARG_TYPES.include?(@specs[sym][:type])
376
- given_args[sym][:params] << params[0, 1] # take the first parameter
377
- num_params_taken = 1
378
- elsif MULTI_ARG_TYPES.include?(@specs[sym][:type])
379
- given_args[sym][:params] << params # take all the parameters
380
- num_params_taken = params.size
397
+ case type
398
+ when :depends
399
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
400
+ when :conflicts
401
+ syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
381
402
  end
382
403
  end
383
404
 
384
- num_params_taken
385
- end
386
-
387
- ## check for version and help args
388
- raise VersionNeeded if given_args.include? :version
389
- raise HelpNeeded if given_args.include? :help
405
+ required.each do |sym, _val|
406
+ raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
407
+ end
390
408
 
391
- ## check constraint satisfaction
392
- @constraints.each do |type, syms|
393
- constraint_sym = syms.find { |sym| given_args[sym] }
394
- next unless constraint_sym
409
+ ## parse parameters
410
+ given_args.each do |sym, given_data|
411
+ arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
395
412
 
396
- case type
397
- when :depends
398
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} requires --#{@specs[sym][:long]}" unless given_args.include? sym }
399
- when :conflicts
400
- syms.each { |sym| raise CommandlineError, "--#{@specs[constraint_sym][:long]} conflicts with --#{@specs[sym][:long]}" if given_args.include?(sym) && (sym != constraint_sym) }
401
- end
402
- end
413
+ opts = @specs[sym]
414
+ if params.empty? && opts[:type] != :flag
415
+ raise CommandlineError, "option '#{arg}' needs a parameter" unless opts[:default]
416
+ params << (opts[:default].is_a?(Array) ? opts[:default].clone : [opts[:default]])
417
+ end
403
418
 
404
- required.each do |sym, val|
405
- raise CommandlineError, "option --#{@specs[sym][:long]} must be specified" unless given_args.include? sym
406
- end
419
+ vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
420
+
421
+ case opts[:type]
422
+ when :flag
423
+ vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
424
+ when :int, :ints
425
+ vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
426
+ when :float, :floats
427
+ vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
428
+ when :string, :strings
429
+ vals[sym] = params.map { |pg| pg.map(&:to_s) }
430
+ when :io, :ios
431
+ vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
432
+ when :date, :dates
433
+ vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
434
+ end
407
435
 
408
- ## parse parameters
409
- given_args.each do |sym, given_data|
410
- arg, params, negative_given = given_data.values_at :arg, :params, :negative_given
436
+ if SINGLE_ARG_TYPES.include?(opts[:type])
437
+ vals[sym] = if opts[:multi] # multiple options, each with a single parameter
438
+ vals[sym].map { |p| p[0] }
439
+ else # single parameter
440
+ vals[sym][0][0]
441
+ end
442
+ elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
443
+ vals[sym] = vals[sym][0] # single option, with multiple parameters
444
+ end
445
+ # else: multiple options, with multiple parameters
411
446
 
412
- opts = @specs[sym]
413
- if params.empty? && opts[:type] != :flag
414
- raise CommandlineError, "option '#{arg}' needs a parameter" unless opts[:default]
415
- params << (opts[:default].kind_of?(Array) ? opts[:default].clone : [opts[:default]])
447
+ opts[:callback].call(vals[sym]) if opts.key?(:callback)
416
448
  end
417
449
 
418
- vals["#{sym}_given".intern] = true # mark argument as specified on the commandline
419
-
420
- case opts[:type]
421
- when :flag
422
- vals[sym] = (sym.to_s =~ /^no_/ ? negative_given : !negative_given)
423
- when :int, :ints
424
- vals[sym] = params.map { |pg| pg.map { |p| parse_integer_parameter p, arg } }
425
- when :float, :floats
426
- vals[sym] = params.map { |pg| pg.map { |p| parse_float_parameter p, arg } }
427
- when :string, :strings
428
- vals[sym] = params.map { |pg| pg.map(&:to_s) }
429
- when :io, :ios
430
- vals[sym] = params.map { |pg| pg.map { |p| parse_io_parameter p, arg } }
431
- when :date, :dates
432
- vals[sym] = params.map { |pg| pg.map { |p| parse_date_parameter p, arg } }
433
- end
450
+ ## modify input in place with only those
451
+ ## arguments we didn't process
452
+ cmdline.clear
453
+ @leftovers.each { |l| cmdline << l }
434
454
 
435
- if SINGLE_ARG_TYPES.include?(opts[:type])
436
- if opts[:multi] # multiple options, each with a single parameter
437
- vals[sym] = vals[sym].map { |p| p[0] }
438
- else # single parameter
439
- vals[sym] = vals[sym][0][0]
455
+ ## allow openstruct-style accessors
456
+ class << vals
457
+ def method_missing(m, *_args)
458
+ self[m] || self[m.to_s]
440
459
  end
441
- elsif MULTI_ARG_TYPES.include?(opts[:type]) && !opts[:multi]
442
- vals[sym] = vals[sym][0] # single option, with multiple parameters
443
460
  end
444
- # else: multiple options, with multiple parameters
445
-
446
- opts[:callback].call(vals[sym]) if opts.key?(:callback)
461
+ vals
447
462
  end
448
463
 
449
- ## modify input in place with only those
450
- ## arguments we didn't process
451
- cmdline.clear
452
- @leftovers.each { |l| cmdline << l }
453
-
454
- ## allow openstruct-style accessors
455
- class << vals
456
- def method_missing(m, *_args)
457
- self[m] || self[m.to_s]
464
+ def parse_date_parameter(param, arg) #:nodoc:
465
+ begin
466
+ require 'chronic'
467
+ time = Chronic.parse(param)
468
+ rescue LoadError
469
+ # chronic is not available
458
470
  end
471
+ time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
472
+ rescue ArgumentError
473
+ raise CommandlineError, "option '#{arg}' needs a date"
459
474
  end
460
- vals
461
- end
462
475
 
463
- def parse_date_parameter(param, arg) #:nodoc:
464
- begin
465
- require 'chronic'
466
- time = Chronic.parse(param)
467
- rescue LoadError
468
- # chronic is not available
469
- end
470
- time ? Date.new(time.year, time.month, time.day) : Date.parse(param)
471
- rescue ArgumentError
472
- raise CommandlineError, "option '#{arg}' needs a date"
473
- end
474
-
475
- ## Print the help message to +stream+.
476
- def educate(stream = $stdout)
477
- width # hack: calculate it now; otherwise we have to be careful not to
478
- # call this unless the cursor's at the beginning of a line.
479
- left = {}
480
- @specs.each do |name, spec|
481
- left[name] =
482
- (spec[:short] && spec[:short] != :none ? "-#{spec[:short]}" : "") +
483
- (spec[:short] && spec[:short] != :none ? ", " : "") + "--#{spec[:long]}" +
484
- case spec[:type]
485
- when :flag then ""
486
- when :int then "=<i>"
487
- when :ints then "=<i+>"
488
- when :string then "=<s>"
489
- when :strings then "=<s+>"
490
- when :float then "=<f>"
491
- when :floats then "=<f+>"
492
- when :io then "=<filename/uri>"
493
- when :ios then "=<filename/uri+>"
494
- when :date then "=<date>"
495
- when :dates then "=<date+>"
496
- end +
497
- (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : "")
498
- end
499
-
500
- leftcol_width = left.values.map(&:length).max || 0
501
- rightcol_start = leftcol_width + 6 # spaces
476
+ ## Print the help message to +stream+.
477
+ def educate(stream = $stdout)
478
+ width # HACK: calculate it now; otherwise we have to be careful not to
479
+ # call this unless the cursor's at the beginning of a line.
480
+ left = {}
481
+ @specs.each do |name, spec|
482
+ left[name] =
483
+ (spec[:short] && spec[:short] != :none ? "-#{spec[:short]}" : '') +
484
+ (spec[:short] && spec[:short] != :none ? ', ' : '') + "--#{spec[:long]}" +
485
+ case spec[:type]
486
+ when :flag then ''
487
+ when :int then '=<i>'
488
+ when :ints then '=<i+>'
489
+ when :string then '=<s>'
490
+ when :strings then '=<s+>'
491
+ when :float then '=<f>'
492
+ when :floats then '=<f+>'
493
+ when :io then '=<filename/uri>'
494
+ when :ios then '=<filename/uri+>'
495
+ when :date then '=<date>'
496
+ when :dates then '=<date+>'
497
+ end +
498
+ (spec[:type] == :flag && spec[:default] ? ", --no-#{spec[:long]}" : '')
499
+ end
502
500
 
503
- unless @order.size > 0 && @order.first.first == :text
504
- command_name = File.basename($0).gsub(/\.[^.]+$/, '')
505
- stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
506
- stream.puts "#{@synopsis}\n" if @synopsis
507
- stream.puts if @usage || @synopsis
508
- stream.puts "#{@version}\n" if @version
509
- stream.puts "Options:"
510
- end
501
+ leftcol_width = left.values.map(&:length).max || 0
502
+ rightcol_start = leftcol_width + 6 # spaces
511
503
 
512
- @order.each do |what, opt|
513
- if what == :text
514
- stream.puts wrap(opt)
515
- next
504
+ unless !@order.empty? && @order.first.first == :text
505
+ command_name = File.basename($PROGRAM_NAME).gsub(/\.[^.]+$/, '')
506
+ stream.puts "Usage: #{command_name} #{@usage}\n" if @usage
507
+ stream.puts "#{@synopsis}\n" if @synopsis
508
+ stream.puts if @usage || @synopsis
509
+ stream.puts "#{@version}\n" if @version
510
+ stream.puts 'Options:'
516
511
  end
517
512
 
518
- spec = @specs[opt]
519
- stream.printf " %-#{leftcol_width}s ", left[opt]
520
- desc = spec[:desc] + begin
521
- default_s = case spec[:default]
522
- when $stdout then "<stdout>"
523
- when $stdin then "<stdin>"
524
- when $stderr then "<stderr>"
525
- when Array
526
- spec[:default].join(", ")
527
- else
528
- spec[:default].to_s
513
+ @order.each do |what, opt|
514
+ if what == :text
515
+ stream.puts wrap(opt)
516
+ next
529
517
  end
530
518
 
531
- if spec[:default]
532
- if spec[:desc] =~ /\.$/
533
- " (Default: #{default_s})"
519
+ spec = @specs[opt]
520
+ stream.printf " %-#{leftcol_width}s ", left[opt]
521
+ desc = spec[:desc] + begin
522
+ default_s = case spec[:default]
523
+ when $stdout then '<stdout>'
524
+ when $stdin then '<stdin>'
525
+ when $stderr then '<stderr>'
526
+ when Array
527
+ spec[:default].join(', ')
528
+ else
529
+ spec[:default].to_s
530
+ end
531
+
532
+ if spec[:default]
533
+ if spec[:desc] =~ /\.$/
534
+ " (Default: #{default_s})"
535
+ else
536
+ " (default: #{default_s})"
537
+ end
534
538
  else
535
- " (default: #{default_s})"
539
+ ''
536
540
  end
537
- else
538
- ""
539
541
  end
542
+ stream.puts wrap(desc, width: width - rightcol_start - 1, prefix: rightcol_start)
540
543
  end
541
- stream.puts wrap(desc, :width => width - rightcol_start - 1, :prefix => rightcol_start)
542
544
  end
543
- end
544
545
 
545
- def width #:nodoc:
546
- @width ||= if $stdout.tty?
547
- begin
548
- require 'io/console'
549
- IO.console.winsize.last
550
- rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
551
- legacy_width
546
+ def width #:nodoc:
547
+ @width ||= if $stdout.tty?
548
+ begin
549
+ require 'io/console'
550
+ IO.console.winsize.last
551
+ rescue LoadError, NoMethodError, Errno::ENOTTY, Errno::EBADF, Errno::EINVAL
552
+ legacy_width
553
+ end
554
+ else
555
+ 80
552
556
  end
553
- else
554
- 80
555
557
  end
556
- end
557
-
558
- def legacy_width
559
- # Support for older Rubies where io/console is not available
560
- `tput cols`.to_i
561
- rescue Errno::ENOENT
562
- 80
563
- end
564
- private :legacy_width
565
558
 
566
- def wrap(str, opts = {}) # :nodoc:
567
- if str == ""
568
- [""]
569
- else
570
- inner = false
571
- str.split("\n").map do |s|
572
- line = wrap_line s, opts.merge(:inner => inner)
573
- inner = true
574
- line
575
- end.flatten
559
+ def legacy_width
560
+ # Support for older Rubies where io/console is not available
561
+ `tput cols`.to_i
562
+ rescue Errno::ENOENT
563
+ 80
576
564
  end
577
- end
565
+ private :legacy_width
578
566
 
579
- ## The per-parser version of Trollop::die (see that for documentation).
580
- def die(arg, msg = nil, error_code = nil)
581
- if msg
582
- $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
583
- else
584
- $stderr.puts "Error: #{arg}."
567
+ def wrap(str, opts = {}) # :nodoc:
568
+ if str == ''
569
+ ['']
570
+ else
571
+ inner = false
572
+ str.split("\n").map do |s|
573
+ line = wrap_line s, opts.merge(inner: inner)
574
+ inner = true
575
+ line
576
+ end.flatten
577
+ end
585
578
  end
586
- if @educate_on_error
587
- $stderr.puts
588
- educate $stderr
589
- else
590
- $stderr.puts "Try --help for help."
579
+
580
+ ## The per-parser version of Trollop::die (see that for documentation).
581
+ def die(arg, msg = nil, error_code = nil)
582
+ if msg
583
+ $stderr.puts "Error: argument --#{@specs[arg][:long]} #{msg}."
584
+ else
585
+ $stderr.puts "Error: #{arg}."
586
+ end
587
+ if @educate_on_error
588
+ $stderr.puts
589
+ educate $stderr
590
+ else
591
+ $stderr.puts 'Try --help for help.'
592
+ end
593
+ exit(error_code || -1)
591
594
  end
592
- exit(error_code || -1)
593
- end
594
595
 
595
- private
596
-
597
- ## yield successive arg, parameter pairs
598
- def each_arg(args)
599
- remains = []
600
- i = 0
601
-
602
- until i >= args.length
603
- return remains += args[i..-1] if @stop_words.member? args[i]
604
- case args[i]
605
- when /^--$/ # arg terminator
606
- return remains += args[(i + 1)..-1]
607
- when /^--(\S+?)=(.*)$/ # long argument with equals
608
- yield "--#{$1}", [$2]
609
- i += 1
610
- when /^--(\S+)$/ # long argument
611
- params = collect_argument_parameters(args, i + 1)
612
- if params.empty?
613
- yield args[i], nil
596
+ private
597
+
598
+ ## yield successive arg, parameter pairs
599
+ def each_arg(args)
600
+ remains = []
601
+ i = 0
602
+
603
+ until i >= args.length
604
+ return remains += args[i..-1] if @stop_words.member? args[i]
605
+ case args[i]
606
+ when /^--$/ # arg terminator
607
+ return remains += args[(i + 1)..-1]
608
+ when /^--(\S+?)=(.*)$/ # long argument with equals
609
+ yield "--#{Regexp.last_match(1)}", [Regexp.last_match(2)]
614
610
  i += 1
615
- else
616
- num_params_taken = yield args[i], params
617
- unless num_params_taken
618
- if @stop_on_unknown
619
- return remains += args[i + 1..-1]
620
- else
621
- remains += params
611
+ when /^--(\S+)$/ # long argument
612
+ params = collect_argument_parameters(args, i + 1)
613
+ if params.empty?
614
+ yield args[i], nil
615
+ i += 1
616
+ else
617
+ num_params_taken = yield args[i], params
618
+ unless num_params_taken
619
+ if @stop_on_unknown
620
+ return remains += args[i + 1..-1]
621
+ else
622
+ remains += params
623
+ end
622
624
  end
625
+ i += 1 + num_params_taken
623
626
  end
624
- i += 1 + num_params_taken
625
- end
626
- when /^-(\S+)$/ # one or more short arguments
627
- shortargs = $1.split(//)
628
- shortargs.each_with_index do |a, j|
629
- if j == (shortargs.length - 1)
630
- params = collect_argument_parameters(args, i + 1)
631
- if params.empty?
632
- yield "-#{a}", nil
633
- i += 1
634
- else
635
- num_params_taken = yield "-#{a}", params
636
- unless num_params_taken
637
- if @stop_on_unknown
638
- return remains += args[i + 1..-1]
639
- else
640
- remains += params
627
+ when /^-(\S+)$/ # one or more short arguments
628
+ shortargs = Regexp.last_match(1).split(//)
629
+ shortargs.each_with_index do |a, j|
630
+ if j == (shortargs.length - 1)
631
+ params = collect_argument_parameters(args, i + 1)
632
+ if params.empty?
633
+ yield "-#{a}", nil
634
+ i += 1
635
+ else
636
+ num_params_taken = yield "-#{a}", params
637
+ unless num_params_taken
638
+ if @stop_on_unknown
639
+ return remains += args[i + 1..-1]
640
+ else
641
+ remains += params
642
+ end
641
643
  end
644
+ i += 1 + num_params_taken
642
645
  end
643
- i += 1 + num_params_taken
646
+ else
647
+ yield "-#{a}", nil
644
648
  end
649
+ end
650
+ else
651
+ if @stop_on_unknown
652
+ return remains += args[i..-1]
645
653
  else
646
- yield "-#{a}", nil
654
+ remains << args[i]
655
+ i += 1
647
656
  end
648
657
  end
658
+ end
659
+
660
+ remains
661
+ end
662
+
663
+ def parse_integer_parameter(param, arg)
664
+ raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
665
+ param.to_i
666
+ end
667
+
668
+ def parse_float_parameter(param, arg)
669
+ raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
670
+ param.to_f
671
+ end
672
+
673
+ def parse_io_parameter(param, arg)
674
+ if param =~ /^(stdin|-)$/i
675
+ $stdin
649
676
  else
650
- if @stop_on_unknown
651
- return remains += args[i..-1]
652
- else
653
- remains << args[i]
654
- i += 1
677
+ require 'open-uri'
678
+ begin
679
+ open param
680
+ rescue SystemCallError => e
681
+ raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
655
682
  end
656
683
  end
657
684
  end
658
685
 
659
- remains
660
- end
686
+ def collect_argument_parameters(args, start_at)
687
+ params = []
688
+ pos = start_at
689
+ while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos])
690
+ params << args[pos]
691
+ pos += 1
692
+ end
693
+ params
694
+ end
661
695
 
662
- def parse_integer_parameter(param, arg)
663
- raise CommandlineError, "option '#{arg}' needs an integer" unless param =~ /^-?[\d_]+$/
664
- param.to_i
665
- end
696
+ def resolve_default_short_options!
697
+ @order.each do |type, name|
698
+ opts = @specs[name]
699
+ next if type != :opt || opts[:short]
666
700
 
667
- def parse_float_parameter(param, arg)
668
- raise CommandlineError, "option '#{arg}' needs a floating-point number" unless param =~ FLOAT_RE
669
- param.to_f
670
- end
701
+ c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
702
+ if c # found a character to use
703
+ opts[:short] = c
704
+ @short[c] = name
705
+ end
706
+ end
707
+ end
671
708
 
672
- def parse_io_parameter(param, arg)
673
- if param =~ /^(stdin|-)$/i
674
- $stdin
675
- else
676
- require 'open-uri'
677
- begin
678
- open param
679
- rescue SystemCallError => e
680
- raise CommandlineError, "file or url for option '#{arg}' cannot be opened: #{e.message}"
709
+ def wrap_line(str, opts = {})
710
+ prefix = opts[:prefix] || 0
711
+ width = opts[:width] || (self.width - 1)
712
+ start = 0
713
+ ret = []
714
+ until start > str.length
715
+ nextt =
716
+ if start + width >= str.length
717
+ str.length
718
+ else
719
+ x = str.rindex(/\s/, start + width)
720
+ x = str.index(/\s/, start) if x && x < start
721
+ x || str.length
722
+ end
723
+ ret << (ret.empty? && !opts[:inner] ? '' : ' ' * prefix) + str[start...nextt]
724
+ start = nextt + 1
681
725
  end
726
+ ret
682
727
  end
683
- end
684
728
 
685
- def collect_argument_parameters(args, start_at)
686
- params = []
687
- pos = start_at
688
- while args[pos] && args[pos] !~ PARAM_RE && !@stop_words.member?(args[pos]) do
689
- params << args[pos]
690
- pos += 1
729
+ ## instance_eval but with ability to handle block arguments
730
+ ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
731
+ def cloaker(&b)
732
+ (class << self; self; end).class_eval do
733
+ define_method :cloaker_, &b
734
+ meth = instance_method :cloaker_
735
+ remove_method :cloaker_
736
+ meth
737
+ end
691
738
  end
692
- params
693
739
  end
694
740
 
695
- def resolve_default_short_options!
696
- @order.each do |type, name|
697
- opts = @specs[name]
698
- next if type != :opt || opts[:short]
741
+ ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
742
+ ## passes the block to it, then parses +args+ with it, handling any errors or
743
+ ## requests for help or version information appropriately (and then exiting).
744
+ ## Modifies +args+ in place. Returns a hash of option values.
745
+ ##
746
+ ## The block passed in should contain zero or more calls to +opt+
747
+ ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
748
+ ## probably a call to +version+ (Parser#version).
749
+ ##
750
+ ## The returned block contains a value for every option specified with
751
+ ## +opt+. The value will be the value given on the commandline, or the
752
+ ## default value if the option was not specified on the commandline. For
753
+ ## every option specified on the commandline, a key "<option
754
+ ## name>_given" will also be set in the hash.
755
+ ##
756
+ ## Example:
757
+ ##
758
+ ## require 'trollop'
759
+ ## opts = Trollop::options do
760
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
761
+ ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
762
+ ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
763
+ ## end
764
+ ##
765
+ ## ## if called with no arguments
766
+ ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
767
+ ##
768
+ ## ## if called with --monkey
769
+ ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
770
+ ##
771
+ ## See more examples at http://trollop.rubyforge.org.
772
+ def options(args = ARGV, *a, &b)
773
+ @last_parser = Parser.new(*a, &b)
774
+ with_standard_exception_handling(@last_parser) { @last_parser.parse args }
775
+ end
699
776
 
700
- c = opts[:long].split(//).find { |d| d !~ INVALID_SHORT_ARG_REGEX && !@short.member?(d) }
701
- if c # found a character to use
702
- opts[:short] = c
703
- @short[c] = name
704
- end
705
- end
777
+ ## If Trollop::options doesn't do quite what you want, you can create a Parser
778
+ ## object and call Parser#parse on it. That method will throw CommandlineError,
779
+ ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
780
+ ## have these handled for you in the standard manner (e.g. show the help
781
+ ## and then exit upon an HelpNeeded exception), call your code from within
782
+ ## a block passed to this method.
783
+ ##
784
+ ## Note that this method will call System#exit after handling an exception!
785
+ ##
786
+ ## Usage example:
787
+ ##
788
+ ## require 'trollop'
789
+ ## p = Trollop::Parser.new do
790
+ ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
791
+ ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
792
+ ## end
793
+ ##
794
+ ## opts = Trollop::with_standard_exception_handling p do
795
+ ## o = p.parse ARGV
796
+ ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
797
+ ## o
798
+ ## end
799
+ ##
800
+ ## Requires passing in the parser object.
801
+
802
+ def with_standard_exception_handling(parser)
803
+ yield
804
+ rescue CommandlineError => e
805
+ parser.die(e.message, nil, e.error_code)
806
+ rescue HelpNeeded
807
+ parser.educate
808
+ exit
809
+ rescue VersionNeeded
810
+ puts parser.version
811
+ exit
706
812
  end
707
813
 
708
- def wrap_line(str, opts = {})
709
- prefix = opts[:prefix] || 0
710
- width = opts[:width] || (self.width - 1)
711
- start = 0
712
- ret = []
713
- until start > str.length
714
- nextt =
715
- if start + width >= str.length
716
- str.length
717
- else
718
- x = str.rindex(/\s/, start + width)
719
- x = str.index(/\s/, start) if x && x < start
720
- x || str.length
721
- end
722
- ret << ((ret.empty? && !opts[:inner]) ? "" : " " * prefix) + str[start...nextt]
723
- start = nextt + 1
814
+ ## Informs the user that their usage of 'arg' was wrong, as detailed by
815
+ ## 'msg', and dies. Example:
816
+ ##
817
+ ## options do
818
+ ## opt :volume, :default => 0.0
819
+ ## end
820
+ ##
821
+ ## die :volume, "too loud" if opts[:volume] > 10.0
822
+ ## die :volume, "too soft" if opts[:volume] < 0.1
823
+ ##
824
+ ## In the one-argument case, simply print that message, a notice
825
+ ## about -h, and die. Example:
826
+ ##
827
+ ## options do
828
+ ## opt :whatever # ...
829
+ ## end
830
+ ##
831
+ ## Trollop::die "need at least one filename" if ARGV.empty?
832
+ def die(arg, msg = nil, error_code = nil)
833
+ if @last_parser
834
+ @last_parser.die arg, msg, error_code
835
+ else
836
+ raise ArgumentError, 'Trollop::die can only be called after Trollop::options'
724
837
  end
725
- ret
726
838
  end
727
839
 
728
- ## instance_eval but with ability to handle block arguments
729
- ## thanks to _why: http://redhanded.hobix.com/inspect/aBlockCostume.html
730
- def cloaker(&b)
731
- (class << self; self; end).class_eval do
732
- define_method :cloaker_, &b
733
- meth = instance_method :cloaker_
734
- remove_method :cloaker_
735
- meth
840
+ ## Displays the help message and dies. Example:
841
+ ##
842
+ ## options do
843
+ ## opt :volume, :default => 0.0
844
+ ## banner <<-EOS
845
+ ## Usage:
846
+ ## #$0 [options] <name>
847
+ ## where [options] are:
848
+ ## EOS
849
+ ## end
850
+ ##
851
+ ## Trollop::educate if ARGV.empty?
852
+ def educate
853
+ if @last_parser
854
+ @last_parser.educate
855
+ exit
856
+ else
857
+ raise ArgumentError, 'Trollop::educate can only be called after Trollop::options'
736
858
  end
737
859
  end
738
- end
739
-
740
- ## The easy, syntactic-sugary entry method into Trollop. Creates a Parser,
741
- ## passes the block to it, then parses +args+ with it, handling any errors or
742
- ## requests for help or version information appropriately (and then exiting).
743
- ## Modifies +args+ in place. Returns a hash of option values.
744
- ##
745
- ## The block passed in should contain zero or more calls to +opt+
746
- ## (Parser#opt), zero or more calls to +text+ (Parser#text), and
747
- ## probably a call to +version+ (Parser#version).
748
- ##
749
- ## The returned block contains a value for every option specified with
750
- ## +opt+. The value will be the value given on the commandline, or the
751
- ## default value if the option was not specified on the commandline. For
752
- ## every option specified on the commandline, a key "<option
753
- ## name>_given" will also be set in the hash.
754
- ##
755
- ## Example:
756
- ##
757
- ## require 'trollop'
758
- ## opts = Trollop::options do
759
- ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
760
- ## opt :name, "Monkey name", :type => :string # a string --name <s>, defaulting to nil
761
- ## opt :num_limbs, "Number of limbs", :default => 4 # an integer --num-limbs <i>, defaulting to 4
762
- ## end
763
- ##
764
- ## ## if called with no arguments
765
- ## p opts # => {:monkey=>false, :name=>nil, :num_limbs=>4, :help=>false}
766
- ##
767
- ## ## if called with --monkey
768
- ## p opts # => {:monkey=>true, :name=>nil, :num_limbs=>4, :help=>false, :monkey_given=>true}
769
- ##
770
- ## See more examples at http://trollop.rubyforge.org.
771
- def options(args = ARGV, *a, &b)
772
- @last_parser = Parser.new(*a, &b)
773
- with_standard_exception_handling(@last_parser) { @last_parser.parse args }
774
- end
775
-
776
- ## If Trollop::options doesn't do quite what you want, you can create a Parser
777
- ## object and call Parser#parse on it. That method will throw CommandlineError,
778
- ## HelpNeeded and VersionNeeded exceptions when necessary; if you want to
779
- ## have these handled for you in the standard manner (e.g. show the help
780
- ## and then exit upon an HelpNeeded exception), call your code from within
781
- ## a block passed to this method.
782
- ##
783
- ## Note that this method will call System#exit after handling an exception!
784
- ##
785
- ## Usage example:
786
- ##
787
- ## require 'trollop'
788
- ## p = Trollop::Parser.new do
789
- ## opt :monkey, "Use monkey mode" # a flag --monkey, defaulting to false
790
- ## opt :goat, "Use goat mode", :default => true # a flag --goat, defaulting to true
791
- ## end
792
- ##
793
- ## opts = Trollop::with_standard_exception_handling p do
794
- ## o = p.parse ARGV
795
- ## raise Trollop::HelpNeeded if ARGV.empty? # show help screen
796
- ## o
797
- ## end
798
- ##
799
- ## Requires passing in the parser object.
800
-
801
- def with_standard_exception_handling(parser)
802
- yield
803
- rescue CommandlineError => e
804
- parser.die(e.message, nil, e.error_code)
805
- rescue HelpNeeded
806
- parser.educate
807
- exit
808
- rescue VersionNeeded
809
- puts parser.version
810
- exit
811
- end
812
-
813
- ## Informs the user that their usage of 'arg' was wrong, as detailed by
814
- ## 'msg', and dies. Example:
815
- ##
816
- ## options do
817
- ## opt :volume, :default => 0.0
818
- ## end
819
- ##
820
- ## die :volume, "too loud" if opts[:volume] > 10.0
821
- ## die :volume, "too soft" if opts[:volume] < 0.1
822
- ##
823
- ## In the one-argument case, simply print that message, a notice
824
- ## about -h, and die. Example:
825
- ##
826
- ## options do
827
- ## opt :whatever # ...
828
- ## end
829
- ##
830
- ## Trollop::die "need at least one filename" if ARGV.empty?
831
- def die(arg, msg = nil, error_code = nil)
832
- if @last_parser
833
- @last_parser.die arg, msg, error_code
834
- else
835
- raise ArgumentError, "Trollop::die can only be called after Trollop::options"
836
- end
837
- end
838
-
839
- ## Displays the help message and dies. Example:
840
- ##
841
- ## options do
842
- ## opt :volume, :default => 0.0
843
- ## banner <<-EOS
844
- ## Usage:
845
- ## #$0 [options] <name>
846
- ## where [options] are:
847
- ## EOS
848
- ## end
849
- ##
850
- ## Trollop::educate if ARGV.empty?
851
- def educate
852
- if @last_parser
853
- @last_parser.educate
854
- exit
855
- else
856
- raise ArgumentError, "Trollop::educate can only be called after Trollop::options"
857
- end
858
- end
859
860
 
860
- module_function :options, :die, :educate, :with_standard_exception_handling
861
+ module_function :options, :die, :educate, :with_standard_exception_handling
861
862
  end # module