Made changes:
- Updated .gitignore to include specs - Re-licensed to MPL-2.0 as I want any modifications to be pushed back to the main codebase so that the ecosystem can benefit from developer adoption - Updated LICENSE & shard.yml with new license - Changed author to personal email as this is not being done for my company
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
require "../spec_helper"
|
||||
|
||||
def make_arg(val, index = 0) : CliGen::Arg
|
||||
CliGen::Arg.new(value: val, index: index)
|
||||
end
|
||||
|
||||
describe CliGen::Arg do
|
||||
describe "#processed" do
|
||||
it "will raise a ArgReprocessedError exception when re-processed" do
|
||||
arg = make_arg("abc")
|
||||
arg.processed
|
||||
expect_raises(CliGen::ArgReprocessedError) do
|
||||
arg.processed
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#flag?" do
|
||||
it "will return true when value is in long flag format --[a-zA-Z0-9-_]+" do
|
||||
arg = make_arg("--help")
|
||||
arg.flag?.should be_true
|
||||
end
|
||||
|
||||
it "will return true when value is in short flag format -[a-zA-Z0-9]" do
|
||||
arg = make_arg("-h")
|
||||
arg.flag?.should be_true
|
||||
end
|
||||
|
||||
it "will return false when value is literally anything else" do
|
||||
arg = make_arg("abc")
|
||||
arg.flag?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#uint?" do
|
||||
it "will return true on a value in an unigned int format ^[[:digit:]]+$" do
|
||||
make_arg("3").uint?.should be_true
|
||||
end
|
||||
|
||||
it "will return false on a value in an signed int format ^[+-][[:digit:]]+$" do
|
||||
make_arg("-3").uint?.should be_false
|
||||
make_arg("+3").uint?.should be_false
|
||||
end
|
||||
|
||||
it "will return false on a value in a mixed format" do
|
||||
make_arg("a2").uint?.should be_false
|
||||
end
|
||||
|
||||
it "will return false on a value in any other format" do
|
||||
make_arg("a").uint?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#int?" do
|
||||
it "will return true on a value in int format ^[[:digit:]]+$" do
|
||||
make_arg("3").int?.should be_true
|
||||
end
|
||||
|
||||
it "will return true on a value in an signed int format ^[+-][[:digit:]]+$" do
|
||||
make_arg("-3").int?.should be_true
|
||||
make_arg("+3").int?.should be_true
|
||||
end
|
||||
|
||||
it "will return false on a value in a mixed format" do
|
||||
make_arg("a2").int?.should be_false
|
||||
end
|
||||
|
||||
it "will return false on a value in any other format" do
|
||||
make_arg("a").int?.should be_false
|
||||
end
|
||||
end
|
||||
|
||||
describe "#float?" do
|
||||
it "will return true on a value in a float format ^[[:digit:]]+\.[[:digit:]]+$" do
|
||||
make_arg("1.1").float?.should be_true
|
||||
end
|
||||
|
||||
it "will return true on a value in a int format ^[[:digit:]]$" do
|
||||
make_arg("1").float?.should be_true
|
||||
end
|
||||
|
||||
it "will return false on a value in any other format" do
|
||||
make_arg("a").float?.should be_false
|
||||
end
|
||||
|
||||
it "will return false on a value that isn't fully int's" do
|
||||
make_arg("a1").float?.should be_false
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,438 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
require "../spec_helper"
|
||||
|
||||
module CliGen
|
||||
class Flag(T)
|
||||
def test_coerce(var : String)
|
||||
coerce(var)
|
||||
end
|
||||
|
||||
def process(args : Array(String))
|
||||
process(make_args(args))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
enum TestEnum
|
||||
ABC
|
||||
DEF
|
||||
GHI
|
||||
end
|
||||
|
||||
class MyGoodData
|
||||
extend CliGen::Coercable
|
||||
extend CliGen::Parsable
|
||||
|
||||
getter value : TestEnum
|
||||
|
||||
def initialize(@value)
|
||||
end
|
||||
|
||||
def self.to_s
|
||||
nil
|
||||
end
|
||||
|
||||
def to_s
|
||||
@value.to_s
|
||||
end
|
||||
|
||||
def to_s(io : IO)
|
||||
io << @value.to_s
|
||||
end
|
||||
|
||||
def self.coerce(arg : String)
|
||||
case arg
|
||||
when "abc"
|
||||
new(TestEnum::ABC)
|
||||
when "def"
|
||||
new(TestEnum::DEF)
|
||||
when "ghi"
|
||||
new(TestEnum::GHI)
|
||||
else
|
||||
raise "WTF?"
|
||||
end
|
||||
end
|
||||
|
||||
def self.parse_args(args : Array(CliGen::Arg))
|
||||
raise "ERROR" if args.empty?
|
||||
arg = args.first
|
||||
arg.processed
|
||||
coerce(arg.value)
|
||||
end
|
||||
end
|
||||
|
||||
class MyBadData < MyGoodData
|
||||
def self.parse_args(args : Array(CliGen::Arg))
|
||||
raise "ERROR" if args.empty?
|
||||
arg = args.first
|
||||
coerce(arg.value)
|
||||
end
|
||||
end
|
||||
|
||||
def make_arg(value, index : Int32 = 0) : CliGen::Arg
|
||||
CliGen::Arg.new(value: value, index: index)
|
||||
end
|
||||
|
||||
|
||||
def make_args(values : Array(String)) : Array(CliGen::Arg)
|
||||
output = [] of CliGen::Arg
|
||||
|
||||
values.each_with_index{|v, i| output << make_arg(v,i)}
|
||||
|
||||
output
|
||||
end
|
||||
|
||||
macro make_flag(type, long = "--test", env_var = "TEST", var = "", description = "", short = nil, delimiter = ",", default = nil, options = nil, validate = nil, on_match = nil, format = nil)
|
||||
CliGen::Flag({{type}}).new(
|
||||
var: {{var}},
|
||||
env_var: {{env_var}},
|
||||
short: {{short}},
|
||||
long: {{long}},
|
||||
description: {{description}},
|
||||
delimiter: {{delimiter}},
|
||||
default: {{default}},
|
||||
options: {{options}},
|
||||
validate: {{validate}},
|
||||
on_match: {{on_match}},
|
||||
format: {{format}}
|
||||
)
|
||||
end
|
||||
|
||||
describe CliGen::Flag do
|
||||
before_each { ENV.delete("TEST") }
|
||||
|
||||
describe "#check!" do
|
||||
it "will throw CliGen::ReservedFlagError if -h is used as short" do
|
||||
expect_raises(CliGen::ReservedFlagError) do
|
||||
make_flag(type: Bool, short: "-h", long: "--not-help").check!
|
||||
end
|
||||
end
|
||||
|
||||
it "will throw CliGen::ReservedFlagError if --help is used in long" do
|
||||
expect_raises(CliGen::ReservedFlagError) do
|
||||
make_flag(type: Bool, short: "-b", long: "--help TOPIC").check!
|
||||
end
|
||||
end
|
||||
|
||||
it "will throw CliGen::ReservedFlagError if --help is used as long_key" do
|
||||
expect_raises(CliGen::ReservedFlagError) do
|
||||
make_flag(type: Bool, short: "-b", long: "--help").check!
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#value!" do
|
||||
it "will throw CliGen::MissingRequiredFlagError if a flag without a default value is called with no way of determining a value" do
|
||||
flg = make_flag(type: String)
|
||||
expect_raises(CliGen::MissingRequiredFlagError) do
|
||||
flg.value!
|
||||
end
|
||||
end
|
||||
|
||||
it "will fallback to provided default if no data is provided by the user" do
|
||||
make_flag(type: String, default: "abc").value!.should eq "abc"
|
||||
end
|
||||
|
||||
it "will resolve values from ENV VAR if not directly supplied with one" do
|
||||
flg = make_flag(type: String, env_var: "MYTESTVAR")
|
||||
ENV["MYTESTVAR"] = "abc"
|
||||
flg.value!.should eq "abc"
|
||||
end
|
||||
|
||||
describe "Flag(Array(String))" do
|
||||
it "returns a valid array when resolving from ENV VAR" do
|
||||
flg = make_flag(type: Array(String), env_var: "ABCDEF")
|
||||
ENV["ABCDEF"]="a,b,c"
|
||||
flg.value!.should eq %w[ a b c ]
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
end
|
||||
|
||||
describe "#coerce" do
|
||||
{% for int in Int.subclasses %}
|
||||
{% uint = int.stringify =~ /^UInt/ ? true : false %}
|
||||
describe "Flag({{int}})" do
|
||||
it "can correctly coerce {{int}}" do
|
||||
make_flag(type: {{int}}).test_coerce("1").should be_a {{int}}
|
||||
end
|
||||
|
||||
it "will raise CliGen::InvalidFlagValueError if provided a value that is not an {{int}}" do
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: {{int}}).test_coerce("abc")
|
||||
end
|
||||
end
|
||||
|
||||
{% if uint %}
|
||||
it "will raise CliGen::InvalidFlagValueError if provided a value in a signed int" do
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: {{int}}).test_coerce("-1")
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
{% if uint %}
|
||||
describe "Flag(Array({{int}}))" do
|
||||
it "Will be correctly parsed when providing an unsigned int" do
|
||||
abc = "1,2,3"
|
||||
make_flag(type: Array({{int}})).test_coerce(abc).should eq([ 1, 2, 3 ] of {{int}})
|
||||
end
|
||||
it "will raise CliGen::InvalidFlagValueError if an signed int is provided" do
|
||||
abc = "-1,2,3"
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: Array({{int}})).test_coerce(abc)
|
||||
end
|
||||
end
|
||||
end
|
||||
{% else %}
|
||||
describe "Flag(Array({{int}}))" do
|
||||
it "Will be correctly parsed when providing a signed int" do
|
||||
abc = "-1,2,3"
|
||||
make_flag(type: Array({{int}})).test_coerce(abc).should eq([ -1, 2, 3 ] of {{int}})
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
{% end %}
|
||||
|
||||
{% for float in Float.subclasses %}
|
||||
describe "Flag({{float}})" do
|
||||
it "can correctly coerce {{float}}" do
|
||||
make_flag(type: {{float}}).test_coerce("1.1").should be_a {{float}}
|
||||
end
|
||||
|
||||
it "will raise CliGen::InvalidFlagValueError if provided value isn't a {{float}}" do
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: {{float}}).test_coerce("why")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(Array({{float}}))" do
|
||||
it "will correctly coerce data in a unsigned int format" do
|
||||
abc = "1,2,3"
|
||||
make_flag(type: Array({{float}})).test_coerce(abc).should eq([1,2,3] of {{float}})
|
||||
end
|
||||
|
||||
it "will correctly coerce data in a signed int format" do
|
||||
abc = "-1,2,3"
|
||||
make_flag(type: Array({{float}})).test_coerce(abc).should eq([-1,2,3] of {{float}})
|
||||
end
|
||||
|
||||
it "will correctly coerce data in a mixed int & float format" do
|
||||
abc = "-1,2,3.2"
|
||||
make_flag(type: Array({{float}})).test_coerce(abc).should eq([-1,2,3.2] of {{float}})
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
|
||||
describe "Flag(Bool)" do
|
||||
it "correctly coerces with valid data" do
|
||||
%w[ t true 1 y yes ].each do |opt|
|
||||
make_flag(type: Bool).test_coerce(opt).should be_true
|
||||
end
|
||||
%w[ f false 0 n no ].each do |opt|
|
||||
make_flag(type: Bool).test_coerce(opt).should be_false
|
||||
end
|
||||
end
|
||||
|
||||
it "raises CliGen::InvalidFlagValueError if provided an invalid value" do
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: Bool).test_coerce("why?")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(Array(CustomObj))" do
|
||||
it "can correctly coerce data from the custom object" do
|
||||
flg = make_flag(type: Array(MyGoodData))
|
||||
flg.test_coerce("abc,def").map(&.to_s.downcase).should eq %w[ abc def ]
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(Array(String))" do
|
||||
it "correctly splits based on delimiter" do
|
||||
flg = make_flag(type: Array(String), long: "--mytest", delimiter: "|")
|
||||
flg.test_coerce("a|b|c").should eq %w[ a b c ]
|
||||
end
|
||||
|
||||
it "will raise CliGen::InvalidFlagValueError if an element of the array doesn't match the format defined" do
|
||||
flg = make_flag(type: Array(String), format: /^test[A-Z]$/)
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
flg.test_coerce("testA,testB,test1")
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#validate!" do
|
||||
it "will raise CliGen::InvalidOptionError if the provided value isn't in the static list of options" do
|
||||
flg = make_flag(type: String, long: "--test", env_var: "TESTVAR", options: %w[ a b c ])
|
||||
ENV["TESTVAR"] = "d"
|
||||
expect_raises(CliGen::InvalidOptionError) do
|
||||
flg.validate!
|
||||
end
|
||||
end
|
||||
|
||||
it "will raise CliGen::ValidationError if the dev provided validation fails" do
|
||||
flg = make_flag(type: String, long: "--test", env_var: "TESTVAR", validate: ->(v : String) : Bool { v == "abc" })
|
||||
ENV["TESTVAR"] = "def"
|
||||
expect_raises(CliGen::ValidationError) do
|
||||
flg.validate!
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(Array(String))" do
|
||||
flg = make_flag(type: Array(String), long: "--test", env_var: "TESTVAR2", options: [%w[ a b c ]])
|
||||
it "will raise CliGen::InvalidOptionError if the provided value isn't in the static list of options" do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "#process" do
|
||||
describe "Flag(String)" do
|
||||
it "will raise CliGen::InvalidFlagValueError if provided string doesn't match format" do
|
||||
flg = make_flag(type: String, env_var: "TEST", short: "-t", long: "--test", format: /^abc$/)
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
flg.process(%w[ def ])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
{% for int in Int.subclasses %}
|
||||
{% uint = int.stringify =~ /^UInt/ ? true : false %}
|
||||
describe "Flag({{int}})" do
|
||||
it "coerces a valid {{int}} from provided arguments" do
|
||||
{% if uint %}
|
||||
val = 1
|
||||
{% else %}
|
||||
val = -1
|
||||
{% end %}
|
||||
|
||||
flg = make_flag(type: {{int}})
|
||||
flg.process(make_args([val.to_s]))
|
||||
flg.value!.should eq val
|
||||
end
|
||||
|
||||
{% if uint %}
|
||||
it "will raise CliGen::InvalidFlagValueError if provided a signed int" do
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
make_flag(type: {{int}}).process([make_arg("-1", 0)])
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
end
|
||||
|
||||
describe "Flag(Array({{int}}))" do
|
||||
{% if uint %}
|
||||
it "will raise when a signed int is provided" do
|
||||
args = make_args(%w[ 1 2 -3 ])
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
flg.process(args)
|
||||
end
|
||||
end
|
||||
it "will raise when a signed int is provided in a delimited string" do
|
||||
args = [make_arg("1,2,-3")]
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
flg.process(args)
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
|
||||
it "can consume positive ints" do
|
||||
args = make_args(%w[ 1 2 3 ])
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, 3] of {{int}})
|
||||
end
|
||||
|
||||
it "can consume positive ints in a delimited string" do
|
||||
args = [make_arg("1,2,3", 0)]
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, 3] of {{int}})
|
||||
end
|
||||
|
||||
it "can consume positive ints in a delimited string and will strip whitespace" do
|
||||
args = [make_arg("1 ,2,3", 0)]
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, 3] of {{int}})
|
||||
end
|
||||
|
||||
it "can consume positive ints in multiple arguments" do
|
||||
args = make_args(%w[ 1,2,3 4 ])
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, 3, 4] of {{int}})
|
||||
end
|
||||
|
||||
{% unless uint %}
|
||||
it "can consume signed ints" do
|
||||
args = make_args(%w[ 1 2 -3 ])
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, -3] of {{int}})
|
||||
end
|
||||
|
||||
it "can consume signed ints in a delimited string" do
|
||||
args = [make_arg("1,2,-3")]
|
||||
flg = make_flag(type: Array({{int}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, -3] of {{int}})
|
||||
end
|
||||
{% end %}
|
||||
|
||||
end
|
||||
{% end %}
|
||||
|
||||
{% for float in Float.subclasses %}
|
||||
describe "Flag({{float}})" do
|
||||
it "coerces a valid {{float}} from provided arguments" do
|
||||
val = {{float}}.new("1.1")
|
||||
flg = make_flag(type: {{float}})
|
||||
flg.process(make_args([val.to_s]))
|
||||
flg.value!.should eq val
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(Array({{float}}))" do
|
||||
it "can handle an array of int/float formatted values" do
|
||||
args = make_args(%w[ 1 2 3 1.1 -1])
|
||||
flg = make_flag(type: Array({{float}}))
|
||||
flg.process(args)
|
||||
flg.value!.should eq([1, 2, 3, 1.1, -1] of {{float}})
|
||||
end
|
||||
end
|
||||
{% end %}
|
||||
describe "Flag(Array(String))" do
|
||||
it "will throw CliGen::InvalidFlagValueError if a deserialized var/item is an invalid format" do
|
||||
flg = make_flag(type: Array(String), format: /^(a|b|c)$/)
|
||||
expect_raises(CliGen::InvalidFlagValueError) do
|
||||
flg.process([ "a,b,c,d" ])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "Flag(CustomObj)" do
|
||||
it "will throw a CliGen::ParseableInvariantError if object doesn't mark data as processed" do
|
||||
flg = make_flag(type: MyBadData)
|
||||
expect_raises(CliGen::ParseableInvariantError) do
|
||||
flg.process(["abc"])
|
||||
end
|
||||
end
|
||||
|
||||
it "will correctly set the value if parsed" do
|
||||
flg = make_flag(type: MyGoodData)
|
||||
flg.process(["abc"])
|
||||
flg.value!.value.should eq TestEnum::ABC
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,7 @@
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
|
||||
require "spec"
|
||||
require "../src/cligen"
|
||||
|
||||
Reference in New Issue
Block a user