Class: ConfigParser
- Includes:
- Utils
- Defined in:
- lib/config_parser.rb,
lib/config_parser/utils.rb,
lib/config_parser/option.rb,
lib/config_parser/switch.rb
Overview
ConfigParser is the Configurable equivalent of OptionParser and uses a similar, simplified (see below) syntax to declare options.
opts = {}
parser = ConfigParser.new do |psr|
psr.on "-s", "--long LONG", "a standard option" do |value|
opts[:long] = value
end
psr.on "--[no-]switch", "a switch" do |value|
opts[:switch] = value
end
psr.on "--flag", "a flag" do
# note: no value is parsed; the block
# only executes if the flag is found
opts[:flag] = true
end
end
parser.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
opts # => {:long => 'arg', :switch => true, :flag => true}
ConfigParser formalizes this pattern of setting values in a hash as they occur, and adds the ability to specify default values. The syntax is not quite as friendly as for ordinary options, but meshes well with Configurable classes:
psr = ConfigParser.new
psr.define(:key, 'default', :desc => 'a standard option')
psr.parse('a b --key option c') # => ['a', 'b', 'c']
psr.config # => {:key => 'option'}
psr.parse('a b c') # => ['a', 'b', 'c']
psr.config # => {:key => 'default'}
And now directly from a Configurable class, the equivalent of the original example:
class ConfigClass
include Configurable
config :long, 'default', :short => 's' # a standard option
config :switch, false, &c.switch # a switch
config :flag, false, &c.flag # a flag
end
psr = ConfigParser.new
psr.add(ConfigClass.configurations)
psr.parse("a b --long arg --switch --flag c") # => ['a', 'b', 'c']
psr.config # => {:long => 'arg', :switch => true, :flag => true}
psr.parse("a b --long=arg --no-switch c") # => ['a', 'b', 'c']
psr.config # => {:long => 'arg', :switch => false, :flag => false}
psr.parse("a b -sarg c") # => ['a', 'b', 'c']
psr.config # => {:long => 'arg', :switch => false, :flag => false}
As you might expect, config attributes are used by ConfigParser to correctly build a corresponding option. In configurations like :switch, the block implies the => :switch attribute and so the config is made into a switch-style option by ConfigParser.
Use the to_s method to convert a ConfigParser into command line documentation:
"\nconfigurations:\n#{psr.to_s}"
# => %q{
# configurations:
# -s, --long LONG a standard option
# --[no-]switch a switch
# --flag a flag
# }
Simplifications
ConfigParser simplifies the OptionParser syntax for ‘on’. ConfigParser does not support automatic conversion of values, gets rid of ‘optional’ arguments for options, and only supports a single description string. Hence:
psr = ConfigParser.new
# incorrect, raises error as this will look
# like multiple descriptions are specified
psr.on("--delay N",
Float,
"Delay N seconds before executing") # !> ArgumentError
# correct
psr.on("--delay N", "Delay N seconds before executing") do |value|
value.to_f
end
# this ALWAYS requires the argument and raises
# an error because multiple descriptions are
# specified
psr.on("-i", "--inplace [EXTENSION]",
"Edit ARGV files in place",
" (make backup if EXTENSION supplied)") # !> ArgumentError
# correct
psr.on("-i", "--inplace EXTENSION",
"Edit ARGV files in place\n (make backup if EXTENSION supplied)")
Defined Under Namespace
Modules: Utils Classes: Option, Switch
Constant Summary
Constants included from Utils
Utils::ALT_SHORT_OPTION, Utils::LONG_OPTION, Utils::OPTION_BREAK, Utils::SHORT_OPTION
Instance Attribute Summary collapse
-
#config ⇒ Object
The hash receiving configurations produced by parse.
-
#default_parse_options ⇒ Object
readonly
A hash of default parsing options that adjust the behavior of parse (see parse).
-
#defaults ⇒ Object
readonly
A hash of default configurations merged into config during parse.
-
#registry ⇒ Object
readonly
Returns an array of the options registered with self, in the order in which they were added.
-
#switches ⇒ Object
readonly
A hash of (switch, Option) pairs mapping command line switches like ‘-s’ or ‘–long’ to the Option that handles them.
Class Method Summary collapse
-
.bind(config = {}) {|parser| ... } ⇒ Object
Generates a new parser bound to a specific config.
-
.nest(hash, split_char = ":") ⇒ Object
Splits and nests compound keys of a hash.
Instance Method Summary collapse
-
#[](key) ⇒ Object
Returns the config value for key.
-
#[]=(key, value) ⇒ Object
Sets the config value for key.
-
#add(delegates, nesting = nil) ⇒ Object
Adds a hash of delegates (for example the configurations for a Configurable class) to self.
-
#define(key, default_value = nil, attributes = {}) ⇒ Object
Defines and registers a config-style option with self.
-
#initialize(config = {}, default_parse_options = {}) {|_self| ... } ⇒ ConfigParser
constructor
Initializes a new ConfigParser and passes it to the block, if given.
-
#nested_config ⇒ Object
Returns the nested form of config (see ConfigParser.nest).
-
#on(*args, &block) ⇒ Object
Constructs an Option using args and registers it with self.
-
#on!(*args, &block) ⇒ Object
Same as on, but overrides options with overlapping switches.
-
#options ⇒ Object
Returns an array of the options registered with self.
-
#parse(argv = ARGV, options = {}) ⇒ Object
Parses options from argv in a non-destructive manner and returns an array of arguments remaining after options have been removed.
-
#parse!(argv = ARGV, options = {}) ⇒ Object
Same as parse, but removes parsed args from argv.
-
#register(opt, override = false) ⇒ Object
Registers the option with self by adding opt to options and mapping the opt switches.
- #scan(argv = ARGV, options = {}) ⇒ Object
-
#separator(str) ⇒ Object
Adds a separator string to self, used in to_s.
-
#to_s ⇒ Object
Converts the options and separators in self into a help string suitable for display on the command line.
- #warn_ignored_args(args) ⇒ Object
Methods included from Utils
infer_arg_name, infer_long, longify, prefix_long, setup_flag, setup_list, setup_option, setup_switch, shortify
Constructor Details
#initialize(config = {}, default_parse_options = {}) {|_self| ... } ⇒ ConfigParser
Initializes a new ConfigParser and passes it to the block, if given.
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 |
# File 'lib/config_parser.rb', line 198 def initialize(config={}, ={}) @registry = [] @switches = {} @config = config @defaults = {} @default_parse_options = { :clear_config => true, :add_defaults => true, :ignore_unknown_options => false, :option_break => OPTION_BREAK, :keep_break => false }.merge() yield(self) if block_given? end |
Instance Attribute Details
#config ⇒ Object
The hash receiving configurations produced by parse.
186 187 188 |
# File 'lib/config_parser.rb', line 186 def config @config end |
#default_parse_options ⇒ Object (readonly)
A hash of default parsing options that adjust the behavior of parse (see parse).
195 196 197 |
# File 'lib/config_parser.rb', line 195 def @default_parse_options end |
#defaults ⇒ Object (readonly)
A hash of default configurations merged into config during parse. These defaults are defined as options are added to self (via define, add, etc) and do not need to be manually specified.
191 192 193 |
# File 'lib/config_parser.rb', line 191 def defaults @defaults end |
#registry ⇒ Object (readonly)
Returns an array of the options registered with self, in the order in which they were added. Separators are also stored in the registry.
178 179 180 |
# File 'lib/config_parser.rb', line 178 def registry @registry end |
#switches ⇒ Object (readonly)
A hash of (switch, Option) pairs mapping command line switches like ‘-s’ or ‘–long’ to the Option that handles them.
183 184 185 |
# File 'lib/config_parser.rb', line 183 def switches @switches end |
Class Method Details
.bind(config = {}) {|parser| ... } ⇒ Object
Generates a new parser bound to a specific config. All this really means is that each time the parser calls parse, configurations will be added to the config without clearing the config, or adding default values. This can be useful in signaling situations where a config needs to be updated multiple times.
psr = ConfigParser.bind
psr.define('a', 'default')
psr.define('b', 'default')
psr.parse %w{--a value}
psr.config # => {"a" => "value"}
psr.parse %w{--b value}
psr.config # => {"a" => "value", "b" => "value"}
167 168 169 170 171 |
# File 'lib/config_parser.rb', line 167 def bind(config={}) parser = new(config, :clear_config => false, :add_defaults => false) yield(parser) if block_given? parser end |
.nest(hash, split_char = ":") ⇒ Object
Splits and nests compound keys of a hash.
ConfigParser.nest('key' => 1, 'compound:key' => 2)
# => {
# 'key' => 1,
# 'compound' => {'key' => 2}
# }
Nest does not do any consistency checking, so be aware that results will be ambiguous for overlapping compound keys.
ConfigParser.nest('key' => {}, 'key:overlap' => 'value')
# =? {'key' => {}}
# =? {'key' => {'overlap' => 'value'}}
131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 |
# File 'lib/config_parser.rb', line 131 def nest(hash, split_char=":") result = {} hash.each_pair do |compound_key, value| if compound_key.kind_of?(String) keys = compound_key.split(split_char) unless keys.length == 1 nested_key = keys.pop nested_hash = keys.inject(result) {|target, key| target[key] ||= {}} nested_hash[nested_key] = value next end end result[compound_key] = value end result end |
Instance Method Details
#[](key) ⇒ Object
Returns the config value for key.
215 216 217 |
# File 'lib/config_parser.rb', line 215 def [](key) config[key] end |
#[]=(key, value) ⇒ Object
Sets the config value for key.
220 221 222 |
# File 'lib/config_parser.rb', line 220 def []=(key, value) config[key] = value end |
#add(delegates, nesting = nil) ⇒ Object
Adds a hash of delegates (for example the configurations for a Configurable class) to self. Configs are added like:
define(key, delegate.default, delegate.attributes)
Nesting
When you nest Configurable classes, a special syntax is necessary to specify nested configurations in a flat format compatible with the command line. As such, nested delegates are recursively added with their key as a prefix. For instance:
class NestClass
include Configurable
nest :nest do
config :key, 'value'
end
end
psr = ConfigParser.new
psr.add(NestClass.configurations)
psr.parse('--nest:key value')
psr.config # => {'nest:key' => 'value'}
psr.nested_config # => {'nest' => {'key' => 'value'}}
Side note: The fact that all the keys end up as strings underscores the importance of having indifferent access for delegates. If you set use_indifferent_access(false), be prepared to symbolize nested keys as necessary.
420 421 422 423 424 425 426 427 428 429 430 431 432 433 |
# File 'lib/config_parser.rb', line 420 def add(delegates, nesting=nil) delegates.each_pair do |key, delegate| key = nesting ? "#{nesting}:#{key}" : key case delegate[:type] when :hidden next when :nest add(delegate.nest_class.configurations, key) else define(key, delegate.default, delegate.attributes) end end end |
#define(key, default_value = nil, attributes = {}) ⇒ Object
Defines and registers a config-style option with self. Define does not take a block; the default value will be added to config, and any parsed value will override the default. Normally the key will be turned into the long switch; specify an alternate long, a short, description, etc using attributes.
psr = ConfigParser.new
psr.define(:one, 'default')
psr.define(:two, 'default', :long => '--long', :short => '-s')
psr.parse("--one one --long two")
psr.config # => {:one => 'one', :two => 'two'}
Define support several types of configurations that define a special block to handle the values parsed from the command line. See the ‘setup_<type>’ methods in Utils. Any type with a corresponding setup method is valid:
psr = ConfigParser.new
psr.define(:flag, false, :type => :flag)
psr.define(:switch, false, :type => :switch)
psr.define(:list, [], :type => :list)
psr.parse("--flag --switch --list one --list two --list three")
psr.config # => {:flag => true, :switch => true, :list => ['one', 'two', 'three']}
New, valid types may be added by implementing new setup_<type> methods following this pattern:
module SpecialType
def setup_special(key, default_value, attributes)
# modify attributes if necessary
attributes[:long] = "--#{key}"
attributes[:arg_name] = 'ARG_NAME'
# return a block handling the input
lambda {|input| config[key] = input.reverse }
end
end
psr = ConfigParser.new.extend SpecialType
psr.define(:opt, false, :type => :special)
psr.parse("--opt value")
psr.config # => {:opt => 'eulav'}
The :hidden type causes no configuration to be defined. Raises an error if key is already set by a different option.
363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 |
# File 'lib/config_parser.rb', line 363 def define(key, default_value=nil, attributes={}) # check for conflicts and register if defaults.has_key?(key) raise ArgumentError, "already set by a different option: #{key.inspect}" end defaults[key] = default_value # ensure setup does not modifiy input attributes attributes = attributes.dup block = case attributes[:type] when :switch then setup_switch(key, default_value, attributes) when :flag then setup_flag(key, default_value, attributes) when :list, :list_select then setup_list(key, attributes) when :hidden then return nil else if respond_to?("setup_#{attributes[:type]}") send("setup_#{attributes[:type]}", key, default_value, attributes) else setup_option(key, attributes) end end on(attributes, &block) end |
#nested_config ⇒ Object
Returns the nested form of config (see ConfigParser.nest). Primarily useful when nested configurations have been added with add.
226 227 228 |
# File 'lib/config_parser.rb', line 226 def nested_config ConfigParser.nest(config) end |
#on(*args, &block) ⇒ Object
Constructs an Option using args and registers it with self. Args may contain (in any order) a short switch, a long switch, and a description string. Either the short or long switch may signal that the option should take an argument by providing an argument name.
psr = ConfigParser.new
# this option takes an argument
psr.on('-s', '--long ARG_NAME', 'description') do |value|
# ...
end
# so does this one
psr.on('-o ARG_NAME', 'description') do |value|
# ...
end
# this option does not
psr.on('-f', '--flag') do
# ...
end
A switch-style option can be specified by prefixing the long switch with ‘–[no-]’. Switch options will pass true to the block for the positive form and false for the negative form.
psr.on('--[no-]switch') do |value|
# ...
end
Args may also contain a trailing hash defining all or part of the option:
psr.on('-k', :long => '--key', :desc => 'description')
# ...
end
306 307 308 |
# File 'lib/config_parser.rb', line 306 def on(*args, &block) register new_option(args, &block) end |
#on!(*args, &block) ⇒ Object
Same as on, but overrides options with overlapping switches.
311 312 313 |
# File 'lib/config_parser.rb', line 311 def on!(*args, &block) register new_option(args, &block), true end |
#options ⇒ Object
Returns an array of the options registered with self.
231 232 233 234 235 |
# File 'lib/config_parser.rb', line 231 def @registry.select do |opt| opt.kind_of?(Option) end end |
#parse(argv = ARGV, options = {}) ⇒ Object
Parses options from argv in a non-destructive manner and returns an array of arguments remaining after options have been removed. If a string argv is provided, it will be splits into an array using Shellwords.
Options
- clear_config
-
clears the currently parsed configs (true)
- add_defaults
-
adds the default values to config (true)
- ignore_unknown_options
-
causes unknown options to be ignored (false)
446 447 448 449 |
# File 'lib/config_parser.rb', line 446 def parse(argv=ARGV, ={}) argv = argv.dup unless argv.kind_of?(String) parse!(argv, ) end |
#parse!(argv = ARGV, options = {}) ⇒ Object
Same as parse, but removes parsed args from argv.
452 453 454 455 456 457 458 459 460 461 |
# File 'lib/config_parser.rb', line 452 def parse!(argv=ARGV, ={}) argv = Shellwords.shellwords(argv) if argv.kind_of?(String) args = [] remainder = scan(argv, ) {|arg| args << arg} args.concat(remainder) argv.replace(args) argv end |
#register(opt, override = false) ⇒ Object
Registers the option with self by adding opt to options and mapping the opt switches. Raises an error for conflicting switches.
If override is specified, options with conflicting switches are removed and no error is raised. Note that this may remove multiple options.
247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 |
# File 'lib/config_parser.rb', line 247 def register(opt, override=false) if override existing = opt.switches.collect do |switch| @switches.delete(switch) end @registry -= existing end unless @registry.include?(opt) @registry << opt end opt.switches.each do |switch| case @switches[switch] when opt then next when nil then @switches[switch] = opt else raise ArgumentError, "switch is already mapped to a different option: #{switch}" end end opt end |
#scan(argv = ARGV, options = {}) ⇒ Object
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 |
# File 'lib/config_parser.rb', line 463 def scan(argv=ARGV, ={}) = .merge() config.clear if [:clear_config] option_break = [:option_break] while !argv.empty? arg = argv.shift # determine if the arg is an option unless arg.kind_of?(String) && arg[0] == ?- yield(arg) next end # add the remaining args and break # for the option break if option_break === arg argv.unshift(arg) if [:keep_break] break end # split the arg... # switch= $1 # value = $2 arg =~ LONG_OPTION || arg =~ SHORT_OPTION || arg =~ ALT_SHORT_OPTION # lookup the option unless option = @switches[$1] raise "unknown option: #{$1 || arg}" end option.parse($1, $2, argv) end defaults.each_pair do |key, default| config[key] = default unless config.has_key?(key) end if [:add_defaults] argv end |
#separator(str) ⇒ Object
Adds a separator string to self, used in to_s.
238 239 240 |
# File 'lib/config_parser.rb', line 238 def separator(str) @registry << str end |
#to_s ⇒ Object
Converts the options and separators in self into a help string suitable for display on the command line.
512 513 514 515 516 |
# File 'lib/config_parser.rb', line 512 def to_s @registry.collect do |option| option.to_s.rstrip end.join("\n") + "\n" end |
#warn_ignored_args(args) ⇒ Object
504 505 506 507 508 |
# File 'lib/config_parser.rb', line 504 def warn_ignored_args(args) if args && !args.empty? warn "ignoring args: #{args.inspect}" end end |