All Files
(
100.0%
covered at
21.31
hits/line
)
16 files in total.
272 relevant lines,
272 lines covered and
0 lines missed.
(
100.0%
)
88 total branches,
88 branches covered and
0 branches missed.
(
100.0%
)
-
# frozen_string_literal: true
-
-
1
require "zeitwerk"
-
-
1
Zeitwerk::Loader.new.then do |loader|
-
1
loader.tag = File.basename __FILE__, ".rb"
-
1
loader.push_dir __dir__
-
1
loader.setup
-
end
-
-
# Main namespace.
-
1
module Marameters
-
1
KINDS = %i[req opt rest nokey keyreq key keyrest block].freeze
-
-
1
def self.loader registry = Zeitwerk::Registry
-
4
@loader ||= registry.loaders.each.find { |loader| loader.tag == File.basename(__FILE__, ".rb") }
-
end
-
-
1
def self.categorize parameters, arguments
-
1
@categorize ||= Categorizer.new
-
1
@categorize.call parameters, arguments
-
end
-
-
1
def self.of(...) = Probe.of(...)
-
-
1
def self.for(...) = Probe.new(...)
-
-
1
def self.signature(...) = Signature.new(...)
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
# Builds the primary argument categories based on method parameters and arguments.
-
1
class Categorizer
-
1
def initialize model: Models::Forward
-
29
@model = model
-
end
-
-
1
def call parameters, arguments
-
29
@record = model.new
-
29
then: 28
else: 1
map parameters, arguments.is_a?(Array) ? arguments : [arguments]
-
end
-
-
1
private
-
-
1
attr_reader :model, :record
-
-
1
def map parameters, arguments
-
29
size = arguments.size
-
130
then: 73
else: 28
parameters.each.with_index { |pair, index| filter pair, arguments[index] if index < size }
-
27
record
-
end
-
-
1
def filter pair, value
-
73
in: 10
case pair
-
10
in: 6
in [:rest] | [:rest, :*] then to_array value
-
6
in: 16
in [:keyrest] | [:keyrest, :**] then record.keywords = Hash value
-
16
in: 10
in [:req, *] | [:opt, *] then record.positionals.append value
-
10
in: 2
in [:rest, *] then record.positionals.append(*value)
-
2
in: 11
in [:nokey] then nil
-
11
in: 6
then: 9
else: 2
in [:keyreq, *] | [:key, *] then record.keywords.merge! value if value
-
6
in: 11
then: 4
else: 2
in [:keyrest, *] then record.keywords.merge!(**value) if value
-
11
else: 1
in [:block, *] then record.block = value
-
1
else fail ArgumentError, "Invalid parameter kind: #{pair.first.inspect}."
-
end
-
rescue TypeError
-
1
raise TypeError, "#{value.inspect} is an invalid #{pair.first.inspect} value."
-
end
-
-
1
def to_array value
-
10
else: 7
then: 3
return unless value
-
-
7
then: 5
else: 2
record.positionals = value.is_a?(Array) ? value : [value]
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Models
-
# Models arguments, by category, for forwarding.
-
1
Forward = Struct.new :positionals, :keywords, :block do
-
1
def initialize(positionals: [], keywords: {}, block: nil) = super
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
require "forwardable"
-
-
1
module Marameters
-
# Provides information on a method's parameters.
-
1
class Probe
-
1
extend Forwardable
-
-
1
CATEGORIES = {positionals: %i[req opt], keywords: %i[keyreq key]}.freeze
-
-
1
def self.of klass, name, collection: []
-
6
method = klass.instance_method name
-
5
collection << new(method.parameters)
-
5
super_method = method.super_method
-
5
of super_method.owner, super_method.name, collection:
-
rescue NameError
-
5
collection
-
end
-
-
1
delegate %i[any? deconstruct empty? hash include? inspect to_a] => :parameters
-
-
1
attr_reader :keywords, :positionals
-
-
1
def initialize parameters, categories: CATEGORIES
-
170
@parameters = parameters
-
510
categories.each { |category, kinds| define_variable category, kinds }
-
170
freeze
-
end
-
-
1
def ==(other) = hash == other.hash
-
-
1
alias eql? ==
-
-
1
def <=>(other) = to_a <=> other.to_a
-
-
1
def keywords? = keywords.any?
-
-
1
def keywords_for(*keys, **attributes)
-
9
attributes.select { |key| !keys.include?(key) || keywords.include?(key) }
-
end
-
-
36
def kind?(value) = parameters.any? { |kind, _name| kind == value }
-
-
8
def kinds = parameters.map { |kind, _name| kind }
-
-
66
def name?(value) = parameters.any? { |_kind, name| name == value }
-
-
15
def names = parameters.map { |_kind, name| name }
-
-
1
def only_bare_splats?
-
10
parameters in [[:rest]] \
-
| [[:rest, :*]] \
-
| [[:keyrest]] \
-
| [[:keyrest, :**]] \
-
| [[:rest], [:keyrest]] \
-
| [[:rest, :*], [:keyrest, :**]]
-
end
-
-
8
def only_double_splats? = (parameters in [[:keyrest]] | [[:keyrest, *]])
-
-
9
def only_single_splats? = (parameters in [[:rest]] | [[:rest, *]])
-
-
1
def positionals? = positionals.any?
-
-
1
def positionals_and_maybe_keywords?
-
42
(positionals? && !keywords?) || (positionals? && keywords?)
-
end
-
-
1
private
-
-
1
attr_reader :parameters
-
-
1
def define_variable category, kinds
-
1304
then: 276
else: 688
parameters.filter_map { |kind, name| next name if kinds.include? kind }
-
340
.then { |collection| instance_variable_set :"@#{category}", collection }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
# Builds a method's parameter signature.
-
1
class Signature
-
1
def initialize parameters, builder: Signatures::Builder.new
-
46
@parameters = parameters
-
46
@builder = builder
-
46
freeze
-
end
-
-
46
then: 2
else: 43
def to_s = parameters == :all ? "..." : build.join(", ")
-
-
1
alias to_str to_s
-
-
1
private
-
-
1
attr_reader :parameters, :builder
-
-
1
def build
-
43
parameters.reduce [] do |signature, (kind, name, default)|
-
59
signature << builder.call(kind, name, default:)
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Signatures
-
# Builds a single parameter for a method's signature.
-
1
class Builder
-
1
def initialize defaulter: Defaulter
-
60
@defaulter = defaulter
-
60
freeze
-
end
-
-
1
def call kind, name = nil, default: nil
-
72
when: 6
case kind
-
6
when: 15
when :req then name
-
15
when: 9
when :opt then "#{name} = #{defaulter.call default}"
-
9
when: 3
when :rest then "*#{name}"
-
3
when: 6
when :nokey then "**nil"
-
6
when: 13
when :keyreq then "#{name}:"
-
13
when: 9
when :key then "#{name}: #{defaulter.call default}"
-
9
when: 9
when :keyrest then "**#{name}"
-
9
else: 2
when :block then "&#{name}"
-
2
else fail ArgumentError, "Wrong kind (#{kind}), name (#{name}), or default (#{default})."
-
end
-
end
-
-
1
private
-
-
1
attr_reader :defaulter
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Signatures
-
# Computes a method parameter's default value.
-
1
Defaulter = lambda do |value, extractor: Sourcers::Function.new|
-
35
case value
-
when: 7
when Proc
-
7
then: 1
else: 6
fail TypeError, "Use procs instead of lambdas for defaults." if value.lambda?
-
6
then: 1
else: 5
fail ArgumentError, "Avoid using parameters for proc defaults." if value.arity.nonzero?
-
-
5
when: 7
extractor.call value
-
7
when: 5
when String then value.dump
-
5
when: 3
when Symbol then value.inspect
-
3
else: 13
when nil then "nil"
-
13
else value
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Signatures
-
# Builds single argument for super method's signature when argument forwarding.
-
1
Forwarder = lambda do |kind, name = nil|
-
39
when: 9
case kind
-
9
when: 7
when :req, :opt then name
-
7
when: 1
when :rest then "*#{name}"
-
1
when: 7
when :nokey then ""
-
7
when: 8
when :keyreq, :key then "#{name}:"
-
8
when: 6
when :keyrest then "**#{name}"
-
6
else: 1
when :block then "&#{name}"
-
1
else fail ArgumentError, "Unable to forward unknown kind: #{kind.inspect}."
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Signatures
-
# Blends ancestor and descendant method parameters together while allowing default overrides.
-
1
class Inheritor
-
1
def initialize key_length: 1, kinds: KINDS
-
22
@key_length = key_length
-
22
@kinds = kinds
-
22
freeze
-
end
-
-
1
def call ancestor, descendant
-
89
merge(ancestor, descendant).values.sort_by! { |(kind, *)| kinds.index kind }
-
end
-
-
1
private
-
-
1
attr_reader :key_length, :kinds
-
-
1
def merge ancestor, descendant
-
21
ancestor.to_a.union(descendant.to_a).each.with_object({}) do |parameter, all|
-
122
key = parameter[..key_length]
-
122
kind = key.first
-
-
122
when: 34
case kind
-
34
when: 1
then: 16
else: 18
when :req, :opt then all[key] = parameter if descendant.positionals_and_maybe_keywords?
-
1
when :nokey then all
-
when: 40
when :keyreq, :key
-
40
different = ancestor.keywords? && ancestor.keywords.sort != descendant.keywords.sort
-
40
then: 32
else: 8
all[:keyrest] = [:keyrest] if different
-
40
else: 47
then: 13
else: 27
all[key] = parameter if descendant.include? parameter
-
47
else all[kind] = parameter
-
end
-
end
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Signatures
-
# Blends ancestor and descendant method arguments for forwarding to the super keyword.
-
1
class Super
-
1
def initialize key_length: 1, kinds: KINDS, forwarder: Signatures::Forwarder
-
22
@key_length = key_length
-
22
@kinds = kinds
-
22
@forwarder = forwarder
-
22
freeze
-
end
-
-
1
def call ancestor, descendant
-
21
then: 1
else: 20
return "" if ancestor.empty?
-
-
20
merge(ancestor, descendant).values
-
27
.sort_by! { |(kind, *)| kinds.index kind }
-
20
.then { |parameters| build parameters }
-
end
-
-
1
private
-
-
1
attr_reader :key_length, :kinds, :forwarder
-
-
1
def merge ancestor, descendant
-
20
ancestor.to_a.union(descendant.to_a).each.with_object({}) do |parameter, all|
-
45
key = parameter[..key_length]
-
45
kind, name = key
-
-
45
case kind
-
when: 14
when :req, :opt
-
14
then: 2
else: 12
if ancestor.positionals? && !descendant.positionals? then all[:rest] = [:rest]
-
12
then: 8
else: 4
elsif ancestor.name? name then all[key] = parameter
-
4
else all
-
when: 2
end
-
3
when :nokey then all.delete_if { |nokey, _| %i[keyreq key keyrest].include? nokey }
-
when: 15
when :keyreq, :key
-
15
included = ancestor.name?(name) && descendant.name?(name)
-
15
different = ancestor.keywords? && ancestor.keywords.sort != descendant.keywords.sort
-
-
15
then: 6
else: 9
all[key] = parameter if included
-
15
else: 14
then: 7
else: 8
all[:keyrest] = [:keyrest] if different
-
14
then: 13
else: 1
else all[kind] = parameter if ancestor.kind? kind
-
end
-
end
-
end
-
-
1
def build parameters
-
47
parameters.filter_map { |kind, name| forwarder.call kind, name }
-
.join ", "
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sourcers
-
# Obtains the literal source of a function's body.
-
1
class Function
-
1
PATTERN = /
-
(?:(?<function>proc|->))? # Statement.
-
\s* # Optional space.
-
\{ # Block open.
-
(?<body>.*?) # Source code body.
-
\} # Block close.
-
/x
-
-
1
def initialize pattern: PATTERN, reader: Readers::Any.new
-
39
@pattern = pattern
-
39
@reader = reader
-
39
freeze
-
end
-
-
8
def call(function) = reader.call(function).then { |line| line.match(pattern)[:body].strip }
-
-
1
private
-
-
1
attr_reader :pattern, :reader
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sourcers
-
1
module Readers
-
# Reads source code of callable from disk or memory.
-
1
class Any
-
1
def initialize parser: RubyVM::InstructionSequence, disk: Disk, memory: Memory
-
43
@parser = parser
-
43
@disk = disk
-
43
@memory = memory
-
43
freeze
-
end
-
-
1
def call callable
-
11
instructions = parser.of callable
-
-
11
else: 9
then: 2
fail StandardError, "Unable to load source for: #{callable.inspect}." unless instructions
-
-
9
then: 8
else: 1
instructions.absolute_path ? disk.call(instructions) : memory.call(instructions)
-
end
-
-
1
private
-
-
1
attr_reader :parser, :disk, :memory
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sourcers
-
1
module Readers
-
# Reads source code from on-disk instruction sequence.
-
1
Disk = lambda do |instructions|
-
10
path = instructions.absolute_path
-
10
line_start, column_start, line_end, column_end = instructions.to_a.dig 4, :code_location
-
10
lines = File.read(path).lines[(line_start - 1)..(line_end - 1)]
-
10
lines[-1] = lines.last.byteslice(...column_end)
-
10
lines[0] = lines.first.byteslice(column_start..)
-
-
10
lines.join
-
end
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sourcers
-
1
module Readers
-
# Reads source code from in-memory instruction sequence.
-
4
Memory = -> instructions { instructions.script_lines.join.chomp }
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sources
-
# Extracts the literal source of a Proc's body.
-
1
class Extractor
-
1
PATTERN = /
-
proc # Proc statement.
-
\s* # Optional space.
-
\{ # Block open.
-
(?<body>.*?) # Source code body.
-
\} # Block close.
-
/x
-
-
1
def initialize pattern: PATTERN, reader: Reader.new
-
4
warn "`#{self.class}` is deprecated, use `Sourcers::Function` instead.",
-
category: :deprecated
-
-
4
@pattern = pattern
-
4
@reader = reader
-
4
@fallback = "nil"
-
4
freeze
-
end
-
-
1
def call function
-
3
reader.call(function).then do |line|
-
2
then: 1
else: 1
line.match?(pattern) ? line.match(pattern)[:body].strip : fallback
-
end
-
end
-
-
1
private
-
-
1
attr_reader :pattern, :reader, :fallback
-
end
-
end
-
end
-
# frozen_string_literal: true
-
-
1
module Marameters
-
1
module Sources
-
# Reads object source code from memory or file (assumes implementation is a one-liner).
-
1
class Reader
-
1
def initialize offset: 1, parser: RubyVM::InstructionSequence, io: File
-
9
warn "`#{self.class}` is deprecated, use `Sourcers::Readers::Any` instead.",
-
category: :deprecated
-
-
9
@offset = offset
-
9
@parser = parser
-
9
@io = io
-
9
freeze
-
end
-
-
1
def call object
-
7
instructions = parser.of object
-
-
7
else: 5
then: 2
fail StandardError, "Unable to load source for: #{object.inspect}." unless instructions
-
-
5
process object, instructions
-
end
-
-
1
private
-
-
1
attr_reader :offset, :parser, :io
-
-
1
def process object, instructions
-
5
lines = instructions.script_lines
-
-
5
then: 1
else: 4
return lines.first if lines
-
4
then: 3
else: 1
return extract(*object.source_location) if io.readable? instructions.absolute_path
-
-
1
fail StandardError, "Unable to load source for: #{object.inspect}."
-
end
-
-
4
def extract(path, line_number) = io.open(path) { |body| pluck body, line_number }
-
-
1
def pluck body, line_number
-
3
body.each_line
-
.with_index
-
67
.find { |_line, index| index + offset == line_number }
-
.first
-
end
-
end
-
end
-
end