diff --git a/spec/spec_helper.cr b/spec/spec_helper.cr index 3b92606..13939a9 100644 --- a/spec/spec_helper.cr +++ b/spec/spec_helper.cr @@ -44,3 +44,16 @@ struct TestConverters @[ArgParser::Field(converter: ArgParser::EnumValueConverter(Color))] getter ncolor : Color end + +struct TestValidators + include ArgParser + + @[ArgParser::Validate::Format(/^[a-zA-Z]+$/)] + getter first_name : String + + @[ArgParser::Validate::Format(/^[a-zA-Z]+$/)] + getter last_name : String? + + @[ArgParser::Validate::InRange(1, 100)] + getter age : Int32 +end diff --git a/spec/unit/from_arg_spec.cr b/spec/unit/from_arg_spec.cr new file mode 100644 index 0000000..9d68e92 --- /dev/null +++ b/spec/unit/from_arg_spec.cr @@ -0,0 +1,7 @@ +require "../spec_helper" + +Spectator.describe "ArgParser from_arg" do + it "should parse a union" do + expect(Union(String, Nil).from_arg("foo")).to eq("foo") + end +end diff --git a/spec/unit/validator_spec.cr b/spec/unit/validator_spec.cr new file mode 100644 index 0000000..1fee737 --- /dev/null +++ b/spec/unit/validator_spec.cr @@ -0,0 +1,28 @@ +require "../spec_helper" + +Spectator.describe "ArgParser validators" do + let(valid_args) { ["positional stuff", "--first_name", "John", "--age", "32"] } + let(invalid_args) { ["positional stuff", "--first_name", "John^", "--last_name", "Doe1", "--age", "101"] } + + context "valid arguments" do + it "should not raise an error" do + expect { TestValidators.new(valid_args) }.not_to raise_error + end + + it "should set the name" do + opts = TestValidators.new(valid_args) + expect(opts.first_name).to eq("John") + end + + it "should set the age" do + opts = TestValidators.new(valid_args) + expect(opts.age).to eq(32) + end + end + + context "invalid arguments" do + it "should raise an error" do + expect { TestValidators.new(invalid_args) }.to raise_error(ArgParser::ValidationError) + end + end +end diff --git a/src/arg_parser.cr b/src/arg_parser.cr index 6691180..144f858 100644 --- a/src/arg_parser.cr +++ b/src/arg_parser.cr @@ -65,34 +65,33 @@ module ArgParser i = 0 while !%args.empty? arg = %args.shift - key = parse_key(arg) - next unless key + next unless key = parse_key(arg) - value = %args.shift rescue "true" - if value.starts_with?("-") + value = %args.shift rescue nil + if value && parse_key(value) %args.unshift(value) value = "true" end + next unless value + case key {% for name, value in properties %} when {{ value[:key].id.stringify }}{% if value[:alias] %}, {{ value[:alias].id.stringify }}{% end %} %found{name} = true begin - {% if value[:type] == String %} - %var{name} = value - {% elsif value[:type] < Array %} + {% if value[:type] < Array %} %var{name} ||= [] of {{value[:type].type_vars[0]}} {% if value[:converter] %} %var{name}.concat {{ value[:converter] }}.from_arg(value) {% else %} - %var{name} << ::Union({{value[:type].type_vars[0]}}).from_arg(value) + %var{name} << {{value[:type].type_vars[0]}}.from_arg(value) {% end %} {% else %} {% if value[:converter] %} %var{name} = {{ value[:converter] }}.from_arg(value) {% else %} - %var{name} = ::Union({{value[:type]}}).from_arg(value) + %var{name} = {{value[:type]}}.from_arg(value) {% end %} {% end %} rescue @@ -106,7 +105,7 @@ module ArgParser {% for name, value in properties %} {% unless value[:nilable] || value[:has_default] %} - if %var{name}.nil? && !%found{name} && !::Union({{value[:type]}}).nilable? + if %var{name}.nil? && !%found{name} && !{{value[:type]}}.nilable? on_missing_attribute({{value[:key].id.stringify}}) end {% end %} @@ -145,8 +144,8 @@ module ArgParser {% end %} %validator{name} = {{ validator.name(generic_args: false) }}.new({{ args.join(", ").id }}) - if %found{name} && !%validator{name}.validate({{name.id.stringify}}, @{{name}}) - on_validation_error({{name.stringify}}, @{{name}}, %validator{name}.errors) + if %found{name} && !%var{name}.nil? && !%validator{name}.validate({{name.id.stringify}}, %var{name}) + on_validation_error({{name.stringify}}, %var{name}, %validator{name}.errors) end {% end %} {% end %} @@ -154,8 +153,10 @@ module ArgParser end # Parse the argument key. - # Standard arg names start with a `--` - # Aliases start with a single `-` + # Standard arg names start with a `--. + # Aliases start with a single `-`. + # + # Arguments without a value become boolean true values. # # Note: You can override this method to change the way keys are parsed. def parse_key(arg : String) : String? diff --git a/src/arg_parser/from_arg.cr b/src/arg_parser/from_arg.cr index 4211ed2..ff14d83 100644 --- a/src/arg_parser/from_arg.cr +++ b/src/arg_parser/from_arg.cr @@ -133,14 +133,19 @@ end def Union.from_arg(arg : String) {% begin %} {% for type in T %} - begin - %val = {{type}}.from_arg(arg) - return %val if %val.is_a?({{type}}) - rescue - end - + {% if type != Nil %} + begin + %val = {{type}}.from_arg(arg) + return %val if %val.is_a?({{type}}) + rescue + end + {% end %} {% end %} - raise ArgParser::Error.new("Argument '#{arg}' cannot be converted to any of the union types: {{T}}") + {% if T.includes?(Nil) %} + nil + {% else %} + raise ArgParser::Error.new("Argument '#{arg}' cannot be converted to any of the union types: {{T}}") + {% end %} {% end %} end