class Flor::Loader

Public Class Methods

new(unit) click to toggle source

NB: tasker configuration entries start with “loa_”

# File lib/flor/unit/loader.rb, line 31
def initialize(unit)

  @unit = unit

  @cache = {}
  @mutex = Mutex.new

  @root = File.absolute_path(
    @unit.conf['lod_path'] || @unit.conf['root'] || '.')
end

Public Instance Methods

definitions(start=nil) click to toggle source
# File lib/flor/unit/loader.rb, line 163
def definitions(start=nil)

  start ||= ''

  Dir[File.join(@root, '**/*.{flo,flor}')]
    .collect { |pa| pa[@root.length..-1] }
    .collect { |pa| pa.gsub(/\A\/usr\//, '/') }
    .collect { |pa| pa.gsub(/\/lib\/(sub)?flows\//, '/') }
    .collect { |pa| pa.gsub(/\.flor?\z/, '') }
    .collect { |pa| pa.gsub(/\//, '.') }
    .collect { |pa| pa.gsub(/\A\./, '') }
    .sort
    .select { |pa| Flor.sub_domain?(start, pa) }
end
domains(start=nil) click to toggle source
# File lib/flor/unit/loader.rb, line 136
def domains(start=nil)

  start ||= ''

  Dir[File.join(@root, '**/*.{json,flo,flor,rb}')]
    .collect { |pa| pa[@root.length..-1] }
    .sort
    .collect { |pa|
      pa = pa[4..-1] if pa.match(/^\/usr\//)
      case pa
      when /\.flor?$/ then extract_flow_domain(pa)
      when /\/lib\/hooks\// then extract_hook_domain(pa)
      when /\/lib\/taskers\// then extract_tasker_domain(pa)
      when /\/etc\/variables\// then extract_variable_domain(pa)
      else nil
      end }
    .compact
    .select { |pa| pa.length > 1 }
    .collect { |pa|
      pa = pa[1..-1] if pa[0, 1] == '/'
      pa = pa[0..-2] if pa.match(/\/$/)
      pa.gsub('/', '.') }
    .sort
    .uniq
    .select { |dm| Flor.sub_domain?(start, dm) }
end
hooks(domain) click to toggle source
# File lib/flor/unit/loader.rb, line 114
def hooks(domain)

  # NB: do not relativize path, because Ruby load path != cwd,
  # stay absolute for `require` and `load`

  Dir[File.join(@root, '**/*.json')]
    .select { |f| f.index('/lib/hooks/') }
    .collect { |pa| [ pa, expose_d(pa, {}) ] }
    .select { |pa, d| Flor.sub_domain?(d, domain) }
    .sort_by { |pa, d| d.count('.') }
    .collect { |pa, d|
      eval_hook_conf(pa, {})
        .each_with_index { |h, i| h['_path'] = "#{pa}:#{i}" } }
    .flatten(1)
end
library(domain, name=nil, opts={}) click to toggle source

If found, returns [ source_path, path ]

# File lib/flor/unit/loader.rb, line 60
def library(domain, name=nil, opts={})

  domain, name, opts = [ domain, nil, name ] if name.is_a?(Hash)
  domain, name = split_dn(domain, name)

  if m = name.match(/(\.flor?)\z/)
    name = name[0..m[1].length - 1]
  end

  path, _, _ = (Dir[File.join(@root, '**/*.{flo,flor}')])
    .select { |f| f.index('/lib/') }
    .collect { |pa| [ pa, *expose_dn(pa, opts) ] }
    .select { |pa, d, n| n == name && Flor.sub_domain?(d, domain) }
    .sort_by { |pa, d, n| d.count('.') }
    .last

  path ? [ Flor.relativize_path(path), File.read(path) ] : nil
end
load_hooks(exid) click to toggle source
# File lib/flor/unit/loader.rb, line 130
def load_hooks(exid)

  hooks(Flor.domain(exid))
    .collect { |h| Flor::Hook.new(@unit, exid, h) }
end
tasker(domain, name, message={}) click to toggle source
# File lib/flor/unit/loader.rb, line 79
def tasker(domain, name, message={})

  # NB: do not relativize path, because Ruby load path != cwd,
  # stay absolute for `require` and `load`

  domain, name = split_dn(domain, name)

  pat, _, nam = Dir[File.join(@root, '**/*.{json,rb}')]
    .select { |pa| pa.index('/lib/taskers/') }
    .collect { |pa| [ pa, *expose_dn(pa, {}) ] }
    .select { |pa, d, n|
      Flor.sub_domain?([ d, n ], domain) ||
      (n == name && Flor.sub_domain?(d, domain)) }
    .sort_by { |pa, d, n| d.count('.') }
    .last

  return nil unless pat

  conf = eval_tasker_conf(pat, message)

  return conf if nam == name

  cnf = conf[name]

  return nil unless cnf

  extras = conf.select { |_, v| ! v.is_a?(Hash) }
  extras['_path'] = pat

  (cnf.is_a?(Array) ? cnf : [ cnf ])
    .each { |h| h.merge!(extras) }

  cnf
end
variables(domain) click to toggle source
# File lib/flor/unit/loader.rb, line 42
def variables(domain)

  Dir[File.join(@root, '**/*.json')]
    .select { |f| f.index('/etc/variables/') }
    .collect { |pa| [ pa, expose_d(pa, {}) ] }
    .select { |pa, d| Flor.sub_domain?(d, domain) }
    .sort_by { |pa, d| d.count('.') }
    .inject({}) { |vars, (pa, _)| vars.merge!(eval_variables(pa, {})) }
end

Protected Instance Methods

eval(path, context) click to toggle source
# File lib/flor/unit/loader.rb, line 282
def eval(path, context)

  ext =
    File.extname(path)

  src =
    @mutex.synchronize do

      mt1 = File.mtime(path)
      val, mt0 = @cache[path]

      if val && mt1 == mt0
        val
      elsif ext == '.rb'
        (@cache[path] = [ File.read(path), mt1 ]).first
      else
        (@cache[path] = [ Flor::ConfExecutor.load(path), mt1 ]).first
      end
    end

  case ext
  when '.rb' then eval_ruby(path, src, context)
  else eval_json(path, src, context)
  end
end
eval_hook_conf(path, context) click to toggle source

TODO like in eval_hook_conf, reject fautly tasker confs… TODO like in eval_hook_conf, reject fautly variables…

# File lib/flor/unit/loader.rb, line 262
def eval_hook_conf(path, context)

  a = eval(path, context)

  fail ArgumentError.new(
    "hook conf at #{path} must be an array of hashes"
  ) unless a.is_a?(Array)

  a.each do |e|
    fail ArgumentError.new(
      "hook conf at #{path} has non-hash entry #{e.inspect}"
    ) unless e.is_a?(Hash)
    fail ArgumentError.new(
      "hook conf at #{path} has incorrect point #{e['point'].inspect}"
    ) unless e['point'].is_a?(String)
  end

  a
end
eval_json(path, src, context) click to toggle source
# File lib/flor/unit/loader.rb, line 308
def eval_json(path, src, context)

  Flor::ConfExecutor.interpret(path, src, context)
end
eval_ruby(path, src, context) click to toggle source
# File lib/flor/unit/loader.rb, line 313
def eval_ruby(path, src, context)

  ks = context.keys.select { |k| k.match(/\A[a-z][a-zA-Z0-9_]+\z/) }

  s = StringIO.new
  s << "lambda {\n"
  ks.each { |k| s << k << " = " << context[k].inspect << "\n" }
  s << src
  s << "\n}.call\n"

  r = Kernel.module_eval(s.string, path, - ks.size)

  r = JSON.parse(JSON.dump(r)) #if r.keys.find { |k| k.is_a?(Symbol) }
    #
    # so that symbols may be used in the .rb file, but plain JSON-like
    # string keys are on the output

  r.merge!('_path' => path)
end
eval_tasker_conf(path, context) click to toggle source
# File lib/flor/unit/loader.rb, line 256
def eval_tasker_conf(path, context)
  eval(path, context)
end
eval_variables(path, context) click to toggle source
# File lib/flor/unit/loader.rb, line 253
def eval_variables(path, context)
  eval(path, context)
end
expose_d(path, opts) click to toggle source
# File lib/flor/unit/loader.rb, line 222
def expose_d(path, opts)

  pa = path[@root.length..-1]
  pa = pa[5..-1] if pa[0, 5] == '/usr/'

  libregex =
    opts[:subflows] ?
    /\/lib\/(subflows|flows|hooks|taskers)\// :
    /\/lib\/(flows|hooks|taskers)\//

  pa
    .sub(/\/etc\/variables\//, '/')
    .sub(libregex, '/')
    .sub(/\/\z/, '')
    .sub(/\/(flo|flor|dot|hooks)\.json\z/, '')
    .sub(/\.(flo|flor|json|rb)\z/, '')
    .sub(/\A\//, '')
    .gsub(/\//, '.')
end
expose_dn(path, opts) click to toggle source
# File lib/flor/unit/loader.rb, line 242
def expose_dn(path, opts)

  pa = expose_d(path, opts)

  if ri = pa.rindex('.')
    [ pa[0..ri - 1], pa[ri + 1..-1] ]
  else
    [ '', pa ]
  end
end
extract_flow_domain(pa) click to toggle source
# File lib/flor/unit/loader.rb, line 180
    def extract_flow_domain(pa)
#o = pa
      pa
        .gsub(/\/lib\/(sub)?flows\//, '/')
        .split('/')[0..-2].join('/')
#.tap { |x| p 'flow:' + o + ':' + x }
    end
extract_hook_domain(pa) click to toggle source
# File lib/flor/unit/loader.rb, line 187
    def extract_hook_domain(pa)
#o = pa
      pa
        .gsub(/\/lib\/hooks\//, '/')
        .gsub(/\/(dot|hooks)\.(json|rb)$/, '/')
        .gsub(/\.(json|rb)$/, '')
#.tap { |x| p 'hook:' + o + ':' + x }
    end
extract_tasker_domain(pa) click to toggle source
# File lib/flor/unit/loader.rb, line 195
    def extract_tasker_domain(pa)
#o = pa
      pa
        .gsub(/\/lib\/taskers\//, '/')
        .gsub(/\/[^\/]+\/(dot|flor|tasker)\.(json|rb)$/, '/')
        .gsub(/\.(json|rb)$/, '')
#.tap { |x| p 'tasker: ' + o + ': ' + x }
    end
extract_variable_domain(pa) click to toggle source
# File lib/flor/unit/loader.rb, line 203
    def extract_variable_domain(pa)
#o = pa
      pa
        .gsub(/\/etc\/variables\//, '/')
        .gsub(/\/(dot|flor)\.(json|rb)$/, '/')
        .gsub(/\.(json|rb)$/, '')
#.tap { |x| p 'variable: ' + o + ': ' + x }
    end
split_dn(domain, name) click to toggle source
# File lib/flor/unit/loader.rb, line 212
def split_dn(domain, name)

  if name
    [ domain, name ]
  else
    elts = domain.split('.')
    [ elts[0..-2].join('.'), elts[-1] ]
  end
end