pipe-ruby 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +176 -0
- data/Rakefile +2 -0
- data/lib/pipe/config.rb +47 -0
- data/lib/pipe/error.rb +38 -0
- data/lib/pipe/ext/inflection.rb +38 -0
- data/lib/pipe/ext/string.rb +17 -0
- data/lib/pipe/iterator.rb +41 -0
- data/lib/pipe/reducer.rb +66 -0
- data/lib/pipe/ruby/version.rb +5 -0
- data/lib/pipe.rb +33 -0
- data/pipe-ruby.gemspec +23 -0
- metadata +86 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: aecb3bc0a365cf5620fc131aefc6a66c0eda8943
|
4
|
+
data.tar.gz: 934bb442c5fa9212d3ae49ea83aed0a98b1e9b91
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 62b208dfa76ff39c8a88a3912888079029670406a401473cc1a354ef9b14c68273ed0e3f307da1729b09ede5e1c98a7d88951e2bb2424d8da658a16725ab58de
|
7
|
+
data.tar.gz: c3d9f27acc430b85f06e2fe43ae1844458a87ca73e39a76182e05bcbdad3d18799fb3e83438968ecdfe349f97167553583fe5f5d44e0801d81ee1e0467b4efb6
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 TeamSnap
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,176 @@
|
|
1
|
+
# Pipe
|
2
|
+
|
3
|
+
`pipe-ruby` is an implementation of the UNIX pipe command. It exposes two
|
4
|
+
instance methods, `pipe` and `pipe_each`.
|
5
|
+
|
6
|
+
## Installation
|
7
|
+
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'pipe-ruby'
|
12
|
+
```
|
13
|
+
|
14
|
+
After bundling, include the `Pipe` module in your class(es)
|
15
|
+
|
16
|
+
```ruby
|
17
|
+
class MyClass
|
18
|
+
include Pipe
|
19
|
+
|
20
|
+
# ...
|
21
|
+
end
|
22
|
+
```
|
23
|
+
|
24
|
+
## Default Usage
|
25
|
+
|
26
|
+
### #pipe
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
pipe(subject, :through => [
|
30
|
+
:method1, :method2#, ...
|
31
|
+
])
|
32
|
+
```
|
33
|
+
|
34
|
+
Just as with the UNIX pipe, `subject` will be passed as the first argument to
|
35
|
+
`method1`. The results of `method1` will be passed to `method2` and on and
|
36
|
+
on. The result of the last method called will be returned from the pipe.
|
37
|
+
|
38
|
+
### #pipe_each
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
pipe_each([subj1, subj2], :through => [
|
42
|
+
:method1, :method2#, ...
|
43
|
+
])
|
44
|
+
```
|
45
|
+
|
46
|
+
`pipe_each` calls `pipe`, passing each individual subject. It will return a
|
47
|
+
mapped array of the responses.
|
48
|
+
|
49
|
+
## Configurable Options
|
50
|
+
|
51
|
+
After implementing the `pipe` method in a few different places, we found that a
|
52
|
+
slightly different version was needed for each use case. `Pipe::Config` allows
|
53
|
+
for this customization per call or per class implementation. There are four
|
54
|
+
configurable options. Here they are with their defaults:
|
55
|
+
|
56
|
+
```ruby
|
57
|
+
Pipe::Config.new(
|
58
|
+
:error_handlers => [], # an array of procs to be called when an error occurs
|
59
|
+
:raise_on_error => true, # tells Pipe to re-raise errors which occur
|
60
|
+
:skip_on => false, # a truthy value or proc which tells pipe to skip the
|
61
|
+
# next method in the `through` array
|
62
|
+
:stop_on => false # a truthy value or proc which tells pipe to stop
|
63
|
+
# processing and return the current value
|
64
|
+
)
|
65
|
+
```
|
66
|
+
|
67
|
+
A `Pipe::Config` object can be passed to the pipe method one of three ways.
|
68
|
+
|
69
|
+
NOTE: The options below are in priority order, meaning an override of the
|
70
|
+
`pipe_config` method will take precedence over an override of the `@pipe_config`
|
71
|
+
instance variable.
|
72
|
+
|
73
|
+
You can pass it to pipe when called:
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
class MyClass
|
77
|
+
include Pipe
|
78
|
+
|
79
|
+
def my_method
|
80
|
+
config = Pipe::Config.new(:raise_on_error => false)
|
81
|
+
subject = Object.new
|
82
|
+
|
83
|
+
pipe(subject, :config => config, :through => [
|
84
|
+
# ...
|
85
|
+
])
|
86
|
+
end
|
87
|
+
|
88
|
+
# ...
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
Or override the `pipe_config` method:
|
93
|
+
|
94
|
+
```ruby
|
95
|
+
class MyClass
|
96
|
+
include Pipe
|
97
|
+
|
98
|
+
def pipe_config
|
99
|
+
Pipe::Config.new(:raise_on_error => false)
|
100
|
+
end
|
101
|
+
|
102
|
+
# ...
|
103
|
+
end
|
104
|
+
```
|
105
|
+
|
106
|
+
Or you can assign it to the `@pipe_config` instance variable:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class MyClass
|
110
|
+
include Pipe
|
111
|
+
|
112
|
+
def initialize
|
113
|
+
@pipe_config = Pipe::Config.new(:raise_on_error => false)
|
114
|
+
end
|
115
|
+
|
116
|
+
# ...
|
117
|
+
end
|
118
|
+
```
|
119
|
+
|
120
|
+
## Error Handling
|
121
|
+
|
122
|
+
As we implemented different versions of `pipe` across our infrastructure, we
|
123
|
+
came across several different error handling needs.
|
124
|
+
|
125
|
+
- logging errors that occur in methods called by pipe without raising them
|
126
|
+
- catching and re-raising errors with additional information so we can still
|
127
|
+
see the real backtrace while also gaining insight into which subject and
|
128
|
+
method combination triggered the error
|
129
|
+
- easily seeing which area of the pipe stack we were in when an error occurred
|
130
|
+
|
131
|
+
The first layer of error handling is the `error_handlers` attribute in
|
132
|
+
`Pipe::Config`. Each proc in this array will be called with two arguments,
|
133
|
+
the actual error object and a context hash containing the method and subject
|
134
|
+
when the error occurred. If an error occurs within one of these handlers it
|
135
|
+
will be re-raised inside the `Pipe::HandlerError` namespace, meaning a
|
136
|
+
`NameError` becomes a `Pipe::HandlerError::NameError`. We also postpend the
|
137
|
+
current method, current subject and original error class to the message.
|
138
|
+
|
139
|
+
NOTE: `Pipe::Config#error_handler` takes a block and adds it to the existing
|
140
|
+
error handlers.
|
141
|
+
|
142
|
+
We have two other namespaces, `Pipe::ReducerError`, which is used when an error
|
143
|
+
occurs inside during `pipe` execution and `Pipe::IteratorError` for errors which
|
144
|
+
occur inside of `pipe_each` execution.
|
145
|
+
|
146
|
+
Whenever an error occurs in execution (but not in error handler processing), the
|
147
|
+
response of `Pipe::Config#raise_on_error?` is checked. If this method returns
|
148
|
+
`true`, the error will be re-raised inside of the appropriate namespace. If it
|
149
|
+
returns `false`, the current value of `subject` will be returned and execution
|
150
|
+
stopped.
|
151
|
+
|
152
|
+
## Skipping / Stopping Execution
|
153
|
+
|
154
|
+
Prior to the execution of each method in the `through` array,
|
155
|
+
`Pipe::Config#break?` is called with the current value of `subject`. If it
|
156
|
+
returns truthy, execution will be stopped and the current value of subject
|
157
|
+
will be returned. A falsey response will allow the execution to move forward.
|
158
|
+
|
159
|
+
If the break test allows us to move forward, `Pipe::Config#skip?` will be called
|
160
|
+
with the current value of `subject`. Truthy responses from this method will
|
161
|
+
cause the existing value of subject to be carried to the next method without
|
162
|
+
executing the current specified method. Falsey responses will allow normal
|
163
|
+
execution of the currently specified method.
|
164
|
+
|
165
|
+
## Testing
|
166
|
+
|
167
|
+
I'll be adding tests in in the very near future. In the meantime, use at your
|
168
|
+
own risk :)
|
169
|
+
|
170
|
+
## Contributing
|
171
|
+
|
172
|
+
1. Fork it ( https://github.com/[my-github-username]/pipe-ruby/fork )
|
173
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
174
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
175
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
176
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
data/lib/pipe/config.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
module Pipe
|
2
|
+
class Config
|
3
|
+
attr_accessor :raise_on_error
|
4
|
+
attr_reader :error_handlers, :skip_on, :stop_on
|
5
|
+
|
6
|
+
def initialize(
|
7
|
+
error_handlers: [],
|
8
|
+
raise_on_error: true,
|
9
|
+
skip_on: false,
|
10
|
+
stop_on: false
|
11
|
+
)
|
12
|
+
@error_handlers = error_handlers
|
13
|
+
@raise_on_error = raise_on_error
|
14
|
+
self.skip_on = skip_on
|
15
|
+
self.stop_on = stop_on
|
16
|
+
end
|
17
|
+
|
18
|
+
def error_handler(&block)
|
19
|
+
error_handlers << block if block_given?
|
20
|
+
end
|
21
|
+
|
22
|
+
def break?(subj)
|
23
|
+
stop_on.call(subj)
|
24
|
+
rescue ArgumentError
|
25
|
+
stop_on.call
|
26
|
+
end
|
27
|
+
|
28
|
+
def raise_on_error?
|
29
|
+
raise_on_error ? true : false
|
30
|
+
end
|
31
|
+
|
32
|
+
def skip?(subj)
|
33
|
+
skip_on.call(subj)
|
34
|
+
rescue ArgumentError
|
35
|
+
skip_on.call
|
36
|
+
end
|
37
|
+
|
38
|
+
def skip_on=(val)
|
39
|
+
@skip_on = (val.respond_to?(:call) ? val : lambda { |obj| val })
|
40
|
+
end
|
41
|
+
|
42
|
+
def stop_on=(val)
|
43
|
+
@stop_on = (val.respond_to?(:call) ? val : lambda { |obj| val })
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
data/lib/pipe/error.rb
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
module Pipe
|
2
|
+
HandlerError = Module.new
|
3
|
+
IterationError = Module.new
|
4
|
+
ReducerError = Module.new
|
5
|
+
|
6
|
+
class Error
|
7
|
+
def self.process(data: {}, error:, namespace:)
|
8
|
+
new(error).rewrite_as(:namespace => namespace, :data => data)
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize(e)
|
12
|
+
@e = e
|
13
|
+
end
|
14
|
+
|
15
|
+
def rewrite_as(data: {}, namespace:)
|
16
|
+
subclass = find_or_create_subclass(namespace)
|
17
|
+
raise subclass, "#{e} [#{data}, #{e.class}]", e.backtrace
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :e
|
23
|
+
|
24
|
+
def find_or_create_subclass(namespace)
|
25
|
+
part = e.class.name.split("::").last
|
26
|
+
subclass_name = "#{namespace.name}::#{part}"
|
27
|
+
|
28
|
+
begin
|
29
|
+
subclass_name.constantize
|
30
|
+
rescue NameError
|
31
|
+
eval "#{subclass_name} = Class.new(StandardError)"
|
32
|
+
end
|
33
|
+
|
34
|
+
subclass_name.constantize
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Pipe
|
2
|
+
module Ext
|
3
|
+
module Inflection
|
4
|
+
# ripped from ActiveSupport#Inflections
|
5
|
+
# https://github.com/rails/rails/blob/861b70e92f4a1fc0e465ffcf2ee62680519c8f6f/activesupport/lib/active_support/inflector/methods.rb#L249
|
6
|
+
def self.constantize(camel_cased_word)
|
7
|
+
names = camel_cased_word.split('::')
|
8
|
+
|
9
|
+
# Trigger a built-in NameError exception including the ill-formed constant in the message.
|
10
|
+
Object.const_get(camel_cased_word) if names.empty?
|
11
|
+
|
12
|
+
# Remove the first blank element in case of '::ClassName' notation.
|
13
|
+
names.shift if names.size > 1 && names.first.empty?
|
14
|
+
|
15
|
+
names.inject(Object) do |constant, name|
|
16
|
+
if constant == Object
|
17
|
+
constant.const_get(name)
|
18
|
+
else
|
19
|
+
candidate = constant.const_get(name)
|
20
|
+
next candidate if constant.const_defined?(name, false)
|
21
|
+
next candidate unless Object.const_defined?(name)
|
22
|
+
|
23
|
+
# Go down the ancestors to check if it is owned directly. The check
|
24
|
+
# stops when we reach Object or the end of ancestors tree.
|
25
|
+
constant = constant.ancestors.inject do |const, ancestor|
|
26
|
+
break const if ancestor == Object
|
27
|
+
break ancestor if ancestor.const_defined?(name, false)
|
28
|
+
const
|
29
|
+
end
|
30
|
+
|
31
|
+
# owner is in Object, so raise
|
32
|
+
constant.const_get(name, false)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require "pipe/ext/inflection"
|
2
|
+
|
3
|
+
module Pipe
|
4
|
+
module Ext
|
5
|
+
module String
|
6
|
+
unless "".respond_to? :constantize
|
7
|
+
def constantize
|
8
|
+
Inflection.constantize(self)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class String
|
16
|
+
include Pipe::Ext::String
|
17
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Pipe
|
2
|
+
class Iterator
|
3
|
+
def initialize(config:, context:, subjects:, through:)
|
4
|
+
self.config = config
|
5
|
+
self.context = context
|
6
|
+
self.subjects = subjects
|
7
|
+
self.through = through
|
8
|
+
end
|
9
|
+
|
10
|
+
def iterate
|
11
|
+
subjects.map { |subject|
|
12
|
+
begin
|
13
|
+
Reducer.new(
|
14
|
+
config: config,
|
15
|
+
context: context,
|
16
|
+
subject: subject,
|
17
|
+
through: through
|
18
|
+
).reduce
|
19
|
+
rescue => e
|
20
|
+
handle_error(:error => e, :subject => subject)
|
21
|
+
subject
|
22
|
+
end
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_accessor :config, :context, :subjects, :through
|
29
|
+
|
30
|
+
def handle_error(error:, subject:)
|
31
|
+
if config.raise_on_error?
|
32
|
+
Error.process(
|
33
|
+
:data => { :subject => subject },
|
34
|
+
:error => e,
|
35
|
+
:namespace => IterationError,
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
data/lib/pipe/reducer.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
module Pipe
|
2
|
+
class Reducer
|
3
|
+
def initialize(config:, context:, subject:, through:)
|
4
|
+
self.config = config
|
5
|
+
self.context = context
|
6
|
+
self.subject = subject
|
7
|
+
self.through = through
|
8
|
+
end
|
9
|
+
|
10
|
+
def reduce
|
11
|
+
through.reduce(subject) { |subj, method|
|
12
|
+
begin
|
13
|
+
break subj if config.break?(subj)
|
14
|
+
|
15
|
+
process(subj, method)
|
16
|
+
rescue => e
|
17
|
+
handle_error(:error => e, :method => method, :subject => subj)
|
18
|
+
break subj
|
19
|
+
end
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
attr_accessor :config, :context, :subject, :through
|
26
|
+
|
27
|
+
def handle_error(error:, method:, subject:)
|
28
|
+
process_error_handlers(
|
29
|
+
:error => error,
|
30
|
+
:method => method,
|
31
|
+
:subject => subject
|
32
|
+
)
|
33
|
+
|
34
|
+
if config.raise_on_error?
|
35
|
+
Error.process(
|
36
|
+
:data => { :method => method, :subject => subject },
|
37
|
+
:error => error,
|
38
|
+
:namespace => ReducerError,
|
39
|
+
)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def process(subj, method)
|
44
|
+
if config.skip?(subj)
|
45
|
+
subj
|
46
|
+
else
|
47
|
+
context.send(method, subj)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def process_error_handlers(error:, method:, subject:)
|
52
|
+
data = { method: method, subject: subject }
|
53
|
+
|
54
|
+
begin
|
55
|
+
config.error_handlers.each { |handler| handler.call(e, data) }
|
56
|
+
rescue => e
|
57
|
+
Error.process(
|
58
|
+
:data => data,
|
59
|
+
:error => e,
|
60
|
+
:namespace => HandlerError,
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
data/lib/pipe.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require "pipe/ext/string"
|
2
|
+
require "pipe/config"
|
3
|
+
require "pipe/error"
|
4
|
+
require "pipe/iterator"
|
5
|
+
require "pipe/reducer"
|
6
|
+
|
7
|
+
module Pipe
|
8
|
+
def pipe(subject, config: pipe_config, through: [])
|
9
|
+
::Pipe::Reducer.new(
|
10
|
+
:config => config,
|
11
|
+
:context => self,
|
12
|
+
:subject => subject,
|
13
|
+
:through => through
|
14
|
+
).reduce
|
15
|
+
end
|
16
|
+
|
17
|
+
def pipe_each(subjects, config: pipe_config, through: [])
|
18
|
+
::Pipe::Iterator.new(
|
19
|
+
:config => config,
|
20
|
+
:context => self,
|
21
|
+
:subjects => subjects,
|
22
|
+
:through => through
|
23
|
+
).iterate
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def pipe_config
|
29
|
+
@pipe_config || Pipe::Config.new
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
|
data/pipe-ruby.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'pipe/ruby/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "pipe-ruby"
|
8
|
+
spec.version = Pipe::Ruby::VERSION
|
9
|
+
spec.authors = ["Dan Matthews"]
|
10
|
+
spec.email = ["oss@teamsnap.com"]
|
11
|
+
spec.summary = %q{Ruby implementation of the UNIX pipe}
|
12
|
+
spec.description = %q{}
|
13
|
+
spec.homepage = ""
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
22
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
23
|
+
end
|
metadata
ADDED
@@ -0,0 +1,86 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pipe-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dan Matthews
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-02-19 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.7'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.7'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
description: ''
|
42
|
+
email:
|
43
|
+
- oss@teamsnap.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- ".gitignore"
|
49
|
+
- Gemfile
|
50
|
+
- LICENSE.txt
|
51
|
+
- README.md
|
52
|
+
- Rakefile
|
53
|
+
- lib/pipe.rb
|
54
|
+
- lib/pipe/config.rb
|
55
|
+
- lib/pipe/error.rb
|
56
|
+
- lib/pipe/ext/inflection.rb
|
57
|
+
- lib/pipe/ext/string.rb
|
58
|
+
- lib/pipe/iterator.rb
|
59
|
+
- lib/pipe/reducer.rb
|
60
|
+
- lib/pipe/ruby/version.rb
|
61
|
+
- pipe-ruby.gemspec
|
62
|
+
homepage: ''
|
63
|
+
licenses:
|
64
|
+
- MIT
|
65
|
+
metadata: {}
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
require_paths:
|
69
|
+
- lib
|
70
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '0'
|
80
|
+
requirements: []
|
81
|
+
rubyforge_project:
|
82
|
+
rubygems_version: 2.2.2
|
83
|
+
signing_key:
|
84
|
+
specification_version: 4
|
85
|
+
summary: Ruby implementation of the UNIX pipe
|
86
|
+
test_files: []
|