Skip to content

Instantly share code, notes, and snippets.

@kejadlen
Last active August 29, 2023 04:03
Show Gist options
  • Save kejadlen/8b1655d9e9d4f3d2d1aeb4220a11fbb6 to your computer and use it in GitHub Desktop.
Save kejadlen/8b1655d9e9d4f3d2d1aeb4220a11fbb6 to your computer and use it in GitHub Desktop.
# 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