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.
- data/.index +53 -0
- data/.yardopts +10 -0
- data/HISTORY.md +33 -0
- data/LICENSE.txt +29 -0
- data/README.md +217 -0
- data/bin/autofire +5 -0
- data/bin/fire +5 -0
- data/demo/03_runner/01_applying_rules.md +51 -0
- data/demo/03_runner/02_resolve_prerequisites.md +95 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/fire.rb +1 -0
- data/demo/applique/rules.rb +5 -0
- data/demo/overview.md +14 -0
- data/lib/fire.rb +3 -0
- data/lib/fire.yml +53 -0
- data/lib/fire/cli.rb +64 -0
- data/lib/fire/core_ext.rb +4 -0
- data/lib/fire/core_ext/boolean.rb +10 -0
- data/lib/fire/core_ext/cli.rb +56 -0
- data/lib/fire/core_ext/true_class.rb +58 -0
- data/lib/fire/digest.rb +112 -0
- data/lib/fire/dsl.rb +34 -0
- data/lib/fire/match.rb +26 -0
- data/lib/fire/rule.rb +103 -0
- data/lib/fire/rulefile.rb +12 -0
- data/lib/fire/runner.rb +76 -0
- data/lib/fire/session.rb +268 -0
- data/lib/fire/shellutils.rb +77 -0
- data/lib/fire/state.rb +91 -0
- data/lib/fire/system.rb +244 -0
- data/lib/fire/task.rb +71 -0
- data/man/fire.1 +47 -0
- data/man/fire.1.html +130 -0
- data/man/fire.1.ronn +52 -0
- metadata +135 -0
data/lib/fire/rule.rb
ADDED
@@ -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
|
data/lib/fire/runner.rb
ADDED
@@ -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
|
data/lib/fire/session.rb
ADDED
@@ -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
|