blue_colr 0.0.9 → 0.1.0
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.
- data/README.rdoc +15 -9
- data/bin/bluecolrd +47 -39
- data/lib/blue_colr/graph_output.rb +74 -0
- data/lib/blue_colr.rb +117 -36
- metadata +26 -9
data/README.rdoc
CHANGED
@@ -18,7 +18,7 @@ features than builtin Ruby's +Logger+.
|
|
18
18
|
|
19
19
|
require 'blue_colr'
|
20
20
|
|
21
|
-
BlueColr.
|
21
|
+
BlueColr.launch do
|
22
22
|
run 'echo These processes'
|
23
23
|
run 'echo will be ran sequentially.'
|
24
24
|
parallel do
|
@@ -43,15 +43,21 @@ Note: the code above will not _start_ the processes by itself, but enqueue them
|
|
43
43
|
to the database, by default. A separate process called +bluecolrd+ is
|
44
44
|
used for that.
|
45
45
|
|
46
|
+
The following chart, generated by the same code above, is its execution sequence:
|
47
|
+
|
48
|
+
http://github.com/downloads/jablan/blue_colr/readme_example.png
|
49
|
+
|
46
50
|
== Requirements and Configuration
|
47
51
|
|
52
|
+
In order to access the database, blue_colr requires sequel ORM library, if you
|
53
|
+
don't have it, its gem will be installed along with blue_colr.
|
54
|
+
|
48
55
|
Blue_colr uses a relational database to simulate a process queue so you will have
|
49
56
|
to provide one. It relies on two tables, named +process_items+ and
|
50
|
-
+process_item_dependencies+ to work. +db/+ directory contains
|
51
|
-
for creating these two
|
57
|
+
+process_item_dependencies+ to work. +db/+ directory contains Sequel migrations
|
58
|
+
for creating these two:
|
52
59
|
|
53
|
-
|
54
|
-
don't have it, its gem will be installed along with blue_colr.
|
60
|
+
sequel -m db/ sqlite://examples/test.db
|
55
61
|
|
56
62
|
Basic configuration is passed to blue_colr either by setting options from your
|
57
63
|
code, or (if not set), blue_colr will parse your command line arguments and
|
@@ -77,12 +83,12 @@ when enqueuing them. Then you can have multiple daemons running, each one of the
|
|
77
83
|
targeting specific environment. That allows easy distribution of your tasks across
|
78
84
|
multiple machines, while keeping them synchronized, like the following scenario:
|
79
85
|
|
80
|
-
* Start tasks a and b on machine X and c on machine Y
|
81
|
-
* When all above are sucessfully done, start task d on machine Z
|
86
|
+
* Start tasks +a+ and +b+ on machine +X+ and +c+ on machine +Y+
|
87
|
+
* When all above are sucessfully done, start task +d+ on machine +Z+
|
82
88
|
|
83
89
|
== ToDo
|
84
90
|
|
85
|
-
*
|
86
|
-
*
|
91
|
+
* More tests
|
92
|
+
* Better docs
|
87
93
|
* Examples
|
88
94
|
|
data/bin/bluecolrd
CHANGED
@@ -23,7 +23,7 @@ def logger(name = nil)
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def init_logger
|
26
|
-
if @conf['log4r_config']
|
26
|
+
if Module::const_defined?(:Log4r) && @conf['log4r_config']
|
27
27
|
log_cfg = Log4r::YamlConfigurator # shorthand
|
28
28
|
log_cfg['ENVIRONMENT'] = @environment if @environment
|
29
29
|
log_cfg['LOGFILENAME'] = @log_file
|
@@ -33,7 +33,7 @@ def init_logger
|
|
33
33
|
|
34
34
|
@logger = Log4r::Logger
|
35
35
|
else
|
36
|
-
@logger = {'default' => Logger.new(@log_file)}
|
36
|
+
@logger = {'default' => Logger.new(@log_file || STDOUT)}
|
37
37
|
end
|
38
38
|
logger.level = @args['debuglevel'] || Logger::WARN
|
39
39
|
end
|
@@ -82,15 +82,15 @@ def ok_to_run?
|
|
82
82
|
# !@args['max'] || @pids.size < @args['max']
|
83
83
|
end
|
84
84
|
|
85
|
-
def run process
|
86
|
-
logger.debug "Running
|
85
|
+
def run process, running_state
|
86
|
+
logger.debug "Running process ##{process[:id]}:"
|
87
87
|
script = process[:cmd]
|
88
88
|
logger.debug script
|
89
89
|
id = process[:id]
|
90
90
|
|
91
91
|
# update process item in the db
|
92
92
|
# set status of process_item to "running"
|
93
|
-
@db[:process_items].filter(:id => id).update(:status =>
|
93
|
+
@db[:process_items].filter(:id => id).update(:status => running_state, :started_at => Time.now)
|
94
94
|
|
95
95
|
log_path = @conf['log_path'] || '.'
|
96
96
|
log_path = (process[:process_from] || Time.now).strftime(log_path) # interpolate date
|
@@ -111,16 +111,17 @@ def run process
|
|
111
111
|
exitstatus = 99
|
112
112
|
end
|
113
113
|
|
114
|
+
final_state = BlueColr.state_from_running(running_state, ok)
|
114
115
|
# find corresponding process_item
|
115
116
|
# change its status in the DB and update ended_at timestamp
|
116
117
|
@db[:process_items].filter(:id => process[:id]).update(
|
117
|
-
:status =>
|
118
|
+
:status => final_state,
|
118
119
|
:exit_code => exitstatus,
|
119
120
|
:ended_at => Time.now
|
120
121
|
)
|
121
|
-
logger(process[:logger]).error(@error_log_msg % process.to_hash) unless ok
|
122
|
+
# logger(process[:logger]).error(@error_log_msg % process.to_hash) unless ok
|
122
123
|
|
123
|
-
logger.info "Process ended: id #{process[:id]} #{$?}"
|
124
|
+
# logger.info "Process ended: id #{process[:id]} #{$?}"
|
124
125
|
end
|
125
126
|
end
|
126
127
|
|
@@ -129,16 +130,17 @@ end
|
|
129
130
|
# pid => process, hash of started processes
|
130
131
|
@pids = {}
|
131
132
|
|
132
|
-
|
133
|
+
@args = parse_command_line(ARGV)
|
133
134
|
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
135
|
+
raise "No configuration file defined (-c <config>)." unless @args && @args["config"]
|
136
|
+
raise "Couldn't read #{@args["config"]} file." unless @args['config'] && @conf = YAML::load(File.new(@args["config"]).read)
|
137
|
+
BlueColr.conf = @conf
|
138
|
+
@max_processes = @args['max'] || @conf['max_processes'] || 0 # default unlimited
|
139
|
+
@environment = @args['environment'] || @conf['environment'] || nil
|
140
|
+
@log_file = @args['logfile'] || "process_daemon_#{@environment}.log"
|
141
|
+
@error_log_msg = @conf['error_log_msg'] || 'Process failed: id %{id}'
|
140
142
|
|
141
|
-
|
143
|
+
init_logger
|
142
144
|
|
143
145
|
begin
|
144
146
|
@db = Sequel.connect(@conf['db_url'], :logger => logger('sequel')) # try to use sequel logger, if defined
|
@@ -147,34 +149,40 @@ begin
|
|
147
149
|
|
148
150
|
loop do
|
149
151
|
# get all pending items
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
152
|
+
pending_processes = @db[:process_items].filter(:status => BlueColr.get_pending_states).all
|
153
|
+
pending_processes = pending_processes.map do |process|
|
154
|
+
# get all the parents' statuses
|
155
|
+
parent_statuses = @db[:process_items].
|
156
|
+
join(:process_item_dependencies, :depends_on_id => :id).
|
157
|
+
filter(:process_item_id => process[:id]).
|
158
|
+
select(:status).
|
159
|
+
map{|h| h[:status]}
|
160
|
+
|
161
|
+
running_status = BlueColr.state_from_pending(process[:status], parent_statuses)
|
162
|
+
[process, running_status]
|
163
|
+
end
|
164
|
+
|
165
|
+
pending_processes.select{|_, running_status| running_status}.each do |process, running_status|
|
166
|
+
logger.debug "Pending item: #{process[:id]}"
|
161
167
|
if ok_to_run?
|
162
|
-
|
163
|
-
run(
|
168
|
+
# item = @db[:process_items].filter(:id => id[:id]).first
|
169
|
+
run(process, running_status)
|
164
170
|
else
|
165
171
|
logger.debug "No available thread, waiting"
|
166
172
|
end
|
167
|
-
end
|
168
|
-
sleep(@conf['sleep_interval'] || 10)
|
169
|
-
end
|
170
173
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
174
|
+
end
|
175
|
+
Kernel.sleep 5
|
176
|
+
# Kernel.sleep(@conf['sleep_interval'] || 10)
|
177
|
+
end # loop
|
178
|
+
|
179
|
+
#rescue Interrupt
|
180
|
+
# if logger
|
181
|
+
# logger.fatal("Ctrl-C received, exiting")
|
182
|
+
# else
|
183
|
+
# puts "Ctrl-C received, exiting"
|
184
|
+
# end
|
185
|
+
# exit 1
|
178
186
|
rescue Exception => ex
|
179
187
|
p ex.class
|
180
188
|
logger.fatal(ex.to_s) if logger
|
@@ -0,0 +1,74 @@
|
|
1
|
+
class BlueColr
|
2
|
+
# this module, when included in BlueColr, generates GraphViz graph of the invoked processes,
|
3
|
+
# instead actually enqueueing them.
|
4
|
+
module GraphOutput
|
5
|
+
|
6
|
+
def self.included target
|
7
|
+
target.instance_eval do
|
8
|
+
# graph nodes are given unique ids
|
9
|
+
def next_id
|
10
|
+
@id ||= 0
|
11
|
+
@id += 1
|
12
|
+
end
|
13
|
+
|
14
|
+
# gets different color for different environments,
|
15
|
+
# currently cycling between couple predefined colors
|
16
|
+
# TODO: enable submitting color through option
|
17
|
+
def get_color group
|
18
|
+
colors = [
|
19
|
+
'#FFFFFF',
|
20
|
+
'#DDFFDD',
|
21
|
+
'#DDDDFF',
|
22
|
+
'#FFDDDD',
|
23
|
+
'#FFFFDD',
|
24
|
+
'#FFDDFF',
|
25
|
+
'#DDFFFF',
|
26
|
+
]
|
27
|
+
@groups ||= []
|
28
|
+
@groups << group unless @groups.member? group
|
29
|
+
colors[@groups.index(group) % colors.length]
|
30
|
+
end
|
31
|
+
|
32
|
+
# override class method launch, we are creating output file here,
|
33
|
+
# and we don't need database
|
34
|
+
def launch &block
|
35
|
+
default_options.gv_filename ||= "output.dot"
|
36
|
+
worker = self.new
|
37
|
+
File.open(default_options.gv_filename, 'w') do |f|
|
38
|
+
default_options.gv_file = f
|
39
|
+
f.puts "digraph G {"
|
40
|
+
worker.instance_eval &block
|
41
|
+
f.puts "}"
|
42
|
+
end
|
43
|
+
worker
|
44
|
+
end
|
45
|
+
|
46
|
+
# override default enqueue method, as just including won't do
|
47
|
+
define_method :enqueue, instance_method(:graph_enqueue)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# original enqueue enqueues the process to the database,
|
52
|
+
# here we should just output a graph elements to the output file
|
53
|
+
def graph_enqueue cmd, waitfor = [], opts = {}
|
54
|
+
gv_file = self.class.default_options.gv_file
|
55
|
+
id = self.class.next_id
|
56
|
+
waitfor.each do |wid|
|
57
|
+
# output graph edges
|
58
|
+
gv_file.puts " b#{wid} -> b#{id};"
|
59
|
+
end
|
60
|
+
# determine node label
|
61
|
+
label = opts[:label] || cmd
|
62
|
+
label.gsub!(/([^\\])"/, '\1""')
|
63
|
+
# determine node color
|
64
|
+
color = self.class.get_color(opts[:group] || opts[:environment])
|
65
|
+
# output node description
|
66
|
+
gv_file.puts " b#{id} [shape=box,style=filled,fillcolor=\"#{color}\",label=\"#{label}\"];"
|
67
|
+
# remember id
|
68
|
+
@all_ids << id
|
69
|
+
id
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
include GraphOutput
|
74
|
+
end
|
data/lib/blue_colr.rb
CHANGED
@@ -11,15 +11,72 @@ require 'sequel'
|
|
11
11
|
|
12
12
|
|
13
13
|
class BlueColr
|
14
|
-
STATUS_OK = 'ok'
|
15
|
-
STATUS_ERROR = 'error'
|
16
|
-
STATUS_PENDING = 'pending'
|
17
|
-
STATUS_RUNNING = 'running'
|
18
|
-
STATUS_PREPARING = 'preparing'
|
19
|
-
STATUS_SKIPPED = 'skipped'
|
14
|
+
# STATUS_OK = 'ok'
|
15
|
+
# STATUS_ERROR = 'error'
|
16
|
+
# STATUS_PENDING = 'pending'
|
17
|
+
# STATUS_RUNNING = 'running'
|
18
|
+
# STATUS_PREPARING = 'preparing'
|
19
|
+
# STATUS_SKIPPED = 'skipped'
|
20
|
+
|
21
|
+
# default state transitions with simple state setup ('PENDING => RUNNING => OK or ERROR')
|
22
|
+
DEFAULT_PENDING_STATE = 'pending'
|
23
|
+
PREPARING_STATE = 'preparing'
|
24
|
+
DEFAULT_STATEMAP = {
|
25
|
+
'on_pending' => {
|
26
|
+
DEFAULT_PENDING_STATE => [
|
27
|
+
['running', ['ok', 'skipped']]
|
28
|
+
]
|
29
|
+
},
|
30
|
+
'on_running' => {
|
31
|
+
'running' => {
|
32
|
+
'error' => 'error',
|
33
|
+
'ok' => 'ok'
|
34
|
+
}
|
35
|
+
},
|
36
|
+
'on_restart' => {
|
37
|
+
'error' => 'pending',
|
38
|
+
'ok' => 'pending'
|
39
|
+
}
|
40
|
+
}
|
20
41
|
|
21
42
|
class << self
|
22
|
-
attr_accessor :
|
43
|
+
attr_accessor :environment
|
44
|
+
attr_writer :statemap, :log, :db, :db_uri, :conf
|
45
|
+
|
46
|
+
def log
|
47
|
+
@log ||= Logger.new('process_daemon')
|
48
|
+
end
|
49
|
+
|
50
|
+
def conf
|
51
|
+
unless @conf
|
52
|
+
parse_command_line unless @args
|
53
|
+
|
54
|
+
raise "No configuration file defined (-c <config>)." if @args["config"].nil?
|
55
|
+
raise "Couldn't read #{@args["config"]} file." unless @args['config'] && @conf = YAML::load(File.new(@args["config"]).read)
|
56
|
+
|
57
|
+
# setting default options that should be written along with all the records to process_items
|
58
|
+
if @conf['default_options']
|
59
|
+
@conf['default_options'].each do |k,v|
|
60
|
+
default_options.send("#{k}=", v)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@conf
|
65
|
+
end
|
66
|
+
|
67
|
+
def db_uri
|
68
|
+
unless @db_uri # get the config from command line
|
69
|
+
@db_uri = self.conf['db_url']
|
70
|
+
end
|
71
|
+
@db_uri
|
72
|
+
end
|
73
|
+
|
74
|
+
def db
|
75
|
+
unless @db # not connected
|
76
|
+
@db = Sequel.connect(self.db_uri, :logger => self.log)
|
77
|
+
end
|
78
|
+
@db
|
79
|
+
end
|
23
80
|
|
24
81
|
# default options to use when launching a process - every field maps to a
|
25
82
|
# column in process_items table
|
@@ -32,6 +89,10 @@ class BlueColr
|
|
32
89
|
@options ||= OpenStruct.new
|
33
90
|
end
|
34
91
|
|
92
|
+
def statemap
|
93
|
+
@statemap ||= conf['statemap'] || DEFAULT_STATEMAP
|
94
|
+
end
|
95
|
+
|
35
96
|
def sequential &block
|
36
97
|
self.new.sequential &block
|
37
98
|
end
|
@@ -48,25 +109,7 @@ class BlueColr
|
|
48
109
|
|
49
110
|
# launch a set of tasks, provided within a given block
|
50
111
|
def launch &block
|
51
|
-
@log ||= Logger.new('process_daemon')
|
52
112
|
|
53
|
-
unless @db # not connected
|
54
|
-
unless @db_uri # get the config from command line
|
55
|
-
@args = parse_command_line ARGV
|
56
|
-
|
57
|
-
raise "No configuration file defined (-c <config>)." if @args["config"].nil?
|
58
|
-
raise "Couldn't read #{@args["config"]} file." unless @args['config'] && @conf = YAML::load(File.new(@args["config"]).read)
|
59
|
-
|
60
|
-
@db_uri = @conf['db_url']
|
61
|
-
# setting default options that should be written along with all the records to process_items
|
62
|
-
if @conf['default_options']
|
63
|
-
@conf['default_options'].each do |k,v|
|
64
|
-
default_options.send("#{k}=", v)
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
@db = Sequel.connect(@db_uri, :logger => @log)
|
69
|
-
end
|
70
113
|
worker = self.new
|
71
114
|
db.transaction do
|
72
115
|
worker.instance_eval &block
|
@@ -80,8 +123,8 @@ class BlueColr
|
|
80
123
|
exit worker.wait
|
81
124
|
end
|
82
125
|
|
83
|
-
def parse_command_line
|
84
|
-
data =
|
126
|
+
def parse_command_line &block
|
127
|
+
data = {}
|
85
128
|
|
86
129
|
OptionParser.new do |opts|
|
87
130
|
opts.banner = "Usage: process_daemon.rb [options]"
|
@@ -91,7 +134,7 @@ class BlueColr
|
|
91
134
|
end
|
92
135
|
|
93
136
|
# process custom args, if given
|
94
|
-
|
137
|
+
block.call(opts) if block_given?
|
95
138
|
|
96
139
|
opts.on_tail('-h', '--help', 'display this help and exit') do
|
97
140
|
puts opts
|
@@ -100,16 +143,53 @@ class BlueColr
|
|
100
143
|
end
|
101
144
|
|
102
145
|
# begin
|
103
|
-
opts.parse(
|
146
|
+
opts.parse(ARGV)
|
104
147
|
# rescue OptionParser::InvalidOption
|
105
148
|
# # do nothing
|
106
149
|
# end
|
107
150
|
|
108
151
|
end
|
109
152
|
|
110
|
-
|
153
|
+
@args = data
|
111
154
|
end
|
112
|
-
|
155
|
+
|
156
|
+
|
157
|
+
# state related methods
|
158
|
+
|
159
|
+
# get the next state from pending, given current state and state of all "parent" processes
|
160
|
+
def state_from_pending current_state, parent_states
|
161
|
+
new_state, _ = self.statemap['on_pending'][current_state].find { |_, required_parent_states|
|
162
|
+
(parent_states - required_parent_states).empty?
|
163
|
+
}
|
164
|
+
new_state
|
165
|
+
end
|
166
|
+
|
167
|
+
# get the next state from running, given current state and whether the command has finished successfully
|
168
|
+
def state_from_running current_state, ok
|
169
|
+
self.statemap['on_running'][current_state][ok ? 'ok' : 'error']
|
170
|
+
end
|
171
|
+
|
172
|
+
# get the next state to get upon restart, given the current state
|
173
|
+
def state_on_restart current_state
|
174
|
+
self.statemap['on_restart'][current_state]
|
175
|
+
end
|
176
|
+
|
177
|
+
# get all possible pending states
|
178
|
+
def get_pending_states
|
179
|
+
self.statemap['on_pending'].map{|state, _| state}
|
180
|
+
end
|
181
|
+
|
182
|
+
# get all possible error states
|
183
|
+
def get_error_states
|
184
|
+
self.statemap['on_running'].map{|_, new_states| new_states['error']}
|
185
|
+
end
|
186
|
+
|
187
|
+
# get all possible ok states
|
188
|
+
def get_ok_states
|
189
|
+
self.statemap['on_running'].map{|_, new_states| new_states['ok']}
|
190
|
+
end
|
191
|
+
|
192
|
+
end # class methods
|
113
193
|
|
114
194
|
attr_reader :all_ids, :result
|
115
195
|
|
@@ -151,14 +231,15 @@ class BlueColr
|
|
151
231
|
|
152
232
|
def enqueue cmd, waitfor = [], opts = {}
|
153
233
|
id = nil
|
234
|
+
opts = {status: DEFAULT_PENDING_STATE}.merge(opts)
|
154
235
|
def_opts = self.class.default_options.send(:table) # convert from OpenStruct to Hash
|
155
|
-
# rejecting fields that do not
|
236
|
+
# rejecting fields that do not have corresponding column in the table:
|
156
237
|
fields = def_opts.merge(opts).select{|k,_| db[:process_items].columns.member? k}
|
157
|
-
id = db[:process_items].insert(fields.merge(:status =>
|
238
|
+
id = db[:process_items].insert(fields.merge(:status => PREPARING_STATE, :cmd => cmd, :queued_at => Time.now))
|
158
239
|
waitfor.each do |wid|
|
159
240
|
db[:process_item_dependencies].insert(:process_item_id => id, :depends_on_id => wid)
|
160
241
|
end
|
161
|
-
db[:process_items].filter(:id => id).update(:status =>
|
242
|
+
db[:process_items].filter(:id => id).update(:status => opts[:status])
|
162
243
|
# id = TaskGroup.counter
|
163
244
|
log.info "enqueueing #{id}: #{cmd}, waiting for #{waitfor.inspect}"
|
164
245
|
# remember id
|
@@ -181,9 +262,9 @@ class BlueColr
|
|
181
262
|
def wait
|
182
263
|
log.info 'Waiting for all processes to finish'
|
183
264
|
loop do
|
184
|
-
failed = db[:process_items].filter(:id => @all_ids, :status =>
|
265
|
+
failed = db[:process_items].filter(:id => @all_ids, :status => BlueColr.get_error_states).first
|
185
266
|
return failed[:exit_code] if failed
|
186
|
-
not_ok_count = db[:process_items].filter(:id => @all_ids).exclude(:status =>
|
267
|
+
not_ok_count = db[:process_items].filter(:id => @all_ids).exclude(:status => BlueColr.get_ok_states).count
|
187
268
|
return 0 if not_ok_count == 0 # all ok, finish
|
188
269
|
sleep 10
|
189
270
|
end
|
metadata
CHANGED
@@ -1,7 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: blue_colr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
5
11
|
platform: ruby
|
6
12
|
authors:
|
7
13
|
- Mladen Jablanovic
|
@@ -9,19 +15,23 @@ autorequire:
|
|
9
15
|
bindir: bin
|
10
16
|
cert_chain: []
|
11
17
|
|
12
|
-
date: 2011-10-
|
18
|
+
date: 2011-10-17 00:00:00 +02:00
|
13
19
|
default_executable:
|
14
20
|
dependencies:
|
15
21
|
- !ruby/object:Gem::Dependency
|
16
22
|
name: sequel
|
17
|
-
|
18
|
-
|
19
|
-
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
20
26
|
requirements:
|
21
27
|
- - ">="
|
22
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
23
32
|
version: "0"
|
24
|
-
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
25
35
|
description: Blue_colr provides simple DSL to enqueue processes in given order, using database table as a queue, and a deamon to run them
|
26
36
|
email:
|
27
37
|
- jablan@radioni.ca
|
@@ -36,6 +46,7 @@ files:
|
|
36
46
|
- bin/bcrun
|
37
47
|
- bin/bluecolrd
|
38
48
|
- lib/blue_colr.rb
|
49
|
+
- lib/blue_colr/graph_output.rb
|
39
50
|
- README.rdoc
|
40
51
|
has_rdoc: true
|
41
52
|
homepage: http://github.com/jablan/blue_colr
|
@@ -47,21 +58,27 @@ rdoc_options: []
|
|
47
58
|
require_paths:
|
48
59
|
- lib
|
49
60
|
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
50
62
|
requirements:
|
51
63
|
- - ">="
|
52
64
|
- !ruby/object:Gem::Version
|
65
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
53
68
|
version: "0"
|
54
|
-
version:
|
55
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
56
71
|
requirements:
|
57
72
|
- - ">="
|
58
73
|
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
59
77
|
version: "0"
|
60
|
-
version:
|
61
78
|
requirements: []
|
62
79
|
|
63
80
|
rubyforge_project:
|
64
|
-
rubygems_version: 1.
|
81
|
+
rubygems_version: 1.6.2
|
65
82
|
signing_key:
|
66
83
|
specification_version: 3
|
67
84
|
summary: Database based process launcher
|