Class: Rap::Task

Inherits:
Tap::Task
  • Object
show all
Extended by:
Declarations
Defined in:
lib/rap/task.rb,
lib/rap/declarations.rb

Overview

Rap tasks are a special breed of Tap::Task designed to behave much like Rake tasks. As such, declaration tasks:

  • return nil and pass nil in workflows

  • only execute once

  • are effectively singletons (one instance per app)

  • allow for multiple actions

The Rap::Task class partially includes Declarations so subclasses may directly declare tasks. A few alias acrobatics makes it so that ONLY Declarations#task is made available (desc cannot be used because Task classes already use that method for documentation, and namespace would be silly).

Weird? Yes, but it leads to this syntax:

# [Rapfile]
# class Subclass < Rap::Task
#   def helper(); "help"; end
# end
#
# # :: a help task
# Subclass.task(:help) {|task, args| puts "got #{task.helper}"}

% rap help
got help

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Declarations

app, desc, instance, namespace, task

Constructor Details

#initialize(config = {}, app = Tap::App.instance) ⇒ Task

Returns a new instance of Task.



213
214
215
216
217
218
219
220
221
222
223
# File 'lib/rap/task.rb', line 213

def initialize(config={}, app=Tap::App.instance)
  super
  @dependencies = []
  @resolved = false
  @args = nil
  
  # setup class dependencies
  self.class.dependencies.each do |dependency_class|
    depends_on dependency_class.instance(app)
  end
end

Class Attribute Details

.actionsObject

An array of actions (blocks) associated with this class. Each of the actions is called during process, with the instance and any args passed to process organized into an OpenStruct.



58
59
60
# File 'lib/rap/task.rb', line 58

def actions
  @actions ||= []
end

.arg_namesObject

The argument names pulled from a task declaration.



66
67
68
# File 'lib/rap/task.rb', line 66

def arg_names
  @arg_names ||= []
end

.dependenciesObject (readonly)

Returns class dependencies



40
41
42
# File 'lib/rap/task.rb', line 40

def dependencies
  @dependencies
end

Instance Attribute Details

#argsObject

The arguments assigned to self.



211
212
213
# File 'lib/rap/task.rb', line 211

def args
  @args
end

#dependenciesObject (readonly)

An array of node dependencies



208
209
210
# File 'lib/rap/task.rb', line 208

def dependencies
  @dependencies
end

Class Method Details

.argsObject

Returns a Lazydoc::Arguments constructed from arg_names.



71
72
73
74
75
# File 'lib/rap/task.rb', line 71

def args
  args = Lazydoc::Arguments.new
  arg_names.each {|name| args.arguments << name.to_s }
  args
end

.build(argh = {}, app = Tap::App.instance) ⇒ Object

Instantiates the instance of self for app and reconfigures it as specified in argh.



104
105
106
107
108
109
110
111
112
113
114
115
116
# File 'lib/rap/task.rb', line 104

def build(argh={}, app=Tap::App.instance)
  instance = self.instance(app)
  
  if config = argh['config']
    instance.reconfigure(config)
  end
  
  if args = argh['args']
    instance.args = args
  end
  
  instance
end

.depends_on(name, dependency_class) ⇒ Object

Sets a class-level dependency; when task class B depends_on another task class A, instances of B are initialized to depend on a shared instance of A. The shared instance is specific to an app and can be accessed through instance(app).

If a non-nil name is specified, depends_on will create a reader of the dependency instance.

class A < Rap::Task
end

class B < Rap::Task
  depends_on :a, A
end

app = Tap::App.new
b = B.new({}, app)
b.dependencies           # => [A.instance(app)]
b.a                      # => A.instance(app)

Returns self.



182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
# File 'lib/rap/task.rb', line 182

def depends_on(name, dependency_class)
  unless dependency_class.ancestors.include?(Rap::Task)
    raise "not a Rap::Task: #{dependency_class}"
  end
  
  unless dependencies.include?(dependency_class)
    dependencies << dependency_class
  end
  
  if name
    # returns the resolved result of the dependency
    define_method(name) do
      dependency_class.instance(app)
    end
  
    public(name)
  end
  
  self
end

.inherited(child) ⇒ Object

:nodoc:



47
48
49
50
# File 'lib/rap/task.rb', line 47

def inherited(child) # :nodoc:
  child.instance_variable_set(:@dependencies, dependencies.dup)
  super
end

.parse!(argv = ARGV, app = Tap::App.instance) ⇒ Object

Parses as normal, but also stores the arguments on the instance to allows arguments to be specified on dependency tasks:

# [Rapfile]
# Rap.task(:a, :obj) {|t, a| puts "A #{a.obj}"}
# Rap.task({:b => :a}, :obj) {|t, a| puts "B #{a.obj}"}

% rap b world -- a hello
A hello
B world


88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rap/task.rb', line 88

def parse!(argv=ARGV, app=Tap::App.instance)
  parser = self.parser
  
  argv = parser.parse!(argv, :add_defaults => false)
  enque = parser.config.delete('enque')
  instance = build({'config' => parser.nested_config, 'args' => argv.dup}, app)
  
  # enque with no inputs to satisfy call, and
  # clear argv so auto-enque will do the same
  instance.enq if enque
  
  [instance, []]
end

.subclass(const_name, configs = {}, dependencies = []) ⇒ Object

Looks up or creates the Rap::Task subclass specified by const_name and adds the configs and dependencies.

Configurations are always validated using the yaml transformation block (see Configurable::Validation).



123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/rap/task.rb', line 123

def subclass(const_name, configs={}, dependencies=[])
  # lookup or generate the subclass
  subclass = Tap::Env::Constant.constantize(const_name.to_s) do |base, constants|
    subclass_const = constants.pop
    constants.inject(base) do |namespace, const|
      # nesting Task classes into other Task classes is required
      # for namespaces with the same name as a task
      namespace.const_set(const, Class.new(Rap::Task))
    end.const_set(subclass_const, Class.new(self))
  end

  # check a correct class was found
  unless subclass.ancestors.include?(self)
    raise "not a #{self}: #{subclass}"
  end

  # append configuration (note that specifying a desc 
  # prevents lazydoc registration of these lines)
  convert_to_yaml = Configurable::Validation.yaml
  configs.each_pair do |key, value|
    subclass.send(:config, key, value, :desc => "", &convert_to_yaml)
  end

  # add dependencies
  dependencies.each do |dependency|
    dependency_name = File.basename(dependency.to_s.underscore)
    
    # this suppresses 'method redefined' warnings
    if subclass.method_defined?(dependency_name)
      subclass.send(:undef_method, dependency_name)
    end
    
    subclass.send(:depends_on, dependency_name, dependency)
  end
  
  subclass
end

Instance Method Details

#callObject

Conditional call to the super call; only calls once and with args (if set). Call recursively resolves dependencies and raises an error for circular dependencies.



229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/rap/task.rb', line 229

def call
  if resolved?
    return
  end
  
  if resolving?
    raise DependencyError.new(self)
  end
  
  @resolved = nil
  begin
    dependencies.each do |dependency|
      dependency.call
    end
  rescue(DependencyError)
    $!.trace.unshift(self)
    raise $!
  end

  @resolved = true
  args ? super(*args) : super()
end

#depends_on(dependency) ⇒ Object

Adds the dependency to self.



297
298
299
300
301
302
303
# File 'lib/rap/task.rb', line 297

def depends_on(dependency)
  raise "cannot depend on self" if dependency == self
  unless dependencies.include?(dependency)
    dependencies << dependency
  end
  self
end

#process(*inputs) ⇒ Object

Collects the inputs into an OpenStruct according to the class arg_names, and calls each class action in turn. This behavior echoes the behavior of Rake tasks.



275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/rap/task.rb', line 275

def process(*inputs)
  # collect inputs to make a rakish-args object
  args = {}
  self.class.arg_names.each do |arg_name|
    break if inputs.empty?
    args[arg_name] = inputs.shift
  end
  args = OpenStruct.new(args)
  
  # execute each block assciated with this task
  self.class.actions.each do |action|
    case action.arity
    when 0 then action.call()
    when 1 then action.call(self)
    else action.call(self, args)
    end
  end
  
  nil
end

#resetObject

Resets self so call will call again. Also sets result to nil.



267
268
269
270
# File 'lib/rap/task.rb', line 267

def reset
  raise "cannot reset when resolving" if resolving?
  @resolved = false
end

#resolve!Object

Alias for call.



253
254
255
# File 'lib/rap/task.rb', line 253

def resolve!
  call
end

#resolved?Boolean

Returns true if already resolved by call.

Returns:

  • (Boolean)


258
259
260
# File 'lib/rap/task.rb', line 258

def resolved?
  @resolved == true
end

#resolving?Boolean

Returns:

  • (Boolean)


262
263
264
# File 'lib/rap/task.rb', line 262

def resolving?
  @resolved == nil
end