Add type capturing arguments

This commit is contained in:
Michael Miller 2022-03-04 17:16:45 -07:00
parent d7f8c2b958
commit de7cd90d11
No known key found for this signature in database
GPG key ID: AC78B32D30CE34A2
2 changed files with 231 additions and 0 deletions

View file

@ -0,0 +1,162 @@
require "../../spec_helper"
Spectator.describe Spectator::Arguments do
subject(arguments) do
Spectator::Arguments.new(
args: {42, "foo"},
kwargs: {bar: "baz", qux: 123}
)
end
it "stores the arguments" do
expect(arguments.args).to eq({42, "foo"})
end
it "stores the keyword arguments" do
expect(arguments.kwargs).to eq({bar: "baz", qux: 123})
end
describe ".capture" do
subject { Spectator::Arguments.capture(42, "foo", bar: "baz", qux: 123) }
it "stores the arguments and keyword arguments" do
is_expected.to have_attributes(args: {42, "foo"}, kwargs: {bar: "baz", qux: 123})
end
end
describe "#to_s" do
subject { arguments.to_s }
it "formats the arguments" do
is_expected.to eq("(42, \"foo\", bar: \"baz\", qux: 123)")
end
end
describe "#==" do
subject { arguments == other }
context "with equal arguments" do
let(other) { arguments }
it "returns true" do
is_expected.to be_true
end
end
context "with different arguments" do
let(other) do
Spectator::Arguments.new(
args: {123, :foo, "bar"},
kwargs: {opt: "foobar"}
)
end
it "returns false" do
is_expected.to be_false
end
end
context "with the same kwargs in a different order" do
let(other) do
Spectator::Arguments.new(
args: arguments.args,
kwargs: {qux: 123, bar: "baz"}
)
end
it "returns true" do
is_expected.to be_true
end
end
context "with a missing kwarg" do
let(other) do
Spectator::Arguments.new(
args: arguments.args,
kwargs: {bar: "baz"}
)
end
it "returns false" do
is_expected.to be_false
end
end
end
describe "#===" do
subject { pattern === arguments }
context "with equal arguments" do
let(pattern) { arguments }
it "returns true" do
is_expected.to be_true
end
end
context "with different arguments" do
let(pattern) do
Spectator::Arguments.new(
args: {123, :foo, "bar"},
kwargs: {opt: "foobar"}
)
end
it "returns false" do
is_expected.to be_false
end
end
context "with the same kwargs in a different order" do
let(pattern) do
Spectator::Arguments.new(
args: arguments.args,
kwargs: {qux: 123, bar: "baz"}
)
end
it "returns true" do
is_expected.to be_true
end
end
context "with a missing kwarg" do
let(pattern) do
Spectator::Arguments.new(
args: arguments.args,
kwargs: {bar: "baz"}
)
end
it "returns false" do
is_expected.to be_false
end
end
context "with matching types and regex" do
let(pattern) do
Spectator::Arguments.new(
args: {Int32, /foo/},
kwargs: {bar: String, qux: 123}
)
end
it "returns true" do
is_expected.to be_true
end
end
context "with different types and regex" do
let(pattern) do
Spectator::Arguments.new(
args: {Symbol, /bar/},
kwargs: {bar: String, qux: 42}
)
end
it "returns false" do
is_expected.to be_false
end
end
end
end

View file

@ -0,0 +1,69 @@
module Spectator
# Arguments used in a method call.
#
# Can also be used to match arguments.
# *T* must be a `Tuple` type representing the positional arguments.
# *NT* must be a `NamedTuple` type representing the keyword arguments.
class Arguments(T, NT)
# Positional arguments.
getter args : T
# Keyword arguments.
getter kwargs : NT
# Creates arguments used in a method call.
def initialize(@args : T, @kwargs : NT)
end
# Constructs an instance from literal arguments.
def self.capture(*args, **kwargs) : self
new(args, kwargs)
end
# Constructs a string representation of the arguments.
def to_s(io : IO) : Nil
io << '('
# Add the positional arguments.
args.each_with_index do |arg, i|
io << ", " if i > 0
arg.inspect(io)
end
# Add the keyword arguments.
size = args.size + kwargs.size
kwargs.each_with_index(args.size) do |k, v, i|
io << ", " if 0 < i < size
io << k << ": "
v.inspect(io)
end
io << ')'
end
# Checks if this set of arguments and another are equal.
def ==(other : Arguments)
args == other.args && kwargs == other.kwargs
end
# Checks if another set of arguments matches this set of arguments.
def ===(other : Arguments)
args === other.args && named_tuples_match?(kwargs, other.kwargs)
end
# Checks if two named tuples match.
#
# Uses case equality (`===`) on every key-value pair.
# NamedTuple doesn't have a `===` operator, even though Tuple does.
private def named_tuples_match?(a : NamedTuple, b : NamedTuple)
return false if a.size != b.size
a.each do |k, v|
return false unless b.has_key?(k)
return false unless v === b[k]
end
true
end
end
end