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 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
+ [![Gem Version](https://badge.fury.io/rb/welder.svg)](https://badge.fury.io/rb/welder)
4
+ [![Build Status](https://travis-ci.org/rb-welder/welder.svg?branch=master)](https://travis-ci.org/rb-welder/welder)
5
+ [![Code Climate](https://codeclimate.com/github/rb-welder/welder/badges/gpa.svg)](https://codeclimate.com/github/rb-welder/welder)
6
+ [![Test Coverage](https://codeclimate.com/github/rb-welder/welder/badges/coverage.svg)](https://codeclimate.com/github/rb-welder/welder/coverage)
7
+ [![Dependency Status](https://gemnasium.com/rb-welder/welder.svg)](https://gemnasium.com/rb-welder/welder)
8
+ [![Inline docs](http://inch-ci.org/github/rb-welder/welder.svg?branch=master)](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
@@ -0,0 +1,3 @@
1
+ module Welder
2
+ VERSION = '0.1.1'.freeze
3
+ 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: []