如何 DRYly 子类化(或以其他方式共享代码)rubys OptionParser 到例如分享选项?
How to DRYly subclass (or otherwise share code with) rubys OptionParser to e.g. share options?
我想为多个脚本共享某些选项,并且更喜欢使用 'builtin' optparse 而不是其他 cli-or-optionparsing-frameworks。
我快速查看了 MRI optparse.rb,但不明白如何最好地子类化 OptionParser(初始化程序需要一个块)。
最理想的情况是我想得到这样的代码
# exe/a_script
require 'mygem'
options = {whatever: 'default'}
Mygem::OptionParser.new do |opts|
opts.on('--whatever') do |w|
options[:whatever] = w
end
end.parse!
第二个脚本作为消费者:
# exe/other_script
require 'mygem'
options = {and_another: 'default'}
Mygem::OptionParser.new do |opts|
opts.on('--and_another') do |a|
options[:and_another] = w
end
end.parse!
并在常见的自定义 OptionParser 中定义 "default option"(说“-v”表示详细,说“-h”表示帮助)。
# lib/mygem/mygem_optionparser.rb
require 'optparse'
module Mygem
class OptionParser < OptionParser
# magic
# define opts.on("-v") -> set options[:verbose],
# define opts.on_tail("-h", "print help and exit") ...
end
end
两个脚本最终都应该拥有并处理“-h”和“-v”标志,理想情况下会填充 "options" 哈希,但可能会将其暴露给 Mygem::OptionParser#default_option_values.
我从哪里开始?或者是否有一种聪明的方法来不同地处理这个问题,例如
# exe/b_script
OptionParser.new do |opts|
define_custom_opts(opts)
end
我想知道我没有找到关于这种情况的任何教程或示例,我认为这不是一个罕见的用例。是的,我绝对想坚持 'optparse'.
Update 我很困惑,没有查看正确的 optpase-source,因此没有看到它产生自我(这让我有点害怕 :)。到目前为止答案很好。
您可以使用默认选项解析定义一个DefaultOptParser
。
# default_parser.rb
require 'optparse'
require 'ostruct'
class DefaultOptParser
attr_accessor :options
def initialize
@options = OpenStruct.new
@parser = OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options.verbose = v
end
end
end
def parse
@parser.parse!
@options
end
end
p DefaultOptParser.new.parse
当你 运行 以上代码时,
> ruby default_parser.rb -v
#<OpenStruct verbose=true>
接下来定义一个 class 是上述 class 的子项并添加额外的选项解析。
# basic_parser.rb
require_relative "default_parser"
class BasicModeParser < DefaultOptParser
def initialize
super
@parser.on("-b", "--basic-mode", "Basic mode operation") do |v|
options.basic = v
end
end
end
p BasicModeParser.new.parse
当你 运行 以上代码时,
> ruby basic_parser.rb -v -b
#<OpenStruct verbose=true, basic=true>
以上作品基于我目前对OptionParser
的理解。
我没有使用过 OptionParser,因此可能有更好的方法来执行此操作,但无论如何我都会尝试一下。
关于 OptionParser#initialize
最重要的事情(对于我们的目的)是它产生 self
给定的块。要使子类的工作方式相同,我们所要做的就是使其 initialize
方法也产生 self
:
require 'optparse'
require 'ostruct'
module MyGem
class OptionParser < ::OptionParser
attr_reader :options
def initialize(*args)
@options = OpenStruct.new
super *args
default_options!
yield(self, options) if block_given?
end
private
def default_options!
on '--whatever=WHATEVER' do |w|
options.whatever = w
end
end
end
end
这会使用所有传递的参数调用 super
除了 传递的块(如果给定的话)。然后它调用 default_options!
来创建这些默认选项(这可以通过将块传递给 super
来完成,但我发现上面的代码更清晰)。
最后,它像超类一样屈服于给定的块,但它传递了第二个参数,即选项对象。然后用户可以像这样使用它:
require 'my_gem/option_parser'
opts = MyGem::OptionParser.new do |parser, options|
parser.on '--and-another=ANOTHER' do |a|
options.another = a
end
end
opts.parse!
p opts.options
这会给用户如下结果:
$ ruby script.rb --whatever=www --and-another=aaa
#<OpenStruct whatever="www", another="aaa">
作为 yield(self, options)
的替代方案,我们可以使用 yield self
,但用户需要执行例如parser.options.whatever = ...
块内。
另一种选择是向 initialize
添加一个 &block
参数,然后执行 instance_eval(&block)
而不是 yield
。这将评估实例上下文中的块,因此用户可以直接访问 options
属性(以及所有其他实例方法等),例如:
parser = MyGem::OptionParser.new do
on '--and-another=ANOTHER' do |a|
options.another = a
end
end
parser.parse!
然而,这有一个缺点,即用户必须知道该块将在实例上下文中进行评估。我个人更喜欢明确的 yield(self, options)
.
我想为多个脚本共享某些选项,并且更喜欢使用 'builtin' optparse 而不是其他 cli-or-optionparsing-frameworks。
我快速查看了 MRI optparse.rb,但不明白如何最好地子类化 OptionParser(初始化程序需要一个块)。
最理想的情况是我想得到这样的代码
# exe/a_script
require 'mygem'
options = {whatever: 'default'}
Mygem::OptionParser.new do |opts|
opts.on('--whatever') do |w|
options[:whatever] = w
end
end.parse!
第二个脚本作为消费者:
# exe/other_script
require 'mygem'
options = {and_another: 'default'}
Mygem::OptionParser.new do |opts|
opts.on('--and_another') do |a|
options[:and_another] = w
end
end.parse!
并在常见的自定义 OptionParser 中定义 "default option"(说“-v”表示详细,说“-h”表示帮助)。
# lib/mygem/mygem_optionparser.rb
require 'optparse'
module Mygem
class OptionParser < OptionParser
# magic
# define opts.on("-v") -> set options[:verbose],
# define opts.on_tail("-h", "print help and exit") ...
end
end
两个脚本最终都应该拥有并处理“-h”和“-v”标志,理想情况下会填充 "options" 哈希,但可能会将其暴露给 Mygem::OptionParser#default_option_values.
我从哪里开始?或者是否有一种聪明的方法来不同地处理这个问题,例如
# exe/b_script
OptionParser.new do |opts|
define_custom_opts(opts)
end
我想知道我没有找到关于这种情况的任何教程或示例,我认为这不是一个罕见的用例。是的,我绝对想坚持 'optparse'.
Update 我很困惑,没有查看正确的 optpase-source,因此没有看到它产生自我(这让我有点害怕 :)。到目前为止答案很好。
您可以使用默认选项解析定义一个DefaultOptParser
。
# default_parser.rb
require 'optparse'
require 'ostruct'
class DefaultOptParser
attr_accessor :options
def initialize
@options = OpenStruct.new
@parser = OptionParser.new do |opts|
opts.banner = "Usage: example.rb [options]"
opts.on("-v", "--[no-]verbose", "Run verbosely") do |v|
options.verbose = v
end
end
end
def parse
@parser.parse!
@options
end
end
p DefaultOptParser.new.parse
当你 运行 以上代码时,
> ruby default_parser.rb -v
#<OpenStruct verbose=true>
接下来定义一个 class 是上述 class 的子项并添加额外的选项解析。
# basic_parser.rb
require_relative "default_parser"
class BasicModeParser < DefaultOptParser
def initialize
super
@parser.on("-b", "--basic-mode", "Basic mode operation") do |v|
options.basic = v
end
end
end
p BasicModeParser.new.parse
当你 运行 以上代码时,
> ruby basic_parser.rb -v -b
#<OpenStruct verbose=true, basic=true>
以上作品基于我目前对OptionParser
的理解。
我没有使用过 OptionParser,因此可能有更好的方法来执行此操作,但无论如何我都会尝试一下。
关于 OptionParser#initialize
最重要的事情(对于我们的目的)是它产生 self
给定的块。要使子类的工作方式相同,我们所要做的就是使其 initialize
方法也产生 self
:
require 'optparse'
require 'ostruct'
module MyGem
class OptionParser < ::OptionParser
attr_reader :options
def initialize(*args)
@options = OpenStruct.new
super *args
default_options!
yield(self, options) if block_given?
end
private
def default_options!
on '--whatever=WHATEVER' do |w|
options.whatever = w
end
end
end
end
这会使用所有传递的参数调用 super
除了 传递的块(如果给定的话)。然后它调用 default_options!
来创建这些默认选项(这可以通过将块传递给 super
来完成,但我发现上面的代码更清晰)。
最后,它像超类一样屈服于给定的块,但它传递了第二个参数,即选项对象。然后用户可以像这样使用它:
require 'my_gem/option_parser'
opts = MyGem::OptionParser.new do |parser, options|
parser.on '--and-another=ANOTHER' do |a|
options.another = a
end
end
opts.parse!
p opts.options
这会给用户如下结果:
$ ruby script.rb --whatever=www --and-another=aaa
#<OpenStruct whatever="www", another="aaa">
作为 yield(self, options)
的替代方案,我们可以使用 yield self
,但用户需要执行例如parser.options.whatever = ...
块内。
另一种选择是向 initialize
添加一个 &block
参数,然后执行 instance_eval(&block)
而不是 yield
。这将评估实例上下文中的块,因此用户可以直接访问 options
属性(以及所有其他实例方法等),例如:
parser = MyGem::OptionParser.new do
on '--and-another=ANOTHER' do |a|
options.another = a
end
end
parser.parse!
然而,这有一个缺点,即用户必须知道该块将在实例上下文中进行评估。我个人更喜欢明确的 yield(self, options)
.