ergo 0.3.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,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