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.
- checksums.yaml +7 -0
- data/.index +59 -0
- data/EXAMPLE.md +66 -64
- data/{HISTORY.rdoc → HISTORY.md} +32 -5
- data/{COPYING.rdoc → LICENSE.txt} +0 -0
- data/README.md +142 -0
- data/bin/detroit +1 -1
- data/lib/detroit.rb +112 -40
- data/lib/detroit.yml +44 -29
- data/lib/detroit/assembly.rb +49 -193
- data/lib/detroit/basic_tool.rb +200 -0
- data/lib/detroit/basic_utils.rb +66 -0
- data/lib/detroit/core_ext.rb +2 -136
- data/lib/detroit/{tool/core_ext → core_ext}/facets.rb +3 -0
- data/lib/detroit/{tool/core_ext → core_ext}/filetest.rb +0 -0
- data/lib/detroit/{tool/core_ext → core_ext}/shell_extensions.rb +0 -0
- data/lib/detroit/{tool/core_ext → core_ext}/to_actual_filename.rb +0 -0
- data/lib/detroit/{tool/core_ext → core_ext}/to_console.rb +1 -0
- data/lib/detroit/{tool/core_ext → core_ext}/to_list.rb +0 -0
- data/lib/detroit/{tool/core_ext → core_ext}/to_yamlfrag.rb +0 -0
- data/lib/detroit/{tool/core_ext → core_ext}/unfold_paragraphs.rb +0 -0
- data/lib/detroit/{tool/email_utils.rb → email_utils.rb} +3 -1
- data/lib/detroit/exec.rb +55 -0
- data/lib/detroit/project.rb +134 -0
- data/lib/detroit/ruby_utils.rb +29 -0
- data/lib/detroit/{tool/shell_utils.rb → shell_utils.rb} +10 -5
- data/lib/detroit/toolchain.rb +6 -0
- data/lib/detroit/toolchain/cli.rb +320 -0
- data/lib/detroit/toolchain/config.rb +223 -0
- data/lib/detroit/toolchain/runner.rb +678 -0
- data/lib/detroit/toolchain/script.rb +248 -0
- data/lib/detroit/toolchain/worker.rb +84 -0
- data/man/detroit.1 +116 -0
- data/man/detroit.1.html +171 -0
- data/man/detroit.1.ronn +99 -0
- metadata +90 -51
- data/.ruby +0 -44
- data/README.rdoc +0 -132
- data/lib/detroit/application.rb +0 -463
- data/lib/detroit/assembly_system.rb +0 -80
- data/lib/detroit/config.rb +0 -203
- data/lib/detroit/control.rb +0 -129
- data/lib/detroit/custom.rb +0 -102
- data/lib/detroit/dsl.rb +0 -55
- data/lib/detroit/service.rb +0 -78
- data/lib/detroit/standard_assembly.rb +0 -51
- data/lib/detroit/tool.rb +0 -295
- data/lib/detroit/tool/core_ext.rb +0 -3
- 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
|