welder 0.1.1
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/LICENSE +21 -0
- data/README.md +59 -0
- data/lib/welder/pipeline.rb +124 -0
- data/lib/welder/support/callable_handler.rb +27 -0
- data/lib/welder/version.rb +3 -0
- data/lib/welder.rb +10 -0
- metadata +64 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA1:
|
|
3
|
+
metadata.gz: 5428ae811f23cbc0f854771f6182c5d10dd915ba
|
|
4
|
+
data.tar.gz: f1fbe9a58f4f9232c1afddf10268fd56b79cf822
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: df3e6580e28d28d88621eb6f4d905da8bd024c1eafe38e19838d8944c0b7acfa42564f9669b0b257a0e7c2d8ed799cc6ee3252e81d3a1e82db1b059818d7b4a8
|
|
7
|
+
data.tar.gz: 195717328350c2d4ff0ca21e3aec5a71e6acca0b82a6491a817175187ac81b8b13b0779e77a64a86f022c482273189dc28d0aa62cb09c75d6be0122e244387e2
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2016 Lorenzo Arribas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# Welder
|
|
2
|
+
|
|
3
|
+
[](https://badge.fury.io/rb/welder)
|
|
4
|
+
[](https://travis-ci.org/rb-welder/welder)
|
|
5
|
+
[](https://codeclimate.com/github/rb-welder/welder)
|
|
6
|
+
[](https://codeclimate.com/github/rb-welder/welder/coverage)
|
|
7
|
+
[](https://gemnasium.com/rb-welder/welder)
|
|
8
|
+
[](http://inch-ci.org/github/rb-welder/welder)
|
|
9
|
+
|
|
10
|
+
**Welder** allows you to define pipelines in a true [Unix style](https://en.wikipedia.org/wiki/Pipeline_(Unix)).
|
|
11
|
+
|
|
12
|
+
It provides a simple and powerful DSL to define you own pipelines and compose them together. You can define a pipeline out of one or more ruby callables:
|
|
13
|
+
```ruby
|
|
14
|
+
read_file = ->(filename) { File.read(filename) }
|
|
15
|
+
count_words = ->(text) { text.split.size }
|
|
16
|
+
|
|
17
|
+
count_words_from_file = Welder::Pipeline.new | read_file | count_words # Define a pipeline
|
|
18
|
+
puts "My book has #{'my_book.txt' | count_words_from_file} words" # Execute it with a specific value
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
_Note that, for the pipe operator to work, the first argument has to be a Welder::Pipeline_
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
## Why pipelines?
|
|
25
|
+
In some use cases, pipelines have several advantages over the natural, imperative style of languages like ruby. Take, for instance, the following example:
|
|
26
|
+
```ruby
|
|
27
|
+
puts "My book has #{'my_book.txt' | read_file | count_words} words"
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Here, the alternative way to write the word count would be `count_words(read_file('my_book.txt'))`. Pipelines, in contrast:
|
|
31
|
+
* Provide a cleaner syntax that is better at expressing the statement's **order and intent**
|
|
32
|
+
* Ease the instrumentation of the whole process (e.g. for debugging and benchmarking purposes)
|
|
33
|
+
* Allow for ways to abstract the way the different stages in the pipeline are called. For instance, creating pipelines of remote methods using RPC
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
## Valves
|
|
37
|
+
Valves are callables that get called at every step of a pipeline with the input to the step, the function processing it, and the generated output. Valves are useful for logging, debugging and code instrumentation. You set valves like this:
|
|
38
|
+
```ruby
|
|
39
|
+
logged_steps = []
|
|
40
|
+
log = ->(i, l, o) { logged_steps << "Executed step #{l.inspect} with input=#{i.inspect} and got #{o.inspect}" }
|
|
41
|
+
count_words_and_log_steps = (Welder::Pipeline.new | read_file | count_words) -log
|
|
42
|
+
|
|
43
|
+
'my_book.txt' | count_words_and_log_steps
|
|
44
|
+
puts logged_steps.size # => 2
|
|
45
|
+
puts logged_steps[0] # Executed step "..." with input="my_book.txt" and output="this is my book"
|
|
46
|
+
puts logged_steps[1] # Executed step "..." with input="this is my book" and output=4
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
## Present and Future
|
|
51
|
+
The next step for Welder will be getting a nice toolbelt to start his work (extra gems with useful pipelines our of the box)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
## Contributing to Welder
|
|
55
|
+
Welder is open for help in any way.
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
## License
|
|
59
|
+
See LICENSE file
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
require 'welder/support/callable_handler'
|
|
2
|
+
|
|
3
|
+
module Welder
|
|
4
|
+
# A wrapper around a sequence of callables (i.e. anything that responds to a
|
|
5
|
+
# single-argument 'call' method, which includes procs, blocks and other
|
|
6
|
+
# pipelines)
|
|
7
|
+
#
|
|
8
|
+
# Pipelines are immutable. There are mechanisms to create more complex
|
|
9
|
+
# pipelines out of existing ones, but they will always create a new pipeline
|
|
10
|
+
class Pipeline
|
|
11
|
+
include Support::CallableHandler
|
|
12
|
+
|
|
13
|
+
# Creates a new pipeline out of a sequence of callables. If a block is
|
|
14
|
+
# given, it is executed as the last step of the pipeline.
|
|
15
|
+
# It also accepts valves, with act as witnesses of the steps the pipeline
|
|
16
|
+
# goes through
|
|
17
|
+
#
|
|
18
|
+
# @param lambdas [Array<#call>] Array of callables accepting 1 argument
|
|
19
|
+
# @param valves [Array<#call>] Array of callables accepting 3 arguments
|
|
20
|
+
# @param block [Block] A block to execute as the last step in the pipeline
|
|
21
|
+
#
|
|
22
|
+
# @raise [CallableExpectedError] When trying to create a pipeline
|
|
23
|
+
# out of a non-callable
|
|
24
|
+
#
|
|
25
|
+
# @example Create an empty pipeline (with no steps)
|
|
26
|
+
# square_and_double = Welder::Pipeline.new
|
|
27
|
+
#
|
|
28
|
+
# @example Create a pipeline from an anonymous function
|
|
29
|
+
# square = Welder::Pipeline.new(->(x){ x ** 2 })
|
|
30
|
+
#
|
|
31
|
+
# @example Create a pipeline from a block
|
|
32
|
+
# square_and_double = Welder::Pipeline.new { |x| x ** 2 }
|
|
33
|
+
def initialize(*lambdas, valves: nil, &block)
|
|
34
|
+
callable!(*lambdas, *valves)
|
|
35
|
+
|
|
36
|
+
@pipes = [*lambdas]
|
|
37
|
+
@pipes << block if block
|
|
38
|
+
|
|
39
|
+
@valves = [*valves]
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Apply the sequence of functions to a particular input. Any valves
|
|
43
|
+
# present in the pipeline are called as a side effect
|
|
44
|
+
#
|
|
45
|
+
# @param input [*] The input for the first element of the pipeline
|
|
46
|
+
# @param valves [Array<#call>] An array of valves to be called at every
|
|
47
|
+
# step of the pipeline
|
|
48
|
+
#
|
|
49
|
+
# @return [*] The output resulting of passing the input through the whole
|
|
50
|
+
# pipeline
|
|
51
|
+
def call(input, valves = [])
|
|
52
|
+
valves = @valves.concat(valves)
|
|
53
|
+
|
|
54
|
+
@pipes.reduce(input) do |a, e|
|
|
55
|
+
if e.is_a?(Pipeline)
|
|
56
|
+
e.call(a, valves)
|
|
57
|
+
else
|
|
58
|
+
e.call(a).tap do |output|
|
|
59
|
+
valves.each { |valve| valve.call(a, e, output) }
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Compose a pipeline with another one (or a callable). This method does not
|
|
66
|
+
# modify existing pipelines. Instead, it creates a new pipeline composed
|
|
67
|
+
# of the previous two
|
|
68
|
+
#
|
|
69
|
+
# @param other [#call] The callable to add as the last step of the
|
|
70
|
+
# pipeline, which has to accept 1 argument
|
|
71
|
+
#
|
|
72
|
+
# @raise [CallableExpectedError] When trying to create a pipeline
|
|
73
|
+
# out of non-callables
|
|
74
|
+
#
|
|
75
|
+
# @return [Welder::Pipeline] a new pipeline composed of the current
|
|
76
|
+
# one and 'other', in that order
|
|
77
|
+
def |(other)
|
|
78
|
+
self.class.new(self, other)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Create a new pipeline that keeps all the steps in the current one,
|
|
82
|
+
# but adds a valve overseeing the process. The valve will get called
|
|
83
|
+
# at every stage, as a side effect, but it will never modify the final
|
|
84
|
+
# outcome of the pipeline
|
|
85
|
+
#
|
|
86
|
+
# @param other [#call] The callable to invoke at every step of the
|
|
87
|
+
# pipeline. It must accept 3 arguments: (input, lambda, output)
|
|
88
|
+
def -(other)
|
|
89
|
+
self.class.new(self, valves: [*other])
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
WELDER_SAVED_PIPE_METHODS = {}
|
|
95
|
+
[Object, Fixnum, TrueClass, FalseClass].each do |klass|
|
|
96
|
+
|
|
97
|
+
WELDER_SAVED_PIPE_METHODS[klass] = begin
|
|
98
|
+
klass.instance_method(:|)
|
|
99
|
+
rescue NameError
|
|
100
|
+
nil
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# The bitwise OR operator is overloaded to force the creation and evaluation
|
|
104
|
+
# of a pipeline whose first element is not a pipeline, but whose second and
|
|
105
|
+
# further are.
|
|
106
|
+
#
|
|
107
|
+
# For any other case (i.e. a case where Welder::Pipeline is not involved),
|
|
108
|
+
# the behavior remains the same. Thus, there is no way for this extension
|
|
109
|
+
# to affect the rest of the application
|
|
110
|
+
#
|
|
111
|
+
# @param other [*] second argument for the pipeline operator
|
|
112
|
+
#
|
|
113
|
+
# @return [*] an evaluated pipeline (see conditions above) or the expected
|
|
114
|
+
# behavior for arbitrary objects
|
|
115
|
+
klass.class_eval <<EOF
|
|
116
|
+
def |(other)
|
|
117
|
+
if other.is_a?(Welder::Pipeline) && !is_a?(Welder::Pipeline)
|
|
118
|
+
return other.call(self)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
WELDER_SAVED_PIPE_METHODS[#{klass}].bind(self).call(other) if WELDER_SAVED_PIPE_METHODS[#{klass}]
|
|
122
|
+
end
|
|
123
|
+
EOF
|
|
124
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module Welder
|
|
2
|
+
module Support
|
|
3
|
+
# A domain error indicating that there was an attempt to create
|
|
4
|
+
# a pipeline out of a non-callable (e.g. a string)
|
|
5
|
+
CallableExpectedError = Class.new(Exception)
|
|
6
|
+
|
|
7
|
+
# Mixin that provides several utilities to handle callable objects
|
|
8
|
+
module CallableHandler
|
|
9
|
+
# Assert that a series of values are callable (respond to call)
|
|
10
|
+
#
|
|
11
|
+
# @param lambdas [Array<*>] Array of values to check
|
|
12
|
+
#
|
|
13
|
+
# @raise [CallableExpectedError] If one or more of the values are
|
|
14
|
+
# not callable
|
|
15
|
+
def callable!(*lambdas)
|
|
16
|
+
non_callable = lambdas.reject { |lambda| lambda.respond_to?(:call) }
|
|
17
|
+
unless non_callable.empty?
|
|
18
|
+
raise(
|
|
19
|
+
CallableExpectedError,
|
|
20
|
+
"Expected #{non_callable.map(&:to_s).join(', ')} " \
|
|
21
|
+
"to respond to 'call'"
|
|
22
|
+
)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/welder.rb
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# The Welder module provides access to the gem's feature set,
|
|
2
|
+
# and offers a few shortcuts to the main Welder entities
|
|
3
|
+
module Welder
|
|
4
|
+
# Support module containing mixins and exensions required by
|
|
5
|
+
# several components of the gem
|
|
6
|
+
module Support
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
require 'welder/pipeline'
|
metadata
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: welder
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Lorenzo Arribas
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2016-02-28 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: bundler
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '1'
|
|
20
|
+
type: :development
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '1'
|
|
27
|
+
description: |2
|
|
28
|
+
Define your application's processes in a simple and powerful way
|
|
29
|
+
email: lorenzo.arribas@me.com
|
|
30
|
+
executables: []
|
|
31
|
+
extensions: []
|
|
32
|
+
extra_rdoc_files: []
|
|
33
|
+
files:
|
|
34
|
+
- LICENSE
|
|
35
|
+
- README.md
|
|
36
|
+
- lib/welder.rb
|
|
37
|
+
- lib/welder/pipeline.rb
|
|
38
|
+
- lib/welder/support/callable_handler.rb
|
|
39
|
+
- lib/welder/version.rb
|
|
40
|
+
homepage: http://rubygems.org/gems/welder
|
|
41
|
+
licenses:
|
|
42
|
+
- MIT
|
|
43
|
+
metadata: {}
|
|
44
|
+
post_install_message:
|
|
45
|
+
rdoc_options: []
|
|
46
|
+
require_paths:
|
|
47
|
+
- lib
|
|
48
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
49
|
+
requirements:
|
|
50
|
+
- - ">="
|
|
51
|
+
- !ruby/object:Gem::Version
|
|
52
|
+
version: 2.0.0
|
|
53
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
54
|
+
requirements:
|
|
55
|
+
- - ">="
|
|
56
|
+
- !ruby/object:Gem::Version
|
|
57
|
+
version: '0'
|
|
58
|
+
requirements: []
|
|
59
|
+
rubyforge_project:
|
|
60
|
+
rubygems_version: 2.5.0
|
|
61
|
+
signing_key:
|
|
62
|
+
specification_version: 4
|
|
63
|
+
summary: Welder
|
|
64
|
+
test_files: []
|