rulebow 0.4.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.
@@ -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