composable_operations 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 +18 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +29 -0
- data/Rakefile +1 -0
- data/composable_operations.gemspec +26 -0
- data/lib/composable_operations.rb +11 -0
- data/lib/composable_operations/composed_operation.rb +78 -0
- data/lib/composable_operations/matcher.rb +4 -0
- data/lib/composable_operations/matcher/fail_to_perform.rb +83 -0
- data/lib/composable_operations/matcher/succeed_to_perform.rb +70 -0
- data/lib/composable_operations/matcher/utilize_operation.rb +92 -0
- data/lib/composable_operations/operation.rb +165 -0
- data/lib/composable_operations/operation_error.rb +4 -0
- data/lib/composable_operations/version.rb +3 -0
- data/spec/composable_operations/composed_operation_spec.rb +135 -0
- data/spec/composable_operations/control_flow_spec.rb +187 -0
- data/spec/composable_operations/operation_spec.rb +229 -0
- data/spec/spec_helper.rb +11 -0
- metadata +124 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2815a0e9caf243a6e502b0f277cb98c36b44b9d5
|
4
|
+
data.tar.gz: 668f9e8de07e9287f7498909fca06f2d61ab4904
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 962cef4189be9cabea7c2df388d1f6be24929a211eafe43f9fadddec9ff4375fd553a977a01641ee812fb1e887fc2d62853bdb3eb29e2636b336f10899d3473f
|
7
|
+
data.tar.gz: e3f3d3df80f08542fa285e27fbe7d4ca9a7c217a41f0d74dfa97e737dc95e9d6742877ec08d2f5f41ccc9f28ac75bffc6b419a33ca1983d6047ea76dfe36db8f
|
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Konstantin Tennhard
|
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,29 @@
|
|
1
|
+
# ComposableOperations
|
2
|
+
|
3
|
+
TODO: Write a gem description
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add this line to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'composable_operations'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install composable_operations
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
TODO: Write usage instructions here
|
22
|
+
|
23
|
+
## Contributing
|
24
|
+
|
25
|
+
1. Fork it
|
26
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
27
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
28
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
29
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'composable_operations/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "composable_operations"
|
8
|
+
spec.version = ComposableOperations::VERSION
|
9
|
+
spec.authors = ["Konstantin Tennhard"]
|
10
|
+
spec.email = ["me@t6d.de"]
|
11
|
+
spec.summary = %q{Tool set for operation pipelines.}
|
12
|
+
spec.description = %q{Composable Operations is a tool set for creating easy-to-use operation pipelines.}
|
13
|
+
spec.homepage = "http://github.com/t6d/composable_operations"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
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_dependency "smart_properties", "~> 1.0"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
24
|
+
spec.add_development_dependency "rake"
|
25
|
+
spec.add_development_dependency "rspec", "~> 2.11"
|
26
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'smart_properties'
|
2
|
+
|
3
|
+
module ComposableOperations
|
4
|
+
# Your code goes here...
|
5
|
+
end
|
6
|
+
|
7
|
+
require_relative "composable_operations/version"
|
8
|
+
require_relative "composable_operations/operation_error"
|
9
|
+
require_relative "composable_operations/operation"
|
10
|
+
require_relative "composable_operations/composed_operation"
|
11
|
+
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
class ComposedOperation < Operation
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def operations
|
6
|
+
[] + Array((super if defined? super)) + Array(@operations)
|
7
|
+
end
|
8
|
+
|
9
|
+
def use(operation)
|
10
|
+
(@operations ||= []) << operation
|
11
|
+
end
|
12
|
+
|
13
|
+
def compose(*operations, &block)
|
14
|
+
raise ArgumentError, "Expects either an array of operations or a block with configuration instructions" unless !!block ^ !operations.empty?
|
15
|
+
|
16
|
+
if block
|
17
|
+
Class.new(self, &block)
|
18
|
+
else
|
19
|
+
Class.new(self) do
|
20
|
+
operations.each do |operation|
|
21
|
+
use operation
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def transitions
|
28
|
+
transitions = []
|
29
|
+
klass = self
|
30
|
+
while klass != Operation
|
31
|
+
klass = klass.superclass
|
32
|
+
transitions += Array(klass.instance_variable_get(:@transitions))
|
33
|
+
end
|
34
|
+
transitions += Array(@transitions)
|
35
|
+
transitions
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def between(&callback)
|
42
|
+
(@transitions ||= []) << callback
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
def operations
|
48
|
+
self.class.operations
|
49
|
+
end
|
50
|
+
|
51
|
+
protected
|
52
|
+
|
53
|
+
def execute
|
54
|
+
[nil, *operations, nil].each_cons(2).inject(input) do |data, operations|
|
55
|
+
if operation = operations.last
|
56
|
+
operation = operation.new(data)
|
57
|
+
operation.perform
|
58
|
+
|
59
|
+
if operation.failed?
|
60
|
+
fail operation.message, operation.result, operation.backtrace
|
61
|
+
elsif operation.halted?
|
62
|
+
halt operation.message, operation.result
|
63
|
+
end
|
64
|
+
|
65
|
+
transition(*operations, data) if operations.first && operations.last
|
66
|
+
operation.result
|
67
|
+
else
|
68
|
+
data
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def transition(a, b, payload)
|
74
|
+
self.class.transitions.each { |transition| instance_exec(a, b, payload, &transition) }
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module FailToPerform
|
4
|
+
class Matcher
|
5
|
+
|
6
|
+
def matches?(operation)
|
7
|
+
self.operation = operation
|
8
|
+
failed? && result_as_expected? && message_as_expected?
|
9
|
+
end
|
10
|
+
|
11
|
+
def because(message)
|
12
|
+
@message = message
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def and_return(result)
|
17
|
+
@result = result
|
18
|
+
self
|
19
|
+
end
|
20
|
+
|
21
|
+
def description
|
22
|
+
description = "fail to perform"
|
23
|
+
description += " because #{message}" if message
|
24
|
+
description += " and return the expected result" if result
|
25
|
+
description
|
26
|
+
end
|
27
|
+
|
28
|
+
def failure_message
|
29
|
+
"the operation did not fail to perform for the following reason(s):\n#{failure_reasons}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def negative_failure_message
|
33
|
+
"the operation failed unexpectedly"
|
34
|
+
end
|
35
|
+
|
36
|
+
protected
|
37
|
+
|
38
|
+
attr_reader :operation
|
39
|
+
attr_reader :message
|
40
|
+
attr_reader :result
|
41
|
+
|
42
|
+
def operation=(operation)
|
43
|
+
operation.perform
|
44
|
+
@operation = operation
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def failed?
|
50
|
+
operation.failed?
|
51
|
+
end
|
52
|
+
|
53
|
+
def message_as_expected?
|
54
|
+
return true unless message
|
55
|
+
operation.message == message
|
56
|
+
end
|
57
|
+
|
58
|
+
def result_as_expected?
|
59
|
+
return true unless result
|
60
|
+
operation.result == result
|
61
|
+
end
|
62
|
+
|
63
|
+
def failure_reasons
|
64
|
+
reasons = []
|
65
|
+
reasons << "it did not fail at all" unless failed?
|
66
|
+
reasons << "its message was not as expected" unless message_as_expected?
|
67
|
+
reasons << "it did not return the expected result" unless result_as_expected?
|
68
|
+
reasons.map { |r| "\t- #{r}" }.join("\n")
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def fail_to_perform
|
74
|
+
Matcher.new
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
RSpec.configure do |config|
|
82
|
+
config.include ComposableOperations::Matcher::FailToPerform
|
83
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module SucceedToPerform
|
4
|
+
class Matcher
|
5
|
+
|
6
|
+
def matches?(operation)
|
7
|
+
self.operation = operation
|
8
|
+
succeeded? && result_as_expected?
|
9
|
+
end
|
10
|
+
|
11
|
+
def and_return(result)
|
12
|
+
@result = result
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
def description
|
17
|
+
description = "succeed to perform"
|
18
|
+
description += " and return the expected result" if result
|
19
|
+
description
|
20
|
+
end
|
21
|
+
|
22
|
+
def failure_message
|
23
|
+
"the operation failed to perform for the following reason(s):\n#{failure_reasons}"
|
24
|
+
end
|
25
|
+
|
26
|
+
def negative_failure_message
|
27
|
+
"the operation succeeded unexpectedly"
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
attr_reader :operation
|
33
|
+
attr_reader :result
|
34
|
+
|
35
|
+
def operation=(operation)
|
36
|
+
operation.perform
|
37
|
+
@operation = operation
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def succeeded?
|
43
|
+
operation.succeeded?
|
44
|
+
end
|
45
|
+
|
46
|
+
def result_as_expected?
|
47
|
+
return true unless result
|
48
|
+
operation.result == result
|
49
|
+
end
|
50
|
+
|
51
|
+
def failure_reasons
|
52
|
+
reasons = []
|
53
|
+
reasons << "it did not succeed at all" unless succeeded?
|
54
|
+
reasons << "it did not return the expected result" unless result_as_expected?
|
55
|
+
reasons.map { |r| "\t- #{r}" }.join("\n")
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
def succeed_to_perform
|
61
|
+
Matcher.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
RSpec.configure do |config|
|
68
|
+
config.include ComposableOperations::Matcher::SucceedToPerform
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
module Matcher
|
3
|
+
module UtilizeOperation
|
4
|
+
|
5
|
+
class DummyOperation
|
6
|
+
def initialize(*args)
|
7
|
+
end
|
8
|
+
|
9
|
+
def failed?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def halted?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def succeeded?
|
18
|
+
true
|
19
|
+
end
|
20
|
+
|
21
|
+
def result
|
22
|
+
Object.new
|
23
|
+
end
|
24
|
+
|
25
|
+
def perform
|
26
|
+
result
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Matcher
|
31
|
+
|
32
|
+
attr_reader :composite_operations
|
33
|
+
attr_reader :tested_operation
|
34
|
+
attr_reader :tested_instance
|
35
|
+
|
36
|
+
def initialize(*composite_operations)
|
37
|
+
@composite_operations = composite_operations.flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def matches?(class_or_instance)
|
41
|
+
if class_or_instance.is_a?(Class)
|
42
|
+
@tested_operation = class_or_instance
|
43
|
+
@tested_instance = class_or_instance.new
|
44
|
+
else
|
45
|
+
@tested_operation = class_or_instance.class
|
46
|
+
@tested_instance = class_or_instance
|
47
|
+
end
|
48
|
+
|
49
|
+
Operation.stub(:new => DummyOperation.new)
|
50
|
+
composite_operations.each do |composite_operation|
|
51
|
+
dummy_operation = DummyOperation.new
|
52
|
+
dummy_operation.should_receive(:perform).and_call_original
|
53
|
+
composite_operation.should_receive(:new).and_return(dummy_operation)
|
54
|
+
end
|
55
|
+
tested_instance.stub(:prepare => true, :finalize => true)
|
56
|
+
tested_instance.perform
|
57
|
+
|
58
|
+
tested_operation.operations == composite_operations
|
59
|
+
end
|
60
|
+
|
61
|
+
def description
|
62
|
+
"utilize the following operations: #{composite_operations.map(&:to_s).join(', ')}"
|
63
|
+
end
|
64
|
+
|
65
|
+
def failure_message
|
66
|
+
expected_but_not_used = composite_operations - tested_operation.operations
|
67
|
+
used_but_not_exptected = tested_operation.operations - composite_operations
|
68
|
+
message = ["Unexpected operation utilization:"]
|
69
|
+
message << "Expected: #{expected_but_not_used.join(', ')}" unless expected_but_not_used.empty?
|
70
|
+
message << "Not expected: #{used_but_not_exptected.join(', ')}" unless used_but_not_exptected.empty?
|
71
|
+
message.join("\n\t")
|
72
|
+
end
|
73
|
+
|
74
|
+
def negative_failure_message
|
75
|
+
"Unexpected operation utilization"
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def utilize_operation(*args)
|
81
|
+
Matcher.new(*args)
|
82
|
+
end
|
83
|
+
alias utilize_operations utilize_operation
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
RSpec.configure do |config|
|
90
|
+
config.include ComposableOperations::Matcher::UtilizeOperation
|
91
|
+
end
|
92
|
+
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module ComposableOperations
|
2
|
+
class Operation
|
3
|
+
|
4
|
+
include SmartProperties
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def perform(*args)
|
9
|
+
operation = new(*args)
|
10
|
+
operation.perform
|
11
|
+
|
12
|
+
raise exception, operation.message, operation.backtrace if operation.failed?
|
13
|
+
|
14
|
+
operation.result
|
15
|
+
end
|
16
|
+
|
17
|
+
def preparators
|
18
|
+
preparators = []
|
19
|
+
klass = self
|
20
|
+
while klass != Operation
|
21
|
+
klass = klass.superclass
|
22
|
+
preparators += Array(klass.instance_variable_get(:@preparators))
|
23
|
+
end
|
24
|
+
preparators += Array(@preparators)
|
25
|
+
preparators
|
26
|
+
end
|
27
|
+
|
28
|
+
def finalizers
|
29
|
+
finalizers = []
|
30
|
+
klass = self
|
31
|
+
while klass != Operation
|
32
|
+
klass = klass.superclass
|
33
|
+
finalizers += Array(klass.instance_variable_get(:@finalizers))
|
34
|
+
end
|
35
|
+
finalizers += Array(@finalizers)
|
36
|
+
finalizers
|
37
|
+
end
|
38
|
+
|
39
|
+
def exception
|
40
|
+
@exception or defined?(super) ? super : OperationError
|
41
|
+
end
|
42
|
+
|
43
|
+
protected
|
44
|
+
|
45
|
+
def before(&callback)
|
46
|
+
(@preparators ||= []) << callback
|
47
|
+
end
|
48
|
+
|
49
|
+
def after(&callback)
|
50
|
+
(@finalizers ||= []) << callback
|
51
|
+
end
|
52
|
+
|
53
|
+
def processes(*names)
|
54
|
+
case names.length
|
55
|
+
when 0
|
56
|
+
raise ArgumentError, "#{self}.#{__callee__} expects at least one argument"
|
57
|
+
when 1
|
58
|
+
alias_method names[0].to_sym, :input
|
59
|
+
else
|
60
|
+
names.each_with_index do |name, index|
|
61
|
+
define_method(name) { input[index] }
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def raises(exception)
|
67
|
+
@exception = exception
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def method_added(method)
|
73
|
+
super
|
74
|
+
protected method if method == :execute
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
|
79
|
+
attr_reader :input
|
80
|
+
attr_reader :result
|
81
|
+
attr_reader :message
|
82
|
+
attr_reader :backtrace
|
83
|
+
|
84
|
+
def initialize(input = nil, options = {})
|
85
|
+
super(options)
|
86
|
+
@input = input
|
87
|
+
end
|
88
|
+
|
89
|
+
def failed?
|
90
|
+
state == :failed
|
91
|
+
end
|
92
|
+
|
93
|
+
def halted?
|
94
|
+
state == :halted
|
95
|
+
end
|
96
|
+
|
97
|
+
def succeeded?
|
98
|
+
state == :succeeded
|
99
|
+
end
|
100
|
+
|
101
|
+
def message?
|
102
|
+
message.present?
|
103
|
+
end
|
104
|
+
|
105
|
+
def name
|
106
|
+
self.class.name
|
107
|
+
end
|
108
|
+
|
109
|
+
def perform
|
110
|
+
self.result = catch(:halt) do
|
111
|
+
prepare
|
112
|
+
result = execute
|
113
|
+
self.state = :succeeded
|
114
|
+
result
|
115
|
+
end
|
116
|
+
|
117
|
+
finalize
|
118
|
+
|
119
|
+
self.result
|
120
|
+
end
|
121
|
+
|
122
|
+
protected
|
123
|
+
|
124
|
+
attr_accessor :state
|
125
|
+
|
126
|
+
attr_writer :message
|
127
|
+
attr_writer :result
|
128
|
+
attr_writer :backtrace
|
129
|
+
|
130
|
+
def execute
|
131
|
+
raise NotImplementedError, "#{name}#execute not implemented"
|
132
|
+
end
|
133
|
+
|
134
|
+
def fail(message = nil, return_value = nil, backtrace = caller)
|
135
|
+
raise "Operation execution has already been aborted" if halted? or failed?
|
136
|
+
|
137
|
+
self.state = :failed
|
138
|
+
self.backtrace = backtrace
|
139
|
+
self.message = message
|
140
|
+
throw :halt, return_value
|
141
|
+
end
|
142
|
+
|
143
|
+
def halt(message = nil, return_value = input)
|
144
|
+
raise "Operation execution has already been aborted" if halted? or failed?
|
145
|
+
|
146
|
+
self.state = :halted
|
147
|
+
self.message = message
|
148
|
+
throw :halt, return_value
|
149
|
+
end
|
150
|
+
|
151
|
+
def prepare
|
152
|
+
self.class.preparators.each { |preparator| instance_eval(&preparator) }
|
153
|
+
end
|
154
|
+
|
155
|
+
def finalize
|
156
|
+
self.class.finalizers.each do |finalizer|
|
157
|
+
self.result = catch(:halt) do
|
158
|
+
instance_eval(&finalizer)
|
159
|
+
self.result
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
165
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe ComposableOperations::ComposedOperation do
|
4
|
+
|
5
|
+
let(:string_generator) do
|
6
|
+
Class.new(ComposableOperations::Operation) do
|
7
|
+
def self.name
|
8
|
+
"StringGenerator"
|
9
|
+
end
|
10
|
+
|
11
|
+
def execute
|
12
|
+
"chunky bacon"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
let(:string_capitalizer) do
|
18
|
+
Class.new(ComposableOperations::Operation) do
|
19
|
+
def self.name
|
20
|
+
"StringCapitalizer"
|
21
|
+
end
|
22
|
+
|
23
|
+
def execute
|
24
|
+
input.upcase
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
let(:halting_operation) do
|
30
|
+
Class.new(ComposableOperations::Operation) do
|
31
|
+
def execute
|
32
|
+
halt
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "when composed of one operation that generates a string no matter the input" do
|
38
|
+
|
39
|
+
subject(:composed_operation) do
|
40
|
+
operation = string_generator
|
41
|
+
|
42
|
+
Class.new(described_class) do
|
43
|
+
use operation
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should return this string as result" do
|
48
|
+
composed_operation.perform(nil).should be == "chunky bacon"
|
49
|
+
end
|
50
|
+
|
51
|
+
end
|
52
|
+
|
53
|
+
context "when composed of two operations using the factory method '#chain'" do
|
54
|
+
|
55
|
+
subject(:composed_operation) do
|
56
|
+
described_class.compose(string_generator, string_capitalizer).new
|
57
|
+
end
|
58
|
+
|
59
|
+
it { should succeed_to_perform.and_return("CHUNKY BACON") }
|
60
|
+
|
61
|
+
it { should utilize_operations(string_generator, string_capitalizer) }
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
context "when composed of two operations, one that generates a string and one that capitalizes strings, " do
|
66
|
+
|
67
|
+
subject(:composed_operation) do
|
68
|
+
operations = [string_generator, string_capitalizer]
|
69
|
+
|
70
|
+
Class.new(described_class) do
|
71
|
+
use operations.first
|
72
|
+
use operations.last
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should return a capitalized version of the generated string" do
|
77
|
+
composed_operation.perform(nil).should be == "CHUNKY BACON"
|
78
|
+
end
|
79
|
+
|
80
|
+
it { should utilize_operations(string_generator, string_capitalizer) }
|
81
|
+
|
82
|
+
end
|
83
|
+
|
84
|
+
context "when composed of three operations, one that generates a string, one that halts and one that capatalizes strings" do
|
85
|
+
|
86
|
+
subject(:composed_operation) do
|
87
|
+
described_class.compose(string_generator, halting_operation, string_capitalizer)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should return a capitalized version of the generated string" do
|
91
|
+
composed_operation.perform.should be == "chunky bacon"
|
92
|
+
end
|
93
|
+
|
94
|
+
it "should only execute the first two operations" do
|
95
|
+
string_generator.any_instance.should_receive(:perform).and_call_original
|
96
|
+
halting_operation.any_instance.should_receive(:perform).and_call_original
|
97
|
+
string_capitalizer.any_instance.should_not_receive(:perform)
|
98
|
+
composed_operation.perform
|
99
|
+
end
|
100
|
+
|
101
|
+
it { should utilize_operations(string_generator, halting_operation, string_capitalizer) }
|
102
|
+
end
|
103
|
+
|
104
|
+
context "when composed of two operations and provided with a between block" do
|
105
|
+
|
106
|
+
|
107
|
+
let(:logger) { stub("Logger").as_null_object }
|
108
|
+
|
109
|
+
subject(:composed_operation) do
|
110
|
+
string_generator = string_generator()
|
111
|
+
string_capitalizer = string_capitalizer()
|
112
|
+
logger = logger()
|
113
|
+
|
114
|
+
operation = described_class.compose do
|
115
|
+
use string_generator
|
116
|
+
use string_capitalizer
|
117
|
+
|
118
|
+
between do |a, b, payload|
|
119
|
+
logger.info("#{a.name} -> #{b.name} with #{payload.inspect} as payload")
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
operation.new
|
124
|
+
end
|
125
|
+
|
126
|
+
it { should succeed_to_perform.and_return("CHUNKY BACON") }
|
127
|
+
|
128
|
+
it "should generate the correct log message" do
|
129
|
+
logger.should_receive(:info).with("StringGenerator -> StringCapitalizer with \"chunky bacon\" as payload")
|
130
|
+
composed_operation.perform
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
@@ -0,0 +1,187 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "An operation with two before and two after filters =>" do
|
4
|
+
|
5
|
+
let(:test_operation) do
|
6
|
+
Class.new(ComposableOperations::Operation) do
|
7
|
+
|
8
|
+
processes :flow_control
|
9
|
+
|
10
|
+
attr_accessor :trace
|
11
|
+
|
12
|
+
def initialize(input = [], options = {})
|
13
|
+
super
|
14
|
+
self.trace = [:initialize]
|
15
|
+
end
|
16
|
+
|
17
|
+
before do
|
18
|
+
trace << :outer_before
|
19
|
+
fail "Fail in outer before" if flow_control.include?(:fail_in_outer_before)
|
20
|
+
halt "Halt in outer before" if flow_control.include?(:halt_in_outer_before)
|
21
|
+
end
|
22
|
+
|
23
|
+
before do
|
24
|
+
trace << :inner_before
|
25
|
+
fail "Fail in inner before" if flow_control.include?(:fail_in_inner_before)
|
26
|
+
halt "Halt in inner before" if flow_control.include?(:halt_in_inner_before)
|
27
|
+
end
|
28
|
+
|
29
|
+
after do
|
30
|
+
trace << :inner_after
|
31
|
+
fail "Fail in inner after" if flow_control.include?(:fail_in_inner_after)
|
32
|
+
halt "Halt in inner after" if flow_control.include?(:halt_in_inner_after)
|
33
|
+
end
|
34
|
+
|
35
|
+
after do
|
36
|
+
trace << :outer_after
|
37
|
+
fail "Fail in outer after" if flow_control.include?(:fail_in_outer_after)
|
38
|
+
halt "Halt in outer after" if flow_control.include?(:halt_in_outer_after)
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute
|
42
|
+
trace << :execute_start
|
43
|
+
fail "Fail in execute" if flow_control.include?(:fail_in_execute)
|
44
|
+
halt "Halt in execute" if flow_control.include?(:halt_in_execute)
|
45
|
+
trace << :execute_stop
|
46
|
+
:final_result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "when run and everything works as expected =>" do
|
52
|
+
subject { test_operation.new }
|
53
|
+
before(:each) { subject.perform }
|
54
|
+
|
55
|
+
it ("should run 'initialize' first") { subject.trace[0].should eq(:initialize) }
|
56
|
+
it ("should run 'outer before' after 'initialize'") { subject.trace[1].should eq(:outer_before) }
|
57
|
+
it ("should run 'inner before' after 'outer before'") { subject.trace[2].should eq(:inner_before) }
|
58
|
+
it ("should start 'execute' after 'inner before'") { subject.trace[3].should eq(:execute_start) }
|
59
|
+
it ("should stop 'execute' after it started 'execute'") { subject.trace[4].should eq(:execute_stop) }
|
60
|
+
it ("should run 'inner after' after 'execute'") { subject.trace[5].should eq(:inner_after) }
|
61
|
+
it ("should run 'outer after' after 'inner after'") { subject.trace[6].should eq(:outer_after) }
|
62
|
+
it ("should return :final_result as result") { subject.result.should eq(:final_result) }
|
63
|
+
it { should be_succeeded }
|
64
|
+
it { should_not be_failed }
|
65
|
+
it { should_not be_halted }
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
# Now: TEST ALL! the possible code flows systematically
|
70
|
+
|
71
|
+
test_vectors = [
|
72
|
+
{
|
73
|
+
:context => "no complications =>",
|
74
|
+
:input => [],
|
75
|
+
:output => :final_result,
|
76
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
77
|
+
:state => :succeeded
|
78
|
+
}, {
|
79
|
+
:context => "failing in outer_before filter =>",
|
80
|
+
:input => [:fail_in_outer_before],
|
81
|
+
:output => nil,
|
82
|
+
:trace => [:initialize, :outer_before, :inner_after, :outer_after],
|
83
|
+
:state => :failed
|
84
|
+
}, {
|
85
|
+
:context => "failing in inner_before filter =>",
|
86
|
+
:input => [:fail_in_inner_before],
|
87
|
+
:output => nil,
|
88
|
+
:trace => [:initialize, :outer_before, :inner_before, :inner_after, :outer_after],
|
89
|
+
:state => :failed
|
90
|
+
}, {
|
91
|
+
:context => "failing in execute =>",
|
92
|
+
:input => [:fail_in_execute],
|
93
|
+
:output => nil,
|
94
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :inner_after, :outer_after],
|
95
|
+
:state => :failed
|
96
|
+
}, {
|
97
|
+
:context => "failing in inner_after filter =>",
|
98
|
+
:input => [:fail_in_inner_after],
|
99
|
+
:output => nil,
|
100
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
101
|
+
:state => :failed
|
102
|
+
}, {
|
103
|
+
:context => "failing in outer_after filter =>",
|
104
|
+
:input => [:fail_in_outer_after],
|
105
|
+
:output => nil,
|
106
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
107
|
+
:state => :failed
|
108
|
+
}, {
|
109
|
+
:context => "halting in outer_before filter =>",
|
110
|
+
:input => [:halt_in_outer_before],
|
111
|
+
:output => [:halt_in_outer_before],
|
112
|
+
:trace => [:initialize, :outer_before, :inner_after, :outer_after],
|
113
|
+
:state => :halted
|
114
|
+
}, {
|
115
|
+
:context => "halting in inner_before filter =>",
|
116
|
+
:input => [:halt_in_inner_before],
|
117
|
+
:output => [:halt_in_inner_before],
|
118
|
+
:trace => [:initialize, :outer_before, :inner_before, :inner_after, :outer_after],
|
119
|
+
:state => :halted
|
120
|
+
}, {
|
121
|
+
:context => "halting in execute =>",
|
122
|
+
:input => [:halt_in_execute],
|
123
|
+
:output => [:halt_in_execute],
|
124
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :inner_after, :outer_after],
|
125
|
+
:state => :halted
|
126
|
+
}, {
|
127
|
+
:context => "halting in inner_after filter =>",
|
128
|
+
:input => [:halt_in_inner_after],
|
129
|
+
:output => [:halt_in_inner_after],
|
130
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
131
|
+
:state => :halted
|
132
|
+
}, {
|
133
|
+
:context => "halting in outer_after filter =>",
|
134
|
+
:input => [:halt_in_outer_after],
|
135
|
+
:output => [:halt_in_outer_after],
|
136
|
+
:trace => [:initialize, :outer_before, :inner_before, :execute_start, :execute_stop, :inner_after, :outer_after],
|
137
|
+
:state => :halted
|
138
|
+
}
|
139
|
+
]
|
140
|
+
|
141
|
+
context "when initialized with input that leads to =>" do
|
142
|
+
subject { test_operation.new(input) }
|
143
|
+
before(:each) { subject.perform }
|
144
|
+
|
145
|
+
test_vectors.each do |tv|
|
146
|
+
context tv[:context] do
|
147
|
+
let(:input) { tv[:input] }
|
148
|
+
let(:trace) { subject.trace }
|
149
|
+
|
150
|
+
it("then its trace should be #{tv[:trace].inspect}") { subject.trace.should eq(tv[:trace]) }
|
151
|
+
it("then its result should be #{tv[:output].inspect}") { subject.result.should eq(tv[:output]) }
|
152
|
+
it("then its succeeded? method should return #{(tv[:state] == :succeeded).inspect}") { subject.succeeded?.should eq(tv[:state] == :succeeded) }
|
153
|
+
it("then its failed? method should return #{(tv[:state] == :failed).inspect}") { subject.failed?.should eq(tv[:state] == :failed) }
|
154
|
+
it("then its halted? method should return #{(tv[:state] == :halted).inspect}") { subject.halted?.should eq(tv[:state] == :halted) }
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context "when halt and fail are used together" do
|
160
|
+
subject { test_operation.new([:halt_in_execute, :fail_in_inner_after]) }
|
161
|
+
it "should raise on calling operation.perform" do
|
162
|
+
expect { subject.perform }.to raise_error
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
context "when fail and halt are used together" do
|
167
|
+
subject { test_operation.new([:fail_in_execute, :halt_in_inner_after]) }
|
168
|
+
it "should raise on calling operation.perform" do
|
169
|
+
expect { subject.perform }.to raise_error
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
context "when halt is used twice" do
|
174
|
+
subject { test_operation.new([:halt_in_execute, :halt_in_inner_after]) }
|
175
|
+
it "should raise on calling operation.perform" do
|
176
|
+
expect { subject.perform }.to raise_error
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context "when fail is used twice" do
|
181
|
+
subject { test_operation.new([:fail_in_execute, :fail_in_inner_after]) }
|
182
|
+
it "should raise on calling operation.perform" do
|
183
|
+
expect { subject.perform }.to raise_error
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
end
|
@@ -0,0 +1,229 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe ComposableOperations::Operation do
|
4
|
+
|
5
|
+
context "that always returns nil when executed" do
|
6
|
+
|
7
|
+
subject(:nil_operation) do
|
8
|
+
class << (operation = described_class.new(''))
|
9
|
+
def execute
|
10
|
+
nil
|
11
|
+
end
|
12
|
+
end
|
13
|
+
operation
|
14
|
+
end
|
15
|
+
|
16
|
+
it { should succeed_to_perform.and_return(nil) }
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
context "that always halts" do
|
21
|
+
|
22
|
+
let(:halting_operation) do
|
23
|
+
Class.new(described_class) do
|
24
|
+
def execute
|
25
|
+
halt "Full stop!"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
let(:halting_operation_instance) do
|
31
|
+
halting_operation.new("Test")
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should return the input value when executed using the class' method perform" do
|
35
|
+
halting_operation.perform("Test").should be == "Test"
|
36
|
+
end
|
37
|
+
|
38
|
+
it "should return the input value when executed using the instance's peform method" do
|
39
|
+
halting_operation_instance.perform.should be == "Test"
|
40
|
+
end
|
41
|
+
|
42
|
+
it "should have halted after performing" do
|
43
|
+
halting_operation_instance.perform
|
44
|
+
halting_operation_instance.should be_halted
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
context "that always fails" do
|
50
|
+
|
51
|
+
let(:failing_operation) do
|
52
|
+
Class.new(described_class) do
|
53
|
+
def execute
|
54
|
+
fail "Operation failed"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
subject(:failing_operation_instance) do
|
60
|
+
failing_operation.new
|
61
|
+
end
|
62
|
+
|
63
|
+
before(:each) do
|
64
|
+
failing_operation_instance.perform
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should have nil as result" do
|
68
|
+
failing_operation_instance.result.should be_nil
|
69
|
+
end
|
70
|
+
|
71
|
+
it "should have failed" do
|
72
|
+
failing_operation_instance.should be_failed
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should have a message" do
|
76
|
+
failing_operation_instance.message.should_not be_nil
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should raise an error when executed using the class method perform" do
|
80
|
+
expect { failing_operation.perform }.to raise_error("Operation failed")
|
81
|
+
end
|
82
|
+
|
83
|
+
context "when extended with a finalizer" do
|
84
|
+
|
85
|
+
let(:supervisor) { mock("Supervisor") }
|
86
|
+
|
87
|
+
let(:failing_operation_instance_with_finalizer) do
|
88
|
+
supervisor = supervisor()
|
89
|
+
Class.new(failing_operation) do
|
90
|
+
after { supervisor.notify }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
subject(:failing_operation_instance_with_finalizer_instance) do
|
95
|
+
failing_operation_instance_with_finalizer.new
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should execute the finalizers" do
|
99
|
+
supervisor.should_receive(:notify)
|
100
|
+
failing_operation_instance_with_finalizer_instance.perform
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
105
|
+
context "when configured to raise a custom exception" do
|
106
|
+
|
107
|
+
let(:custom_exception) { Class.new(RuntimeError) }
|
108
|
+
|
109
|
+
subject(:failing_operation_with_custom_exception) do
|
110
|
+
custom_exception = custom_exception()
|
111
|
+
Class.new(failing_operation) do
|
112
|
+
raises custom_exception
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should raise the custom exeception when executed using the class method perform" do
|
117
|
+
expect { failing_operation_with_custom_exception.perform }.to raise_error(custom_exception, "Operation failed")
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
|
122
|
+
end
|
123
|
+
|
124
|
+
context "that always returns something when executed" do
|
125
|
+
|
126
|
+
let(:simple_operation) do
|
127
|
+
Class.new(described_class) do
|
128
|
+
def execute
|
129
|
+
""
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
subject(:simple_operation_instance) do
|
135
|
+
simple_operation.new
|
136
|
+
end
|
137
|
+
|
138
|
+
before(:each) do
|
139
|
+
simple_operation_instance.perform
|
140
|
+
end
|
141
|
+
|
142
|
+
it "should have a result" do
|
143
|
+
simple_operation_instance.result.should be
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should have succeeded" do
|
147
|
+
simple_operation_instance.should be_succeeded
|
148
|
+
end
|
149
|
+
|
150
|
+
context "when extended with a preparator and a finalizer" do
|
151
|
+
|
152
|
+
let(:logger) { double("Logger") }
|
153
|
+
|
154
|
+
subject(:simple_operation_with_preparator_and_finalizer) do
|
155
|
+
logger = logger()
|
156
|
+
Class.new(simple_operation) do
|
157
|
+
before { logger.info("preparing") }
|
158
|
+
after { logger.info("finalizing") }
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "should execute the preparator and finalizer when performing" do
|
163
|
+
logger.should_receive(:info).ordered.with("preparing")
|
164
|
+
logger.should_receive(:info).ordered.with("finalizing")
|
165
|
+
simple_operation_with_preparator_and_finalizer.perform
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
|
170
|
+
context "when extended with a finalizer that checks that the result is not an empty string" do
|
171
|
+
|
172
|
+
let(:simple_operation_with_sanity_check) do
|
173
|
+
Class.new(simple_operation) do
|
174
|
+
after { fail "the operational result is an empty string" if self.result == "" }
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
subject(:simple_operation_with_sanity_check_instance) do
|
179
|
+
simple_operation_with_sanity_check.new
|
180
|
+
end
|
181
|
+
|
182
|
+
it { should fail_to_perform.because("the operational result is an empty string") }
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
187
|
+
|
188
|
+
context "that can be parameterized" do
|
189
|
+
|
190
|
+
subject(:string_multiplier) do
|
191
|
+
Class.new(described_class) do
|
192
|
+
property :multiplier, :default => 3
|
193
|
+
|
194
|
+
def execute
|
195
|
+
input.to_s * multiplier
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should operate according to the specified default value" do
|
201
|
+
string_multiplier.perform("-").should be == "---"
|
202
|
+
end
|
203
|
+
|
204
|
+
it "should allow to overwrite default settings" do
|
205
|
+
string_multiplier.perform("-", :multiplier => 5).should be == "-----"
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
context "that processes two values (a string and a multiplier)" do
|
211
|
+
|
212
|
+
subject(:string_multiplier) do
|
213
|
+
Class.new(described_class) do
|
214
|
+
processes :string, :multiplier
|
215
|
+
|
216
|
+
def execute
|
217
|
+
string * multiplier
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should build a string that is multiplier-times long" do
|
223
|
+
string_multiplier.perform(["-", 3]).should be == "---"
|
224
|
+
end
|
225
|
+
|
226
|
+
end
|
227
|
+
|
228
|
+
end
|
229
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,124 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: composable_operations
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Konstantin Tennhard
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-10 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: smart_properties
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: bundler
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ~>
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.3'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ~>
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.3'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - '>='
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '2.11'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ~>
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '2.11'
|
69
|
+
description: Composable Operations is a tool set for creating easy-to-use operation
|
70
|
+
pipelines.
|
71
|
+
email:
|
72
|
+
- me@t6d.de
|
73
|
+
executables: []
|
74
|
+
extensions: []
|
75
|
+
extra_rdoc_files: []
|
76
|
+
files:
|
77
|
+
- .gitignore
|
78
|
+
- Gemfile
|
79
|
+
- LICENSE.txt
|
80
|
+
- README.md
|
81
|
+
- Rakefile
|
82
|
+
- composable_operations.gemspec
|
83
|
+
- lib/composable_operations.rb
|
84
|
+
- lib/composable_operations/composed_operation.rb
|
85
|
+
- lib/composable_operations/matcher.rb
|
86
|
+
- lib/composable_operations/matcher/fail_to_perform.rb
|
87
|
+
- lib/composable_operations/matcher/succeed_to_perform.rb
|
88
|
+
- lib/composable_operations/matcher/utilize_operation.rb
|
89
|
+
- lib/composable_operations/operation.rb
|
90
|
+
- lib/composable_operations/operation_error.rb
|
91
|
+
- lib/composable_operations/version.rb
|
92
|
+
- spec/composable_operations/composed_operation_spec.rb
|
93
|
+
- spec/composable_operations/control_flow_spec.rb
|
94
|
+
- spec/composable_operations/operation_spec.rb
|
95
|
+
- spec/spec_helper.rb
|
96
|
+
homepage: http://github.com/t6d/composable_operations
|
97
|
+
licenses:
|
98
|
+
- MIT
|
99
|
+
metadata: {}
|
100
|
+
post_install_message:
|
101
|
+
rdoc_options: []
|
102
|
+
require_paths:
|
103
|
+
- lib
|
104
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - '>='
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - '>='
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '0'
|
114
|
+
requirements: []
|
115
|
+
rubyforge_project:
|
116
|
+
rubygems_version: 2.0.3
|
117
|
+
signing_key:
|
118
|
+
specification_version: 4
|
119
|
+
summary: Tool set for operation pipelines.
|
120
|
+
test_files:
|
121
|
+
- spec/composable_operations/composed_operation_spec.rb
|
122
|
+
- spec/composable_operations/control_flow_spec.rb
|
123
|
+
- spec/composable_operations/operation_spec.rb
|
124
|
+
- spec/spec_helper.rb
|