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