method-pipeline 1.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Gemfile +19 -0
- data/LICENSE.txt +35 -0
- data/README.md +148 -0
- data/Rakefile +8 -0
- data/Steepfile +4 -0
- data/lib/pipeline.rb +54 -0
- data/pipeline.gemspec +24 -0
- data/rbs_collection.yaml +7 -0
- data/sig/pipeline.rbs +13 -0
- data/test/pipeline_test.rb +43 -0
- metadata +57 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 197ac28d84c9435d6f894925841cd79b2f25510529b1f027b539b6cb181dac17
|
4
|
+
data.tar.gz: 6308de9cb33fac936542ca87904530649c45f791cfc67ebc60a09f357d967e7a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 82f756e9b6fa5faf26aff5e832a3c50d7305b9a0ad0a398b8fccbe59f7121a9916ac242329b598df064439db0cc6d8f6c5a479e5d8235d7c4ffa0f40813ee631
|
7
|
+
data.tar.gz: b94a698fed672c08d6d939c1bedbd8056b2de306bce1eeda964d67b05a110c76656b23bbe4ef447e3bd90a948a5d3a4dcce47fa5814adcfbb07456f7c45606f3
|
data/Gemfile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
source 'https://rubygems.org'
|
3
|
+
gemspec
|
4
|
+
|
5
|
+
# Development Apps
|
6
|
+
group :development do
|
7
|
+
group :type_check do
|
8
|
+
gem 'rbs', '~> 3.1.0', require: false
|
9
|
+
gem 'steep', '~> 1.5.0', require: false
|
10
|
+
end
|
11
|
+
group :documentation do
|
12
|
+
gem 'yard', '~> 0.9.0', require: false
|
13
|
+
gem 'commonmarker', '~> 0.23.0', require: false
|
14
|
+
end
|
15
|
+
group :test do
|
16
|
+
gem 'rake', '~> 13.0.0'
|
17
|
+
gem 'minitest', '~> 5.19.0'
|
18
|
+
end
|
19
|
+
end
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
Copyright (c) 2023 ParadoxV5
|
2
|
+
|
3
|
+
The Universal Permissive License (UPL), Version 1.0
|
4
|
+
|
5
|
+
Subject to the condition set forth below, permission is hereby granted to any
|
6
|
+
person obtaining a copy of this software, associated documentation and/or data
|
7
|
+
(collectively the "Software"), free of charge and under any and all copyright
|
8
|
+
rights in the Software, and any and all patent rights owned or freely
|
9
|
+
licensable by each licensor hereunder covering either (i) the unmodified
|
10
|
+
Software as contributed to or provided by such licensor, or (ii) the Larger
|
11
|
+
Works (as defined below), to deal in both
|
12
|
+
|
13
|
+
(a) the Software, and
|
14
|
+
(b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
|
15
|
+
one is included with the Software (each a "Larger Work" to which the Software
|
16
|
+
is contributed by such licensors),
|
17
|
+
|
18
|
+
without restriction, including without limitation the rights to copy, create
|
19
|
+
derivative works of, display, perform, and distribute the Software and make,
|
20
|
+
use, sell, offer for sale, import, export, have made, and have sold the
|
21
|
+
Software and the Larger Work(s), and to sublicense the foregoing rights on
|
22
|
+
either these or other terms.
|
23
|
+
|
24
|
+
This license is subject to the following condition:
|
25
|
+
The above copyright notice and either this complete permission notice or at
|
26
|
+
a minimum a reference to the UPL must be included in all copies or
|
27
|
+
substantial portions of the Software.
|
28
|
+
|
29
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
30
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
31
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
32
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
33
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
34
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
35
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
The `Pipeline` Refinement module bundles a method duo that builds
|
2
|
+
a clean and clever pure-Ruby solution to rightward method piping.
|
3
|
+
|
4
|
+
A reminder that Refinement modules are activated with `using` and last only
|
5
|
+
for that module/class block or file (if top-level). See: `refinements.rdoc`
|
6
|
+
([on docs.ruby-lang.org](https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc.html))
|
7
|
+
|
8
|
+
|
9
|
+
## Synopsis Example
|
10
|
+
|
11
|
+
Here’s the example from `Kernel#then` straight off the Ruby 3.2 docs:
|
12
|
+
```ruby
|
13
|
+
require 'open-uri'
|
14
|
+
require 'json'
|
15
|
+
|
16
|
+
construct_url(arguments).
|
17
|
+
then {|url| URI(url).read }.
|
18
|
+
then {|response| JSON.parse(response) }
|
19
|
+
```
|
20
|
+
|
21
|
+
With the new Refinement active: (Oh gosh, this looks like a brand-new language!)
|
22
|
+
```ruby
|
23
|
+
using Pipeline
|
24
|
+
|
25
|
+
arguments.then_pipe(
|
26
|
+
`construct_url`,
|
27
|
+
`URI`,
|
28
|
+
:read,
|
29
|
+
JSON.`:parse
|
30
|
+
)
|
31
|
+
```
|
32
|
+
|
33
|
+
|
34
|
+
## Walkthrough Introduction
|
35
|
+
|
36
|
+
We traditionally pipe methods leftwards as nesting arguments:
|
37
|
+
```ruby
|
38
|
+
def parse(string) = …
|
39
|
+
do_stuff = ->(a){ … }
|
40
|
+
|
41
|
+
# This is even more incomprehensible if omitting round parentheses (poetry mode)
|
42
|
+
STDOUT.puts( # different-scope method
|
43
|
+
do_stuff.call( # Proc
|
44
|
+
parse( # same-scope method
|
45
|
+
input
|
46
|
+
)
|
47
|
+
)
|
48
|
+
)
|
49
|
+
```
|
50
|
+
|
51
|
+
The trending Rightward Operations improve readability –
|
52
|
+
especially for wordy expressions like the above –
|
53
|
+
by matching English’s left-to-right writing direction.
|
54
|
+
We currently accomplish this with `Object#then` and *light-weight Procs*:
|
55
|
+
```ruby
|
56
|
+
input
|
57
|
+
.then { parse _1 }
|
58
|
+
.then { do_stuff.call _1 }
|
59
|
+
.then { STDOUT.puts _1 }
|
60
|
+
```
|
61
|
+
|
62
|
+
We alternatively can deconstruct the corresponding `#to_proc` objects with `&`:
|
63
|
+
```ruby
|
64
|
+
# heck, these `method` calls can't even go poetry mode
|
65
|
+
input
|
66
|
+
.then(&method(:parse))
|
67
|
+
.then(&do_stuff)
|
68
|
+
.then(&STDOUT.method(:puts))
|
69
|
+
```
|
70
|
+
|
71
|
+
The Refinement module `Pipeline` introduces `Object#then_pipe` to
|
72
|
+
eliminate repeating `then(&…)`. It gives a beautiful vibe similar to
|
73
|
+
that of the Pipe Operator `|>` in some other languages (namely Elixir).
|
74
|
+
```ruby
|
75
|
+
using Pipeline
|
76
|
+
|
77
|
+
input.then_pipe(
|
78
|
+
method(:parse),
|
79
|
+
do_stuff,
|
80
|
+
STDOUT.method(:puts)
|
81
|
+
)
|
82
|
+
```
|
83
|
+
|
84
|
+
However, unlike Lisp-1 languages like Python or JavaScript,
|
85
|
+
Lisp-2s like Ruby face the additional challenge of namespace disambiguation.
|
86
|
+
Ruby solves it with the reflection methods `method` & co., but, as seen above,
|
87
|
+
their verbosity makes them eyesores inside otherwise elegant syntax.
|
88
|
+
Therefore, `Pipeline` further improves the grammar by replacing the `` `…` ``
|
89
|
+
syntax (powered by `` Kernel#` ``) with an `alias` of the bulky `Object#method`:
|
90
|
+
```ruby
|
91
|
+
using Pipeline
|
92
|
+
|
93
|
+
input.then_pipe(
|
94
|
+
`parse`,
|
95
|
+
do_stuff,
|
96
|
+
STDOUT.`:puts
|
97
|
+
)
|
98
|
+
```
|
99
|
+
|
100
|
+
|
101
|
+
## Why `` #` ``?
|
102
|
+
|
103
|
+
* It is the backend of both `` `…` `` and `%x{…}` syntaxes –
|
104
|
+
``self.`:meth`` can instead be `%x:meth:` or `` `meth` ``.
|
105
|
+
* A Ruby script is not (strictly) a Shell script. User system differences aside,
|
106
|
+
summoning subshells should be the last resort when there are no Ruby/Gem APIs.
|
107
|
+
Dedicating the `` ` `` char to subshells is a waste;
|
108
|
+
e.g., you typically see `$(…)` in bash rather than `` `…` ``.
|
109
|
+
`Pipeline` assigns `` #` `` a new and recurrent purpose; it also `alias`es
|
110
|
+
the original `` Kernel#` `` to `Object#sys` in the event of its preference.
|
111
|
+
|
112
|
+
|
113
|
+
## Limitations
|
114
|
+
|
115
|
+
* **The new `` #` `` ignores method visibilities (`private` or `protected`).**
|
116
|
+
* This is a Ruby limitation – one can bypass visibilities with
|
117
|
+
`Object#method` and `Method#call` regardless of this Refinement.
|
118
|
+
A security engineer would love reflection APIs that respect visibilities.
|
119
|
+
* The original design for the new `` #` `` was to match `Object#public_method`,
|
120
|
+
but it couldn’t retrieve oneself’s own `private` and `protected` methods.
|
121
|
+
* **`#then_pipe` cannot pass additional arguments at each step.**
|
122
|
+
* The current `Object#then`-based solution is as good as it can get –
|
123
|
+
There are no syntactical benefits to avoid a block while calling rightwards.
|
124
|
+
Hey, unlike `#then_pipe`, it welcomes (inner) blocks.
|
125
|
+
```ruby
|
126
|
+
# a lambda – a Proc with fixed arities, unlike regula-o’ procs (or blocks)
|
127
|
+
do_stuff = ->(a, o, z){ … }
|
128
|
+
|
129
|
+
# This essentially wraps `do_stuff` in a one-arg block (as in F#).
|
130
|
+
# The `_1` resembles Hack’s special pipe variable `$$`.
|
131
|
+
o.then { do_stuff.(a, _1, z) }
|
132
|
+
# The previous, adapted for `then_pipe`
|
133
|
+
o.then_pipe ->{ do_stuff.(a, _1, z) }
|
134
|
+
# Pure-rightward solution
|
135
|
+
[a, o, b].then { do_stuff.(*_1) }
|
136
|
+
```
|
137
|
+
|
138
|
+
|
139
|
+
## Miniblog: My insights on a Pipeline Operator for Ruby
|
140
|
+
|
141
|
+
https://gist.github.com/ParadoxV5/4f563e609decbdac07d40e5f2dead751
|
142
|
+
|
143
|
+
|
144
|
+
## License
|
145
|
+
|
146
|
+
Copyright (c) 2023 ParadoxV5
|
147
|
+
|
148
|
+
Licensed under the [Universal Permissive License v 1.0](https://oss.oracle.com/licenses/upl/)
|
data/Rakefile
ADDED
@@ -0,0 +1,8 @@
|
|
1
|
+
require 'minitest/test_task'
|
2
|
+
# Create the following tasks:
|
3
|
+
# * test : run tests
|
4
|
+
# * test:cmd : print the testing command
|
5
|
+
# * test:isolated : run tests independently to surface order dependencies
|
6
|
+
# * test:deps : (alias of test:isolated)
|
7
|
+
# * test:slow : run tests and reports the slowest 25
|
8
|
+
Minitest::TestTask.create
|
data/Steepfile
ADDED
data/lib/pipeline.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# The `Pipeline` Refinement module bundles a method duo that builds
|
2
|
+
# a clean and clever pure-Ruby solution to rightward method piping.
|
3
|
+
# Check out [the README](index.html) for examples.
|
4
|
+
#
|
5
|
+
# Reminder: activate a Refinement module with `using`:
|
6
|
+
# ```ruby
|
7
|
+
# using Pipeline
|
8
|
+
# my_obj.then_pipe(…)
|
9
|
+
# ```
|
10
|
+
# Refinements are only active for the module/class block or file
|
11
|
+
# (if top-level) that’s `using` them. See: `refinements.rdoc`
|
12
|
+
# ([on docs.ruby-lang.org](https://docs.ruby-lang.org/en/master/syntax/refinements_rdoc.html))
|
13
|
+
#
|
14
|
+
# @!method then_pipe(*procs)
|
15
|
+
# Yield `self` to the first `Proc` (or `#to_proc` object) argument,
|
16
|
+
# then the result to the second argument, and so forth.
|
17
|
+
# ```ruby
|
18
|
+
# construct_url(arguments).then_pipe(
|
19
|
+
# proc {|url| URI(url).read },
|
20
|
+
# JSON.public_method :parse
|
21
|
+
# )
|
22
|
+
# ```
|
23
|
+
# @param procs `Proc` or `#to_proc` objects to call
|
24
|
+
# @return The result of the last call, or `self` if no arguments were given.
|
25
|
+
#
|
26
|
+
# @!method sys(command)
|
27
|
+
# Provide a replacement alias for `` Kernel#` ``, the subshell method.
|
28
|
+
# @see #`
|
29
|
+
#
|
30
|
+
# @!method `(name)
|
31
|
+
# Alias `Object#method`.
|
32
|
+
# ```ruby
|
33
|
+
# m = 42.` :to_s
|
34
|
+
# m.call #=> "42"
|
35
|
+
# ```
|
36
|
+
# The `` ` `` method is the backend to the `` `…` `` and `%x{…}` syntaxes.
|
37
|
+
# ```ruby
|
38
|
+
# class MyArray < Array
|
39
|
+
# using Pipeline
|
40
|
+
# def fetch_values(*indices)
|
41
|
+
# indices.map(&`[]`)
|
42
|
+
# end
|
43
|
+
# end
|
44
|
+
# a = MyArray.new(['A', 'B', 'C'])
|
45
|
+
# a.fetch_values 1, 3 #=> ["B", nil]
|
46
|
+
# ```
|
47
|
+
# @see #sys
|
48
|
+
|
49
|
+
module Pipeline; refine Object do
|
50
|
+
def then_pipe(*procs) = procs.reduce(self) { _1.then(&_2) }
|
51
|
+
|
52
|
+
alias sys `
|
53
|
+
alias ` method
|
54
|
+
end end
|
data/pipeline.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Gem::Specification.new do |spec|
|
4
|
+
spec.name = 'method-pipeline'
|
5
|
+
spec.summary = 'Pure-Ruby solution to method pipelines'
|
6
|
+
spec.version = '1.0.3'
|
7
|
+
spec.author = 'ParadoxV5'
|
8
|
+
spec.license = 'UPL-1.0'
|
9
|
+
|
10
|
+
|
11
|
+
github_account = spec.author
|
12
|
+
github = File.join 'https://github.com', github_account, spec.name
|
13
|
+
spec.homepage = github
|
14
|
+
spec.metadata = {
|
15
|
+
'homepage_uri' => spec.homepage,
|
16
|
+
'source_code_uri' => github,
|
17
|
+
'changelog_uri' => File.join(github, 'releases'),
|
18
|
+
'bug_tracker_uri' => File.join(github, 'issues'),
|
19
|
+
'documentation_uri' => File.join('https://rubydoc.info/gems', spec.name)
|
20
|
+
}
|
21
|
+
|
22
|
+
spec.files = Dir['**/*']
|
23
|
+
spec.required_ruby_version = '~> 3.2'
|
24
|
+
end
|
data/rbs_collection.yaml
ADDED
data/sig/pipeline.rbs
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'pipeline'
|
4
|
+
describe Pipeline do
|
5
|
+
|
6
|
+
it 'is a module that refines the Object class and only that' do
|
7
|
+
assert_kind_of Module, Pipeline
|
8
|
+
assert_equal [Object], Pipeline.refinements.map(&:refined_class)
|
9
|
+
assert_empty Pipeline.instance_methods
|
10
|
+
assert_empty Pipeline.private_instance_methods
|
11
|
+
end
|
12
|
+
using Pipeline
|
13
|
+
|
14
|
+
describe '`#method` shorthand' do
|
15
|
+
it 'overrides `backticks` with a public delegate to #method or equivalent' do
|
16
|
+
o = Object.new
|
17
|
+
def o.echo = # do nothing for a phony name
|
18
|
+
assert_equal o.method(:echo), o.`('echo')
|
19
|
+
assert_equal o.method(:__id__), o.`(:__id__)
|
20
|
+
pass if o.`(:initialize)
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'renames Object#` to Object#sys' do
|
24
|
+
assert_equal :`, Object.instance_method(:sys).original_name
|
25
|
+
random_string = Kernel.rand.to_s
|
26
|
+
assert_equal random_string, sys("echo #{random_string}").chomp
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'defines Object#then_pipe that chain-calls each of the given _ToProc’s with the receiver as the first arg' do
|
31
|
+
receiver, proc_return = Object.new, Object.new
|
32
|
+
# It is neither required nor forbidden for `#then_pipe` to also
|
33
|
+
# pass their blocks to their _ToProc arg(s) (block-ception Lol)
|
34
|
+
a_proc = proc do|arg|
|
35
|
+
assert_same receiver, arg
|
36
|
+
proc_return
|
37
|
+
end
|
38
|
+
a_proc_like = Object.new
|
39
|
+
a_proc_like.define_singleton_method(:to_proc) { a_proc }
|
40
|
+
assert_same proc_return, receiver.then_pipe(a_proc_like)
|
41
|
+
receiver.then_pipe(a_proc, ->{ assert_same proc_return, _1 })
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: method-pipeline
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- ParadoxV5
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-07-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description:
|
14
|
+
email:
|
15
|
+
executables: []
|
16
|
+
extensions: []
|
17
|
+
extra_rdoc_files: []
|
18
|
+
files:
|
19
|
+
- Gemfile
|
20
|
+
- LICENSE.txt
|
21
|
+
- README.md
|
22
|
+
- Rakefile
|
23
|
+
- Steepfile
|
24
|
+
- lib/pipeline.rb
|
25
|
+
- pipeline.gemspec
|
26
|
+
- rbs_collection.yaml
|
27
|
+
- sig/pipeline.rbs
|
28
|
+
- test/pipeline_test.rb
|
29
|
+
homepage: https://github.com/ParadoxV5/method-pipeline
|
30
|
+
licenses:
|
31
|
+
- UPL-1.0
|
32
|
+
metadata:
|
33
|
+
homepage_uri: https://github.com/ParadoxV5/method-pipeline
|
34
|
+
source_code_uri: https://github.com/ParadoxV5/method-pipeline
|
35
|
+
changelog_uri: https://github.com/ParadoxV5/method-pipeline/releases
|
36
|
+
bug_tracker_uri: https://github.com/ParadoxV5/method-pipeline/issues
|
37
|
+
documentation_uri: https://rubydoc.info/gems/method-pipeline
|
38
|
+
post_install_message:
|
39
|
+
rdoc_options: []
|
40
|
+
require_paths:
|
41
|
+
- lib
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3.2'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
requirements:
|
49
|
+
- - ">="
|
50
|
+
- !ruby/object:Gem::Version
|
51
|
+
version: '0'
|
52
|
+
requirements: []
|
53
|
+
rubygems_version: 3.4.10
|
54
|
+
signing_key:
|
55
|
+
specification_version: 4
|
56
|
+
summary: Pure-Ruby solution to method pipelines
|
57
|
+
test_files: []
|