fire 0.2.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,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