class Detroit::Toolchain::Runner

Assembly::Runner class is the main controller class for running a session of Detroit.

Attributes

defaults[R]

Custom service defaults. This is a mapping of service names to default settings. Very useful for when using the same service more than once.

@return [Hash] default settings

options[R]

Options (generally from cli).

toolchains[R]

The list of a project’s assembly files.

@return [Array<String>] routine files

tools[R]

Tool configurations from Assembly or *.assembly files.

@return [Hash] service settings

Public Class Methods

new(options) click to toggle source

Create a new Detroit Application instance.

# File lib/detroit/toolchain/runner.rb, line 28
def initialize(options)
  @options = options

  self.skip       = options[:skip]
  self.quiet      = options[:quiet]
  self.assembly   = options[:assembly]
  self.multitask  = options[:multitask]

  self.toolchain_files = options[:toolchains]

  @toolchains = {}
  @tools      = {}
  @defaults   = {}

  @loaded_plugins = {}

  #load_plugins
  #load_defaults
  load_toolchains
end

Public Instance Methods

active_workers(track=nil) click to toggle source

Active workers are tool instance configured in a project’s assembly files that do not have their active setting turned off.

@return [Array<Worker>] Active worker instances.

# File lib/detroit/toolchain/runner.rb, line 183
def active_workers(track=nil)
  @active_workers ||= (
    list = []

    tools.each do |key, opts|
      next unless opts
      next unless opts['active'] != false

      if opts['track']
        next unless opts['track'].include?((track || 'main').to_s)
      end

      next if skip.include?(key.to_s)

      if opts.key?('tool') && opts.key?('tooltype')
        abort "Two tool types given for `#{key}'."
      end

      # TODO: Ultimately deprecate completely.
      if opts.key?('service')
        abort "The `service` setting has been renamed. " +
              "Use `tool` or `tooltype` for `#{key}' instead."
      end

      tool_type = (
        opts.delete('class') || 
        opts.delete('tool')  ||
        key
      ).to_s.downcase

      unless Detroit.tools.key?(tool_type)
        load_plugin(tool_type)
      end

      tool_class = Detroit.tools[tool_type]

      abort "Unknown tool `#{tool_type}'." unless tool_class

      if tool_class.available? #(project)
        #opts = inject_environment(opts) # TODO: DEPRECATE
        options = defaults[tool_type.downcase].to_h
        options = options.merge(common_tool_options)
        options = options.merge(opts)

        options['project'] = project

        list << Worker.new(key, tool_class, options) #script,
      #else
      #  warn "Worker #{tool_class} is not available."
      end
    end

    # sorting here trickles down to processing later
    #list = list.sort_by{ |s| s.priority || 0 }

    list
  )
end
ansize(text, code) click to toggle source
# File lib/detroit/toolchain/runner.rb, line 496
def ansize(text, code)
  #return text unless text.color
  if RUBY_PLATFORM =~ /win/
    text.to_s
  else
    ANSI::Code.send(code.to_sym) + text
  end
end
assembly() click to toggle source

Name of the assembly (default is ‘:standard`).

# File lib/detroit/toolchain/runner.rb, line 74
def assembly
  @assembly
end
assembly=(name) click to toggle source

Set assembly system to use.

# File lib/detroit/toolchain/runner.rb, line 79
def assembly=(name)
  @assembly = (name || DEFAULT_TOOLCHAIN).to_sym
end
common_tool_options() click to toggle source

TODO: Do we need verbose?

# File lib/detroit/toolchain/runner.rb, line 336
def common_tool_options
  {
    'trial'   => options[:trial],
    'trace'   => options[:trace],
    'quiet'   => options[:quiet],
    'force'   => options[:force],
    'verbose' => options[:verbose]
  }
end
config_template(name) click to toggle source

Generates a configuration template for any particular tool. This is only used for reference purposes.

# File lib/detroit/toolchain/runner.rb, line 159
def config_template(name)
  if not Detroit.tools.key?(name)
    load_plugin(name)
  end
  list = {name => Detroit.tools[name]}
  cfg = {}
  list.each do |tool_name, tool_class|
    attrs = tool_class.options #instance_methods.select{ |m| m.to_s =~ /\w+=$/ && !%w{taguri=}.include?(m.to_s) }
    atcfg = attrs.inject({}){ |h, m| h[m.to_s.chomp('=')] = nil; h }
    atcfg['tool']    = tool_class.basename.downcase
    atcfg['active']  = false
    cfg[tool_name] = atcfg
  end
  cfg
end
defaults=(hash) click to toggle source

Set defaults.

# File lib/detroit/toolchain/runner.rb, line 136
def defaults=(hash)
  @defaults = hash.to_h
end
display_action(action_item) click to toggle source
# File lib/detroit/toolchain/runner.rb, line 435
def display_action(action_item)
  phase, service, action, parameters = *action_item
  puts "  %-10s %-10s %-10s" % [phase.to_s.capitalize, service.service_title, action]
  #status_line(service.service_title, phase.to_s.capitalize)
end
display_help(name) click to toggle source

Display detailed help for a given tool.

@return [void]

# File lib/detroit/toolchain/runner.rb, line 143
def display_help(name)
  if not Detroit.tools.key?(name)
    load_plugin(name)
  end
  tool = Detroit.tools[name]
  if tool.const_defined?(:MANPAGE)
    man_page = tool.const_get(:MANPAGE)
    Kernel.system "man #{man_page}"
  else
    puts "No detailed help available for `#{name}'."
  end
end
header_message() click to toggle source

— Print Methods ——————————————————-

# File lib/detroit/toolchain/runner.rb, line 396
def header_message
  if multitask?
    ["#{project.metadata.title} v#{project.metadata.version}   [M]", "#{project.root}"]
  else
    ["#{project.metadata.title} v#{project.metadata.version}", "#{project.root}"]
  end
end
load_plugin(name) click to toggle source

Load a plugin.

# File lib/detroit/toolchain/runner.rb, line 520
def load_plugin(name)
  @loaded_plugins[name] ||= (
    begin
      require "detroit-#{name}"
    rescue LoadError => e
      $stderr.puts "ERROR: #{e.message.capitalize}"
      $stderr.puts "       Perhaps `gem install detroit-#{name}`?"
      exit -1
    end
    name # true ?
  )
end
load_toolchain_file(file) click to toggle source

Load toolchain file.

# File lib/detroit/toolchain/runner.rb, line 575
def load_toolchain_file(file)
  @toolchains[file] = Toolchain::Script.load(File.new(file), project)
  @tools.merge!(toolchains[file].tools)
end
load_toolchains() click to toggle source
# File lib/detroit/toolchain/runner.rb, line 552
def load_toolchains
  toolchain_filenames.each do |file|
    load_toolchain_file(file)
  end

  #if config = eval('self', TOPLEVEL_BINDING).rc_detroit
  #  @toolchains['(rc)'] = Script.new(&config)
  #  @tools.merge!(toolchains['(rc)'].tools)
  #end

  #if config = Detroit.rc_config
  #  tc = Script.new do
  #    tools.each do |c|
  #      track(c.profile, &c)
  #    end
  #  end
  #  @toolchains['(rc)'] = tc
  #  @tools.merge!(toolchains['(rc)'].tools)
  #end
end
multitask=(boolean) click to toggle source

Set multi-task mode.

# File lib/detroit/toolchain/runner.rb, line 89
def multitask=(boolean)
  if boolean && !defined?(Parallel)
    puts "Parallel gem must be installed to multitask."
    @multitask = false
  else
    @multitask = boolean
  end
end
multitask?() click to toggle source

Multitask mode?

# File lib/detroit/toolchain/runner.rb, line 84
def multitask?
  @multitask      
end
print_header(left, right) click to toggle source
print_phase(left, right) click to toggle source
printline(left, right='', options={}) click to toggle source
# File lib/detroit/toolchain/runner.rb, line 462
def printline(left, right='', options={})
  return if quiet?

  separator = options[:seperator] || options[:sep] || ' '
  padding   = options[:padding]   || options[:pad] || 0

  left, right = left.to_s, right.to_s

  left_size  = left.size
  right_size = right.size

  #left  = colorize(left)
  #right = colorize(right)

  l = padding
  r = -(right_size + padding)

  style  = options[:style] || []
  lstyle = options[:left]  || []
  rstyle = options[:right] || []

  left  = lstyle.inject(left) { |s, c| ansize(s, c) }
  right = rstyle.inject(right){ |s, c| ansize(s, c) }

  line = separator * screen_width
  line[l, left_size]  = left  if left_size != 0
  line[r, right_size] = right if right_size != 0

  line = style.inject(line){ |s, c| ansize(s, c) }

  puts line + ansize('', :clear)
end
project() click to toggle source

Provides access to the Project instance via ‘Detroit.project` class method.

# File lib/detroit/toolchain/runner.rb, line 109
def project
  @project ||= Detroit.project(root)
end
quiet=(boolean) click to toggle source

Set quiet mode.

@return [Boolean]

# File lib/detroit/toolchain/runner.rb, line 59
def quiet=(boolean)
  @quiet = !!boolean
end
quiet?() click to toggle source

Quiet mode?

@return [Boolean]

# File lib/detroit/toolchain/runner.rb, line 52
def quiet?
  @quiet
end
root() click to toggle source

TODO: Lookup project root.

# File lib/detroit/toolchain/runner.rb, line 513
def root
  Pathname.new(Dir.pwd)
end
run(stop) click to toggle source

Run up to the specified track stop.

# File lib/detroit/toolchain/runner.rb, line 250
def run(stop)
  raise "Malformed destination -- #{stop}" unless /^\w+\:{0,1}\w+$/ =~ stop

  track, stop = stop.split(':')
  track, stop = 'main', track unless stop

  track = track.to_sym
  stop  = stop.to_sym if stop

  # TODO: Using #preconfigure as part of the protocol should probably change.

  ## prime the workers (so as to fail early)
  active_workers(track).each do |w|
    w.preconfigure if w.respond_to?("preconfigure")
  end

  sys = Detroit.assemblies[assembly.to_sym]

  raise "Unknown assembly `#{assembly}'" unless sys

  # Lookup chain by stop name.
  chain = sys.find(stop)

  #if stop
  #  system = track.route_with_stop(stop)
  #  raise "Unknown stop -- #{stop}" unless system

  unless chain
    #overview
    $stderr.puts "Unknown stop `#{stop}'."
    exit 0
  end

  @destination = stop

  status_header(*header_message)

  start_time = Time.now

  chain.each do |run_stop|
    next if skip.include?("#{run_stop}")  # TODO: Should we really allow skipping stops?
    #tool_hooks(name, ('pre_' + run_stop.to_s).to_sym)
    tool_calls(track, ('pre_' + run_stop.to_s).to_sym)
    tool_calls(track, run_stop)
    tool_calls(track, ('aft_' + run_stop.to_s).to_sym)
    #tool_hooks(name, ('aft_' + run_stop.to_s).to_sym)
    break if stop == run_stop
  end

  stop_time = Time.now

  puts "\nFinished in #{stop_time - start_time} seconds." unless quiet?
end
run_a_worker(worker, track, stop) click to toggle source

Invoke a worker given the worker, track and stop name.

@todo Provide more robust options, rather than just ‘@destination`.

TODO: Rename this method.

@return [void]

# File lib/detroit/toolchain/runner.rb, line 381
def run_a_worker(worker, track, stop)
  if target = worker.stop?(stop, @destination)
    target = stop if TrueClass === target
    label  = stop.to_s.gsub('_', '-').capitalize
    if options[:trace] #options[:verbose]
      status_line("#{worker.key.to_s} (#{worker.class}##{target})", label)
    else
      status_line("#{worker.key.to_s}", label)
    end
    worker.invoke(target, @destination)
  end
end
screen_width() click to toggle source

Get the terminals width.

@return [Integer]

# File lib/detroit/toolchain/runner.rb, line 508
def screen_width
  ANSI::Terminal.terminal_width
end
skip() click to toggle source

List of tool names to skip.

# File lib/detroit/toolchain/runner.rb, line 64
def skip
  @skip
end
skip=(list) click to toggle source

Set skip list.

# File lib/detroit/toolchain/runner.rb, line 69
def skip=(list)
  @skip = list.to_list.map{ |s| s.downcase }
end
start(stop) click to toggle source

Change direectory to project root and run.

# File lib/detroit/toolchain/runner.rb, line 243
def start(stop)
  Dir.chdir(project.root) do       # change into project directory
    run(stop)
  end
end
status_header(left, right='') click to toggle source

Print a status header, which consists of project name and version on the left and stop location on the right.

# File lib/detroit/toolchain/runner.rb, line 407
def status_header(left, right='')
  left, right = left.to_s, right.to_s
  #left.color  = 'blue'
  #right.color = 'magenta'
  unless quiet?
    puts
    print_header(left, right)
    #puts "=" * io.screen_width
  end
end
status_line(left, right='') click to toggle source

Print a status line, which consists of worker name on the left and stop name on the right.

# File lib/detroit/toolchain/runner.rb, line 421
def status_line(left, right='')
  left, right = left.to_s, right.to_s
  #left.color  = 'blue'
  #right.color = 'magenta'
  unless quiet?
    puts
    #puts "-" * io.screen_width
    print_phase(left, right)
    #puts "-" * io.screen_width
    #puts
  end
end
tool_calls(track, stop) click to toggle source

Make tool calls.

This groups workers by priority b/c groups of the same priority can be run in parallel if the multitask option is on.

# File lib/detroit/toolchain/runner.rb, line 351
def tool_calls(track, stop)
  prioritized_workers = active_workers(track).group_by{ |w| w.priority }.sort_by{ |k,v| k }
  prioritized_workers.each do |priority, workers|
    ## remove any workers specified by the --skip option on the comamndline
    #workers = workers.reject{ |w| skip.include?(w.key.to_s) }

    ## only servies that are on the track
    #workers = workers.select{ |w| w.tracks.nil? or w.tracks.include?(w.to_s) }

    worklist = workers.map{ |w| [w, track, stop] }

    if multitask?
      results = Parallel.in_processes(worklist.size) do |i|
        run_a_worker(*worklist[i])
      end
    else
      worklist.each do |args|
        run_a_worker(*args)
      end
    end
  end
end
tool_class_options(tool_class) click to toggle source
# File lib/detroit/toolchain/runner.rb, line 176
def tool_class_options(tool_class)
end
toolchain_filenames() click to toggle source

If a ‘Toolchain` or `.toolchain` file exists, then it is returned. Otherwise all `*.toolchain` files are loaded. To load `*.toolchain` files from another directory add the directory to config options file.

TODO: Simplify this to just ‘toolchain`.

# File lib/detroit/toolchain/runner.rb, line 586
def toolchain_filenames
  @toolchain_filenames ||= (
    files = []
    ## match 'Toolchain' or '.toolchain' file
    files = project.root.glob("{,.,*.}#{FILE_EXTENSION}{,.rb,.yml,.yaml}", :casefold)
    ## only files
    files = files.select{ |f| File.file?(f) }
    ##
    if files.empty?
      ## match '.detroit/*.toolchain' or 'detroit/*.toolchain'
      #files += project.root.glob("{,.}#{DIRECTORY}/*.#{FILE_EXTENSION}", :casefold)
      ## match 'task/*.toolchain' (OLD SCHOOL)
      files += project.root.glob("{task,tasks}/*.#{FILE_EXTENSION}", :casefold)
      ## only files
      files = files.select{ |f| File.file?(f) }
    end
    files
  )
end
toolchain_files() click to toggle source

List of toolchain files to use.

# File lib/detroit/toolchain/runner.rb, line 99
def toolchain_files
  @toolchain_files
end
toolchain_files=(files) click to toggle source
# File lib/detroit/toolchain/runner.rb, line 104
def toolchain_files=(files)
  @toolchain_files = files
end