ergo 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.index +62 -0
- data/.yardopts +10 -0
- data/HISTORY.md +47 -0
- data/LICENSE.txt +25 -0
- data/README.md +161 -0
- data/bin/ergo +4 -0
- data/demo/03_runner/01_applying_rules.md +51 -0
- data/demo/applique/ae.rb +1 -0
- data/demo/applique/ergo.rb +7 -0
- data/demo/overview.md +0 -0
- data/lib/ergo.rb +20 -0
- data/lib/ergo.yml +62 -0
- data/lib/ergo/book.rb +218 -0
- data/lib/ergo/cli.rb +185 -0
- data/lib/ergo/core_ext.rb +4 -0
- data/lib/ergo/core_ext/boolean.rb +10 -0
- data/lib/ergo/core_ext/cli.rb +56 -0
- data/lib/ergo/core_ext/true_class.rb +58 -0
- data/lib/ergo/digest.rb +196 -0
- data/lib/ergo/ignore.rb +146 -0
- data/lib/ergo/match.rb +26 -0
- data/lib/ergo/rule.rb +134 -0
- data/lib/ergo/runner.rb +377 -0
- data/lib/ergo/shellutils.rb +79 -0
- data/lib/ergo/state.rb +112 -0
- data/lib/ergo/system.rb +51 -0
- data/man/.gitignore +2 -0
- data/man/ergo.1.ronn +50 -0
- metadata +163 -0
data/lib/ergo/book.rb
ADDED
@@ -0,0 +1,218 @@
|
|
1
|
+
module Ergo
|
2
|
+
|
3
|
+
##
|
4
|
+
# Ergo rulebook stores defined states and rules.
|
5
|
+
#
|
6
|
+
# TODO: There are some minor namespace issues with this implementation.
|
7
|
+
# We don't necessarily want a rule block to be able to
|
8
|
+
# call #rule. However, the scoping is a bit complicated,
|
9
|
+
# so it's an acceptable niggle for now.
|
10
|
+
#
|
11
|
+
class Book < Module
|
12
|
+
|
13
|
+
# Instantiate new system.
|
14
|
+
#
|
15
|
+
# Arguments
|
16
|
+
# system - The system to which this book belongs. [System]
|
17
|
+
# name - Name of the book.
|
18
|
+
#
|
19
|
+
# Yields the script defining the books rules.
|
20
|
+
def initialize(system, name, &block)
|
21
|
+
extend ShellUtils
|
22
|
+
extend system
|
23
|
+
extend self
|
24
|
+
|
25
|
+
@scripts = []
|
26
|
+
@rules = []
|
27
|
+
@states = {}
|
28
|
+
|
29
|
+
@name = name.to_s
|
30
|
+
@ignore = system.ignore
|
31
|
+
@session = system.session
|
32
|
+
|
33
|
+
clear_rule_options
|
34
|
+
|
35
|
+
module_eval(&block) if block
|
36
|
+
end
|
37
|
+
|
38
|
+
# Book name
|
39
|
+
attr :name
|
40
|
+
|
41
|
+
# Current session.
|
42
|
+
attr :session
|
43
|
+
|
44
|
+
# Rule scripts.
|
45
|
+
attr :scripts
|
46
|
+
|
47
|
+
# Array of defined states.
|
48
|
+
attr :states
|
49
|
+
|
50
|
+
# Array of defined rules.
|
51
|
+
attr :rules
|
52
|
+
|
53
|
+
# Import from another file, or glob of files, relative to project root.
|
54
|
+
#
|
55
|
+
# TODO: Should importing be relative the importing file?
|
56
|
+
#
|
57
|
+
# Returns nothing.
|
58
|
+
def import(*globs)
|
59
|
+
globs.each do |glob|
|
60
|
+
#if File.relative?(glob)
|
61
|
+
# dir = Dir.pwd #session.root #File.dirname(caller[0])
|
62
|
+
# glob = File.join(dir, glob)
|
63
|
+
#end
|
64
|
+
Dir[glob].each do |file|
|
65
|
+
next unless File.file?(file) # add warning
|
66
|
+
next if @scripts.include?(file)
|
67
|
+
@scripts << file
|
68
|
+
module_eval(File.read(file), file)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Add paths to be ignored in file rules.
|
74
|
+
#
|
75
|
+
# globs - List of file globs. [Array<String>]
|
76
|
+
#
|
77
|
+
# Returns [Array<String>]
|
78
|
+
def ignore(*globs)
|
79
|
+
@ignore.concat(globs) unless globs.empty?
|
80
|
+
@ignore
|
81
|
+
end
|
82
|
+
|
83
|
+
# Replace globs in ignore list.
|
84
|
+
#
|
85
|
+
# globs - List of file globs. [Array<String>]
|
86
|
+
#
|
87
|
+
# Returns [Array<String>]
|
88
|
+
def ignore!(*globs)
|
89
|
+
@ignore.replace(globs)
|
90
|
+
@ignore
|
91
|
+
end
|
92
|
+
|
93
|
+
# Define a named state. States define conditions that are used to trigger
|
94
|
+
# rules. Named states are kept in a hash table to ensure that only one state
|
95
|
+
# is ever defined for a given name. Calling state again with the same name
|
96
|
+
# as a previously defined state will redefine the condition of that state.
|
97
|
+
#
|
98
|
+
# Examples
|
99
|
+
# state :no_rdocs? do
|
100
|
+
# files = Dir.glob('lib/**/*.rb')
|
101
|
+
# FileUtils.uptodate?('doc', files) ? files : false
|
102
|
+
# end
|
103
|
+
#
|
104
|
+
# Returns nil if state name is given. [nil]
|
105
|
+
# Returns State in no name is given. [State]
|
106
|
+
def state(name=nil, &condition)
|
107
|
+
if name
|
108
|
+
if condition
|
109
|
+
@states[name.to_sym] = condition
|
110
|
+
define_method(name) do |*args|
|
111
|
+
state = @states[name.to_sym]
|
112
|
+
State.new{ states[name.to_sym].call(*args) }
|
113
|
+
end
|
114
|
+
else
|
115
|
+
raise ArgumentError
|
116
|
+
end
|
117
|
+
else
|
118
|
+
State.new{ condition.call(*args) }
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Define a file state.
|
123
|
+
#
|
124
|
+
# Returns [FileState]
|
125
|
+
def file(pattern)
|
126
|
+
FileState.new(pattern)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Define an environment state.
|
130
|
+
#
|
131
|
+
# Examples
|
132
|
+
# env('PATH'=>/foo/)
|
133
|
+
#
|
134
|
+
# Returns [State]
|
135
|
+
def env(name_to_pattern)
|
136
|
+
State.new do
|
137
|
+
name_to_pattern.any? do |name, re|
|
138
|
+
re === ENV[name.to_s] # or `all?` instead?
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Define a rule. Rules are procedures that are tiggered
|
144
|
+
# by logical states.
|
145
|
+
#
|
146
|
+
# Examples
|
147
|
+
# rule no_rdocs do |files|
|
148
|
+
# sh "rdoc --output doc/rdoc " + files.join(" ")
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# Returns [Rule]
|
152
|
+
def rule(state, &procedure)
|
153
|
+
case state
|
154
|
+
when String, Regexp
|
155
|
+
state = file(state)
|
156
|
+
when Symbol
|
157
|
+
# TODO: Is this really the best idea?
|
158
|
+
#@states[state.to_sym]
|
159
|
+
end
|
160
|
+
rule = Rule.new(state, get_rule_options, &procedure)
|
161
|
+
@rules << rule
|
162
|
+
clear_rule_options
|
163
|
+
rule
|
164
|
+
end
|
165
|
+
|
166
|
+
# Check a name state.
|
167
|
+
#
|
168
|
+
# Returns [Array,Boolean]
|
169
|
+
def state?(name, *args)
|
170
|
+
@states[name.to_sym].call(*args)
|
171
|
+
end
|
172
|
+
|
173
|
+
# Set rule description. The next rule defined will get the most
|
174
|
+
# recently defined description attached to it.
|
175
|
+
#
|
176
|
+
# Returns [String]
|
177
|
+
def desc(description)
|
178
|
+
@_desc = description
|
179
|
+
end
|
180
|
+
|
181
|
+
# Bookmark the rule.
|
182
|
+
#
|
183
|
+
# Returns nothing.
|
184
|
+
def mark(*names)
|
185
|
+
@_mark.concat(names)
|
186
|
+
end
|
187
|
+
alias :bookmark :mark
|
188
|
+
|
189
|
+
#
|
190
|
+
#
|
191
|
+
def private(*methods)
|
192
|
+
@_priv = true
|
193
|
+
super(*methods) # TODO: why doesn't this work as expected?
|
194
|
+
end
|
195
|
+
|
196
|
+
# Issue notification.
|
197
|
+
#
|
198
|
+
# Returns nothing.
|
199
|
+
def notify(message, options={})
|
200
|
+
title = options.delete(:title) || 'Fire Notification'
|
201
|
+
Notify.notify(title, message.to_s, options)
|
202
|
+
end
|
203
|
+
|
204
|
+
private
|
205
|
+
|
206
|
+
def get_rule_options
|
207
|
+
{ :desc=>@_desc, :mark=>@_mark, :private=>@_priv }
|
208
|
+
end
|
209
|
+
|
210
|
+
def clear_rule_options
|
211
|
+
@_mark = [name].compact
|
212
|
+
@_desc = nil
|
213
|
+
@_priv = false
|
214
|
+
end
|
215
|
+
|
216
|
+
end
|
217
|
+
|
218
|
+
end
|
data/lib/ergo/cli.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
module Ergo
|
2
|
+
|
3
|
+
##
|
4
|
+
# Fire's command line interface.
|
5
|
+
#
|
6
|
+
class CLI
|
7
|
+
|
8
|
+
# Fire her up!
|
9
|
+
def self.fire!(argv=ARGV)
|
10
|
+
new(argv).fire!
|
11
|
+
end
|
12
|
+
|
13
|
+
# Initialize new instance of Ergo::CLI.
|
14
|
+
# If `argv` is not provided than ARGV is uses.
|
15
|
+
#
|
16
|
+
# argv - Command line argument. [Array<String>]
|
17
|
+
#
|
18
|
+
# Returns nothing.
|
19
|
+
def initialize(argv=nil)
|
20
|
+
begin
|
21
|
+
require 'dotopts'
|
22
|
+
rescue LoadError
|
23
|
+
end
|
24
|
+
|
25
|
+
@argv = Array(argv || ARGV)
|
26
|
+
|
27
|
+
@script = nil
|
28
|
+
@watch = nil
|
29
|
+
@fresh = false
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns runner instance. [Runner]
|
33
|
+
def runner
|
34
|
+
@runner ||= (
|
35
|
+
Runner.new(
|
36
|
+
:script => @script,
|
37
|
+
:fresh => @fresh,
|
38
|
+
:watch => @watch
|
39
|
+
)
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Execute command.
|
44
|
+
#
|
45
|
+
# command - Which command to execute.
|
46
|
+
#
|
47
|
+
# Returns nothing.
|
48
|
+
def fire!(argv=ARGV)
|
49
|
+
$DEBUG = argv.include?('--debug') || $DEBUG
|
50
|
+
return fire if $DEBUG
|
51
|
+
begin
|
52
|
+
fire
|
53
|
+
rescue => err
|
54
|
+
puts "ergo: error #{err}"
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# Fire her up!
|
59
|
+
def fire
|
60
|
+
args = cli_parse
|
61
|
+
|
62
|
+
ensure_options(args)
|
63
|
+
|
64
|
+
#if args.first == 'init' && !runner.root?
|
65
|
+
# init_project(*args)
|
66
|
+
#end
|
67
|
+
|
68
|
+
case @command
|
69
|
+
when :list
|
70
|
+
print_rules(*args)
|
71
|
+
else
|
72
|
+
runner.run(*args)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Parse command line arguments with just the prettiest
|
77
|
+
# little CLI parser there ever was.
|
78
|
+
def cli_parse
|
79
|
+
@command = nil
|
80
|
+
|
81
|
+
cli @argv,
|
82
|
+
"-R --rules" => lambda{ @command = :list },
|
83
|
+
"-a --auto" => method(:watch=),
|
84
|
+
"-f --fresh" => method(:fresh!),
|
85
|
+
"-s --script" => method(:script=),
|
86
|
+
" --debug" => method(:debug!)
|
87
|
+
end
|
88
|
+
|
89
|
+
#
|
90
|
+
def ensure_options(args)
|
91
|
+
erropts = args.select{ |a| a.start_with?('-') }
|
92
|
+
unless erropts.empty?
|
93
|
+
raise "unsupported options #{erropts.join(' ')}"
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Shall we make a fresh start of it, and remove all digests?
|
98
|
+
#
|
99
|
+
# Returns [Boolean]
|
100
|
+
def fresh?
|
101
|
+
@fresh
|
102
|
+
end
|
103
|
+
|
104
|
+
# Set fresh flag to true.
|
105
|
+
#
|
106
|
+
# Returns [Boolean]
|
107
|
+
def fresh!
|
108
|
+
@fresh = true
|
109
|
+
end
|
110
|
+
|
111
|
+
# Shall we make a fresh start of it, and remove all digests?
|
112
|
+
#
|
113
|
+
# Returns [Boolean]
|
114
|
+
def debug?
|
115
|
+
@debug
|
116
|
+
end
|
117
|
+
|
118
|
+
# Set debug flag to true.
|
119
|
+
#
|
120
|
+
# Returns [Boolean]
|
121
|
+
def debug!
|
122
|
+
@debug = true
|
123
|
+
end
|
124
|
+
|
125
|
+
# Set the "watch" period --the rate at which
|
126
|
+
# autofiring of occurs.
|
127
|
+
#
|
128
|
+
# Returns [Fixnum[
|
129
|
+
def watch=(seconds)
|
130
|
+
@watch = seconds.to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
# Use alternate ergo script.
|
134
|
+
#
|
135
|
+
# Returns [Array]
|
136
|
+
def script=(script)
|
137
|
+
@script = script.to_s
|
138
|
+
end
|
139
|
+
|
140
|
+
#
|
141
|
+
#
|
142
|
+
# Returns nothing.
|
143
|
+
def init_project(*args)
|
144
|
+
FileUtils.mkdir_p('.ergo')
|
145
|
+
end
|
146
|
+
|
147
|
+
# Print out a list of availabe manual triggers.
|
148
|
+
#
|
149
|
+
# Returns nothing.
|
150
|
+
def print_rules(*names)
|
151
|
+
names = nil if names.empty?
|
152
|
+
|
153
|
+
list = []
|
154
|
+
runner.rules.each do |rule|
|
155
|
+
if Book === rule
|
156
|
+
rule.rules.each do |r|
|
157
|
+
next unless names.any?{ |n| r.mark?(n) } if names
|
158
|
+
list << r.to_a
|
159
|
+
end
|
160
|
+
else
|
161
|
+
next unless names.any?{ |n| rule.mark?(n) } if names
|
162
|
+
list << rule.to_a
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
list.reject!{ |desc, marks, prv| desc.to_s == "" }
|
167
|
+
|
168
|
+
puts "(#{runner.root})"
|
169
|
+
|
170
|
+
i = 1
|
171
|
+
list.each do |desc, marks, prv|
|
172
|
+
if marks.empty?
|
173
|
+
puts "%4d. %s%s" % [i, desc, prv ? '*' : '']
|
174
|
+
else
|
175
|
+
puts "%4d. %s%s (%s)" % [i, desc, prv ? '*' : '', marks.join(' ')]
|
176
|
+
end
|
177
|
+
i += 1
|
178
|
+
end
|
179
|
+
|
180
|
+
exit
|
181
|
+
end
|
182
|
+
|
183
|
+
end
|
184
|
+
|
185
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module Kernel
|
2
|
+
private
|
3
|
+
#
|
4
|
+
# CLI is based on Clap library
|
5
|
+
# Copyright (c) 2010 Michel Martens
|
6
|
+
#
|
7
|
+
def cli(*args)
|
8
|
+
opts = (Hash === args.last ? args.pop : nil)
|
9
|
+
argv = args.first || ARGV.dup
|
10
|
+
args = []
|
11
|
+
|
12
|
+
#raise ArgumentError unless opts
|
13
|
+
|
14
|
+
# Split option aliases.
|
15
|
+
opts = opts.inject({}) do |h,(k,v)|
|
16
|
+
k.to_s.split(/\s+/).each{|o| h[o]=v}; h
|
17
|
+
end
|
18
|
+
|
19
|
+
# Convert single dash flags into multiple flags.
|
20
|
+
argv = argv.inject([]) do |a, v|
|
21
|
+
if v[0,1] == '-' && v[1,1] != '-'
|
22
|
+
a.concat(v[1..-1].chars.map{|c| "-#{c}"})
|
23
|
+
else
|
24
|
+
a << v
|
25
|
+
end
|
26
|
+
a
|
27
|
+
end
|
28
|
+
|
29
|
+
while argv.any?
|
30
|
+
item = argv.shift
|
31
|
+
flag = opts[item]
|
32
|
+
|
33
|
+
if flag
|
34
|
+
# Work around lambda semantics in 1.8.7.
|
35
|
+
arity = [flag.arity, 0].max
|
36
|
+
|
37
|
+
# Raise if there are not enough parameters
|
38
|
+
# available for the flag.
|
39
|
+
if argv.size < arity
|
40
|
+
raise ArgumentError
|
41
|
+
end
|
42
|
+
|
43
|
+
# Call the lambda with N items from argv,
|
44
|
+
# where N is the lambda's arity.
|
45
|
+
flag.call(*argv.shift(arity))
|
46
|
+
else
|
47
|
+
|
48
|
+
# Collect the items that don't correspond to
|
49
|
+
# flags.
|
50
|
+
args << item
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
args
|
55
|
+
end
|
56
|
+
end
|