Science Score: 44.0%
This score indicates how likely this project is to be science-related based on various indicators:
-
✓CITATION.cff file
Found CITATION.cff file -
✓codemeta.json file
Found codemeta.json file -
✓.zenodo.json file
Found .zenodo.json file -
○DOI references
-
○Academic publication links
-
○Committers with academic emails
-
○Institutional organization owner
-
○JOSS paper metadata
-
○Scientific vocabulary similarity
Low similarity (10.5%) to scientific vocabulary
Keywords
metaprogramming
methods
parameters
ruby
Last synced: 4 months ago
·
JSON representation
·
Repository
A dynamic method parameter inspector.
Basic Info
- Host: GitHub
- Owner: bkuhlmann
- License: other
- Language: Ruby
- Default Branch: main
- Homepage: https://alchemists.io/projects/marameters
- Size: 376 KB
Statistics
- Stars: 5
- Watchers: 2
- Forks: 0
- Open Issues: 0
- Releases: 0
Topics
metaprogramming
methods
parameters
ruby
Created almost 4 years ago
· Last pushed 4 months ago
Metadata Files
Readme
Funding
License
Citation
README.adoc
:toc: macro
:toclevels: 5
:figure-caption!:
:amazing_print_link: link:https://github.com/amazing-print/amazing_print[Amazing Print]
:article_link: link:https://alchemists.io/articles/ruby_method_parameters_and_arguments[method parameters and arguments]
:infusible_link: link:/projects/infusible[Infusible]
= Marameters
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:
* *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`.
* *Arguments*: Represents the _actual_ values passed to the method when messaged. Example: `demo 1, two: 2`.
This gem will help you debug methods or aid your workflow when
metaprogramming -- as used in the link:https://alchemists.io/projects/infusible[Infusible] gem -- when architecting more sophisticated applications.
toc::[]
== Features
* Provides specialized objects for keyword, positional, and block parameters.
== Requirements
. link:https://www.ruby-lang.org[Ruby].
. A solid understanding of {article_link}.
== Setup
To install _with_ security, run:
[source,bash]
----
# 💡 Skip this line if you already have the public certificate installed.
gem cert --add <(curl --compressed --location https://alchemists.io/gems.pem)
gem install marameters --trust-policy HighSecurity
----
To install _without_ security, run:
[source,bash]
----
gem install marameters
----
You can also add the gem directly to your project:
[source,bash]
----
bundle add marameters
----
Once the gem is installed, you only need to require it:
[source,ruby]
----
require "marameters"
----
== Usage
At a high level, you can use `Marameters` as a single Object API for accessing all capabilities provided by this gem. Here's an overview:
*Setup*
[source,ruby]
----
def demo(one, two = 2, three: 3) = puts "One: #{one}, Two: #{two}, Three: #{three}"
parameters = method(:demo).parameters
arguments = %w[one two]
----
*Categorize*
[source,ruby]
----
Marameters.categorize parameters, arguments
# #
----
*Probe*
[source,ruby]
----
Marameters.of self, :demo # []
probe = Marameters.for parameters
probe.positionals # [:one, :two]
probe.keywords # [:three]
probe.to_a # [[:req, :one], [:opt, :two], [:key, :three]]
----
*Signature*
[source,ruby]
----
Marameters.signature([%i[req one], [:opt, :two, 2], [:key, :three, 3]]).to_s
# "one, two = 2, three: 3"
----
=== Constants
The `KINDS` constant allows you to know the kinds of parameters allowed:
[source,ruby]
----
Marameters::KINDS
# [
# :req,
# :opt,
# :rest,
# :nokey,
# :keyreq,
# :key,
# :keyrest,
# :block
# ]
----
=== Probe
The probe (`Marameters::Probe`) allows you to analyze a method's parameters. To understand how, consider the following:
[source,ruby]
----
class Demo
def initialize logger: Logger.new(STDOUT)
@logger = logger
end
def all one, two = nil, *three, four:, five: nil, **six, &seven
logger.debug [one, two, three, four, five, six, seven]
end
def none = logger.debug "Nothing to see here."
private
attr_reader :logger
end
----
You can then probe the `#all` method's parameters as follows:
[source,ruby]
----
probe = Marameters.for Demo.instance_method(:all).parameters
probe.deconstruct # (same as to_a, see below)
probe.empty? # false
probe.include? %i[req one] # true
probe.keywords # [:four, :five]
probe.keywords? # true
probe.keywords_for :four, four: :demo # {four: :demo}
probe.kind?(:keyrest) # true
probe.kinds
# [:req, :opt, :rest, :keyreq, :key, :keyrest, :block]
probe.name?(:three) # true
probe.names
# [:one, :two, :three, :four, :five, :six, :seven]
probe.only_bare_splats? # false
probe.only_double_splats? # false
probe.only_single_splats? # false
probe.positionals # [:one, :two]
probe.positionals? # true
probe.positionals_and_maybe_keywords? # true
probe.to_a
# [
# [:req, :one],
# [:opt, :two],
# [:rest, :three],
# [:keyreq, :four],
# [:key, :five],
# [:keyrest, :six],
# [:block, :seven]
# ]
----
In contrast to the above, we can probe the `#none` method which has no parameters for a completely
different result:
[source,ruby]
----
probe = Marameters.for Demo.instance_method(:none).parameters
probe.deconstruct # (same as to_a, see below)
probe.empty? # true
probe.include? %i[req one] # false
probe.keywords # []
probe.keywords? # false
probe.keywords_for :four, four: :demo # {}
probe.kind?(:req) # true
probe.kinds # []
probe.name?(:three) # false
probe.names # []
probe.only_bare_splats? # false
probe.only_double_splats? # false
probe.only_single_splats? # false
probe.positionals # []
probe.positionals? # false
probe.positionals_and_maybe_keywords? # false
probe.to_a # []
----
The `#keywords_for` method might need additional explaining because it's meant for selecting keywords which adhere to _either_ of the following criteria:
* The given keys don't match any key in the given attributes.
* The given keys match the parameter keywords.
[source,ruby]
----
module Demo
def self.keywords(four:, five: 5, **six) = puts "Four: #{four}, Five: #{five}, Six: #{six}"
end
probe = Marameters.for Demo.method(:keywords).parameters
probe.keywords_for :a, a: 1, four: 4 # {four: 4}
probe.keywords_for :four, a: 1 # {a: 1}
probe.keywords_for :a, four: 4, five: :five # {four: 4, five: :five}
probe.keywords_for :a, six: {name: :test} # {six: {name: :test}}
----
This useful in gems, like {infusible_link}, when determining which keyword arguments to pass up to the superclass.
=== Categorize
Categorization (`Marameters::Categorizer`) allows you to dynamically build positional, keyword, and block arguments for message passing. This is most valuable when you know the object and method while needing to align the arguments in the right order. Here's a demonstration where {amazing_print_link} (i.e. `ap`) is used to format the output:
[source,ruby]
----
function = proc { "test" }
module Demo
def self.test one, two = nil, *three, four:, five: nil, **six, &seven
puts "The .#{__method__} method received the following arguments:\n"
[one, two, three, four, five, six, seven].each.with_index 1 do |argument, index|
puts "#{index}. #{argument.inspect}"
end
puts
end
end
module Inspector
def self.call arguments
Marameters.categorize(Demo.method(:test).parameters, arguments)
.then do |record|
ap record
puts
Demo.test(*record.positionals, **record.keywords, &record.block)
end
end
end
Inspector.call [1, nil, nil, {four: 4}]
# # 4
# },
# positionals = [
# 1,
# nil
# ]
# >
#
# The .test method received the following arguments:
# 1. 1
# 2. nil
# 3. []
# 4. 4
# 5. nil
# 6. {}
# 7. nil
----
When we step through the above implementation and output, we see the following unfold:
. The `Demo` module allows us to define a maximum set of parameters and then print the arguments received for inspection purposes.
. The `Inspector` module provides a wrapper around the categorization so we can conveniently pass in different arguments for experimentation purposes.
. We pass in our arguments to `Inspector.call` where `nil` is used for optional arguments and hashes for keyword arguments.
. Once inside `Inspector.call`, the `Categorizer` is initialized with the `Demo.test` method parameters.
. Then the `splat` (i.e. Struct) is printed out so you can see the categorized positional, keyword, and block arguments.
. Finally, `Demo.test` method is called with the splatted arguments.
The above example satisfies the minimum required arguments but if we pass in the maximum arguments -- loosely speaking -- we see more detail:
[source,ruby]
----
Inspector.call [1, 2, [98, 99], {four: 4}, {five: 5}, {twenty: 20, thirty: 30}, function]
# Output
# #,
# keywords = {
# :four => 4,
# :five => 5,
# :twenty => 20,
# :thirty => 30
# },
# positionals = [
# 1,
# 2,
# 98,
# 99
# ]
# >
#
# The .test method received the following arguments:
# 1. 1
# 2. 2
# 3. [98, 99]
# 4. 4
# 5. 5
# 6. {:twenty=>20, :thirty=>30}
# 7. #
----
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 -- using the above example -- we can compare the parameters to the arguments as follows:
[source,ruby]
----
parameters = Demo.method(:test).parameters
arguments = [1, 2, [98, 99], {four: 4}, {five: 5}, {twenty: 20, thirty: 30}, function]
----
With {amazing_print_link}, we can print out this information:
[source,ruby]
----
ap parameters
ap arguments
----
...which can be further illustrated by this comparison table:
[options="header"]
|===
| Parameter | Argument
| `%i[reg one]` | `1`
| `%i[opt two]` | `2`
| `%i[rest three]` | `[98, 99]`
| `%i[keyreq four]` | `{four: 4}`
| `%i[key five]` | `{five: 5}`
| `%i[keyrest six]` | `{twenty: 20, thirty: 30}`
| `%i[block seven]` | `#`
|===
This also means:
* All positions must 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://docs.ruby-lang.org/en/master/Method.html#method-i-parameters[Method#parameters] works. Use `nil` to fill an optional argument when you don't need it.
* The `:rest` (single splat) argument must be an array or `nil` if not present because even though it is _optional_, it is still _positional_.
* The `:keyrest` (double splat) argument -- much like the `:rest` argument -- must be a hash or `nil` if not present.
Lastly, in all of the above examples, only an array of arguments has been used but you can pass in a single argument too (i.e. non-array). This is handy for method signatures which have only a single parameter or only use splats.
For C-based primitives, like `Struct`, `Data`, etc., you'll want to provide a conversion method. Example:
[source,ruby]
----
url = Struct.new(:label, :url) do
def self.for(**) = new(**)
end
Marameters.categorize(url.method(:for).parameters, label: "Example", url: "https://example.com")
.then { |record| url.for(**record.keywords) }
# Yields: #
----
For further details, please refer back to my {article_link} article mentioned in the xref:_requirements[Requirements] section.
=== Signature
The signature (`Marameters::Signature`) is the opposite of the probe class which allows you to turn a raw array of parameters into a method signature. This is most useful when metaprogramming and needing to dynamically build method signatures. Example:
[source,ruby]
----
signature = Marameters.signature [[:opt, :text, "This is a test."]]
Example = Module.new do
module_eval <<~METHOD, __FILE__, __LINE__ + 1
def self.say(#{signature}) = text
METHOD
end
puts Example.say # "This is a test."
puts Example.say("Hello") # "Hello"
----
==== Keys
The following demonstrates how you can construct a method signature with all possible parameters using the same keys as used by `Method#parameters`:
[source,ruby]
----
signature = Marameters.signature [
%i[req one],
%i[opt two],
%i[rest three],
%i[keyreq four],
%i[key five],
%i[keyrest six],
%i[block seven]
]
puts signature
# "one, two = nil, *three, four:, five: nil, **six, &seven"
----
==== Values
With the above examples, each sub-array uses a simple key/value pair to map the kind of parameter with the corresponding name. You can also provide a _third_ value when needing to provide a default value for _optional_ parameters. Example:
[source,ruby]
----
puts Marameters.signature([[:opt, :one, 1], [:key, :two, 2]])
# one = 1, two: 2
----
This can be demonstrated further by using optional keywords (same applies for optional positionals):
[source,ruby]
----
# With implicit nil.
puts Marameters.signature([%i[key demo]])
# "demo: nil"
# With explicit nil.
puts Marameters.signature([[:key, :demo, nil]])
# "demo: nil"
# With any primitive.
puts Marameters.signature([[:key, :demo, :test]])
# "demo: :test"
# With proc (no parameters).
puts Marameters.signature([[:key, :demo, proc { Object.new }]])
# "demo: Object.new"
# With proc (with parameters).
puts Marameters.signature([[:key, :demo, proc { |no| no }]])
# Avoid using parameters for proc defaults. (ArgumentError)
# With lambda.
puts Marameters.signature([[:key, :demo, -> { Object.new }]])
# Use procs instead of lambdas for defaults. (TypeError)
----
You can use any primitive, custom object, etc. as a default despite the limited examples shown above.
Procs _must_ be used when supplying complex objects as default values. _Avoid_ using parameters when using procs because only the source (body) of your proc will be used as a _literal_ string when building the method signature in order to ensure lazy evaluation.
Lastly, you can use anonymous splats/blocks by only supplying their kind. Example:
[source,ruby]
----
puts Marameters.signature([[:rest], [:keyrest], [:block]])
# "*, **, &"
----
You can supply `nil` as a second element (i.e. the name) for each kind but that is the equivalent of the above.
==== Argument Forwarding
Use `:all` for building a method signature with argument forwarding. Example:
[source, ruby]
----
puts Marameters.signature(:all)
# "..."
----
Use of `:all` is special in that you must _only_ supply `:all` with no other keys/values or you'll get an `ArgumentError`.
💡 This is only provided for convenience and completeness. In truth, you're better off writing `my_method(+...+)`, for example, than using this class.
==== Bare
Use an empty array when you need a bare method signature. Example:
[source,ruby]
----
puts Marameters.signature []
# ""
----
💡 This is only provided for convenience and completeness. In truth, if you need a bare method, then you don't need to use this class.
==== Inheritance
Object/method inheritance is more complicated than building a signature for a single method because you need to blend the super and sub parameters as a unified set of parameters. Additionally, you have to account for the arguments that need to be forwarded to the super method via the `super` keyword. To aid in this endeavor, the following objects are available to help you build these more complex method parameters and arguments:
* `Marameters::Signatures::Inheritor`: Blends super and sub parameters to produce a unified set of parameters you can turn into a method signature.
* `Marameters::Signatures::Super`: Blends super and sub parameters to produce arguments for forwarding via the `super` keyword. _This does not support disabled block forwarding (i.e. `&nil`) since there is no way to determine this from the super and sub parameters alone._
Here's an example which incorporates both of the above:
[source,ruby]
----
module Demo
def self.parent(one, two = 2, *three, &block) = nil
end
super_parameters = Marameters.for Demo.method(:parent).parameters
sub_parameters = Marameters.for [
[:opt, :two, 22],
%i[keyreq four],
[:key, :five, 5],
%i[keyrest six]
]
inheritor = Marameters::Signatures::Inheritor.new
forwarder = Marameters::Signatures::Super.new
puts Marameters.signature inheritor.call(super_parameters, sub_parameters)
# "one, two = 22, *three, four:, five: 5, **six, &block"
puts forwarder.call(super_parameters, sub_parameters)
# "one, two, *three, &block"
----
As you can see, the above combines the parameters of your super method with the parameters of your sub method in order to produce a method signature -- with no duplicates -- while ensuring you can forward all necessary parameters that the `super` keyword requires. Defaults, if given, will override previously defined defaults as is identical with standard object inheritance.
== Development
To contribute, run:
[source,bash]
----
git clone https://github.com/bkuhlmann/marameters
cd marameters
bin/setup
----
You can also use the IRB console for direct access to all objects:
[source,bash]
----
bin/console
----
== Tests
To test, run:
[source,bash]
----
bin/rake
----
== link:https://alchemists.io/policies/license[License]
== link:https://alchemists.io/policies/security[Security]
== link:https://alchemists.io/policies/code_of_conduct[Code of Conduct]
== link:https://alchemists.io/policies/contributions[Contributions]
== link:https://alchemists.io/policies/developer_certificate_of_origin[Developer Certificate of Origin]
== link:https://alchemists.io/projects/marameters/versions[Versions]
== link:https://alchemists.io/community[Community]
== Credits
* Built with link:https://alchemists.io/projects/gemsmith[Gemsmith].
* Engineered by link:https://alchemists.io/team/brooke_kuhlmann[Brooke Kuhlmann].
Owner
- Name: Brooke Kuhlmann
- Login: bkuhlmann
- Kind: user
- Location: Boulder, CO USA
- Company: Alchemists
- Website: https://alchemists.io
- Repositories: 56
- Profile: https://github.com/bkuhlmann
Quality over quantity.
Citation (CITATION.cff)
cff-version: 1.2.0
message: Please use the following metadata when citing this project in your work.
title: Marameters
abstract: A dynamic method parameter inspector.
version: 4.4.0
license: Hippocratic-2.1
date-released: 2025-07-19
authors:
- family-names: Kuhlmann
given-names: Brooke
affiliation: Alchemists
orcid: https://orcid.org/0000-0002-5810-6268
keywords:
- ruby
repository-code: https://github.com/bkuhlmann/marameters
repository-artifact: https://rubygems.org/gems/marameters
url: https://alchemists.io/projects/marameters
GitHub Events
Total
- Delete event: 79
- Push event: 129
- Create event: 11
Last Year
- Delete event: 79
- Push event: 129
- Create event: 11
Committers
Last synced: 5 months ago
Top Committers
| Name | Commits | |
|---|---|---|
| Brooke Kuhlmann | b****e@a****o | 247 |
Committer Domains (Top 20 + Academic)
Issues and Pull Requests
Last synced: 4 months ago
All Time
- Total issues: 0
- Total pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Total issue authors: 0
- Total pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0
Past Year
- Issues: 0
- Pull requests: 0
- Average time to close issues: N/A
- Average time to close pull requests: N/A
- Issue authors: 0
- Pull request authors: 0
- Average comments per issue: 0
- Average comments per pull request: 0
- Merged pull requests: 0
- Bot issues: 0
- Bot pull requests: 0
Top Authors
Issue Authors
Pull Request Authors
Top Labels
Issue Labels
Pull Request Labels
Packages
- Total packages: 1
-
Total downloads:
- rubygems 128,779 total
- Total dependent packages: 4
- Total dependent repositories: 7
- Total versions: 43
- Total maintainers: 1
rubygems.org: marameters
A dynamic method parameter enhancer.
- Homepage: https://alchemists.io/projects/marameters
- Documentation: http://www.rubydoc.info/gems/marameters/
- License: Hippocratic-2.1
-
Latest release: 4.4.0
published 5 months ago
Rankings
Dependent packages count: 4.0%
Dependent repos count: 8.4%
Downloads: 12.3%
Average: 15.2%
Stargazers count: 19.5%
Forks count: 31.6%
Maintainers (1)
Funding
- https://github.com/sponsors/bkuhlmann
Last synced:
4 months ago
Dependencies
Gemfile
rubygems
- amazing_print ~> 1.4 development
- caliber ~> 0.25 development
- debug ~> 1.7 development
- git-lint ~> 5.0 development
- guard-rspec ~> 4.7 development
- rake ~> 13.0 development
- reek ~> 6.1 development
- rspec ~> 3.12 development
- simplecov ~> 0.22 development
marameters.gemspec
rubygems
- refinements ~> 10.0
- zeitwerk ~> 2.6