marameters 0.5.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: af268f91432bad4b5c91c3f1b1fcddefc040b1bc389d7da8e663b31fbd2d9a90
4
- data.tar.gz: 13f2ff72a569930d5f46d83ca4bc021dc9f6106db52f65037387ad3110da2eba
3
+ metadata.gz: 0656ba29b3d12d44f9f34ae3a012542dc3a274712d2cabf8ba153053d1491310
4
+ data.tar.gz: 21e3a15d709561a6c30adb4a212b1193c0d45a2e0f783761719054412a014136
5
5
  SHA512:
6
- metadata.gz: d0307136a0e90462de20ffcf4b6e05120e864c3befbce8090046cfc604a143553f356ba615b3ea24bfc27039d31f0a6056e80dc625945a40bdc81214f922af02
7
- data.tar.gz: 7aa0bcfbcf048e4697bdba6d4135c8d0fdf59d19e2a202007fb8e9ee3fdf267317c3a15b3fb5839bbd2d9877615be898147505c08e571b2e19edf62eb3b45dbb
6
+ metadata.gz: 152aa26bb96768bc363f55a7fa43b5aed99f08038ac1bbb4a943a1efdf8b5138ce214b6a4b8012f179f12d00894a44869df6d25d938521712de35a3ab2c6efbd
7
+ data.tar.gz: 620bf72c7c8bbe25b48dc370cee7d3a0e1b03f5df8090c326bc760be1dfb400168d62dac304e55acded56f75da24ca821c4ce04fa9cfb2740ad749434de69fec
checksums.yaml.gz.sig CHANGED
Binary file
data/README.adoc CHANGED
@@ -2,30 +2,29 @@
2
2
  :toclevels: 5
3
3
  :figure-caption!:
4
4
 
5
+ :amazing_print_link: link:https://github.com/amazing-print/amazing_print[Amazing Print]
6
+ :article_link: link:https://www.alchemists.io/articles/ruby_method_parameters_and_arguments[method parameters and arguments]
7
+
5
8
  = Marameters
6
9
 
7
- Marameters is a portmanteau (i.e. `[m]ethod + p[arameters] = marameters`) which is designed to
8
- provide additional insight and diagnostics to method parameters. For context, the difference between
9
- a method's parameters and arguments is:
10
+ Marameters is a portmanteau (i.e. `[m]ethod + p[arameters] = marameters`) which is designed to provide additional insight and diagnostics for method parameters. For context, the difference between a method's parameters and arguments is:
10
11
 
11
- * *Parameters*: Represents the _expected_ values to be passed to a method when
12
- messaged as defined when the method is implemented. Example: `def demo(one, two: nil)`.
13
- * *Arguments*: Represents the _actual_ values passed to the method when messaged.
14
- Example: `demo 1, two: 2`.
12
+ * *Parameters*: Represents the _expected_ values to be passed to a method when messaged as defined when the method is implemented. Example: `def demo one, two: nil`.
13
+ * *Arguments*: Represents the _actual_ values passed to the method when messaged. Example: `demo 1, two: 2`.
15
14
 
16
- This gem will help you debug methods or -- more importantly -- aid your workflow when
17
- metaprogramming, as used in the link:https://www.alchemists.io/projects/auto_injector[Auto Injector]
18
- gem, when architecting more sophisticated applications.
15
+ This gem will help you debug methods or aid your workflow when
16
+ metaprogramming -- as used in the link:https://www.alchemists.io/projects/auto_injector[Auto Injector] gem -- when architecting more sophisticated applications.
19
17
 
20
18
  toc::[]
21
19
 
22
20
  == Features
23
21
 
24
- * Provides specialized objects for keyword, positional, and splatted parameters.
22
+ * Provides specialized objects for keyword, positional, and block parameters.
25
23
 
26
24
  == Requirements
27
25
 
28
26
  . link:https://www.ruby-lang.org[Ruby].
27
+ . A solid understanding of {article_link}.
29
28
 
30
29
  == Setup
31
30
 
@@ -45,16 +44,42 @@ gem "marameters"
45
44
 
46
45
  == Usage
47
46
 
48
- There are two main objects you'll want to interact with:
47
+ At a high level, you can use `Marameters` as a single object interface for accessing all capabilities provided by this gem. Here's an overview:
48
+
49
+ [source,ruby]
50
+ ----
51
+ # Setup
52
+ def demo(one, two = 2, three: 3) = puts "One: #{one}, Two: #{two}, Three: #{three}"
53
+
54
+ parameters = method(:demo).parameters
55
+ arguments = %w[one two]
56
+
57
+ # Marameters::Categorizer wrapper
58
+
59
+ Marameters.categorize parameters, arguments
60
+ # #<struct Marameters::Splat positionals=["one", "two"], keywords={}, block=nil>
49
61
 
50
- * *Marameters::Probe*: Allows you to analyze a method's parameters.
51
- * *Marameters::Signature*: Allows you to dynamically build a method signature from raw parameters.
62
+ # Marameters::Probe wrapper
52
63
 
53
- Both of these objects are meant to serve as building blocks to more complex architectures.
64
+ Marameters.of self, :demo # []
65
+
66
+ probe = Marameters.probe parameters
67
+ probe.to_a # [[:req, :one], [:opt, :two], [:key, :three]]
68
+ probe.positionals # [:one, :two]
69
+ probe.keywords # [:three]
70
+ probe.block # nil
71
+
72
+ # Marameters::Signature wrapper
73
+
74
+ Marameters.signature({req: :one, opt: [:two, 2], key: [:three, 3]}).to_s
75
+ # one, two = 2, three: 3
76
+ ----
77
+
78
+ Read on to learn more about the details on how each of these methods work and the objects they wrap.
54
79
 
55
80
  === Probe
56
81
 
57
- To understand how to analyze a method's parameters, consider the following demonstration class:
82
+ The probe allows you to analyze a method's parameters. To understand how, consider the following demonstration class:
58
83
 
59
84
  [source,ruby]
60
85
  ----
@@ -98,7 +123,6 @@ probe.positionals? # true
98
123
  probe.splats # [:three, :six]
99
124
  probe.splats? # true
100
125
  probe.to_a # [[:req, :one], [:opt, :two], [:rest, :three], [:keyreq, :four], [:key, :five], [:keyrest, :six], [:block, :seven]]
101
- probe.to_h # {:req=>:one, :opt=>:two, :rest=>:three, :keyreq=>:four, :key=>:five, :keyrest=>:six, :block=>:seven}
102
126
  ----
103
127
 
104
128
  In contrast the above, we can also probe the `#none` method which has no parameters for a completely
@@ -125,14 +149,146 @@ probe.positionals? # false
125
149
  probe.splats # []
126
150
  probe.splats? # false
127
151
  probe.to_a # []
128
- probe.to_h # {}
129
152
  ----
130
153
 
154
+ === Categorizer
155
+
156
+ The categorizer allows you to dynamically build positional, keyword, and block arguments for message passing. This is most valuable when you know the object, method, and arguments while also needing to assemble the arguments are in the right order. Here's a demonstration where {amazing_print_link} (i.e. `ap`) is used to format the output:
157
+
158
+ [source,ruby]
159
+ ----
160
+ function = proc { "test" }
161
+
162
+ module Demo
163
+ def self.test one, two = nil, *three, four:, five: nil, **six, &seven
164
+ puts "The .#{__method__} method received the following arguments:\n"
165
+
166
+ [one, two, three, four, five, six, seven].each.with_index 1 do |argument, index|
167
+ puts "#{index}. #{argument.inspect}"
168
+ end
169
+
170
+ puts
171
+ end
172
+ end
173
+
174
+ module Inspector
175
+ def self.call arguments
176
+ Marameters::Categorizer.new(Demo.method(:test).parameters)
177
+ .call(arguments).then do |splat|
178
+ ap splat
179
+ puts
180
+ Demo.test(*splat.positionals, **splat.keywords, &splat.block)
181
+ end
182
+ end
183
+ end
184
+
185
+ Inspector.call [1, nil, nil, {four: 4}]
186
+
187
+ # #<Struct:Marameters::Splat:0x00021930
188
+ # block = nil,
189
+ # keywords = {
190
+ # :four => 4
191
+ # },
192
+ # positionals = [
193
+ # 1,
194
+ # nil
195
+ # ]
196
+ # >
197
+ #
198
+ # The .test method received the following arguments:
199
+ # 1. 1
200
+ # 2. nil
201
+ # 3. []
202
+ # 4. 4
203
+ # 5. nil
204
+ # 6. {}
205
+ # 7. nil
206
+ ----
207
+
208
+ When we step through the above implementation and output, we see the following unfold:
209
+
210
+ . The `Demo` module allows us define a maximum set of parameters and then print the arguments received for inspection purposes.
211
+ . The `Inspector` module provides a wrapper around the `Categorizer` so we can conveniently pass in different arguments for experimentation purposes.
212
+ . We pass in our arguments to `Inspector.call` where `nil` is used for optional arguments and hashes for keyword arguments.
213
+ . Once inside `Inspector.call`, the `Categorizer` is initialized with the `Demo.test` method parameters.
214
+ . Then the `splat` (i.e. Struct) is printed out so you can see the categorized positional, keyword, and block arguments.
215
+ . Finally, `Demo.test` method is called with the splatted arguments.
216
+
217
+ The above example satisfies the minimum required arguments but if we pass in the maximum arguments -- loosely speaking -- we see more detail:
218
+
219
+ [source,ruby]
220
+ ----
221
+ Inspector.call [1, 2, [98, 99], {four: 4}, {five: 5}, {twenty: 20, thirty: 30}, function]
222
+
223
+ # Output
224
+
225
+ # #<Struct:Marameters::Splat:0x00029cc0
226
+ # block = #<Proc:0x000000010a88cec0 (irb):1>,
227
+ # keywords = {
228
+ # :four => 4,
229
+ # :five => 5,
230
+ # :twenty => 20,
231
+ # :thirty => 30
232
+ # },
233
+ # positionals = [
234
+ # 1,
235
+ # 2,
236
+ # 98,
237
+ # 99
238
+ # ]
239
+ # >
240
+ #
241
+ # The .test method received the following arguments:
242
+ # 1. 1
243
+ # 2. 2
244
+ # 3. [98, 99]
245
+ # 4. 4
246
+ # 5. 5
247
+ # 6. {:twenty=>20, :thirty=>30}
248
+ # 7. #<Proc:0x000000010a88cec0 (irb):1>
249
+ ----
250
+
251
+ Once again, it is important to keep in mind that the argument positions _must_ align with the parameter positions since the parameters are an array of elements too. For illustration purposes -- and using the above example -- we can compare the parameters to the arguments as follows:
252
+
253
+ [source,ruby]
254
+ ----
255
+ parameters = Demo.method(:test).parameters
256
+ arguments = [1, 2, [98, 99], {four: 4}, {five: 5}, {twenty: 20, thirty: 30}, function]
257
+ ----
258
+
259
+ With {amazing_print_link}, we can print out this information:
260
+
261
+ [source,ruby]
262
+ ----
263
+ ap parameters
264
+ ap arguments
265
+ ----
266
+
267
+ ...which can be further illustrated by this comparison table:
268
+
269
+ [options="header"]
270
+ |===
271
+ | Parameter | Argument
272
+ | `%i[reg one]` | `1`
273
+ | `%i[opt two]` | `2`
274
+ | `%i[rest three]` | `[98, 99]`
275
+ | `%i[keyreq four]` | `{four: 4}`
276
+ | `%i[key five]` | `{five: 5}`
277
+ | `%i[keyrest six]` | `{twenty: 20, thirty: 30}`
278
+ | `%i[block seven]` | `#<Proc:0x0000000108edc778>`
279
+ |===
280
+
281
+ This also means that:
282
+
283
+ * All positions much be filled if you want to supply arguments beyond the first couple of positions because everything is positional due to the nature of how link:https://rubyapi.org/o/method#method-i-parameters[Method#parameters] works. Use `nil` to fill an optional argument when you don't need it.
284
+ * The `:rest` (single splat) argument must an array or `nil` if not present because even though it is _optional_, it is still _positional_.
285
+ * The `:keyrest` (double splat) argument -- much like the `:rest` argument -- must be a hash or `nil` if not present.
286
+
287
+ For further details, please refer back to my {article_link} article mentioned in the _Requirements_ section.
288
+
131
289
  === Signature
132
290
 
133
- The signature class is the opposite of the probe in that you want to feed it parameters for turning
134
- into a method signature. This is useful when dynamically building method signatures or using the
135
- same signature when metaprogramming multiple methods.
291
+ The signature class is the inverse of the probe class in that you want to feed it parameters for turning into a method signature. This is useful when dynamically building method signatures or using the same signature when metaprogramming multiple methods.
136
292
 
137
293
  The following demonstrates how you might construct a method signature with all possible parameters:
138
294
 
@@ -7,7 +7,6 @@ module Marameters
7
7
  @defaulter = defaulter
8
8
  end
9
9
 
10
- # :reek:DuplicateMethodCall
11
10
  def call kind, name, default: nil
12
11
  case kind
13
12
  when :req then name
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "refinements/structs"
4
+
5
+ module Marameters
6
+ # Builds the primary argument categories based on method parameters and arguments.
7
+ class Categorizer
8
+ using Refinements::Structs
9
+
10
+ def initialize parameters, model: Splat
11
+ @parameters = parameters
12
+ @model = model
13
+ end
14
+
15
+ def call arguments
16
+ @record = model.new
17
+ map arguments
18
+ record
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :parameters, :model, :record
24
+
25
+ def map arguments
26
+ parameters.each.with_index { |pair, index| filter pair, arguments[index] }
27
+ end
28
+
29
+ def filter pair, value
30
+ case pair
31
+ in [:rest] | [:rest, :*] then splat_positional value
32
+ in [:keyrest] | [:keyrest, :**] then record.keywords = Hash value
33
+ in [:req, *] | [:opt, *] then record.positionals.append value
34
+ in [:rest, *] then record.positionals.append(*value)
35
+ in [:nokey] then nil
36
+ in [:keyreq, *] | [:key, *] then record.keywords.merge! value if value
37
+ in [:keyrest, *] then record.keywords.merge!(**value) if value
38
+ in [:block, *] then record.block = value
39
+ else fail ArgumentError, "Invalid parameter kind: #{pair.first.inspect}."
40
+ end
41
+ rescue TypeError
42
+ raise TypeError, "#{value.inspect} is an invalid #{pair.first.inspect} value."
43
+ end
44
+
45
+ def splat_positional value
46
+ return unless value
47
+
48
+ record.positionals = value.is_a?(Array) ? value : [value]
49
+ end
50
+ end
51
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Marameters
4
- # Calculates the default for a given value when used within a method's parameter.
4
+ # Computes a method parameter's default value.
5
5
  class Defaulter
6
6
  PASSTHROUGH = "*"
7
7
 
@@ -3,16 +3,18 @@
3
3
  require "refinements/arrays"
4
4
 
5
5
  module Marameters
6
- # Provides insight into a method's parameters.
6
+ # Provides information on a method's parameters.
7
7
  class Probe
8
8
  using Refinements::Arrays
9
9
 
10
- # :reek:TooManyStatements
10
+ CATEGORIES = {
11
+ positionals: %i[req opt],
12
+ keywords: %i[keyreq key],
13
+ splats: %i[rest keyrest]
14
+ }.freeze
15
+
11
16
  def self.of klass, name, collection: []
12
17
  method = klass.instance_method name
13
-
14
- return collection unless method
15
-
16
18
  collection << new(method.parameters)
17
19
  super_method = method.super_method
18
20
  of super_method.owner, super_method.name, collection:
@@ -20,32 +22,32 @@ module Marameters
20
22
  collection
21
23
  end
22
24
 
23
- def initialize parameters
25
+ attr_reader :keywords, :positionals, :splats
26
+
27
+ def initialize parameters, categories: CATEGORIES
24
28
  @parameters = parameters
25
- @items = parameters.reduce({}) { |all, (kind, name)| all.merge kind => name }
29
+ categories.each { |category, kinds| define_variable category, kinds }
26
30
  end
27
31
 
28
- def block = items[:block]
32
+ def block = parameters.find { |kind, name| break name if kind == :block }
29
33
 
30
- def block? = items.key? :block
34
+ def block? = (parameters in [*, [:block, *]])
31
35
 
32
- def empty? = items.empty?
36
+ def empty? = parameters.empty?
33
37
 
34
38
  def keyword_slice collection, keys:
35
39
  collection.select { |key| !keys.include?(key) || keywords.include?(key) }
36
40
  end
37
41
 
38
- def keywords = items.values_at(:keyreq, :key).compress!
39
-
40
42
  def keywords? = keywords.any?
41
43
 
42
- def kind?(kind) = items.key? kind
44
+ def kind?(value) = parameters.any? { |kind, _name| kind == value }
43
45
 
44
- def kinds = items.keys
46
+ def kinds = parameters.map { |kind, _name| kind }
45
47
 
46
- def name?(name) = items.value? name
48
+ def name?(value) = parameters.any? { |_kind, name| name == value }
47
49
 
48
- def names = items.values
50
+ def names = parameters.map { |_kind, name| name }
49
51
 
50
52
  def only_bare_splats? = (parameters in [[:rest]] | [[:keyrest]] | [[:rest], [:keyrest]])
51
53
 
@@ -53,20 +55,19 @@ module Marameters
53
55
 
54
56
  def only_single_splats? = (parameters in [[:rest, *]])
55
57
 
56
- def positionals = items.values_at(:req, :opt).compress!
57
-
58
58
  def positionals? = positionals.any?
59
59
 
60
- def splats = items.values_at(:rest, :keyrest).compress!
61
-
62
60
  def splats? = splats.any?
63
61
 
64
62
  def to_a = parameters
65
63
 
66
- def to_h = items
67
-
68
64
  private
69
65
 
70
- attr_reader :parameters, :items
66
+ attr_reader :parameters
67
+
68
+ def define_variable category, kinds
69
+ parameters.filter_map { |kind, name| next name if kinds.include? kind }
70
+ .then { |collection| instance_variable_set "@#{category}", collection }
71
+ end
71
72
  end
72
73
  end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Marameters
4
- # Produces a method signature for given parameters.
4
+ # Builds a method's parameter signature.
5
5
  class Signature
6
6
  def initialize parameters, builder: Builder.new
7
7
  @parameters = parameters
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Marameters
4
+ # Captures arguments, by category, for message splatting.
5
+ Splat = Struct.new :positionals, :keywords, :block, keyword_init: true do
6
+ def initialize *arguments
7
+ super
8
+
9
+ self[:positionals] ||= []
10
+ self[:keywords] ||= {}
11
+ end
12
+ end
13
+ end
data/lib/marameters.rb CHANGED
@@ -6,4 +6,11 @@ Zeitwerk::Loader.for_gem.setup
6
6
 
7
7
  # Main namespace.
8
8
  module Marameters
9
+ def self.categorize(parameters, arguments) = Categorizer.new(parameters).call(arguments)
10
+
11
+ def self.of(...) = Probe.of(...)
12
+
13
+ def self.probe(...) = Probe.new(...)
14
+
15
+ def self.signature(...) = Signature.new(...)
9
16
  end
data/marameters.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "marameters"
5
- spec.version = "0.5.0"
5
+ spec.version = "0.8.0"
6
6
  spec.authors = ["Brooke Kuhlmann"]
7
7
  spec.email = ["brooke@alchemists.io"]
8
8
  spec.homepage = "https://www.alchemists.io/projects/marameters"
@@ -24,7 +24,7 @@ Gem::Specification.new do |spec|
24
24
 
25
25
  spec.required_ruby_version = "~> 3.1"
26
26
  spec.add_dependency "refinements", "~> 9.6"
27
- spec.add_dependency "zeitwerk", "~> 2.5"
27
+ spec.add_dependency "zeitwerk", "~> 2.6"
28
28
 
29
29
  spec.extra_rdoc_files = Dir["README*", "LICENSE*"]
30
30
  spec.files = Dir["*.gemspec", "lib/**/*"]
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: marameters
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brooke Kuhlmann
@@ -28,7 +28,7 @@ cert_chain:
28
28
  CxDe2+VuChj4I1nvIHdu+E6XoEVlanUPKmSg6nddhkKn2gC45Kyzh6FZqnzH/CRp
29
29
  RFE=
30
30
  -----END CERTIFICATE-----
31
- date: 2022-07-17 00:00:00.000000000 Z
31
+ date: 2022-09-03 00:00:00.000000000 Z
32
32
  dependencies:
33
33
  - !ruby/object:Gem::Dependency
34
34
  name: refinements
@@ -50,14 +50,14 @@ dependencies:
50
50
  requirements:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
- version: '2.5'
53
+ version: '2.6'
54
54
  type: :runtime
55
55
  prerelease: false
56
56
  version_requirements: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - "~>"
59
59
  - !ruby/object:Gem::Version
60
- version: '2.5'
60
+ version: '2.6'
61
61
  description:
62
62
  email:
63
63
  - brooke@alchemists.io
@@ -71,9 +71,11 @@ files:
71
71
  - README.adoc
72
72
  - lib/marameters.rb
73
73
  - lib/marameters/builder.rb
74
+ - lib/marameters/categorizer.rb
74
75
  - lib/marameters/defaulter.rb
75
76
  - lib/marameters/probe.rb
76
77
  - lib/marameters/signature.rb
78
+ - lib/marameters/splat.rb
77
79
  - marameters.gemspec
78
80
  homepage: https://www.alchemists.io/projects/marameters
79
81
  licenses:
@@ -101,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
101
103
  - !ruby/object:Gem::Version
102
104
  version: '0'
103
105
  requirements: []
104
- rubygems_version: 3.3.18
106
+ rubygems_version: 3.3.21
105
107
  signing_key:
106
108
  specification_version: 4
107
109
  summary: Provides dynamic method parameter construction and deconstruction.
metadata.gz.sig CHANGED
Binary file