detroit 0.3.0 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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