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.
- 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
|