jimweirich-rake 0.8.4.99 → 0.8.5

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/lib/rake/task.rb ADDED
@@ -0,0 +1,301 @@
1
+ module Rake
2
+
3
+ # #########################################################################
4
+ # A Task is the basic unit of work in a Rakefile. Tasks have associated
5
+ # actions (possibly more than one) and a list of prerequisites. When
6
+ # invoked, a task will first ensure that all of its prerequisites have an
7
+ # opportunity to run and then it will execute its own actions.
8
+ #
9
+ # Tasks are not usually created directly using the new method, but rather
10
+ # use the +file+ and +task+ convenience methods.
11
+ #
12
+ class Task
13
+ # List of prerequisites for a task.
14
+ attr_reader :prerequisites
15
+
16
+ # List of actions attached to a task.
17
+ attr_reader :actions
18
+
19
+ # Application owning this task.
20
+ attr_accessor :application
21
+
22
+ # Comment for this task. Restricted to a single line of no more than 50
23
+ # characters.
24
+ attr_reader :comment
25
+
26
+ # Full text of the (possibly multi-line) comment.
27
+ attr_reader :full_comment
28
+
29
+ # Array of nested namespaces names used for task lookup by this task.
30
+ attr_reader :scope
31
+
32
+ # Return task name
33
+ def to_s
34
+ name
35
+ end
36
+
37
+ def inspect
38
+ "<#{self.class} #{name} => [#{prerequisites.join(', ')}]>"
39
+ end
40
+
41
+ # List of sources for task.
42
+ attr_writer :sources
43
+ def sources
44
+ @sources ||= []
45
+ end
46
+
47
+ # First source from a rule (nil if no sources)
48
+ def source
49
+ @sources.first if defined?(@sources)
50
+ end
51
+
52
+ # Create a task named +task_name+ with no actions or prerequisites. Use
53
+ # +enhance+ to add actions and prerequisites.
54
+ def initialize(task_name, app)
55
+ @name = task_name.to_s
56
+ @prerequisites = []
57
+ @actions = []
58
+ @already_invoked = false
59
+ @full_comment = nil
60
+ @comment = nil
61
+ @lock = Monitor.new
62
+ @application = app
63
+ @scope = app.current_scope
64
+ @arg_names = nil
65
+ end
66
+
67
+ # Enhance a task with prerequisites or actions. Returns self.
68
+ def enhance(deps=nil, &block)
69
+ @prerequisites |= deps if deps
70
+ @actions << block if block_given?
71
+ self
72
+ end
73
+
74
+ # Name of the task, including any namespace qualifiers.
75
+ def name
76
+ @name.to_s
77
+ end
78
+
79
+ # Name of task with argument list description.
80
+ def name_with_args # :nodoc:
81
+ if arg_description
82
+ "#{name}#{arg_description}"
83
+ else
84
+ name
85
+ end
86
+ end
87
+
88
+ # Argument description (nil if none).
89
+ def arg_description # :nodoc:
90
+ @arg_names ? "[#{(arg_names || []).join(',')}]" : nil
91
+ end
92
+
93
+ # Name of arguments for this task.
94
+ def arg_names
95
+ @arg_names || []
96
+ end
97
+
98
+ # Reenable the task, allowing its tasks to be executed if the task
99
+ # is invoked again.
100
+ def reenable
101
+ @already_invoked = false
102
+ end
103
+
104
+ # Clear the existing prerequisites and actions of a rake task.
105
+ def clear
106
+ clear_prerequisites
107
+ clear_actions
108
+ self
109
+ end
110
+
111
+ # Clear the existing prerequisites of a rake task.
112
+ def clear_prerequisites
113
+ prerequisites.clear
114
+ self
115
+ end
116
+
117
+ # Clear the existing actions on a rake task.
118
+ def clear_actions
119
+ actions.clear
120
+ self
121
+ end
122
+
123
+ # Invoke the task if it is needed. Prerequites are invoked first.
124
+ def invoke(*args)
125
+ task_args = TaskArguments.new(arg_names, args)
126
+ invoke_with_call_chain(task_args, InvocationChain::EMPTY)
127
+ end
128
+
129
+ # Same as invoke, but explicitly pass a call chain to detect
130
+ # circular dependencies.
131
+ def invoke_with_call_chain(task_args, invocation_chain) # :nodoc:
132
+ new_chain = InvocationChain.append(self, invocation_chain)
133
+ @lock.synchronize do
134
+ if application.options.trace
135
+ puts "** Invoke #{name} #{format_trace_flags}"
136
+ end
137
+ return if @already_invoked
138
+ @already_invoked = true
139
+ invoke_prerequisites(task_args, new_chain)
140
+ execute(task_args) if needed?
141
+ end
142
+ end
143
+ protected :invoke_with_call_chain
144
+
145
+ # Invoke all the prerequisites of a task.
146
+ def invoke_prerequisites(task_args, invocation_chain) # :nodoc:
147
+ @prerequisites.each { |n|
148
+ prereq = application[n, @scope]
149
+ prereq_args = task_args.new_scope(prereq.arg_names)
150
+ prereq.invoke_with_call_chain(prereq_args, invocation_chain)
151
+ }
152
+ end
153
+
154
+ # Format the trace flags for display.
155
+ def format_trace_flags
156
+ flags = []
157
+ flags << "first_time" unless @already_invoked
158
+ flags << "not_needed" unless needed?
159
+ flags.empty? ? "" : "(" + flags.join(", ") + ")"
160
+ end
161
+ private :format_trace_flags
162
+
163
+ # Execute the actions associated with this task.
164
+ def execute(args=nil)
165
+ args ||= EMPTY_TASK_ARGS
166
+ if application.options.dryrun
167
+ puts "** Execute (dry run) #{name}"
168
+ return
169
+ end
170
+ if application.options.trace
171
+ puts "** Execute #{name}"
172
+ end
173
+ application.enhance_with_matching_rule(name) if @actions.empty?
174
+ @actions.each do |act|
175
+ case act.arity
176
+ when 1
177
+ act.call(self)
178
+ else
179
+ act.call(self, args)
180
+ end
181
+ end
182
+ end
183
+
184
+ # Is this task needed?
185
+ def needed?
186
+ true
187
+ end
188
+
189
+ # Timestamp for this task. Basic tasks return the current time for their
190
+ # time stamp. Other tasks can be more sophisticated.
191
+ def timestamp
192
+ @prerequisites.collect { |p| application[p].timestamp }.max || Time.now
193
+ end
194
+
195
+ # Add a description to the task. The description can consist of an option
196
+ # argument list (enclosed brackets) and an optional comment.
197
+ def add_description(description)
198
+ return if ! description
199
+ comment = description.strip
200
+ add_comment(comment) if comment && ! comment.empty?
201
+ end
202
+
203
+ # Writing to the comment attribute is the same as adding a description.
204
+ def comment=(description)
205
+ add_description(description)
206
+ end
207
+
208
+ # Add a comment to the task. If a comment alread exists, separate
209
+ # the new comment with " / ".
210
+ def add_comment(comment)
211
+ if @full_comment
212
+ @full_comment << " / "
213
+ else
214
+ @full_comment = ''
215
+ end
216
+ @full_comment << comment
217
+ if @full_comment =~ /\A([^.]+?\.)( |$)/
218
+ @comment = $1
219
+ else
220
+ @comment = @full_comment
221
+ end
222
+ end
223
+ private :add_comment
224
+
225
+ # Set the names of the arguments for this task. +args+ should be
226
+ # an array of symbols, one for each argument name.
227
+ def set_arg_names(args)
228
+ @arg_names = args.map { |a| a.to_sym }
229
+ end
230
+
231
+ # Return a string describing the internal state of a task. Useful for
232
+ # debugging.
233
+ def investigation
234
+ result = "------------------------------\n"
235
+ result << "Investigating #{name}\n"
236
+ result << "class: #{self.class}\n"
237
+ result << "task needed: #{needed?}\n"
238
+ result << "timestamp: #{timestamp}\n"
239
+ result << "pre-requisites: \n"
240
+ prereqs = @prerequisites.collect {|name| application[name]}
241
+ prereqs.sort! {|a,b| a.timestamp <=> b.timestamp}
242
+ prereqs.each do |p|
243
+ result << "--#{p.name} (#{p.timestamp})\n"
244
+ end
245
+ latest_prereq = @prerequisites.collect{|n| application[n].timestamp}.max
246
+ result << "latest-prerequisite time: #{latest_prereq}\n"
247
+ result << "................................\n\n"
248
+ return result
249
+ end
250
+
251
+ # ----------------------------------------------------------------
252
+ # Rake Module Methods
253
+ #
254
+ class << self
255
+
256
+ # Clear the task list. This cause rake to immediately forget all the
257
+ # tasks that have been assigned. (Normally used in the unit tests.)
258
+ def clear
259
+ Rake.application.clear
260
+ end
261
+
262
+ # List of all defined tasks.
263
+ def tasks
264
+ Rake.application.tasks
265
+ end
266
+
267
+ # Return a task with the given name. If the task is not currently
268
+ # known, try to synthesize one from the defined rules. If no rules are
269
+ # found, but an existing file matches the task name, assume it is a file
270
+ # task with no dependencies or actions.
271
+ def [](task_name)
272
+ Rake.application[task_name]
273
+ end
274
+
275
+ # TRUE if the task name is already defined.
276
+ def task_defined?(task_name)
277
+ Rake.application.lookup(task_name) != nil
278
+ end
279
+
280
+ # Define a task given +args+ and an option block. If a rule with the
281
+ # given name already exists, the prerequisites and actions are added to
282
+ # the existing task. Returns the defined task.
283
+ def define_task(*args, &block)
284
+ Rake.application.define_task(self, *args, &block)
285
+ end
286
+
287
+ # Define a rule for synthesizing tasks.
288
+ def create_rule(*args, &block)
289
+ Rake.application.create_rule(*args, &block)
290
+ end
291
+
292
+ # Apply the scope to the task name according to the rules for
293
+ # this kind of task. Generic tasks will accept the scope as
294
+ # part of the name.
295
+ def scope_name(scope, task_name)
296
+ (scope + [task_name]).join(':')
297
+ end
298
+
299
+ end # class << Rake::Task
300
+ end # class Rake::Task
301
+ end
@@ -0,0 +1,7 @@
1
+ module Rake
2
+
3
+ # Error indicating an ill-formed task declaration.
4
+ class TaskArgumentError < ArgumentError
5
+ end
6
+
7
+ end
@@ -0,0 +1,78 @@
1
+ module Rake
2
+
3
+ ####################################################################
4
+ # TaskAguments manage the arguments passed to a task.
5
+ #
6
+ class TaskArguments
7
+ include Enumerable
8
+
9
+ attr_reader :names
10
+
11
+ # Create a TaskArgument object with a list of named arguments
12
+ # (given by :names) and a set of associated values (given by
13
+ # :values). :parent is the parent argument object.
14
+ def initialize(names, values, parent=nil)
15
+ @names = names
16
+ @parent = parent
17
+ @hash = {}
18
+ names.each_with_index { |name, i|
19
+ @hash[name.to_sym] = values[i] unless values[i].nil?
20
+ }
21
+ end
22
+
23
+ # Create a new argument scope using the prerequisite argument
24
+ # names.
25
+ def new_scope(names)
26
+ values = names.collect { |n| self[n] }
27
+ self.class.new(names, values, self)
28
+ end
29
+
30
+ # Find an argument value by name or index.
31
+ def [](index)
32
+ lookup(index.to_sym)
33
+ end
34
+
35
+ # Specify a hash of default values for task arguments. Use the
36
+ # defaults only if there is no specific value for the given
37
+ # argument.
38
+ def with_defaults(defaults)
39
+ @hash = defaults.merge(@hash)
40
+ end
41
+
42
+ def each(&block)
43
+ @hash.each(&block)
44
+ end
45
+
46
+ def method_missing(sym, *args, &block)
47
+ lookup(sym.to_sym)
48
+ end
49
+
50
+ def to_hash
51
+ @hash
52
+ end
53
+
54
+ def to_s
55
+ @hash.inspect
56
+ end
57
+
58
+ def inspect
59
+ to_s
60
+ end
61
+
62
+ protected
63
+
64
+ def lookup(name)
65
+ if @hash.has_key?(name)
66
+ @hash[name]
67
+ elsif ENV.has_key?(name.to_s)
68
+ ENV[name.to_s]
69
+ elsif ENV.has_key?(name.to_s.upcase)
70
+ ENV[name.to_s.upcase]
71
+ elsif @parent
72
+ @parent.lookup(name)
73
+ end
74
+ end
75
+ end
76
+
77
+ EMPTY_TASK_ARGS = TaskArguments.new([], [])
78
+ end
@@ -0,0 +1,306 @@
1
+ module Rake
2
+
3
+ # The TaskManager module is a mixin for managing tasks.
4
+ module TaskManager
5
+ # Track the last comment made in the Rakefile.
6
+ attr_accessor :last_description
7
+ alias :last_comment :last_description # Backwards compatibility
8
+
9
+ def initialize
10
+ super
11
+ @tasks = Hash.new
12
+ @rules = Array.new
13
+ @scope = Array.new
14
+ @last_description = nil
15
+ end
16
+
17
+ def create_rule(*args, &block)
18
+ pattern, arg_names, deps = resolve_args(args)
19
+ pattern = Regexp.new(Regexp.quote(pattern) + '$') if String === pattern
20
+ @rules << [pattern, deps, block]
21
+ end
22
+
23
+ def define_task(task_class, *args, &block)
24
+ task_name, arg_names, deps = resolve_args(args)
25
+ task_name = task_class.scope_name(@scope, task_name)
26
+ deps = [deps] unless deps.respond_to?(:to_ary)
27
+ deps = deps.collect {|d| d.to_s }
28
+ task = intern(task_class, task_name)
29
+ task.set_arg_names(arg_names) unless arg_names.empty?
30
+ task.add_description(get_description)
31
+ task.enhance(deps, &block)
32
+ task
33
+ end
34
+
35
+ # Lookup a task. Return an existing task if found, otherwise
36
+ # create a task of the current type.
37
+ def intern(task_class, task_name)
38
+ @tasks[task_name.to_s] ||= task_class.new(task_name, self)
39
+ end
40
+
41
+ # Find a matching task for +task_name+.
42
+ def [](task_name, scopes=nil)
43
+ task_name = task_name.to_s
44
+ self.lookup(task_name, scopes) or
45
+ enhance_with_matching_rule(task_name) or
46
+ synthesize_file_task(task_name) or
47
+ fail "Don't know how to build task '#{task_name}'"
48
+ end
49
+
50
+ def synthesize_file_task(task_name)
51
+ return nil unless File.exist?(task_name)
52
+ define_task(Rake::FileTask, task_name)
53
+ end
54
+
55
+ # Resolve the arguments for a task/rule. Returns a triplet of
56
+ # [task_name, arg_name_list, prerequisites].
57
+ def resolve_args(args)
58
+ if args.last.is_a?(Hash)
59
+ deps = args.pop
60
+ resolve_args_with_dependencies(args, deps)
61
+ else
62
+ resolve_args_without_dependencies(args)
63
+ end
64
+ end
65
+
66
+ # Resolve task arguments for a task or rule when there are no
67
+ # dependencies declared.
68
+ #
69
+ # The patterns recognized by this argument resolving function are:
70
+ #
71
+ # task :t
72
+ # task :t, [:a]
73
+ # task :t, :a (deprecated)
74
+ #
75
+ def resolve_args_without_dependencies(args)
76
+ task_name = args.shift
77
+ if args.size == 1 && args.first.respond_to?(:to_ary)
78
+ arg_names = args.first.to_ary
79
+ else
80
+ arg_names = args
81
+ end
82
+ [task_name, arg_names, []]
83
+ end
84
+ private :resolve_args_without_dependencies
85
+
86
+ # Resolve task arguments for a task or rule when there are
87
+ # dependencies declared.
88
+ #
89
+ # The patterns recognized by this argument resolving function are:
90
+ #
91
+ # task :t => [:d]
92
+ # task :t, [a] => [:d]
93
+ # task :t, :needs => [:d] (deprecated)
94
+ # task :t, :a, :needs => [:d] (deprecated)
95
+ #
96
+ def resolve_args_with_dependencies(args, hash) # :nodoc:
97
+ fail "Task Argument Error" if hash.size != 1
98
+ key, value = hash.map { |k, v| [k,v] }.first
99
+ if args.empty?
100
+ task_name = key
101
+ arg_names = []
102
+ deps = value
103
+ elsif key == :needs
104
+ task_name = args.shift
105
+ arg_names = args
106
+ deps = value
107
+ else
108
+ task_name = args.shift
109
+ arg_names = key
110
+ deps = value
111
+ end
112
+ deps = [deps] unless deps.respond_to?(:to_ary)
113
+ [task_name, arg_names, deps]
114
+ end
115
+ private :resolve_args_with_dependencies
116
+
117
+ # If a rule can be found that matches the task name, enhance the
118
+ # task with the prerequisites and actions from the rule. Set the
119
+ # source attribute of the task appropriately for the rule. Return
120
+ # the enhanced task or nil of no rule was found.
121
+ def enhance_with_matching_rule(task_name, level=0)
122
+ fail Rake::RuleRecursionOverflowError,
123
+ "Rule Recursion Too Deep" if level >= 16
124
+ @rules.each do |pattern, extensions, block|
125
+ if md = pattern.match(task_name)
126
+ task = attempt_rule(task_name, extensions, block, level)
127
+ return task if task
128
+ end
129
+ end
130
+ nil
131
+ rescue Rake::RuleRecursionOverflowError => ex
132
+ ex.add_target(task_name)
133
+ fail ex
134
+ end
135
+
136
+ # List of all defined tasks in this application.
137
+ def tasks
138
+ @tasks.values.sort_by { |t| t.name }
139
+ end
140
+
141
+ # List of all the tasks defined in the given scope (and its
142
+ # sub-scopes).
143
+ def tasks_in_scope(scope)
144
+ prefix = scope.join(":")
145
+ tasks.select { |t|
146
+ /^#{prefix}:/ =~ t.name
147
+ }
148
+ end
149
+
150
+ # Clear all tasks in this application.
151
+ def clear
152
+ @tasks.clear
153
+ @rules.clear
154
+ end
155
+
156
+ # Lookup a task, using scope and the scope hints in the task name.
157
+ # This method performs straight lookups without trying to
158
+ # synthesize file tasks or rules. Special scope names (e.g. '^')
159
+ # are recognized. If no scope argument is supplied, use the
160
+ # current scope. Return nil if the task cannot be found.
161
+ def lookup(task_name, initial_scope=nil)
162
+ initial_scope ||= @scope
163
+ task_name = task_name.to_s
164
+ if task_name =~ /^rake:/
165
+ scopes = []
166
+ task_name = task_name.sub(/^rake:/, '')
167
+ elsif task_name =~ /^(\^+)/
168
+ scopes = initial_scope[0, initial_scope.size - $1.size]
169
+ task_name = task_name.sub(/^(\^+)/, '')
170
+ else
171
+ scopes = initial_scope
172
+ end
173
+ lookup_in_scope(task_name, scopes)
174
+ end
175
+
176
+ # Lookup the task name
177
+ def lookup_in_scope(name, scope)
178
+ n = scope.size
179
+ while n >= 0
180
+ tn = (scope[0,n] + [name]).join(':')
181
+ task = @tasks[tn]
182
+ return task if task
183
+ n -= 1
184
+ end
185
+ nil
186
+ end
187
+ private :lookup_in_scope
188
+
189
+ # Return the list of scope names currently active in the task
190
+ # manager.
191
+ def current_scope
192
+ @scope.dup
193
+ end
194
+
195
+ # Evaluate the block in a nested namespace named +name+. Create
196
+ # an anonymous namespace if +name+ is nil.
197
+ def in_namespace(name)
198
+ name ||= generate_name
199
+ @scope.push(name)
200
+ ns = NameSpace.new(self, @scope)
201
+ yield(ns)
202
+ ns
203
+ ensure
204
+ @scope.pop
205
+ end
206
+
207
+ private
208
+
209
+ # Generate an anonymous namespace name.
210
+ def generate_name
211
+ @seed ||= 0
212
+ @seed += 1
213
+ "_anon_#{@seed}"
214
+ end
215
+
216
+ def trace_rule(level, message)
217
+ puts "#{" "*level}#{message}" if Rake.application.options.trace_rules
218
+ end
219
+
220
+ # Attempt to create a rule given the list of prerequisites.
221
+ def attempt_rule(task_name, extensions, block, level)
222
+ sources = make_sources(task_name, extensions)
223
+ prereqs = sources.collect { |source|
224
+ trace_rule level, "Attempting Rule #{task_name} => #{source}"
225
+ if File.exist?(source) || Rake::Task.task_defined?(source)
226
+ trace_rule level, "(#{task_name} => #{source} ... EXIST)"
227
+ source
228
+ elsif parent = enhance_with_matching_rule(source, level+1)
229
+ trace_rule level, "(#{task_name} => #{source} ... ENHANCE)"
230
+ parent.name
231
+ else
232
+ trace_rule level, "(#{task_name} => #{source} ... FAIL)"
233
+ return nil
234
+ end
235
+ }
236
+ task = FileTask.define_task({task_name => prereqs}, &block)
237
+ task.sources = prereqs
238
+ task
239
+ end
240
+
241
+ # Make a list of sources from the list of file name extensions /
242
+ # translation procs.
243
+ def make_sources(task_name, extensions)
244
+ extensions.collect { |ext|
245
+ case ext
246
+ when /%/
247
+ task_name.pathmap(ext)
248
+ when %r{/}
249
+ ext
250
+ when /^\./
251
+ task_name.ext(ext)
252
+ when String
253
+ ext
254
+ when Proc
255
+ if ext.arity == 1
256
+ ext.call(task_name)
257
+ else
258
+ ext.call
259
+ end
260
+ else
261
+ fail "Don't know how to handle rule dependent: #{ext.inspect}"
262
+ end
263
+ }.flatten
264
+ end
265
+
266
+
267
+ private
268
+
269
+ # Return the current description. If there isn't one, try to find it
270
+ # by reading in the source file and looking for a comment immediately
271
+ # prior to the task definition
272
+ def get_description
273
+ desc = @last_description || find_preceding_comment_for_task
274
+ @last_description = nil
275
+ desc
276
+ end
277
+
278
+ def find_preceding_comment_for_task
279
+ stack = caller
280
+ begin
281
+ where = stack.shift
282
+ end until stack.empty? || where =~ /in `task'/
283
+ return nil if stack.empty?
284
+ file_name, line = parse_stack_line(stack.shift)
285
+ return nil unless file_name
286
+ comment_from_file(file_name, line)
287
+ end
288
+
289
+ def parse_stack_line(where)
290
+ if where =~ /^(.*):(\d+)/
291
+ [ $1, Integer($2) ]
292
+ else
293
+ nil
294
+ end
295
+ end
296
+
297
+ def comment_from_file(file_name, line)
298
+ @file_cache ||= {}
299
+ content = (@file_cache[file_name] ||= File.readlines(file_name))
300
+ line -= 2
301
+ return nil unless content[line] =~ /^\s*#\s*(.*)/
302
+ $1
303
+ end
304
+ end
305
+
306
+ end