Last active
August 29, 2023 04:03
-
-
Save kejadlen/8b1655d9e9d4f3d2d1aeb4220a11fbb6 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# https://codingdojo.org/kata/Args/ | |
require "minitest" | |
class TestArgs < Minitest::Test | |
def test_args | |
parser = ArgsParser.new(l: :bool, p: :int, d: :str) | |
args = parser.parse(*%w[-l -p 8080 -d /usr/logs]) | |
assert_equal true, args[:l] | |
assert_equal 8080, args[:p] | |
assert_equal "/usr/logs", args[:d] | |
end | |
def test_negative_ints | |
parser = ArgsParser.new(l: :bool, p: :int, d: :str) | |
args = parser.parse(*%w[-p -8080]) | |
assert_equal -8080, args[:p] | |
end | |
def test_invalid_schema | |
err = assert_raises ArgsParser::Errors do | |
ArgsParser.new(ab: :bool, a: :float) | |
end | |
assert_equal [ | |
ArgsParser::InvalidFlag.new(:ab), | |
ArgsParser::InvalidType.new(:a, "float"), | |
], err.errors | |
end | |
def test_invalid_input | |
parser = ArgsParser.new(l: :bool, p: :int, d: :str) | |
err = assert_raises ArgsParser::Errors do | |
parser.parse(*%w[-a -ll -p abcd -l a -p 80 80]) | |
end | |
assert_equal [ | |
ArgsParser::InvalidFlag.new(:a), | |
ArgsParser::InvalidFlag.new(:ll), | |
ArgsParser::InvalidValue.new("abcd"), | |
ArgsParser::TooManyValues.new(:l), | |
ArgsParser::TooManyValues.new(:p), | |
], err.errors | |
end | |
end | |
class ArgsParser | |
# Sort of weird and awkward since I'm returning these instead of raising them | |
# and using `message` to pass values along instead of an actual message... | |
Error = Class.new(StandardError) | |
InvalidFlag = Class.new(Error) | |
class InvalidType < Error | |
def initialize(flag, type) | |
@flag, @type = flag, type | |
end | |
end | |
InvalidValue = Class.new(Error) | |
TooManyValues = Class.new(Error) | |
class Errors < StandardError | |
attr_reader :errors | |
def initialize(errors) | |
@errors = errors | |
end | |
end | |
Parser = Data.define(:default, :parse_value) | |
PARSERS = { | |
bool: Parser.new(false, -> { true }), | |
int: Parser.new(0, -> { Integer(_1) }), | |
str: Parser.new("", -> { _1 }), | |
} | |
def initialize(**schema) | |
invalid_flags = schema.keys.reject { _1 =~ /^[[:alpha:]]$/ } | |
invalid_types = schema.reject { PARSERS.has_key?(_2) } | |
raise Errors.new([ | |
*invalid_flags.map { InvalidFlag.new(_1) }, | |
*invalid_types.map { InvalidType.new(_1, _2) }, | |
]) unless invalid_flags.empty? && invalid_types.empty? | |
@schema = schema.transform_values { PARSERS.fetch(_1) } | |
end | |
def parse(*input) | |
parsed = input | |
.slice_before(/-(#{@schema.keys.join(?|)})/) | |
.map {|flag, *values| | |
begin | |
parse_slice(flag, values) | |
rescue Error => e | |
e | |
end | |
} | |
errors, parsed = parsed.partition { Error === _1 } | |
raise Errors.new(errors) unless errors.empty? | |
defaults = @schema.to_h { [_1, _2.default] } | |
defaults.merge(parsed.to_h) | |
end | |
private | |
def parse_slice(flag, values) | |
flag = flag[1..].to_sym | |
return InvalidFlag.new(flag) unless @schema.has_key?(flag) | |
parser = @schema.fetch(flag) | |
return TooManyValues.new(flag) if values.size > parser.parse_value.arity | |
begin | |
value = parser.parse_value.(*values) | |
rescue | |
return InvalidValue.new(values[0]) | |
end | |
[flag, value] | |
end | |
end | |
if __FILE__ == $0 | |
require "minitest/autorun" | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment