rulebow 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,445 @@
1
+ module Rulebow
2
+
3
+ # Runner is the main class which controls execution.
4
+ #
5
+ class Runner
6
+
7
+ RULEBOOK_GLOB = "{,.,_}{R,r}ulebook{,.rb}"
8
+
9
+ # Initialize new Runner instance.
10
+ #
11
+ # Returns nothing.
12
+ def initialize(options={})
13
+ self.ignore = options[:ignore]
14
+
15
+ self.trial = options[:trial]
16
+ self.fresh = options[:fresh]
17
+ self.watch = options[:watch]
18
+
19
+ if options[:system]
20
+ @system = options[:system]
21
+ @root = @system.root
22
+ else
23
+ locate_root
24
+ @system = System.new(:root=>root)
25
+ end
26
+ end
27
+
28
+ # Project's root directory.
29
+ #
30
+ # Returns [String]
31
+ def root
32
+ @root
33
+ end
34
+
35
+ # Locate project root. This method ascends up the file system starting
36
+ # as the current working directory looking for a `Rulebook` file.
37
+ # When found, the directory in which it is found is returned as the root.
38
+ def locate_root
39
+ d = Dir.pwd
40
+ while d != home && d != '/'
41
+ f = Dir.glob(File.join(d, RULEBOOK_GLOB)).first
42
+ if f
43
+ @root = d
44
+ break
45
+ end
46
+ d = File.dirname(d)
47
+ end
48
+ raise(RootError, "cannot locate project root") unless @root
49
+ @root
50
+ end
51
+
52
+ #
53
+ #def root=(path)
54
+ # @root = path
55
+ #end
56
+
57
+ # Home directory.
58
+ #
59
+ # Returns [String]
60
+ def home
61
+ @home ||= File.expand_path('~')
62
+ end
63
+
64
+ # Config script.
65
+ #def config
66
+ # @config ||= Dir[CONFIG_SCRIPT].first
67
+ #end
68
+
69
+ # Set config script.
70
+ #def config=(script)
71
+ # @config = script
72
+ #end
73
+
74
+ # Watch period, default is every 5 minutes.
75
+ #
76
+ # Returns [Fixnum]
77
+ def watch
78
+ @watch
79
+ end
80
+
81
+ # Set watch seconds. Minimum watch time is 1 second.
82
+ # Setting watch before calling #run creates a simple loop.
83
+ # It can eat up CPU cycles so use it wisely. A watch time
84
+ # of 4 seconds is a good time period. If you are patient
85
+ # go for 15 seconds or more.
86
+ #
87
+ # Returns [Fixnum,nil]
88
+ def watch=(seconds)
89
+ if seconds
90
+ seconds = seconds.to_i
91
+ seconds = 1 if seconds < 1
92
+ @watch = seconds
93
+ else
94
+ @watch = nil
95
+ end
96
+ end
97
+
98
+ # Nullify digest and make a fresh run?
99
+ #
100
+ # Returns [Boolean]
101
+ def fresh?
102
+ @fresh
103
+ end
104
+
105
+ # Set whether to nullify digest and make a fresh run.
106
+ #
107
+ # Returns [Boolean]
108
+ def fresh=(boolean)
109
+ @fresh = !! boolean
110
+ end
111
+
112
+ # Is this trial-run only?
113
+ #
114
+ # TODO: Trial mode is not implemented yet!
115
+ #
116
+ # Returns [Boolean]
117
+ def trial?
118
+ @trial
119
+ end
120
+
121
+ # Set trial run mode.
122
+ #
123
+ # Arguments
124
+ # bool - Flag for trial mode. [Boolean]
125
+ #
126
+ # Returns `bool` flag. [Boolean]
127
+ def trial=(bool)
128
+ @trial = !!bool
129
+ end
130
+
131
+ # Set the root directory.
132
+ #
133
+ # Returns [String]
134
+ #def root=(dir)
135
+ # @root = dir if dir
136
+ #end
137
+
138
+ # Instance of {Rulebow::System}.
139
+ #
140
+ # Returns [System]
141
+ def system
142
+ @system #||= System.new(script)
143
+ end
144
+
145
+ ## Rules script to load.
146
+ ##
147
+ ## Returns List of file paths. [Array]
148
+ #def script
149
+ # @script || (@system ? nil : Dir[RULES_SCRIPT].first)
150
+ #end
151
+
152
+ #
153
+ #
154
+ #
155
+ def rulesets
156
+ system.rulesets
157
+ end
158
+
159
+ # File globs to ignore.
160
+ #
161
+ # Returns [Ignore] instance.
162
+ #def digest
163
+ # @digest ||= Digest.new(:ignore=>ignore)
164
+ #end
165
+
166
+ # File globs to ignore.
167
+ #
168
+ # Returns [Ignore] instance.
169
+ def ignore
170
+ @ignore ||= []
171
+ end
172
+
173
+ # Set ignore.
174
+ def ignore=(list)
175
+ @ignore = list.to_a.flatten
176
+ end
177
+
178
+ # List of rules from the system.
179
+ #
180
+ # Returns [Array<Rule>]
181
+ def rules
182
+ system.rules
183
+ end
184
+
185
+ # Run rules.
186
+ #
187
+ # Returns nothing.
188
+ def run(name)
189
+ name = (name || :default).to_sym
190
+
191
+ if watch
192
+ autorun(name)
193
+ else
194
+ monorun(name)
195
+ end
196
+ end
197
+
198
+ private
199
+
200
+ # Run rules once.
201
+ #
202
+ # Returns nothing.
203
+ def monorun(name)
204
+ Dir.chdir(root) do
205
+ fresh_digest(name) if fresh?
206
+ run_ruleset(name)
207
+ end
208
+ end
209
+
210
+ # Run rules periodically.
211
+ #
212
+ # Returns nothing.
213
+ def autorun(name)
214
+ Dir.chdir(root) do
215
+ fresh_digest(name) if fresh?
216
+
217
+ trap("INT") { puts "\nBows down."; exit }
218
+
219
+ puts " ( RULEBOW "
220
+ puts " \\ (pid #{Process.pid})"
221
+ puts " ) "
222
+ puts " ##--------> "
223
+ puts " ) "
224
+ puts " / "
225
+ puts " ( "
226
+
227
+ loop do
228
+ run_ruleset(name)
229
+ sleep(watch)
230
+ end
231
+ end
232
+ end
233
+
234
+ # Run a specific ruleruleset.
235
+ #
236
+ # name - Nmae of ruleset. [String].
237
+ #
238
+ # Returns nothing.
239
+ def run_ruleset(name)
240
+ rulesets = ruleset_chain(name)
241
+ rulesets.each do |ruleset|
242
+ run_rules(ruleset)
243
+ digest.save(ruleset)
244
+ end
245
+ end
246
+
247
+ # Run all rulesets.
248
+ #
249
+ def run_all
250
+ run_rules(system)
251
+ digest.clear
252
+ digest.save
253
+ end
254
+
255
+ =begin
256
+ # Run only those rules with a specific rulesetmark.
257
+ #
258
+ # marks - Bookmark names. [Array<String>].
259
+ #
260
+ # Returns nothing.
261
+ def run_rulesetmarks(*marks)
262
+ system.rules.each do |rule|
263
+ case rule
264
+ when Ruleset
265
+ ruleset = rule
266
+ ruleset.rules.each do |rule|
267
+ next unless marks.any?{ |mark| rule.mark?(mark) }
268
+ rule.apply(latest_digest(rule))
269
+ end
270
+ else
271
+ next unless marks.any?{ |mark| rule.mark?(mark) }
272
+ rule.apply(latest_digest(rule))
273
+ end
274
+ end
275
+
276
+ save_digests(*marks)
277
+ end
278
+ =end
279
+
280
+ # Run set of rules.
281
+ #
282
+ # Returns nothing.
283
+ def run_rules(ruleset)
284
+ ruleset.rules.each do |rule|
285
+ rule.apply(digest[ruleset])
286
+ end
287
+ end
288
+
289
+ # Instance of Digest for this system.
290
+ def digest
291
+ @digest ||= Digest.new(system)
292
+ end
293
+
294
+ # Start with a clean slate by removing the digest.
295
+ #
296
+ # Returns nothing.
297
+ def fresh_digest(name)
298
+ if name
299
+ chain = ruleset_chain(name)
300
+ chain.each do |n|
301
+ digest.remove(n)
302
+ end
303
+ else
304
+ digest.clear_all
305
+ end
306
+ end
307
+
308
+ # Save digests for given rulesets.
309
+ #
310
+ # Returns nothing.
311
+ def save_digests(*rulesets)
312
+ rulesets.each do |name|
313
+ digest.save(name)
314
+ end
315
+ end
316
+
317
+ # Get ruleset instance for a given command.
318
+ def ruleset_chain(name)
319
+ ruleset = verify_ruleset!(name)
320
+ chain = []
321
+ build_chain(ruleset, chain)
322
+ chain.uniq
323
+ end
324
+
325
+ #
326
+ def build_chain(ruleset, chain=[])
327
+ ruleset.chain.each do |name|
328
+ verify_ruleset!(name)
329
+ build_chain(rulesets[name], chain)
330
+ end
331
+ chain << ruleset
332
+ return chain
333
+ end
334
+
335
+ #
336
+ def verify_ruleset!(name)
337
+ name = name.to_sym
338
+ unless rulesets.key?(name)
339
+ raise(ArgumentError, "unknown ruleset name -- #{name}")
340
+ end
341
+ rulesets[name]
342
+ end
343
+
344
+ =begin
345
+ #
346
+ def calc_chain(*marks)
347
+ chain = []
348
+ order = []
349
+
350
+ marks.each do |mark|
351
+ if mark.include?(':')
352
+ k, p = mark.split(':')
353
+ raise "unknown chain -- #{k}" unless system.chains.key?(k)
354
+ order << system.chains[k][0..(system.chains[k].index(p))]
355
+ else
356
+ order << mark
357
+ end
358
+ end
359
+ order = order.flatten.uniq
360
+
361
+ order.each do |name|
362
+ complete_chain(name, chain)
363
+ end
364
+
365
+ return chain.uniq
366
+ end
367
+
368
+ #
369
+ def complete_chain(name, chain)
370
+ if system.rulesets.key?(name)
371
+ system.rulesets[name].chain.each do |n|
372
+ complete_chain(n, chain)
373
+ end
374
+ end
375
+ chain << name
376
+ return chain
377
+ end
378
+ =end
379
+
380
+ # TODO: If we ever need this, we will need to put it in the state file.
381
+ #def save_pid
382
+ # File.open('.bow/pid', 'w') do |f|
383
+ # f << Process.pid.to_s
384
+ # end
385
+ #end
386
+
387
+ # Save file digest.
388
+ #
389
+ # Returns nothing.
390
+ #def save_digest(*marks)
391
+ # digest.save(*marks)
392
+ #end
393
+
394
+ # System runner.
395
+ #
396
+ # Returns [Runner]
397
+ #def runner
398
+ # Runner.new(system)
399
+ #end
400
+
401
+ # TODO: load configuration
402
+ #
403
+ #def rc?
404
+ # Dir.glob('{.c,c,C}onfig{.rb,}').first
405
+ #end
406
+
407
+ # Oh why is this still around? It's the original routine
408
+ # for running rules. It worked ass backward too. Checking
409
+ # facts and then applying rules that were attached to those
410
+ # facts.
411
+ #
412
+ #def run
413
+ # @facts.each do |fact|
414
+ # session = OpenStruct.new
415
+ # next unless fact.active?(info)
416
+ # @rules.each do |rule|
417
+ # if md = rule.match?(fact)
418
+ # if rule.arity == 0 or md == true
419
+ # rule.call(info)
420
+ # else
421
+ # rule.call(info,*md[1..-1])
422
+ # end
423
+ # end
424
+ # end
425
+ # end
426
+ #end
427
+
428
+ # TODO: support rc profiles
429
+ #if config = Rulebow.rc_config
430
+ # config.each do |c|
431
+ # if c.arity == 0
432
+ # system.instance_eval(&c)
433
+ # else
434
+ # c.call(system)
435
+ # end
436
+ # end
437
+ #end
438
+
439
+ #
440
+ class RootError < RuntimeError
441
+ end
442
+
443
+ end
444
+
445
+ end
@@ -0,0 +1,84 @@
1
+ module Rulebow
2
+
3
+ # TODO: Borrow code from Detroit for ShellUtils and beef her up!
4
+
5
+ # File system utility methods.
6
+ #
7
+ module ShellUtils
8
+ # Shell out via system call.
9
+ #
10
+ # Arguments
11
+ # args - Argument vector. [Array]
12
+ #
13
+ # Returns success of shell invocation.
14
+ def sh(*args)
15
+ env = (Hash === args.last ? args.pop : {})
16
+ puts args.join(' ')
17
+ system(env, *args)
18
+ end
19
+
20
+ # Same as `#sh` but raises an error if shell fails.
21
+ def shell(*args)
22
+ success = sh(*args)
23
+ raise "shell failure: #{args.join(' ')}" unless success
24
+ end
25
+ alias :run :shell
26
+
27
+ #
28
+ def directory?(path)
29
+ File.directory?(path)
30
+ end
31
+
32
+ # Synchronize a destination directory with a source directory.
33
+ #
34
+ # TODO: Augment FileUtils instead.
35
+ # TODO: Not every action needs to be verbose.
36
+ #
37
+ def sync(src, dst, options={})
38
+ src_files = Dir[File.join(src, '**', '*')].map{ |f| f.sub(src+'/', '') }
39
+ dst_files = Dir[File.join(dst, '**', '*')].map{ |f| f.sub(dst+'/', '') }
40
+
41
+ removal = dst_files - src_files
42
+
43
+ rm_dirs, rm_files = [], []
44
+ removal.each do |f|
45
+ path = File.join(dst, f)
46
+ if File.directory?(path)
47
+ rm_dirs << path
48
+ else
49
+ rm_files << path
50
+ end
51
+ end
52
+
53
+ rm_files.each { |f| rm(f) }
54
+ rm_dirs.each { |d| rmdir(d) }
55
+
56
+ src_files.each do |f|
57
+ src_path = File.join(src, f)
58
+ dst_path = File.join(dst, f)
59
+ if File.directory?(src_path)
60
+ mkdir_p(dst_path)
61
+ else
62
+ parent = File.dirname(dst_path)
63
+ mkdir_p(parent) unless File.directory?(parent)
64
+ install(src_path, dst_path)
65
+ end
66
+ end
67
+ end
68
+
69
+ # If FileUtils responds to a missing method, then call it.
70
+ #
71
+ def method_missing(s, *a, &b)
72
+ if FileUtils.respond_to?(s)
73
+ if $DRYRUN
74
+ FileUtils::DryRun.__send__(s, *a, &b)
75
+ else
76
+ FileUtils::Verbose.__send__(s, *a, &b)
77
+ end
78
+ else
79
+ super(s, *a, &b)
80
+ end
81
+ end
82
+ end
83
+
84
+ end