Ruby Command-Line Argument Processor
17 Sep 2020
Without looking at any source code, I wanted to try to put together a readable, testable command-line argument processor.
class CommandUtil
def self.flag_args
ARGV.select { |f| f =~ /^\-\-\w+$/ }
end
def self.flag_strings
flag_args.map { |f| f.scan(/\w+/)[0] }
end
def self.flags
flag_strings.map(&:to_sym)
end
def self.each_flag
flags.each { |i| yield i }
end
def self.with_flags(*args)
yield if args.any? { |f| flags.include?(f.to_sym) }
end
def self.flag_hash
flags.inject({}) { |hash, f| hash[f] = true; hash }
end
def self.flag_values
key_value_strings = ARGV.map { |arg| arg.scan(/\w+\=\w+/) }.flatten
key_value_array = key_value_strings.map { |str| str.split("=") }
key_value_array.map { |i| [i[0].to_sym, i[1]] }
end
def self.flag_value_hash
Hash[*(flag_values.flatten)]
end
def self.each_flag_value
flag_value_hash.each { |key,value| yield key, value }
end
def self.options
flag_hash.merge(flag_value_hash)
end
end
COLORS = ["red", "green", "blue"]
custom_opts = {}
CommandUtil.with_flags(*COLORS) do
custom_opts[:color_mode] = true
end
full_opts = CommandUtil.options.merge(custom_opts)
puts full_opts
$ ruby test.rb --red --format=html
{:red=>true, :format=>"html", :color_mode=>true}
Let's add some tests.
describe CommandUtil do
before(:each) do
stub_const("ARGV", ["--red", "--blue", "--format=html"])
end
describe ".flags" do
before do
@flags = CommandUtil.flags
end
it "includes all flags" do
expect(@flags).to include(:red)
expect(@flags).to include(:blue)
end
it "does not include option flags" do
expect(@flags).not_to include(:format)
end
end
describe ".flag_strings" do
before do
@flags = CommandUtil.flag_strings
end
it "includes all flag strings" do
expect(@flags).to include("red")
expect(@flags).to include("blue")
end
end
describe ".with_flags" do
before do
ARGV.replace ["--red", "--blue", "--format=html"]
end
it "executes the block if one of the symbol arguments is in the flags" do
CommandUtil.with_flags(:red, :green) do
@color_mode = true
end
expect(@color_mode).to be(true)
end
it "executes the block if one of the string arguments is in the flags" do
CommandUtil.with_flags("red", "green") do
@color_mode = true
end
expect(@color_mode).to be(true)
end
it "does not execute the block if none of the arguments are in the flags" do
CommandUtil.with_flags(:white, :black) do
@color_mode = true
end
expect(@color_mode).to be(nil)
end
end
describe ".flag_hash" do
before do
@flag_hash = CommandUtil.flag_hash
end
it "includes all flag keys" do
expect(@flag_hash[:red]).to be(true)
expect(@flag_hash[:blue]).to be(true)
end
it "does not include option flags" do
expect(@flag_hash[:format]).to be(nil)
end
end
describe ".flag_value_hash" do
before do
@flag_value_hash = CommandUtil.flag_value_hash
end
it "includes all flag keys" do
expect(@flag_value_hash[:format]).to eq("html")
end
it "does not include flags" do
expect(@flag_value_hash[:red]).to be(nil)
end
end
describe ".options" do
before do
@options = CommandUtil.options
end
it "includes all flag keys" do
expect(@options[:red]).to be(true)
expect(@options[:blue]).to be(true)
expect(@options[:format]).to eq("html")
end
end
end