simpler_workflow 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in simpler_workflow.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Snugg Home LLC
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,180 @@
1
+ # SimplerWorkflow
2
+
3
+ A wrapper around Amazon's Simple Workflow Service meant to simplify declaring and using activities and workflow. Provides some sane defaults
4
+ and work around some idiosyncracies of the platform.
5
+
6
+ ## Installation
7
+
8
+ Add this line to your application's Gemfile:
9
+
10
+ gem 'simpler_workflow'
11
+
12
+ And then execute:
13
+
14
+ $ bundle
15
+
16
+ Or install it yourself as:
17
+
18
+ $ gem install simpler_workflow
19
+
20
+ ## Usage
21
+
22
+ ### Configuring AWS Gem
23
+
24
+ We are using the aws-sdk gem to communicate with Amazon's service. You can configure the service by putting an ```aws.yml``` file
25
+ in your config directory. The file should contain the following information:
26
+
27
+ ```yaml
28
+ development:
29
+ access_key_id: <Amazon Acess Key ID>
30
+ secret_access_key: <Amazon's secret access key>
31
+ ```
32
+
33
+ This will authenticate your application or script against AWS and give you access to your SWF domain, workflows and activity.
34
+
35
+ ### Access a domain
36
+
37
+ You will need to get a handle on a SWF domain before you can do anything else. Domains are accessed using the ```SimplerWorkflow::Domain```
38
+ class. This declares a domain that does not retain workflow execution data:
39
+
40
+ ```ruby
41
+ domain = SimplerWorkflow::Domain["my-domain"]
42
+ ```
43
+ An other option is to use the ```domains``` method to get a handle on a domain and pass a block. This allows you to write the following code:
44
+
45
+ ```ruby
46
+ domain = SimplerWorkflow::Domain.domains("my_domain") do
47
+ # Register activities
48
+ register_activity(:an_activity, '1.0.0') do
49
+ # See details below...
50
+ end
51
+ # Register workflow(s)
52
+ register_workflow(:a_workflow, '1.1.0') do
53
+ # See details below...
54
+ end
55
+ end
56
+ ```
57
+
58
+ You can also get a handle on a domain that retains information about workflow execution for 10 days with the following code:
59
+
60
+ ```ruby
61
+ domain = SimplerWorkflow::Domain.new("my-domain", 10)
62
+ ```
63
+
64
+ Domains are scoped by AWS accounts. The name of the domain must be unique within the account. You do not need to create the domain on AWS
65
+ since it is created the first time it is accessed.
66
+
67
+ ### Creating an activity
68
+
69
+ Activities perform the work attached to the workflow and report back to SWF when the activity completes or it fails.
70
+
71
+ ```SimplerWorkflow``` makes it easier to register an activity with your domain.
72
+
73
+ Activities must provide the following:
74
+ * A name
75
+ * A version
76
+ * Some code to run when it is invoked
77
+
78
+ You can also optionaly declare when what to do when the activity fails or succeeds.
79
+
80
+ ```ruby
81
+ my_activity = domain.register_activity :my_activity, "1.0.0" do
82
+ perform_activity do |task|
83
+ input = task.input
84
+ puts task
85
+ end
86
+ end
87
+
88
+ my_activity.start_activity_loop
89
+ ```
90
+
91
+ The activity manages a loop that waits for messages from SWF.
92
+
93
+ Activities are passed a task parameter. This parameter is provided to the activity by SWF and provides a lot of information about the task
94
+ at end. One item passed is the input attribute.
95
+
96
+ The block attached to the perform_activity method is called when the activity is invoked. This block contains the actions that an activity
97
+ will perform. The ```SimplerWorkflow::Activity``` class will automatically report that the activity completed successfully when the
98
+ block returns unless a response has been provided in the block. It will automatically report that an activity failed when an unhandled
99
+ exception is thrown within the block.
100
+
101
+ The activity can influence what happens when the activity succeeds or fail. You can specify the activity's failure response through the
102
+ ```SimplerWorkflow::Activity#on_fail``` method. By default, the activity will ask the workflow to abort itself on failure. You can also
103
+ ask the workflow to repeat the activity by passing ```:retry``` to the method:
104
+
105
+ ```ruby
106
+ my_activity = domain.register_activity :my_activity, "1.0.0" do
107
+ on_fail :retry
108
+
109
+ perform_activity do |task|
110
+ # ...
111
+ end
112
+ end
113
+ ```
114
+
115
+ The activity can also tell a workflow what activity to trigger next on the workflow. This only works when using the default decision
116
+ loop (described later). This is done by declaring what is the next activity should be:
117
+
118
+ ```ruby
119
+ my_activity = domain.register_activity :my_activity, "1.0.0" do
120
+ on_success :my_next_activity, "1.0.0"
121
+
122
+ perform_activity do |task|
123
+ # ...
124
+ end
125
+ end
126
+ ```
127
+
128
+ ### Workflow and Decision Loops
129
+
130
+ The next key concept in ```SimplerWorkflow``` is the workflow. The workflow decides what activities to invoke, what to do when
131
+ they complete and what to do when they fail. The ```SimplerWorkflow::Workflow``` object manages the decision loop.
132
+
133
+ By default, the workflow is setup to allow for a linear set of activities until the list runs out. This is convenient for simple
134
+ workflows. There are also hooks to override what happens with each decision point to accomodate more complex workflows.
135
+
136
+ Workflows are declared and registered through the ```SimplerWorkflow::Domain#register_workflow``` method. This will register a
137
+ workflow and configure it to start a linear workflow with version 1.0.0 of the :my_activity activity:
138
+
139
+ ```ruby
140
+ my_workflow = domain.register_workflow :my_workflow, '1.0.0' do
141
+ initial_activity :my_activity, '1.0.0'
142
+ end
143
+ ```
144
+
145
+ The next step is to start the decision loop:
146
+
147
+ ```ruby
148
+ my_workflow.decision_loop
149
+ ```
150
+
151
+ #### Customizing the workflow
152
+
153
+ There are hooks for different section of the decision loop. You can specify what happens when the workflow is started with the ```on_start_execution```
154
+ method:
155
+
156
+ ```ruby
157
+ my_workflow = domain.register_workflow :my_workflow, '1.0.0' do
158
+ on_start_execution do |task, event|
159
+ puts "Mary had a little lamb"
160
+ task.schedule_activity_task my_activity.to_activity_type, :input => { :my_param => 'value'}
161
+ end
162
+ end
163
+ ```
164
+
165
+ The task and event parameters are received from SWF. Unfortunately, you must still work within the constraints of the AWS SWF SDK. The ```SimplerWorkflow::Activity#to_activity_type``` generates the proper incantation used by SWF to identify and locate an activity.
166
+
167
+ You can also define similar hooks for events using the following methods:
168
+
169
+ * ```on_activity_completed``` is called when an activity completes and SWF reports back to the decision loop.
170
+ * ```on_activity_failed``` is called when an activity reports a failure to SWF.
171
+
172
+ ## Contributing
173
+
174
+ We welcome all kinds of contributions. This include code, fixes, issues, documentation, tests... Here's how you can contribute:
175
+
176
+ 1. Fork it
177
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
178
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
179
+ 4. Push to the branch (`git push origin my-new-feature`)
180
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ require 'aws-sdk'
2
+
3
+ module SimplerWorkflow
4
+ def SimplerWorkflow.domain(domain_name)
5
+ @domains ||= {}
6
+ @domains[domain_name.to_sym] ||= Domain.new(domain_name)
7
+ end
8
+
9
+ def SimplerWorkflow.swf
10
+ @swf ||= ::AWS::SimpleWorkflow.new
11
+ end
12
+
13
+ autoload :Version, 'simpler_workflow/version'
14
+ autoload :Domain, 'simpler_workflow/domain'
15
+ autoload :Workflow, 'simpler_workflow/workflow'
16
+ autoload :Activity, 'simpler_workflow/activity'
17
+ end
18
+
19
+ class Map
20
+ def Map.from_json(json)
21
+ from_hash(JSON.parse(json))
22
+ end
23
+ end
@@ -0,0 +1,125 @@
1
+ module SimplerWorkflow
2
+ class Activity
3
+ attr_reader :domain, :name, :version, :options, :next_activity
4
+
5
+ def initialize(domain, name, version, options = {})
6
+ Activity.activities[[name, version]] ||= begin
7
+ default_options = {
8
+ :default_task_list => name,
9
+ :default_task_start_to_close_timeout => 1 * 60 * 60,
10
+ :default_task_schedule_to_start_timeout => 20,
11
+ :default_task_schedule_to_close_timeout => 1 * 60 * 60,
12
+ :default_task_heartbeat_timeout => :none
13
+ }
14
+ @options = default_options.merge(options)
15
+ @domain = domain
16
+ @name = name
17
+ @version = version
18
+ @failure_policy = :abort
19
+ self
20
+ end
21
+ end
22
+
23
+ def method_missing(meth_name, *args)
24
+ if @options.has_key?(meth_name.to_sym)
25
+ @options[meth_name.to_sym] = args[0]
26
+ else
27
+ super
28
+ end
29
+ end
30
+
31
+ def on_success(activity, version = nil)
32
+ case activity
33
+ when Hash
34
+ activity.symbolize_keys!
35
+ name = activity[:name].to_sym
36
+ version = activity[:version]
37
+ when String
38
+ name = activity.to_sym
39
+ when Symbol
40
+ name = activity
41
+ end
42
+ @next_activity = { :name => name, :version => version }
43
+ end
44
+
45
+ def on_fail(failure_policy)
46
+ @failure_policy = failure_policy.to_sym
47
+ end
48
+
49
+ def failure_policy
50
+ @failure_policy || :abort
51
+ end
52
+
53
+ def perform_activity(&block)
54
+ @perform_task = block
55
+ end
56
+
57
+ def perform_task(task)
58
+ logger.info("Performing task #{name}")
59
+ @perform_task.call(task)
60
+ rescue => e
61
+ logger.error e.message
62
+ logger.error e.backtrace.join("\n")
63
+ task.fail! :reason => e.message, :details => {:failure_policy => failure_policy}.to_json
64
+ end
65
+
66
+ def to_activity_type
67
+ domain.activity_types[name.to_s, version]
68
+ end
69
+
70
+ def start_activity_loop
71
+ Thread.new do
72
+ loop do
73
+ begin
74
+ logger.info("Starting activity_loop for #{name}")
75
+ domain.activity_tasks.poll(name.to_s) do |task|
76
+ logger.info("Received task...")
77
+ perform_task(task)
78
+ unless task.responded?
79
+ if next_activity
80
+ result = {:next_activity => next_activity}.to_json
81
+ task.complete!(:result => result)
82
+ else
83
+ task.complete!
84
+ end
85
+ end
86
+ end
87
+ rescue Timeout::Error => e
88
+ rescue => e
89
+ logger.error(e.message)
90
+ logger.error(e.backtrace.join("\n"))
91
+ end
92
+ end
93
+ end
94
+ end
95
+
96
+ def self.[](name, version = nil)
97
+ case name
98
+ when String
99
+ name = name.to_sym
100
+ when Hash
101
+ name.symbolize_keys!
102
+ version = name[:version]
103
+ name = name[:name]
104
+ end
105
+ activities[[name, version]]
106
+ end
107
+
108
+ def self.register(name, version, activity)
109
+ activities[[name, version]] = activity
110
+ end
111
+
112
+ protected
113
+ def self.activities
114
+ @activities ||= {}
115
+ end
116
+
117
+ def self.swf
118
+ SimplerWorkflow.swf
119
+ end
120
+
121
+ def logger
122
+ $logger || Rails.logger
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,97 @@
1
+ module SimplerWorkflow
2
+ class Domain
3
+ def initialize(domain_name, retention = 2, &block)
4
+ domain_name = domain_name.to_s
5
+ @domain = swf.domains[domain_name]
6
+ unless swf.domains.include?(@domain)
7
+ @domain = swf.domains.create(domain_name, retention)
8
+ end
9
+
10
+ self.instance_eval(&block) if block
11
+
12
+ self
13
+ end
14
+
15
+ def Domain.domains(domain_name, &block)
16
+ @domains ||= {}
17
+ @domains[domain_name] ||= Domain.new(domain_name)
18
+ @domains[domain_name].instance_eval(&block) if block
19
+ @domains[domain_name]
20
+ end
21
+
22
+ def Domain.[](domain_name)
23
+ Domain.domains(domain_name)
24
+ end
25
+
26
+ def register_workflow(name, version, &block)
27
+ unless workflow = Workflow[name, version]
28
+ workflow = Workflow.new(self, name, version)
29
+ end
30
+
31
+ workflow.instance_eval(&block) if block
32
+
33
+ begin
34
+ self.domain.workflow_types.register(name.to_s, version, workflow.options)
35
+ rescue ::AWS::SimpleWorkflow::Errors::TypeAlreadyExistsFault => e
36
+ # Instance already registered...
37
+ end
38
+ workflow
39
+ end
40
+
41
+ def workflows
42
+ Workflow
43
+ end
44
+
45
+ def start_workflow(name, version, input)
46
+ logger.info("Starting workflow[#{name},#{version}]")
47
+ workflow = Workflow[name, version] || Workflow.new(self, name, version)
48
+ workflow.start_workflow(input)
49
+ end
50
+
51
+ def activities
52
+ Activity
53
+ end
54
+
55
+ def activity_types
56
+ domain.activity_types
57
+ end
58
+
59
+ def register_activity(name, version, &block)
60
+ unless activity = Activity[name, version]
61
+ logger.info("Registering Activity[#{name},#{version}]")
62
+ activity = Activity.new(self, name, version)
63
+ end
64
+
65
+ activity.instance_eval(&block) if block
66
+
67
+ begin
68
+ self.domain.activity_types.register(name.to_s, version, activity.options)
69
+ rescue ::AWS::SimpleWorkflow::Errors::TypeAlreadyExistsFault
70
+ # Nothing to do, should probably log something here...
71
+ end
72
+
73
+ activity
74
+ end
75
+
76
+ def method_missing(meth_name, *args)
77
+ if domain.respond_to?(meth_name.to_sym)
78
+ domain.send(meth_name.to_sym, *args)
79
+ else
80
+ super
81
+ end
82
+ end
83
+
84
+ protected
85
+ def swf
86
+ SimplerWorkflow.swf
87
+ end
88
+
89
+ def domain
90
+ @domain
91
+ end
92
+
93
+ def logger
94
+ $logger || Rails.logger
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,3 @@
1
+ module SimplerWorkflow
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,150 @@
1
+ module SimplerWorkflow
2
+ class Workflow
3
+ attr_reader :task_list, :domain, :name, :version, :options, :initial_activity_type
4
+
5
+ def initialize(domain, name, version, options = {})
6
+ Workflow.workflows[[name, version]] ||= begin
7
+ default_options = {
8
+ :default_task_list => name,
9
+ :default_task_start_to_close_timeout => 1 * 24 * 60 * 60,
10
+ :default_execution_start_to_close_timeout => 1 * 24 * 60 * 60,
11
+ :default_child_policy => :terminate
12
+ }
13
+ @options = default_options.merge(options)
14
+ @domain = domain
15
+ @name = name
16
+ @version = version
17
+ self
18
+ end
19
+ end
20
+
21
+ def initial_activity(name, version = nil)
22
+ if activity = Activity[name.to_sym, version]
23
+ @initial_activity_type = activity.to_activity_type
24
+ elsif activity = domain.activity_types[name.to_s, version]
25
+ @initial_activity_type = activity
26
+ end
27
+ end
28
+
29
+ def decision_loop
30
+ logger.info("Starting decision loop for #{name.to_s}, #{version} listening to #{task_list}")
31
+ domain.decision_tasks.poll(task_list) do |decision_task|
32
+ logger.info("Received decision task")
33
+ decision_task.new_events.each do |event|
34
+ logger.info("Processing #{event.event_type}")
35
+ case event.event_type
36
+ when 'WorkflowExecutionStarted'
37
+ start_execution(decision_task, event)
38
+ when 'ActivityTaskCompleted'
39
+ activity_completed(decision_task, event)
40
+ when 'ActivityTaskFailed'
41
+ activity_failed(decision_task, event)
42
+ end
43
+ end
44
+ end
45
+ rescue Timeout::Error => e
46
+ retry
47
+ end
48
+
49
+ def task_list
50
+ @options[:default_task_list][:name].to_s
51
+ end
52
+
53
+ def start_execution(decision_task, event)
54
+ logger.info "Starting the execution of the job."
55
+ if @on_start_execution && @on_start_execution.respond_to?(:call)
56
+ @on_start_execution.call(decision_task, event)
57
+ else
58
+ decision_task.schedule_activity_task initial_activity_type, :input => event.attributes.input
59
+ end
60
+ end
61
+
62
+ def activity_completed(decision_task, event)
63
+ if @on_activity_completed && @on_activity_completed.respond_to?(:call)
64
+ @on_activity_completed.call(decision_task, event)
65
+ else
66
+ if event.attributes.keys.include?(:result)
67
+ result = Map.from_json(event.attributes.result)
68
+ next_activity = result[:next_activity]
69
+ activity_type = domain.activity_types[next_activity[:name], next_activity[:version]]
70
+ decision_task.schedule_activity_task activity_type, :input => scheduled_event(decision_task, event).attributes.input
71
+ else
72
+ logger.info("Workflow #{name}, #{version} completed")
73
+ decision_task.complete_workflow_execution :result => 'success'
74
+ end
75
+ end
76
+ end
77
+
78
+ def activity_failed(decision_task, event)
79
+ logger.info("Activity failed.")
80
+ if @on_activity_failed && @on_activity_failed.respond_to?(:call)
81
+ @on_activity_failed.call(decision_task, event)
82
+ else
83
+ if event.attributes.keys.include?(:details)
84
+ details = Map.from_json(event.attributes.details)
85
+ case details.failure_policy.to_sym
86
+ when :abort
87
+ decision_task.cancel_workflow_execution
88
+ when :retry
89
+ logger.info("Retrying activity #{last_activity(decision_task, event).name} #{last_activity(decision_task, event).version}")
90
+ decision_task.schedule_activity_task last_activity(decision_task, event), :input => last_input(decision_task, event)
91
+ end
92
+ else
93
+ decision_task.cancel_workflow_execution
94
+ end
95
+ end
96
+ end
97
+
98
+ def start_workflow(input)
99
+ domain.workflow_types[name.to_s, version].start_execution(:input => input)
100
+ end
101
+
102
+ def on_start_execution(&block)
103
+ @on_start_execution = block
104
+ end
105
+
106
+ def on_activity_completed(&block)
107
+ @on_activity_completed = block
108
+ end
109
+
110
+ def on_activity_failed(&block)
111
+ @on_activity_failed = block
112
+ end
113
+
114
+ def self.[](name, version)
115
+ workflows[[name, version]]
116
+ end
117
+
118
+ def self.register(name, version, workflow)
119
+ workflows[[name, version]] = workflow
120
+ end
121
+
122
+ def method_missing(meth_name, *args)
123
+ if @options.has_key?(meth_name.to_sym)
124
+ @options[meth_name.to_sym] = args[0]
125
+ else
126
+ super
127
+ end
128
+ end
129
+
130
+ protected
131
+ def self.workflows
132
+ @workflows ||= {}
133
+ end
134
+
135
+ def self.swf
136
+ SimplerWorkflow.swf
137
+ end
138
+
139
+ def scheduled_event(decision_task, event)
140
+ decision_task.events.to_a[event.attributes.scheduled_event_id - 1]
141
+ end
142
+ def last_activity(decision_task, event)
143
+ scheduled_event(decision_task, event).attributes.activity_type
144
+ end
145
+
146
+ def last_input(decision_task, event)
147
+ scheduled_event(decision_task, event).attributes.input
148
+ end
149
+ end
150
+ end
@@ -0,0 +1,16 @@
1
+ module SimplerWorkflow
2
+ class WorkflowCollection
3
+ def [](name, version)
4
+ registry[[name,version]]
5
+ end
6
+
7
+ def []=(name, version, value)
8
+ registry[[name, version]] = value
9
+ end
10
+
11
+ protected
12
+ def registry
13
+ @registry ||= {}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/simpler_workflow/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Frederic Jean"]
6
+ gem.email = ["fred@snugghome.com"]
7
+ gem.description = %q{A wrapper around Amazon's Simple Workflow Service}
8
+ gem.summary = %q{A wrapper and DSL around Amazon's Simple Workflow Service with the goal of making it almost pleasant to define workflows.}
9
+ gem.homepage = "https://github.com/SnuggHome/simpler_workflow"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "simpler_workflow"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = SimplerWorkflow::VERSION
17
+
18
+ gem.add_dependency 'aws-sdk', '~> 1.4.0'
19
+ gem.add_dependency 'map'
20
+ gem.add_development_dependency 'rspec'
21
+ end
metadata ADDED
@@ -0,0 +1,120 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: simpler_workflow
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Frederic Jean
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-04-24 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: aws-sdk
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ hash: 7
29
+ segments:
30
+ - 1
31
+ - 4
32
+ - 0
33
+ version: 1.4.0
34
+ type: :runtime
35
+ version_requirements: *id001
36
+ - !ruby/object:Gem::Dependency
37
+ name: map
38
+ prerelease: false
39
+ requirement: &id002 !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ">="
43
+ - !ruby/object:Gem::Version
44
+ hash: 3
45
+ segments:
46
+ - 0
47
+ version: "0"
48
+ type: :runtime
49
+ version_requirements: *id002
50
+ - !ruby/object:Gem::Dependency
51
+ name: rspec
52
+ prerelease: false
53
+ requirement: &id003 !ruby/object:Gem::Requirement
54
+ none: false
55
+ requirements:
56
+ - - ">="
57
+ - !ruby/object:Gem::Version
58
+ hash: 3
59
+ segments:
60
+ - 0
61
+ version: "0"
62
+ type: :development
63
+ version_requirements: *id003
64
+ description: A wrapper around Amazon's Simple Workflow Service
65
+ email:
66
+ - fred@snugghome.com
67
+ executables: []
68
+
69
+ extensions: []
70
+
71
+ extra_rdoc_files: []
72
+
73
+ files:
74
+ - .gitignore
75
+ - Gemfile
76
+ - LICENSE
77
+ - README.md
78
+ - Rakefile
79
+ - lib/simpler_workflow.rb
80
+ - lib/simpler_workflow/activity.rb
81
+ - lib/simpler_workflow/domain.rb
82
+ - lib/simpler_workflow/version.rb
83
+ - lib/simpler_workflow/workflow.rb
84
+ - lib/simpler_workflow/workflow_collection.rb
85
+ - simpler_workflow.gemspec
86
+ homepage: https://github.com/SnuggHome/simpler_workflow
87
+ licenses: []
88
+
89
+ post_install_message:
90
+ rdoc_options: []
91
+
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ">="
98
+ - !ruby/object:Gem::Version
99
+ hash: 3
100
+ segments:
101
+ - 0
102
+ version: "0"
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ hash: 3
109
+ segments:
110
+ - 0
111
+ version: "0"
112
+ requirements: []
113
+
114
+ rubyforge_project:
115
+ rubygems_version: 1.8.20
116
+ signing_key:
117
+ specification_version: 3
118
+ summary: A wrapper and DSL around Amazon's Simple Workflow Service with the goal of making it almost pleasant to define workflows.
119
+ test_files: []
120
+