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