mirror of
https://gitea.invidious.io/iv-org/shard-spectator.git
synced 2024-08-15 00:53:35 +00:00
Initial rework of arguments to support named positionals
This commit is contained in:
parent
70d0009db5
commit
e38e3ecc32
3 changed files with 132 additions and 97 deletions
|
@ -1,26 +1,22 @@
|
||||||
require "../../spec_helper"
|
require "../../spec_helper"
|
||||||
|
|
||||||
Spectator.describe Spectator::Arguments do
|
Spectator.describe Spectator::Arguments do
|
||||||
subject(arguments) do
|
subject(arguments) { Spectator::Arguments.new({42, "foo"}, :splat, {:x, :y, :z}, {bar: "baz", qux: 123}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: {42, "foo"},
|
|
||||||
kwargs: {bar: "baz", qux: 123}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "stores the arguments" do
|
it "stores the arguments" do
|
||||||
expect(arguments.args).to eq({42, "foo"})
|
expect(arguments).to have_attributes(
|
||||||
end
|
positional: {42, "foo"},
|
||||||
|
splat_name: :splat,
|
||||||
it "stores the keyword arguments" do
|
extra: {:x, :y, :z},
|
||||||
expect(arguments.kwargs).to eq({bar: "baz", qux: 123})
|
kwargs: {bar: "baz", qux: 123}
|
||||||
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
describe ".capture" do
|
describe ".capture" do
|
||||||
subject { Spectator::Arguments.capture(42, "foo", bar: "baz", qux: 123) }
|
subject { Spectator::Arguments.capture(42, "foo", bar: "baz", qux: 123) }
|
||||||
|
|
||||||
it "stores the arguments and keyword arguments" do
|
it "stores the arguments and keyword arguments" do
|
||||||
is_expected.to have_attributes(args: {42, "foo"}, kwargs: {bar: "baz", qux: 123})
|
is_expected.to have_attributes(positional: {42, "foo"}, kwargs: {bar: "baz", qux: 123})
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -72,12 +68,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different arguments" do
|
context "with different arguments" do
|
||||||
let(other) do
|
let(other) { Spectator::Arguments.new({123, :foo, "bar"}, nil, nil, {opt: "foobar"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: {123, :foo, "bar"},
|
|
||||||
kwargs: {opt: "foobar"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false" do
|
it "returns false" do
|
||||||
is_expected.to be_false
|
is_expected.to be_false
|
||||||
|
@ -85,12 +76,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the same kwargs in a different order" do
|
context "with the same kwargs in a different order" do
|
||||||
let(other) do
|
let(other) { Spectator::Arguments.new(arguments.positional, nil, nil, {qux: 123, bar: "baz"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: arguments.args,
|
|
||||||
kwargs: {qux: 123, bar: "baz"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns true" do
|
it "returns true" do
|
||||||
is_expected.to be_true
|
is_expected.to be_true
|
||||||
|
@ -98,12 +84,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a missing kwarg" do
|
context "with a missing kwarg" do
|
||||||
let(other) do
|
let(other) { Spectator::Arguments.new(arguments.positional, nil, nil, {bar: "baz"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: arguments.args,
|
|
||||||
kwargs: {bar: "baz"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false" do
|
it "returns false" do
|
||||||
is_expected.to be_false
|
is_expected.to be_false
|
||||||
|
@ -123,12 +104,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different arguments" do
|
context "with different arguments" do
|
||||||
let(pattern) do
|
let(pattern) { Spectator::Arguments.new({123, :foo, "bar"}, nil, nil, {opt: "foobar"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: {123, :foo, "bar"},
|
|
||||||
kwargs: {opt: "foobar"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false" do
|
it "returns false" do
|
||||||
is_expected.to be_false
|
is_expected.to be_false
|
||||||
|
@ -136,12 +112,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with the same kwargs in a different order" do
|
context "with the same kwargs in a different order" do
|
||||||
let(pattern) do
|
let(pattern) { Spectator::Arguments.new(arguments.positional, nil, nil, {qux: 123, bar: "baz"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: arguments.args,
|
|
||||||
kwargs: {qux: 123, bar: "baz"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns true" do
|
it "returns true" do
|
||||||
is_expected.to be_true
|
is_expected.to be_true
|
||||||
|
@ -149,12 +120,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a missing kwarg" do
|
context "with a missing kwarg" do
|
||||||
let(pattern) do
|
let(pattern) { Spectator::Arguments.new(arguments.positional, nil, nil, {bar: "baz"}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: arguments.args,
|
|
||||||
kwargs: {bar: "baz"}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false" do
|
it "returns false" do
|
||||||
is_expected.to be_false
|
is_expected.to be_false
|
||||||
|
@ -162,12 +128,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with matching types and regex" do
|
context "with matching types and regex" do
|
||||||
let(pattern) do
|
let(pattern) { Spectator::Arguments.new({Int32, /foo/}, nil, nil, {bar: String, qux: 123}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: {Int32, /foo/},
|
|
||||||
kwargs: {bar: String, qux: 123}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns true" do
|
it "returns true" do
|
||||||
is_expected.to be_true
|
is_expected.to be_true
|
||||||
|
@ -175,12 +136,7 @@ Spectator.describe Spectator::Arguments do
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with different types and regex" do
|
context "with different types and regex" do
|
||||||
let(pattern) do
|
let(pattern) { Spectator::Arguments.new({Symbol, /bar/}, nil, nil, {bar: String, qux: 42}) }
|
||||||
Spectator::Arguments.new(
|
|
||||||
args: {Symbol, /bar/},
|
|
||||||
kwargs: {bar: String, qux: 42}
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "returns false" do
|
it "returns false" do
|
||||||
is_expected.to be_false
|
is_expected.to be_false
|
||||||
|
|
|
@ -4,22 +4,29 @@ module Spectator
|
||||||
# Arguments used in a method call.
|
# Arguments used in a method call.
|
||||||
#
|
#
|
||||||
# Can also be used to match arguments.
|
# Can also be used to match arguments.
|
||||||
# *T* must be a `Tuple` type representing the positional arguments.
|
# *Positional* must be a `Tuple` or `NamedTuple` type representing the standard arguments.
|
||||||
# *NT* must be a `NamedTuple` type representing the keyword arguments.
|
# *Splat* must be a `Tuple` type representing the extra positional arguments.
|
||||||
class Arguments(T, NT) < AbstractArguments
|
# *DoubleSplat* must be a `NamedTuple` type representing extra keyword arguments.
|
||||||
|
class Arguments(Positional, Splat, DoubleSplat) < AbstractArguments
|
||||||
# Positional arguments.
|
# Positional arguments.
|
||||||
getter args : T
|
getter positional : Positional
|
||||||
|
|
||||||
|
# Additional positional arguments.
|
||||||
|
getter extra : Splat
|
||||||
|
|
||||||
# Keyword arguments.
|
# Keyword arguments.
|
||||||
getter kwargs : NT
|
getter kwargs : DoubleSplat
|
||||||
|
|
||||||
|
# Name of the splat argument, if used.
|
||||||
|
getter splat_name : Symbol?
|
||||||
|
|
||||||
# Creates arguments used in a method call.
|
# Creates arguments used in a method call.
|
||||||
def initialize(@args : T, @kwargs : NT)
|
def initialize(@positional : Positional, @splat_name : Symbol?, @extra : Splat, @kwargs : DoubleSplat)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Constructs an instance from literal arguments.
|
# Creates arguments used in a method call.
|
||||||
def self.capture(*args, **kwargs) : AbstractArguments
|
def self.new(positional : Positional, kwargs : DoubleSplat)
|
||||||
new(args, kwargs).as(AbstractArguments)
|
new(positional, nil, nil, kwargs)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Instance of empty arguments.
|
# Instance of empty arguments.
|
||||||
|
@ -30,34 +37,80 @@ module Spectator
|
||||||
nil.as(AbstractArguments?)
|
nil.as(AbstractArguments?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Captures arguments passed to a call.
|
||||||
|
def self.build(positional = Tuple.new, kwargs = NamedTuple.new)
|
||||||
|
new(positional, nil, nil, kwargs)
|
||||||
|
end
|
||||||
|
|
||||||
|
# :ditto:
|
||||||
|
def self.build(positional : NamedTuple, splat_name : Symbol, extra : Tuple, kwargs = NamedTuple.new)
|
||||||
|
new(positional, splat_name, extra, kwargs)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Friendlier constructor for capturing arguments.
|
||||||
|
def self.capture(*args, **kwargs)
|
||||||
|
new(args, nil, nil, kwargs)
|
||||||
|
end
|
||||||
|
|
||||||
# Returns the positional argument at the specified index.
|
# Returns the positional argument at the specified index.
|
||||||
def [](index : Int)
|
def [](index : Int)
|
||||||
@args[index]
|
{% if Positional < NamedTuple %}
|
||||||
|
@positional.values[index]
|
||||||
|
{% else %}
|
||||||
|
@positional[index]
|
||||||
|
{% end %}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Returns the specified named argument.
|
# Returns the specified named argument.
|
||||||
def [](arg : Symbol)
|
def [](arg : Symbol)
|
||||||
|
{% if Positional < NamedTuple %}
|
||||||
|
return @positional[arg] if @positional.has_key?(arg)
|
||||||
|
{% end %}
|
||||||
@kwargs[arg]
|
@kwargs[arg]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Constructs a string representation of the arguments.
|
# Constructs a string representation of the arguments.
|
||||||
def to_s(io : IO) : Nil
|
def to_s(io : IO) : Nil
|
||||||
return io << "(no args)" if args.empty? && kwargs.empty?
|
return io << "(no args)" if positional.empty? && ((extra = @extra).nil? || extra.empty?) && kwargs.empty?
|
||||||
|
|
||||||
io << '('
|
io << '('
|
||||||
|
|
||||||
# Add the positional arguments.
|
# Add the positional arguments.
|
||||||
args.each_with_index do |arg, i|
|
{% if Positional < NamedTuple %}
|
||||||
io << ", " if i > 0
|
# Include argument names.
|
||||||
arg.inspect(io)
|
positional.each_with_index do |name, value, i|
|
||||||
|
io << ", " if i > 0
|
||||||
|
io << name << ": "
|
||||||
|
value.inspect(io)
|
||||||
|
end
|
||||||
|
{% else %}
|
||||||
|
positional.each_with_index do |arg, i|
|
||||||
|
io << ", " if i > 0
|
||||||
|
arg.inspect(io)
|
||||||
|
end
|
||||||
|
{% end %}
|
||||||
|
|
||||||
|
# Add the splat arguments.
|
||||||
|
if (extra = @extra) && !extra.empty?
|
||||||
|
if splat = @splat_name
|
||||||
|
io << splat << ": {"
|
||||||
|
end
|
||||||
|
io << ", " unless positional.empty?
|
||||||
|
extra.each_with_index do |arg, i|
|
||||||
|
io << ", " if i > 0
|
||||||
|
arg.inspect(io)
|
||||||
|
end
|
||||||
|
io << '}' if @splat_name
|
||||||
|
io << ", " unless kwargs.empty?
|
||||||
end
|
end
|
||||||
|
|
||||||
# Add the keyword arguments.
|
# Add the keyword arguments.
|
||||||
size = args.size + kwargs.size
|
offset = positional.size
|
||||||
kwargs.each_with_index(args.size) do |k, v, i|
|
offset += extra.size if (extra = @extra)
|
||||||
io << ", " if 0 < i < size
|
kwargs.each_with_index(offset) do |name, value, i|
|
||||||
io << k << ": "
|
io << ", " if i > 0
|
||||||
v.inspect(io)
|
io << name << ": "
|
||||||
|
value.inspect(io)
|
||||||
end
|
end
|
||||||
|
|
||||||
io << ')'
|
io << ')'
|
||||||
|
@ -65,27 +118,47 @@ module Spectator
|
||||||
|
|
||||||
# Checks if this set of arguments and another are equal.
|
# Checks if this set of arguments and another are equal.
|
||||||
def ==(other : Arguments)
|
def ==(other : Arguments)
|
||||||
args == other.args && kwargs == other.kwargs
|
ordered = simplify_positional
|
||||||
|
other_ordered = other.simplify_positional
|
||||||
|
ordered == other_ordered && kwargs == other.kwargs
|
||||||
end
|
end
|
||||||
|
|
||||||
# Checks if another set of arguments matches this set of arguments.
|
# Checks if another set of arguments matches this set of arguments.
|
||||||
def ===(other : Arguments)
|
def ===(other : Arguments)
|
||||||
args === other.args && named_tuples_match?(kwargs, other.kwargs)
|
{% if Positional < NamedTuple %}
|
||||||
end
|
if (other_positional = other.positional).is_a?(NamedTuple)
|
||||||
|
positional.each do |k, v|
|
||||||
|
return false unless other_positional.has_key?(k)
|
||||||
|
return false unless v === other_positional[k]
|
||||||
|
end
|
||||||
|
else
|
||||||
|
return false if positional.size != other_positional
|
||||||
|
positional.each_with_index do |k, v, i|
|
||||||
|
return false unless v === other_positional.unsafe_fetch(i)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
{% else %}
|
||||||
|
return false unless positional === other.simplify_positional
|
||||||
|
{% end %}
|
||||||
|
|
||||||
# Checks if two named tuples match.
|
if extra = @extra
|
||||||
#
|
return false unless extra === other.extra
|
||||||
# Uses case equality (`===`) on every key-value pair.
|
end
|
||||||
# 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|
|
kwargs.each do |k, v|
|
||||||
return false unless b.has_key?(k)
|
return false unless other.kwargs.has_key?(k)
|
||||||
return false unless v === b[k]
|
return false unless v === other.kwargs[k]
|
||||||
end
|
end
|
||||||
|
|
||||||
true
|
true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
protected def simplify_positional
|
||||||
|
if (extra = @extra)
|
||||||
|
{% if Positional < NamedTuple %}positional.values{% else %}positional{% end %} + extra
|
||||||
|
else
|
||||||
|
{% if Positional < NamedTuple %}positional.values{% else %}positional{% end %}
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -140,9 +140,12 @@ module Spectator
|
||||||
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
|
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
|
||||||
|
|
||||||
# Capture information about the call.
|
# Capture information about the call.
|
||||||
%args = ::Spectator::Arguments.capture(
|
%args = ::Spectator::Arguments.build(
|
||||||
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg.internal_name}}, {% end %}
|
::NamedTuple.new(
|
||||||
{% if method.double_splat %}**{{method.double_splat}}{% end %}
|
{% for arg, i in method.args %}{% if !method.splat_index || i < method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||||
|
),
|
||||||
|
{% if method.splat_index && (splat = method.args[method.splat_index].internal_name) %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||||
|
{{method.double_splat}}
|
||||||
)
|
)
|
||||||
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
|
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
|
||||||
_spectator_record_call(%call)
|
_spectator_record_call(%call)
|
||||||
|
@ -237,9 +240,12 @@ module Spectator
|
||||||
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
|
){% if method.return_type %} : {{method.return_type}}{% end %}{% if !method.free_vars.empty? %} forall {{method.free_vars.splat}}{% end %}
|
||||||
|
|
||||||
# Capture information about the call.
|
# Capture information about the call.
|
||||||
%args = ::Spectator::Arguments.capture(
|
%args = ::Spectator::Arguments.build(
|
||||||
{% for arg, i in method.args %}{% if i == method.splat_index %}*{% end %}{{arg.internal_name}}, {% end %}
|
::NamedTuple.new(
|
||||||
{% if method.double_splat %}**{{method.double_splat}}{% end %}
|
{% for arg, i in method.args %}{% if !method.splat_index || i < method.splat_index %}{{arg.internal_name.stringify}}: {{arg.internal_name}}, {% end %}{% end %}
|
||||||
|
),
|
||||||
|
{% if method.splat_index && (splat = method.args[method.splat_index].internal_name) %}{{splat.symbolize}}, {{splat}},{% end %}
|
||||||
|
{{method.double_splat}}
|
||||||
)
|
)
|
||||||
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
|
%call = ::Spectator::MethodCall.new({{method.name.symbolize}}, %args)
|
||||||
_spectator_record_call(%call)
|
_spectator_record_call(%call)
|
||||||
|
|
Loading…
Reference in a new issue