detroit 0.3.0 → 0.4.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/.index +59 -0
  3. data/EXAMPLE.md +66 -64
  4. data/{HISTORY.rdoc → HISTORY.md} +32 -5
  5. data/{COPYING.rdoc → LICENSE.txt} +0 -0
  6. data/README.md +142 -0
  7. data/bin/detroit +1 -1
  8. data/lib/detroit.rb +112 -40
  9. data/lib/detroit.yml +44 -29
  10. data/lib/detroit/assembly.rb +49 -193
  11. data/lib/detroit/basic_tool.rb +200 -0
  12. data/lib/detroit/basic_utils.rb +66 -0
  13. data/lib/detroit/core_ext.rb +2 -136
  14. data/lib/detroit/{tool/core_ext → core_ext}/facets.rb +3 -0
  15. data/lib/detroit/{tool/core_ext → core_ext}/filetest.rb +0 -0
  16. data/lib/detroit/{tool/core_ext → core_ext}/shell_extensions.rb +0 -0
  17. data/lib/detroit/{tool/core_ext → core_ext}/to_actual_filename.rb +0 -0
  18. data/lib/detroit/{tool/core_ext → core_ext}/to_console.rb +1 -0
  19. data/lib/detroit/{tool/core_ext → core_ext}/to_list.rb +0 -0
  20. data/lib/detroit/{tool/core_ext → core_ext}/to_yamlfrag.rb +0 -0
  21. data/lib/detroit/{tool/core_ext → core_ext}/unfold_paragraphs.rb +0 -0
  22. data/lib/detroit/{tool/email_utils.rb → email_utils.rb} +3 -1
  23. data/lib/detroit/exec.rb +55 -0
  24. data/lib/detroit/project.rb +134 -0
  25. data/lib/detroit/ruby_utils.rb +29 -0
  26. data/lib/detroit/{tool/shell_utils.rb → shell_utils.rb} +10 -5
  27. data/lib/detroit/toolchain.rb +6 -0
  28. data/lib/detroit/toolchain/cli.rb +320 -0
  29. data/lib/detroit/toolchain/config.rb +223 -0
  30. data/lib/detroit/toolchain/runner.rb +678 -0
  31. data/lib/detroit/toolchain/script.rb +248 -0
  32. data/lib/detroit/toolchain/worker.rb +84 -0
  33. data/man/detroit.1 +116 -0
  34. data/man/detroit.1.html +171 -0
  35. data/man/detroit.1.ronn +99 -0
  36. metadata +90 -51
  37. data/.ruby +0 -44
  38. data/README.rdoc +0 -132
  39. data/lib/detroit/application.rb +0 -463
  40. data/lib/detroit/assembly_system.rb +0 -80
  41. data/lib/detroit/config.rb +0 -203
  42. data/lib/detroit/control.rb +0 -129
  43. data/lib/detroit/custom.rb +0 -102
  44. data/lib/detroit/dsl.rb +0 -55
  45. data/lib/detroit/service.rb +0 -78
  46. data/lib/detroit/standard_assembly.rb +0 -51
  47. data/lib/detroit/tool.rb +0 -295
  48. data/lib/detroit/tool/core_ext.rb +0 -3
  49. data/lib/detroit/tool/project_utils.rb +0 -41
@@ -0,0 +1,223 @@
1
+ raise "DEPRECATED"
2
+
3
+ module Detroit
4
+
5
+ module Toolchain
6
+
7
+ # Detroit configuration.
8
+ #
9
+ # TODO: Greatly simplify this, to support
10
+ #
11
+ class Config
12
+ #instance_methods.each{ |m| private m unless /^__/ =~ m.to_s }
13
+
14
+ # Configuration directory name (most likely a hidden "dot" directory).
15
+ DIRECTORY = "detroit"
16
+
17
+ # File identifier used to find a project's Assembly(s).
18
+ FILE_EXTENSION = "assembly"
19
+
20
+ # TODO: Should this be project instead?
21
+ attr :root
22
+
23
+ # The list of a project's assembly files.
24
+ #
25
+ # @return [Array<String>] routine files
26
+ attr :assemblies
27
+
28
+ # Worker configurations from Assembly or *.assembly files.
29
+ #
30
+ # @return [Hash]
31
+ attr :workers
32
+
33
+ # Worker defaults. This is a mapping of worker names to
34
+ # default settings. Very useful for when using the same
35
+ # worker more than once.
36
+ #
37
+ # @return [Hash] default settings
38
+ attr :defaults
39
+
40
+ #
41
+ def initialize(root, assembly_files=nil)
42
+ p "CONFIG!!!!!!!!!!!!!!!!!"
43
+ @root = root
44
+
45
+ if assembly_files && !assembly_files.empty?
46
+ @assembly_filenames = assembly_files
47
+ else
48
+ @assembly_filenames = nil
49
+ end
50
+
51
+ @assemblies = {}
52
+ @workers = {}
53
+ @defaults = {}
54
+
55
+ @loaded_plugins = {}
56
+
57
+ load_plugins
58
+ load_defaults
59
+ load_assemblies
60
+ end
61
+
62
+ # Load a plugin.
63
+ def load_plugin(name)
64
+ @loaded_plugins[name] ||= (
65
+ begin
66
+ require "detroit-#{name}"
67
+ rescue LoadError => e
68
+ $stderr.puts "ERROR: #{e.message.capitalize}"
69
+ $stderr.puts " Perhaps `gem install detroit-#{name}`?"
70
+ exit -1
71
+ end
72
+ name # true ?
73
+ )
74
+ end
75
+
76
+ # Pre-load plugins using `.detroit/plugins.rb`.
77
+ def load_plugins
78
+ if file = root.glob('{.,}#{DIRECTORY}/plugins{,.rb}').first
79
+ require file
80
+ else
81
+ self.defaults = {}
82
+ end
83
+ end
84
+
85
+ # Load defaults from `.detroit/defaults.yml`.
86
+ def load_defaults
87
+ if file = root.glob('{.,}#{DIRECTORY}/defaults{,.yml,.yaml}').first
88
+ self.defaults = YAML.load(File.new(file))
89
+ else
90
+ self.defaults = {}
91
+ end
92
+ end
93
+
94
+ #
95
+ def load_assemblies
96
+ assembly_filenames.each do |file|
97
+ load_assembly_file(file)
98
+ end
99
+
100
+ #if config = eval('self', TOPLEVEL_BINDING).rc_detroit
101
+ # @assemblies['(rc)'] = Assembly.new(&config)
102
+ # @workers.merge!(assemblies['(rc)'].workers)
103
+ #end
104
+
105
+ #if config = Detroit.rc_config
106
+ # assembly = Assembly.new do
107
+ # config.each do |c|
108
+ # track(c.profile, &c)
109
+ # end
110
+ # end
111
+ # @assemblies['(rc)'] = assembly
112
+ # @workers.merge!(assemblies['(rc)'].workers)
113
+ #end
114
+ end
115
+
116
+ #
117
+ def load_assembly_file(file)
118
+ p "HERE!!!!!!!!!"
119
+ @assemblies[file] = Assembly::Script.load(File.new(file))
120
+ @workers.merge!(assemblies[file].workers)
121
+ end
122
+
123
+ # Set defaults.
124
+ def defaults=(hash)
125
+ @defaults = hash.to_h
126
+ end
127
+
128
+ # If a `Assembly` or `.assembly` file exists, then it is returned. Otherwise
129
+ # all `*.assembly` files are loaded. To load `*.assembly` files from another
130
+ # directory add the directory to config options file.
131
+ def assembly_filenames
132
+ @assembly_filenames ||= (
133
+ files = []
134
+ ## match 'Assembly' or '.assembly' file
135
+ files = root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
136
+ ## only files
137
+ files = files.select{ |f| File.file?(f) }
138
+ ##
139
+ if files.empty?
140
+ ## match '.detroit/*.assembly' or 'detroit/*.assembly'
141
+ files += root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
142
+ ## match 'task/*.assembly' (OLD SCHOOL)
143
+ files += root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
144
+ ## only files
145
+ files = files.select{ |f| File.file?(f) }
146
+ end
147
+ files
148
+ )
149
+ end
150
+
151
+ #
152
+ def each(&block)
153
+ workers.each(&block)
154
+ end
155
+
156
+ #
157
+ def size
158
+ workers.size
159
+ end
160
+
161
+ =begin
162
+ # If using a `Routine` file and want to import antoher file then use
163
+ # `import:` entry.
164
+ def load_detroit_file(file)
165
+ #@dir = File.dirname(file)
166
+
167
+ assemblies[file] =
168
+
169
+ # TODO: can we just read the first line of the file and go from there?
170
+ #text = File.read(file).strip
171
+
172
+ ## if yaml vs. ruby file
173
+ #if (/\A---/ =~ text || /\.(yml|yaml)$/ =~ File.extname(file))
174
+ # #data = parse_detroit_file_yaml(text, file)
175
+ # YAML.load(text)
176
+ #else
177
+ # data = parse_detroit_file_ruby(text, file)
178
+ #end
179
+
180
+ ## extract defaults
181
+ #if defaults = data.delete('defaults')
182
+ # @defaults.merge!(defaults)
183
+ #end
184
+
185
+ ## import other files
186
+ #if import = data.delete('import')
187
+ # [import].flatten.each do |glob|
188
+ # routine(glob)
189
+ # end
190
+ #end
191
+
192
+ ## require plugins
193
+ #if plugins = data.delete('plugins')
194
+ # [plugins].flatten.each do |file|
195
+ # require file
196
+ # end
197
+ #end
198
+
199
+ #@workers.update(data)
200
+ end
201
+ =end
202
+
203
+ ## Parse a YAML-based routine.
204
+ #def parse_detroit_file_yaml(text, file)
205
+ # YAMLParser.parse(self, text, file)
206
+ #end
207
+
208
+ ## Parse a Ruby-based routine.
209
+ #def parse_detroit_file_ruby(text, file)
210
+ # RubyParser.parse(self, text, file)
211
+ #end
212
+
213
+ ## TODO: Should the +dir+ be relative to the file or root?
214
+ #def routine(glob)
215
+ # pattern = File.join(@dir, glob)
216
+ # Dir[pattern].each{ |f| load_detroit_file(f) }
217
+ #end
218
+
219
+ end
220
+
221
+ end
222
+
223
+ end
@@ -0,0 +1,678 @@
1
+ #require_relative 'core_ext'
2
+ require_relative 'script'
3
+ #require_relative 'config'
4
+ require_relative 'worker'
5
+
6
+ module Detroit
7
+
8
+ module Toolchain
9
+
10
+ # Configuration directory name (most likely a hidden "dot" directory).
11
+ DIRECTORY = "detroit"
12
+
13
+ # File identifier used to find a project's Assembly file(s).
14
+ FILE_EXTENSION = "toolchain"
15
+
16
+ # The default assembly system to use.
17
+ DEFAULT_TOOLCHAIN = :standard
18
+
19
+ # Assembly::Runner class is the main controller class for running
20
+ # a session of Detroit.
21
+ #
22
+ class Runner
23
+
24
+ # Options (generally from #cli).
25
+ attr :options
26
+
27
+ # Create a new Detroit Application instance.
28
+ def initialize(options)
29
+ @options = options
30
+
31
+ self.skip = options[:skip]
32
+ self.quiet = options[:quiet]
33
+ self.assembly = options[:assembly]
34
+ self.multitask = options[:multitask]
35
+
36
+ self.toolchain_files = options[:toolchains]
37
+
38
+ @toolchains = {}
39
+ @tools = {}
40
+ @defaults = {}
41
+
42
+ @loaded_plugins = {}
43
+
44
+ #load_plugins
45
+ #load_defaults
46
+ load_toolchains
47
+ end
48
+
49
+ # Quiet mode?
50
+ #
51
+ # @return [Boolean]
52
+ def quiet?
53
+ @quiet
54
+ end
55
+
56
+ # Set quiet mode.
57
+ #
58
+ # @return [Boolean]
59
+ def quiet=(boolean)
60
+ @quiet = !!boolean
61
+ end
62
+
63
+ # List of tool names to skip.
64
+ def skip
65
+ @skip
66
+ end
67
+
68
+ # Set skip list.
69
+ def skip=(list)
70
+ @skip = list.to_list.map{ |s| s.downcase }
71
+ end
72
+
73
+ # Name of the assembly (default is `:standard`).
74
+ def assembly
75
+ @assembly
76
+ end
77
+
78
+ # Set assembly system to use.
79
+ def assembly=(name)
80
+ @assembly = (name || DEFAULT_TOOLCHAIN).to_sym
81
+ end
82
+
83
+ # Multitask mode?
84
+ def multitask?
85
+ @multitask
86
+ end
87
+
88
+ # Set multi-task mode.
89
+ def multitask=(boolean)
90
+ if boolean && !defined?(Parallel)
91
+ puts "Parallel gem must be installed to multitask."
92
+ @multitask = false
93
+ else
94
+ @multitask = boolean
95
+ end
96
+ end
97
+
98
+ # List of toolchain files to use.
99
+ def toolchain_files
100
+ @toolchain_files
101
+ end
102
+
103
+ #
104
+ def toolchain_files=(files)
105
+ @toolchain_files = files
106
+ end
107
+
108
+ # Provides access to the Project instance via `Detroit.project` class method.
109
+ def project
110
+ @project ||= Detroit.project(root)
111
+ end
112
+
113
+ # Detroit configuration.
114
+ #def config
115
+ # @config ||= Toolchain::Config.new(root, assembly_files)
116
+ #end
117
+
118
+ # The list of a project's assembly files.
119
+ #
120
+ # @return [Array<String>] routine files
121
+ attr :toolchains
122
+
123
+ # Tool configurations from Assembly or *.assembly files.
124
+ #
125
+ # @return [Hash] service settings
126
+ attr :tools
127
+
128
+ # Custom service defaults. This is a mapping of service names to
129
+ # default settings. Very useful for when using the same
130
+ # service more than once.
131
+ #
132
+ # @return [Hash] default settings
133
+ attr :defaults
134
+
135
+ # Set defaults.
136
+ def defaults=(hash)
137
+ @defaults = hash.to_h
138
+ end
139
+
140
+ # Display detailed help for a given tool.
141
+ #
142
+ # @return [void]
143
+ def display_help(name)
144
+ if not Detroit.tools.key?(name)
145
+ load_plugin(name)
146
+ end
147
+ tool = Detroit.tools[name]
148
+ if tool.const_defined?(:MANPAGE)
149
+ man_page = tool.const_get(:MANPAGE)
150
+ Kernel.system "man #{man_page}"
151
+ else
152
+ puts "No detailed help available for `#{name}'."
153
+ end
154
+ end
155
+
156
+ # Generates a configuration template for any particular tool.
157
+ # This is only used for reference purposes.
158
+ #
159
+ def config_template(name)
160
+ if not Detroit.tools.key?(name)
161
+ load_plugin(name)
162
+ end
163
+ list = {name => Detroit.tools[name]}
164
+ cfg = {}
165
+ list.each do |tool_name, tool_class|
166
+ attrs = tool_class.options #instance_methods.select{ |m| m.to_s =~ /\w+=$/ && !%w{taguri=}.include?(m.to_s) }
167
+ atcfg = attrs.inject({}){ |h, m| h[m.to_s.chomp('=')] = nil; h }
168
+ atcfg['tool'] = tool_class.basename.downcase
169
+ atcfg['active'] = false
170
+ cfg[tool_name] = atcfg
171
+ end
172
+ cfg
173
+ end
174
+
175
+ #
176
+ def tool_class_options(tool_class)
177
+ end
178
+
179
+ # Active workers are tool instance configured in a project's assembly files
180
+ # that do not have their active setting turned off.
181
+ #
182
+ # @return [Array<Worker>] Active worker instances.
183
+ def active_workers(track=nil)
184
+ @active_workers ||= (
185
+ list = []
186
+
187
+ tools.each do |key, opts|
188
+ next unless opts
189
+ next unless opts['active'] != false
190
+
191
+ if opts['track']
192
+ next unless opts['track'].include?((track || 'main').to_s)
193
+ end
194
+
195
+ next if skip.include?(key.to_s)
196
+
197
+ if opts.key?('tool') && opts.key?('tooltype')
198
+ abort "Two tool types given for `#{key}'."
199
+ end
200
+
201
+ # TODO: Ultimately deprecate completely.
202
+ if opts.key?('service')
203
+ abort "The `service` setting has been renamed. " +
204
+ "Use `tool` or `tooltype` for `#{key}' instead."
205
+ end
206
+
207
+ tool_type = (
208
+ opts.delete('class') ||
209
+ opts.delete('tool') ||
210
+ key
211
+ ).to_s.downcase
212
+
213
+ unless Detroit.tools.key?(tool_type)
214
+ load_plugin(tool_type)
215
+ end
216
+
217
+ tool_class = Detroit.tools[tool_type]
218
+
219
+ abort "Unknown tool `#{tool_type}'." unless tool_class
220
+
221
+ if tool_class.available? #(project)
222
+ #opts = inject_environment(opts) # TODO: DEPRECATE
223
+ options = defaults[tool_type.downcase].to_h
224
+ options = options.merge(common_tool_options)
225
+ options = options.merge(opts)
226
+
227
+ options['project'] = project
228
+
229
+ list << Worker.new(key, tool_class, options) #script,
230
+ #else
231
+ # warn "Worker #{tool_class} is not available."
232
+ end
233
+ end
234
+
235
+ # sorting here trickles down to processing later
236
+ #list = list.sort_by{ |s| s.priority || 0 }
237
+
238
+ list
239
+ )
240
+ end
241
+
242
+ # Change direectory to project root and run.
243
+ def start(stop)
244
+ Dir.chdir(project.root) do # change into project directory
245
+ run(stop)
246
+ end
247
+ end
248
+
249
+ # Run up to the specified track stop.
250
+ def run(stop)
251
+ raise "Malformed destination -- #{stop}" unless /^\w+\:{0,1}\w+$/ =~ stop
252
+
253
+ track, stop = stop.split(':')
254
+ track, stop = 'main', track unless stop
255
+
256
+ track = track.to_sym
257
+ stop = stop.to_sym if stop
258
+
259
+ # TODO: Using #preconfigure as part of the protocol should probably change.
260
+
261
+ ## prime the workers (so as to fail early)
262
+ active_workers(track).each do |w|
263
+ w.preconfigure if w.respond_to?("preconfigure")
264
+ end
265
+
266
+ sys = Detroit.assemblies[assembly.to_sym]
267
+
268
+ raise "Unknown assembly `#{assembly}'" unless sys
269
+
270
+ # Lookup chain by stop name.
271
+ chain = sys.find(stop)
272
+
273
+ #if stop
274
+ # system = track.route_with_stop(stop)
275
+ # raise "Unknown stop -- #{stop}" unless system
276
+
277
+ unless chain
278
+ #overview
279
+ $stderr.puts "Unknown stop `#{stop}'."
280
+ exit 0
281
+ end
282
+
283
+ @destination = stop
284
+
285
+ status_header(*header_message)
286
+
287
+ start_time = Time.now
288
+
289
+ chain.each do |run_stop|
290
+ next if skip.include?("#{run_stop}") # TODO: Should we really allow skipping stops?
291
+ #tool_hooks(name, ('pre_' + run_stop.to_s).to_sym)
292
+ tool_calls(track, ('pre_' + run_stop.to_s).to_sym)
293
+ tool_calls(track, run_stop)
294
+ tool_calls(track, ('aft_' + run_stop.to_s).to_sym)
295
+ #tool_hooks(name, ('aft_' + run_stop.to_s).to_sym)
296
+ break if stop == run_stop
297
+ end
298
+
299
+ stop_time = Time.now
300
+
301
+ puts "\nFinished in #{stop_time - start_time} seconds." unless quiet?
302
+ end
303
+
304
+ =begin
305
+ # TODO: Deprecate service hooks?
306
+
307
+ #
308
+ # Execute service hook for given track and destination.
309
+ #
310
+ # @todo Currently only stop counts, maybe add track subdirs.
311
+ #
312
+ def service_hooks(track, stop)
313
+ #hook = dir + ("#{track}/#{stop}.rb".gsub('_', '-'))
314
+ dir = hook_directory
315
+ return unless dir
316
+ name = stop.to_s.gsub('_', '-')
317
+ hook = dir + "#{name}.rb"
318
+ if hook.exist?
319
+ status_line("hook", name.capitalize)
320
+ hook_tool.instance_eval(hook.read)
321
+ end
322
+ end
323
+
324
+ # Returns a project's Detroit hooks directory.
325
+ def hook_directory
326
+ project.root.glob("{.,}detroit/hooks").first
327
+ end
328
+
329
+ #
330
+ def hook_tool
331
+ @hook_tool ||= Tool.new(common_tool_options)
332
+ end
333
+ =end
334
+
335
+ # TODO: Do we need verbose?
336
+ def common_tool_options
337
+ {
338
+ 'trial' => options[:trial],
339
+ 'trace' => options[:trace],
340
+ 'quiet' => options[:quiet],
341
+ 'force' => options[:force],
342
+ 'verbose' => options[:verbose]
343
+ }
344
+ end
345
+
346
+ # Make tool calls.
347
+ #
348
+ # This groups workers by priority b/c groups of the same priority can be run
349
+ # in parallel if the multitask option is on.
350
+ #
351
+ def tool_calls(track, stop)
352
+ prioritized_workers = active_workers(track).group_by{ |w| w.priority }.sort_by{ |k,v| k }
353
+ prioritized_workers.each do |priority, workers|
354
+ ## remove any workers specified by the --skip option on the comamndline
355
+ #workers = workers.reject{ |w| skip.include?(w.key.to_s) }
356
+
357
+ ## only servies that are on the track
358
+ #workers = workers.select{ |w| w.tracks.nil? or w.tracks.include?(w.to_s) }
359
+
360
+ worklist = workers.map{ |w| [w, track, stop] }
361
+
362
+ if multitask?
363
+ results = Parallel.in_processes(worklist.size) do |i|
364
+ run_a_worker(*worklist[i])
365
+ end
366
+ else
367
+ worklist.each do |args|
368
+ run_a_worker(*args)
369
+ end
370
+ end
371
+ end
372
+ end
373
+
374
+ # Invoke a worker given the worker, track and stop name.
375
+ #
376
+ # @todo Provide more robust options, rather than just `@destination`.
377
+ #
378
+ # TODO: Rename this method.
379
+ #
380
+ # @return [void]
381
+ def run_a_worker(worker, track, stop)
382
+ if target = worker.stop?(stop, @destination)
383
+ target = stop if TrueClass === target
384
+ label = stop.to_s.gsub('_', '-').capitalize
385
+ if options[:trace] #options[:verbose]
386
+ status_line("#{worker.key.to_s} (#{worker.class}##{target})", label)
387
+ else
388
+ status_line("#{worker.key.to_s}", label)
389
+ end
390
+ worker.invoke(target, @destination)
391
+ end
392
+ end
393
+
394
+ # --- Print Methods -------------------------------------------------------
395
+
396
+ def header_message
397
+ if multitask?
398
+ ["#{project.metadata.title} v#{project.metadata.version} [M]", "#{project.root}"]
399
+ else
400
+ ["#{project.metadata.title} v#{project.metadata.version}", "#{project.root}"]
401
+ end
402
+ end
403
+
404
+ # Print a status header, which consists of project name and version on the
405
+ # left and stop location on the right.
406
+ #
407
+ def status_header(left, right='')
408
+ left, right = left.to_s, right.to_s
409
+ #left.color = 'blue'
410
+ #right.color = 'magenta'
411
+ unless quiet?
412
+ puts
413
+ print_header(left, right)
414
+ #puts "=" * io.screen_width
415
+ end
416
+ end
417
+
418
+ # Print a status line, which consists of worker name on the left
419
+ # and stop name on the right.
420
+ #
421
+ def status_line(left, right='')
422
+ left, right = left.to_s, right.to_s
423
+ #left.color = 'blue'
424
+ #right.color = 'magenta'
425
+ unless quiet?
426
+ puts
427
+ #puts "-" * io.screen_width
428
+ print_phase(left, right)
429
+ #puts "-" * io.screen_width
430
+ #puts
431
+ end
432
+ end
433
+
434
+ #
435
+ def display_action(action_item)
436
+ phase, service, action, parameters = *action_item
437
+ puts " %-10s %-10s %-10s" % [phase.to_s.capitalize, service.service_title, action]
438
+ #status_line(service.service_title, phase.to_s.capitalize)
439
+ end
440
+
441
+ #
442
+ def print_header(left, right)
443
+ if $ansi #ANSI::SUPPORTED
444
+ printline('', '', :pad=>1, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
445
+ printline(left, right, :pad=>2, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
446
+ printline('', '', :pad=>1, :sep=>' ', :style=>[:negative, :bold], :left=>[:bold], :right=>[:bold])
447
+ else
448
+ printline(left, right, :pad=>2, :sep=>'=')
449
+ end
450
+ end
451
+
452
+ #
453
+ def print_phase(left, right)
454
+ if $ansi #ANSI::SUPPORTED
455
+ printline(left, right, :pad=>2, :sep=>' ', :style=>[:on_white, :black, :bold], :left=>[:bold], :right=>[:bold])
456
+ else
457
+ printline(left, right, :pad=>2, :sep=>'-')
458
+ end
459
+ end
460
+
461
+ #
462
+ def printline(left, right='', options={})
463
+ return if quiet?
464
+
465
+ separator = options[:seperator] || options[:sep] || ' '
466
+ padding = options[:padding] || options[:pad] || 0
467
+
468
+ left, right = left.to_s, right.to_s
469
+
470
+ left_size = left.size
471
+ right_size = right.size
472
+
473
+ #left = colorize(left)
474
+ #right = colorize(right)
475
+
476
+ l = padding
477
+ r = -(right_size + padding)
478
+
479
+ style = options[:style] || []
480
+ lstyle = options[:left] || []
481
+ rstyle = options[:right] || []
482
+
483
+ left = lstyle.inject(left) { |s, c| ansize(s, c) }
484
+ right = rstyle.inject(right){ |s, c| ansize(s, c) }
485
+
486
+ line = separator * screen_width
487
+ line[l, left_size] = left if left_size != 0
488
+ line[r, right_size] = right if right_size != 0
489
+
490
+ line = style.inject(line){ |s, c| ansize(s, c) }
491
+
492
+ puts line + ansize('', :clear)
493
+ end
494
+
495
+ #
496
+ def ansize(text, code)
497
+ #return text unless text.color
498
+ if RUBY_PLATFORM =~ /win/
499
+ text.to_s
500
+ else
501
+ ANSI::Code.send(code.to_sym) + text
502
+ end
503
+ end
504
+
505
+ # Get the terminals width.
506
+ #
507
+ # @return [Integer]
508
+ def screen_width
509
+ ANSI::Terminal.terminal_width
510
+ end
511
+
512
+ # TODO: Lookup project root.
513
+ def root
514
+ Pathname.new(Dir.pwd)
515
+ end
516
+
517
+ # -----------------------------------------------------------------------
518
+
519
+ # Load a plugin.
520
+ def load_plugin(name)
521
+ @loaded_plugins[name] ||= (
522
+ begin
523
+ require "detroit-#{name}"
524
+ rescue LoadError => e
525
+ $stderr.puts "ERROR: #{e.message.capitalize}"
526
+ $stderr.puts " Perhaps `gem install detroit-#{name}`?"
527
+ exit -1
528
+ end
529
+ name # true ?
530
+ )
531
+ end
532
+
533
+ ## Pre-load plugins using `.detroit/plugins.rb`.
534
+ #def load_plugins
535
+ # if file = project.root.glob('{.,}#{DIRECTORY}/plugins{,.rb}').first
536
+ # require file
537
+ # else
538
+ # self.defaults = {}
539
+ # end
540
+ #end
541
+
542
+ ## Load defaults from `.detroit/defaults.yml`.
543
+ #def load_defaults
544
+ # if file = project.root.glob('{.,}#{DIRECTORY}/defaults{,.yml,.yaml}').first
545
+ # self.defaults = YAML.load(File.new(file))
546
+ # else
547
+ # self.defaults = {}
548
+ # end
549
+ #end
550
+
551
+ #
552
+ def load_toolchains
553
+ toolchain_filenames.each do |file|
554
+ load_toolchain_file(file)
555
+ end
556
+
557
+ #if config = eval('self', TOPLEVEL_BINDING).rc_detroit
558
+ # @toolchains['(rc)'] = Script.new(&config)
559
+ # @tools.merge!(toolchains['(rc)'].tools)
560
+ #end
561
+
562
+ #if config = Detroit.rc_config
563
+ # tc = Script.new do
564
+ # tools.each do |c|
565
+ # track(c.profile, &c)
566
+ # end
567
+ # end
568
+ # @toolchains['(rc)'] = tc
569
+ # @tools.merge!(toolchains['(rc)'].tools)
570
+ #end
571
+ end
572
+
573
+ # Load toolchain file.
574
+ #
575
+ def load_toolchain_file(file)
576
+ @toolchains[file] = Toolchain::Script.load(File.new(file), project)
577
+ @tools.merge!(toolchains[file].tools)
578
+ end
579
+
580
+ # If a `Toolchain` or `.toolchain` file exists, then it is returned. Otherwise
581
+ # all `*.toolchain` files are loaded. To load `*.toolchain` files from another
582
+ # directory add the directory to config options file.
583
+ #
584
+ # TODO: Simplify this to just `toolchain`.
585
+ #
586
+ def toolchain_filenames
587
+ @toolchain_filenames ||= (
588
+ files = []
589
+ ## match 'Toolchain' or '.toolchain' file
590
+ files = project.root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
591
+ ## only files
592
+ files = files.select{ |f| File.file?(f) }
593
+ ##
594
+ if files.empty?
595
+ ## match '.detroit/*.toolchain' or 'detroit/*.toolchain'
596
+ #files += project.root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
597
+ ## match 'task/*.toolchain' (OLD SCHOOL)
598
+ files += project.root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
599
+ ## only files
600
+ files = files.select{ |f| File.file?(f) }
601
+ end
602
+ files
603
+ )
604
+ end
605
+
606
+ #
607
+ #def each(&block)
608
+ # tools.each(&block)
609
+ #end
610
+
611
+ #
612
+ #def size
613
+ # tools.size
614
+ #end
615
+
616
+ =begin
617
+ # If using a `Routine` file and want to import antoher file then use
618
+ # `import:` entry.
619
+ def load_detroit_file(file)
620
+ #@dir = File.dirname(file)
621
+
622
+ assemblies[file] =
623
+
624
+ # TODO: can we just read the first line of the file and go from there?
625
+ #text = File.read(file).strip
626
+
627
+ ## if yaml vs. ruby file
628
+ #if (/\A---/ =~ text || /\.(yml|yaml)$/ =~ File.extname(file))
629
+ # #data = parse_detroit_file_yaml(text, file)
630
+ # YAML.load(text)
631
+ #else
632
+ # data = parse_detroit_file_ruby(text, file)
633
+ #end
634
+
635
+ ## extract defaults
636
+ #if defaults = data.delete('defaults')
637
+ # @defaults.merge!(defaults)
638
+ #end
639
+
640
+ ## import other files
641
+ #if import = data.delete('import')
642
+ # [import].flatten.each do |glob|
643
+ # routine(glob)
644
+ # end
645
+ #end
646
+
647
+ ## require plugins
648
+ #if plugins = data.delete('plugins')
649
+ # [plugins].flatten.each do |file|
650
+ # require file
651
+ # end
652
+ #end
653
+
654
+ #@services.update(data)
655
+ end
656
+ =end
657
+
658
+ ## Parse a YAML-based routine.
659
+ #def parse_detroit_file_yaml(text, file)
660
+ # YAMLParser.parse(self, text, file)
661
+ #end
662
+
663
+ ## Parse a Ruby-based routine.
664
+ #def parse_detroit_file_ruby(text, file)
665
+ # RubyParser.parse(self, text, file)
666
+ #end
667
+
668
+ ## TODO: Should the +dir+ be relative to the file or root?
669
+ #def routine(glob)
670
+ # pattern = File.join(@dir, glob)
671
+ # Dir[pattern].each{ |f| load_detroit_file(f) }
672
+ #end
673
+
674
+ end
675
+
676
+ end
677
+
678
+ end #module Detroit