ergo 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,377 @@
1
+ module Ergo
2
+
3
+ # Default rules file.
4
+ RULES_SCRIPT = ".ergo/script.rb"
5
+
6
+ # Runner is the main class which controls execution.
7
+ #
8
+ class Runner
9
+
10
+ # Initialize new Session instance.
11
+ #
12
+ # Returns nothing.
13
+ def initialize(options={})
14
+ @script = options[:script]
15
+ @system = options[:system]
16
+
17
+ self.root = options[:root]
18
+ self.trial = options[:trial]
19
+ self.fresh = options[:fresh]
20
+ self.watch = options[:watch]
21
+ self.ignore = options[:ignore]
22
+
23
+ @digests = {}
24
+ end
25
+
26
+ # Watch period, default is every 5 minutes.
27
+ #
28
+ # Returns [Fixnum]
29
+ def watch
30
+ @watch
31
+ end
32
+
33
+ # Set watch seconds. Minimum watch time is 1 second.
34
+ # Setting watch before calling #run creates a simple loop.
35
+ # It can eat up CPU cycles so use it wisely. A watch time
36
+ # of 4 seconds is a good time period. If you are patient
37
+ # go for 15 seconds or more.
38
+ #
39
+ # Returns [Fixnum,nil]
40
+ def watch=(seconds)
41
+ if seconds
42
+ seconds = seconds.to_i
43
+ seconds = 1 if seconds < 1
44
+ @watch = seconds
45
+ else
46
+ @watch = nil
47
+ end
48
+ end
49
+
50
+ # Nullify digest and make a fresh run?
51
+ #
52
+ # Returns [Boolean]
53
+ def fresh?
54
+ @fresh
55
+ end
56
+
57
+ # Set whether to nullify digest and make a fresh run.
58
+ #
59
+ # Returns [Boolean]
60
+ def fresh=(boolean)
61
+ @fresh = !! boolean
62
+ end
63
+
64
+ # Is this trial-run only?
65
+ #
66
+ # TODO: Trial mode is not implemented yet!
67
+ #
68
+ # Returns [Boolean]
69
+ def trial?
70
+ @trial
71
+ end
72
+
73
+ # Set trial run mode.
74
+ #
75
+ # Arguments
76
+ # bool - Flag for trial mode. [Boolean]
77
+ #
78
+ # Returns `bool` flag. [Boolean]
79
+ def trial=(bool)
80
+ @trial = !!bool
81
+ end
82
+
83
+ # Locate project root. This method ascends up the file system starting
84
+ # as the current working directory looking for a `.ergo` directory.
85
+ # When found, the directory in which it is found is returned as the root.
86
+ # It is also memoized, so repeated calls to this method will not repeat
87
+ # the search.
88
+ #
89
+ # Returns [String]
90
+ def root
91
+ dir = root?
92
+ raise RootError, "cannot locate project root" unless dir
93
+ dir
94
+ end
95
+
96
+ #
97
+ def root?
98
+ @root ||= (
99
+ r = nil
100
+ d = Dir.pwd
101
+ while d != home && d != '/'
102
+ if File.directory?('.ergo')
103
+ break r = d
104
+ end
105
+ d = File.dirname(d)
106
+ end
107
+ r
108
+ )
109
+ end
110
+
111
+ # Set the root directory.
112
+ #
113
+ # Returns [String]
114
+ def root=(dir)
115
+ @root = dir if dir
116
+ end
117
+
118
+ # Home directory.
119
+ #
120
+ # Returns [String]
121
+ def home
122
+ @home ||= File.expand_path('~')
123
+ end
124
+
125
+ # Instance of {Ergo::System}.
126
+ #
127
+ # Returns [System]
128
+ def system
129
+ @system ||= System.new(script)
130
+ end
131
+
132
+ # Rules script to load.
133
+ #
134
+ # Returns List of file paths. [Array]
135
+ def script
136
+ @script || (@system ? nil : Dir[RULES_SCRIPT].first)
137
+ end
138
+
139
+ # File globs to ignore.
140
+ #
141
+ # Returns [Ignore] instance.
142
+ #def digest
143
+ # @digest ||= Digest.new(:ignore=>ignore)
144
+ #end
145
+
146
+ # File globs to ignore.
147
+ #
148
+ # Returns [Ignore] instance.
149
+ def ignore
150
+ @ignore ||= Ignore.new(:root=>root)
151
+ end
152
+
153
+ # Set ignore.
154
+ def ignore=(file)
155
+ @ignore = Ignore.new(:root=>root, :file=>file)
156
+ end
157
+
158
+ # List of rules from the system.
159
+ #
160
+ # Returns [Array<Rule>]
161
+ def rules
162
+ system.rules
163
+ end
164
+
165
+ # Run rules.
166
+ #
167
+ # Returns nothing.
168
+ def run(*marks)
169
+ raise ArgumentError, "invalid bookmark" unless marks.all?{ |m| /\w+/ =~ m }
170
+
171
+ if watch
172
+ autorun(*marks)
173
+ else
174
+ monorun(*marks)
175
+ end
176
+ end
177
+
178
+ private
179
+
180
+ # Run rules once.
181
+ #
182
+ # Returns nothing.
183
+ def monorun(*marks)
184
+ Dir.chdir(root) do
185
+ fresh_digest(*marks) if fresh?
186
+
187
+ if marks.size > 0
188
+ run_bookmarks(*marks)
189
+ else
190
+ run_rules
191
+ end
192
+ end
193
+ end
194
+
195
+ # Run rules periodically.
196
+ #
197
+ # Returns nothing.
198
+ def autorun(*marks)
199
+ Dir.chdir(root) do
200
+ fresh_digest(*marks) if fresh?
201
+
202
+ trap("INT") { puts "\nPutting out the fire!"; exit }
203
+ puts "Fire started! (pid #{Process.pid})"
204
+
205
+ if marks.size > 0
206
+ loop do
207
+ run_bookmarks(*marks)
208
+ sleep(watch)
209
+ end
210
+ else
211
+ loop do
212
+ run_rules
213
+ sleep(watch)
214
+ end
215
+ end
216
+ end
217
+ end
218
+
219
+ # Returns [Hash]
220
+ attr :digests
221
+
222
+ # Run all rules (expect private rules).
223
+ #
224
+ # Returns nothing.
225
+ def run_rules
226
+ system.rules.each do |rule|
227
+ case rule
228
+ when Book
229
+ book = rule
230
+ book.rules.each do |rule|
231
+ next if rule.private?
232
+ rule.apply(latest_digest(rule))
233
+ end
234
+ else
235
+ next if rule.private?
236
+ rule.apply(latest_digest(rule))
237
+ end
238
+ end
239
+
240
+ clear_digests
241
+
242
+ digest.save
243
+ end
244
+
245
+ # Run only those rules with a specific bookmark.
246
+ #
247
+ # marks - Bookmark names. [Array<String>].
248
+ #
249
+ # Returns nothing.
250
+ def run_bookmarks(*marks)
251
+ system.rules.each do |rule|
252
+ case rule
253
+ when Book
254
+ book = rule
255
+ book.rules.each do |rule|
256
+ next unless marks.any?{ |mark| rule.mark?(mark) }
257
+ rule.apply(latest_digest(rule))
258
+ end
259
+ else
260
+ next unless marks.any?{ |mark| rule.mark?(mark) }
261
+ rule.apply(latest_digest(rule))
262
+ end
263
+ end
264
+
265
+ save_digests(*marks)
266
+ end
267
+
268
+ # get digest by name, if it doesn't exit create a new one.
269
+ def digest(name=nil)
270
+ @digests[name] ||= Digest.new(:ignore=>ignore, :name=>name)
271
+ end
272
+
273
+ # Get the most recent digest for a given rule.
274
+ #
275
+ # Returns [Digest]
276
+ def latest_digest(rule)
277
+ name = Digest.latest(*rule.bookmarks)
278
+ digest(name)
279
+ end
280
+
281
+ # Start with a clean slate by remove the digest.
282
+ #
283
+ # Returns nothing.
284
+ def fresh_digest(*marks)
285
+ if marks.empty?
286
+ clear_digests
287
+ else
288
+ marks.each do |mark|
289
+ d = @digests.delete(mark)
290
+ d.remove if d
291
+ end
292
+ end
293
+ end
294
+
295
+ # Clear away all digests but the main digest.
296
+ #
297
+ # Returns nothing.
298
+ def clear_digests
299
+ Digest.clear_digests
300
+ @digests = {}
301
+ end
302
+
303
+ # Save digests for given bookmarks.
304
+ #
305
+ # Returns nothing.
306
+ def save_digests(*bookmarks)
307
+ bookmarks.each do |mark|
308
+ digest(mark).save
309
+ end
310
+ end
311
+
312
+ #
313
+ #def save_pid
314
+ # File.open('.ergo/pid', 'w') do |f|
315
+ # f << Process.pid.to_s
316
+ # end
317
+ #end
318
+
319
+ # Save file digest.
320
+ #
321
+ # Returns nothing.
322
+ #def save_digest(*marks)
323
+ # digest.save(*marks)
324
+ #end
325
+
326
+ # System runner.
327
+ #
328
+ # Returns [Runner]
329
+ #def runner
330
+ # Runner.new(system)
331
+ #end
332
+
333
+ # TODO: load configuration
334
+ #
335
+ #def rc?
336
+ # Dir.glob('{.c,c,C}onfig{.rb,}').first
337
+ #end
338
+
339
+ # Oh why is this still around? It's the original routine
340
+ # for running rules. It worked ass backward too. Checking
341
+ # states and then applying rules that were attached to those
342
+ # states.
343
+ #
344
+ #def run
345
+ # @states.each do |state|
346
+ # session = OpenStruct.new
347
+ # next unless state.active?(info)
348
+ # @rules.each do |rule|
349
+ # if md = rule.match?(state)
350
+ # if rule.arity == 0 or md == true
351
+ # rule.call(info)
352
+ # else
353
+ # rule.call(info,*md[1..-1])
354
+ # end
355
+ # end
356
+ # end
357
+ # end
358
+ #end
359
+
360
+ # TODO: support rc profiles
361
+ #if config = Ergo.rc_config
362
+ # config.each do |c|
363
+ # if c.arity == 0
364
+ # system.instance_eval(&c)
365
+ # else
366
+ # c.call(system)
367
+ # end
368
+ # end
369
+ #end
370
+
371
+ #
372
+ class RootError < RuntimeError
373
+ end
374
+
375
+ end
376
+
377
+ end
@@ -0,0 +1,79 @@
1
+ module Ergo
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
+ #
21
+ def directory?(path)
22
+ File.directory?(path)
23
+ end
24
+
25
+ #
26
+ # Synchronize a destination directory with a source directory.
27
+ #
28
+ # TODO: Augment FileUtils instead.
29
+ # TODO: Not every action needs to be verbose.
30
+ #
31
+ def sync(src, dst, options={})
32
+ src_files = Dir[File.join(src, '**', '*')].map{ |f| f.sub(src+'/', '') }
33
+ dst_files = Dir[File.join(dst, '**', '*')].map{ |f| f.sub(dst+'/', '') }
34
+
35
+ removal = dst_files - src_files
36
+
37
+ rm_dirs, rm_files = [], []
38
+ removal.each do |f|
39
+ path = File.join(dst, f)
40
+ if File.directory?(path)
41
+ rm_dirs << path
42
+ else
43
+ rm_files << path
44
+ end
45
+ end
46
+
47
+ rm_files.each { |f| rm(f) }
48
+ rm_dirs.each { |d| rmdir(d) }
49
+
50
+ src_files.each do |f|
51
+ src_path = File.join(src, f)
52
+ dst_path = File.join(dst, f)
53
+ if File.directory?(src_path)
54
+ mkdir_p(dst_path)
55
+ else
56
+ parent = File.dirname(dst_path)
57
+ mkdir_p(parent) unless File.directory?(parent)
58
+ install(src_path, dst_path)
59
+ end
60
+ end
61
+ end
62
+
63
+ #
64
+ # If FileUtils responds to a missing method, then call it.
65
+ #
66
+ def method_missing(s, *a, &b)
67
+ if FileUtils.respond_to?(s)
68
+ if $DRYRUN
69
+ FileUtils::DryRun.__send__(s, *a, &b)
70
+ else
71
+ FileUtils::Verbose.__send__(s, *a, &b)
72
+ end
73
+ else
74
+ super(s, *a, &b)
75
+ end
76
+ end
77
+ end
78
+
79
+ end