fire 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,103 @@
1
+ module Fire
2
+
3
+ # Rule class encapsulates a *rule* definition.
4
+ #
5
+ class Rule
6
+ # Initialize new instanance of Rule.
7
+ #
8
+ # logic - Logic condition. [Logic]
9
+ # procedure - Procedure to run if logic condition is met. [Proc]
10
+ #
11
+ # Options
12
+ # :todo - Names of prerequisite tasks. [Array<Symbol>]
13
+ #
14
+ def initialize(logic, options={}, &procedure)
15
+ @logic = logic
16
+ @requisite = options[:todo]
17
+ @procedure = procedure
18
+ end
19
+
20
+ # Access logic condition.
21
+ #
22
+ # Returns [Logic]
23
+ attr :logic
24
+
25
+ # Returns [Proc]
26
+ attr :procedure
27
+
28
+ # Names of requisite tasks.
29
+ def requisite
30
+ @requisite
31
+ end
32
+
33
+ # More convenient alias for `#requisite`.
34
+ alias :todo :requisite
35
+
36
+ # Rules don't generally have names, but task rules do.
37
+ def name
38
+ nil
39
+ end
40
+
41
+ # Apply logic, running the rule's prcedure if the logic
42
+ # condition is satisfied.
43
+ #
44
+ # Returns nothing.
45
+ def apply(&prepare)
46
+ case logic
47
+ when true
48
+ call
49
+ when false, nil
50
+ else
51
+ result_set = logic.call
52
+ if result_set && !result_set.empty?
53
+ prepare.call
54
+ call(*result_set)
55
+ end
56
+ end
57
+ end
58
+
59
+ # Alias for #apply.
60
+ alias :invoke :apply
61
+
62
+ protected
63
+
64
+ # Run rule procedure.
65
+ #
66
+ # result_set - The result set returned by the logic condition.
67
+ #
68
+ # Returns whatever the procedure returns. [Object]
69
+ def call(*result_set)
70
+ if @procedure.arity == 0
71
+ @procedure.call
72
+ else
73
+ #@procedure.call(session, *args)
74
+ @procedure.call(*result_set)
75
+ end
76
+ end
77
+
78
+ =begin
79
+ private
80
+
81
+ # Reduce todo list to the set of tasks to be run.
82
+ #
83
+ # Returns [Array<Task>]
84
+ def reduce
85
+ return [] if @_reducing
86
+ list = []
87
+ begin
88
+ @_reducing = true
89
+ @requisite.each do |r|
90
+ next if @system.post.include?(r.to_sym)
91
+ list << @system.tasks[r.to_sym].reduce
92
+ end
93
+ list << self
94
+ ensure
95
+ @_reducing = false
96
+ end
97
+ list.flatten.uniq
98
+ end
99
+ =end
100
+
101
+ end
102
+
103
+ end
@@ -0,0 +1,12 @@
1
+ # TODO: This may be used in future to parse individual rulefiles.
2
+
3
+ #require 'facets/lazy'
4
+ #require 'ostruct'
5
+
6
+ #module Auto
7
+ #
8
+ # class Rulefile < Module
9
+ # end
10
+ #
11
+ #end
12
+
@@ -0,0 +1,76 @@
1
+ module Fire
2
+
3
+ # Time to DCI this shit!
4
+
5
+ # Runner class takes a rule system and runs it.
6
+ #
7
+ class Runner
8
+
9
+ def initialize(system)
10
+ @system = system
11
+ @_post = []
12
+ end
13
+
14
+ #
15
+ attr :system
16
+
17
+ #
18
+ def run_rules
19
+ system.rules.each do |rule|
20
+ rule.apply{ prepare(rule) }
21
+ end
22
+ end
23
+
24
+ #
25
+ def run_task(trigger)
26
+ task = system.tasks[trigger.to_sym]
27
+ task.apply{ prepare(task) }
28
+ end
29
+
30
+ private
31
+
32
+ # Execute rule by first running any outstanding prerequistes
33
+ # then then the rul procedure itself.
34
+ def prepare(rule)
35
+ pre = resolve(rule)
36
+ pre = pre - post
37
+ pre = pre - [rule.name.to_sym] if rule.name
38
+ pre.each do |r|
39
+ r.call
40
+ end
41
+ post(pre)
42
+ end
43
+
44
+ # TODO: It would be nice #resolve could detect infinite recursions
45
+ # and raise an error.
46
+ #
47
+
48
+ # Resolve prerequistes.
49
+ #
50
+ # Returns [Array<Symbol>]
51
+ def resolve(rule, todo=[])
52
+ return [] if (rule.todo - todo).empty?
53
+ left = rule.todo - todo
54
+ list = left
55
+ todo.concat(left)
56
+ left.each do |r|
57
+ t = system.tasks[r.to_sym]
58
+ x = resolve(t, todo)
59
+ list.concat(x)
60
+ end
61
+ list.uniq
62
+ end
63
+
64
+ # List of prerequistes that have already been run.
65
+ # Keeping this list prevents the same prequistes
66
+ # from ever being run twice in the same session.
67
+ #
68
+ # Returns [Array<Symbol>]
69
+ def post(pre=nil)
70
+ @_post.concat(pre) if pre
71
+ @_post
72
+ end
73
+
74
+ end
75
+
76
+ end
@@ -0,0 +1,268 @@
1
+ require 'fire/system'
2
+ require 'fire/runner'
3
+ require 'fileutils'
4
+
5
+ module Fire
6
+
7
+ # Markers to look for to identify a project's root directory.
8
+ ROOT_INDICATORS = %w{.fire task/fire.rb .git .hg _darcs .gemspec *.gemspec}
9
+
10
+ # This file can be used as an alternative to using the #ignore method
11
+ # to define what paths to ignore.
12
+ IGNORE_FILE = '.fire/ignore'
13
+
14
+ # Session is the main Fire class which controls execution.
15
+ #
16
+ # TODO: Maybee call this Runner, and have a special Session class that limits interface.
17
+ #
18
+ class Session
19
+
20
+ #
21
+ # Initialize new Session instance.
22
+ #
23
+ # Returns nothing.
24
+ #
25
+ def initialize(options={})
26
+ self.watch = options[:watch]
27
+ self.trial = options[:trial]
28
+
29
+ load_system
30
+ end
31
+
32
+ #
33
+ # Watch period, default is every 5 minutes.
34
+ #
35
+ def watch
36
+ @watch || 300
37
+ end
38
+
39
+ #
40
+ # Set watch seconds. Minimum watch time is 1 second.
41
+ # Setting watch before calling #run creates a simple loop.
42
+ # It can eat up CPU cycles so use it wisely. A watch time
43
+ # of 4 seconds is a good time period. If you are patient
44
+ # go for 15 seconds or more.
45
+ #
46
+ def watch=(seconds)
47
+ if seconds
48
+ seconds = seconds.to_i
49
+ seconds = 1 if seconds < 1
50
+ @watch = seconds
51
+ else
52
+ @watch = nil
53
+ end
54
+ end
55
+
56
+ # Is this trial-run only?
57
+ def trial?
58
+ @trial
59
+ end
60
+
61
+ # Set trial run mode.
62
+ #
63
+ # Arguments
64
+ # bool - Flag for trial mode. [Boolean]
65
+ #
66
+ # Returns `bool` flag. [Boolean]
67
+ def trial=(bool)
68
+ @trial = !!bool
69
+ end
70
+
71
+ # Instance of {Fire::System}.
72
+ def system
73
+ #@system ||= System.new(:ignore=>ignore, :files=>scripts)
74
+ @system ||= Fire.system
75
+ end
76
+
77
+ # TODO: load configuration
78
+ #
79
+ #def rc?
80
+ # Dir.glob('{.c,c,C}onfig{.rb,}').first
81
+ #end
82
+
83
+ #
84
+ # Default rules script is the first file matching `.fire/rules.rb`
85
+ # or `rules.rb` from the the project's root directory. The easiest
86
+ # way to customize this is to use `.fire/rules.rb` and use `import`
87
+ # to load which ever files you prefer, e.g. `import "task/*.fire"`.
88
+ #
89
+ # @return [Array] List of file paths.
90
+ #
91
+ def script
92
+ @script ||= (
93
+ glob = File.join(root, '{.fire/rules.rb,rules.rb}')
94
+ Dir.glob(glob, File::FNM_CASEFOLD).first
95
+ )
96
+ end
97
+ alias :scripts :script
98
+
99
+ #
100
+ # Change this rules script(s) that are loaded.
101
+ #
102
+ def script=(files)
103
+ @script = Array(files)
104
+ end
105
+ alias :scripts= :script=
106
+
107
+ #
108
+ # File globs to ignore.
109
+ #
110
+ # @return [Array] List of file globs.
111
+ #
112
+ def ignore
113
+ @ignore ||= (
114
+ f = File.join(root, IGNORE_FILE)
115
+ i = []
116
+ if File.exist?(f)
117
+ File.read(f).lines.each do |line|
118
+ glob = line.strip
119
+ i << glob unless glob.empty?
120
+ end
121
+ end
122
+ i
123
+ )
124
+ end
125
+
126
+ # Run once.
127
+ def run(argv)
128
+ Dir.chdir(root) do
129
+ if argv.size > 0
130
+ run_task(*argv)
131
+ else
132
+ run_rules
133
+ end
134
+ end
135
+ end
136
+
137
+ # Run periodically.
138
+ def autorun(argv)
139
+ Dir.chdir(root) do
140
+ trap("INT") { puts "\nPutting out the fire!"; exit }
141
+ puts "Fire started! (pid #{Process.pid})"
142
+ loop do
143
+ run_rules
144
+ sleep(watch)
145
+ end
146
+ end
147
+ end
148
+
149
+ private
150
+
151
+ #
152
+ def load_system
153
+ system.ignore(*ignore)
154
+ system.import(*script)
155
+ end
156
+
157
+ # Run the rules.
158
+ def run_rules
159
+ runner.run_rules
160
+ save_digest
161
+ end
162
+
163
+ # Run the rules.
164
+ def run_task(*argv)
165
+ runner.run_task(*argv)
166
+ end
167
+
168
+ #
169
+ #def save_pid
170
+ # File.open('.fire/pid', 'w') do |f|
171
+ # f << Process.pid.to_s
172
+ # end
173
+ #end
174
+
175
+ #
176
+ def save_digest
177
+ digest = Digest.new(:ignore=>ignore)
178
+ digest.save
179
+ end
180
+
181
+ #
182
+ def runner
183
+ Runner.new(system)
184
+ end
185
+
186
+ #def run
187
+ # @states.each do |state|
188
+ # session = OpenStruct.new
189
+ # next unless state.active?(info)
190
+ # @rules.each do |rule|
191
+ # if md = rule.match?(state)
192
+ # if rule.arity == 0 or md == true
193
+ # rule.call(info)
194
+ # else
195
+ # rule.call(info,*md[1..-1])
196
+ # end
197
+ # end
198
+ # end
199
+ # end
200
+ #end
201
+
202
+ # List of rules from the system.
203
+ def rules
204
+ system.rules
205
+ end
206
+
207
+ # Mapping of tasks from the system.
208
+ def tasks
209
+ system.tasks
210
+ end
211
+
212
+ # Produce a printable list of tasks that can run from the command line.
213
+ #
214
+ # Returns [String]
215
+ def task_sheet
216
+ max = tasks.map{|n,t| n.to_s.size}.max.to_i
217
+ layout = "ou %-#{max}s # %s"
218
+ text = []
219
+ tasks.each do |name, task|
220
+ text << layout % [name, task.description] if task.description
221
+ end
222
+ text.join("\n")
223
+ end
224
+
225
+ # Locate project root. This method ascends up the file system starting
226
+ # as the current working directory looking for `ROOT_INDICATORS`. When
227
+ # a match is found, the directory in which it is found is returned as
228
+ # the root. It is also memoized, so repeated calls to this method will
229
+ # not repeat the search.
230
+ #
231
+ # Returns [String]
232
+ def root
233
+ @root ||= (
234
+ r = nil
235
+ d = Dir.pwd
236
+ while d != home && d != '/'
237
+ if ROOT_INDICATORS.any?{ |g| Dir.glob(File.join(d, g), File::FNM_CASEFOLD).first }
238
+ break r = d
239
+ end
240
+ d = File.dirname(d)
241
+ end
242
+ abort "Can't locate project root." unless r
243
+ r
244
+ )
245
+ end
246
+
247
+ # Home directory.
248
+ #
249
+ # Returns [String]
250
+ def home
251
+ @home ||= File.expand_path('~')
252
+ end
253
+
254
+
255
+ # TODO: support rc profiles
256
+ #if config = Fire.rc_config
257
+ # config.each do |c|
258
+ # if c.arity == 0
259
+ # system.instance_eval(&c)
260
+ # else
261
+ # c.call(system)
262
+ # end
263
+ # end
264
+ #end
265
+
266
+ end
267
+
268
+ end