ergo 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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,4 @@
1
+ #require 'facets/cli'
2
+ require 'ergo/core_ext/cli'
3
+ require 'ergo/core_ext/boolean'
4
+ require 'ergo/core_ext/true_class'
@@ -0,0 +1,10 @@
1
+ module Boolean
2
+ end
3
+
4
+ class TrueClass
5
+ include Boolean
6
+ end
7
+
8
+ class FalseClass
9
+ include Boolean
10
+ 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