module Enginery::Helpers

Public Class Methods

extract_setup(input) click to toggle source
# File lib/enginery/helpers/input.rb, line 117
def extract_setup input
  input.scan(/:(.+)/).flatten.last
end
fail(*failures) click to toggle source
# File lib/enginery/helpers/validations.rb, line 12
def fail *failures
  throw :enginery_failures, Failure.new(*failures)
end
fail_verbosely(*failures) click to toggle source
# File lib/enginery/helpers/validations.rb, line 17
def fail_verbosely *failures
  o *failures
  fail *failures
end
o(*chunks) click to toggle source
# File lib/enginery/helpers/generic.rb, line 93
def o *chunks
  @logger ||= Logger.new(STDOUT)
  opts = chunks.last.is_a?(Hash) ? chunks.pop : {}
  @logger << "%s\n" % chunks.join(opts[:join].to_s)
end
parse_input(*input) click to toggle source

TODO: refactor this huge method

# File lib/enginery/helpers/input.rb, line 5
def parse_input *input
  input.flatten!
  args, setups, string_setups = [], {}, []
  input.each do |a|
    case

    # generator
    when a =~ /\Ao(rm)?:/
      orm = extract_setup(a)
      if valid_orm = valid_orm?(orm)
        setups[:orm] = valid_orm
        string_setups << a
      else
        fail_verbosely 'Invalid ORM provided - "%s"' % orm, \
          'Supported ORMs: ActiveRecord, DataMapper, Sequel'
      end
    when a =~ /\Ae(ngine)?:/
      smth = extract_setup(a)
      if engine = valid_engine?(smth)
        setups[:engine] = engine
        string_setups << a
      else
        fail_verbosely 'Invalid engine provided - %s' % smth, \
          'Supported engines(Case Sensitive): %s' % EConstants::VIEW__ENGINE_BY_SYM.keys.join(', ')
      end
    when a =~ /\Af(ormat|ile)?:/
      if format = extract_setup(a)
        # format if used by generator, file is used by migrator
        setups[:format] = setups[:file] = format
        string_setups << a
      end
    when a =~ /\Ar(oute)?:/
      if route = extract_setup(a)
        setups[:route] = route
        string_setups << a
      end
    when a =~ /\Adb/
      [:type, :host, :port, :name, :user, :pass].each do |s|
        if (a =~ /\Adb(_)?#{s}:/) && (v = extract_setup(a))
          (setups[:db] ||= {}).update s => (s == :type ? valid_db_type?(v) : v)
          string_setups << a
        end
      end
    when a =~ /\As(erver)?:/
      smth = extract_setup(a)
      if server = valid_server?(smth)
        setups[:server] = server.to_sym
        string_setups << a
      else
        fail_verbosely 'Unknown server provided - %s' % smth, \
          'It wont be added to Gemfile nor to config.yml', \
          'Known servers(Case Sensitive): %s' % KNOWN_WEB_SERVERS.join(', ')
      end
    when a =~ /\Ap(ort)?:/
      smth = extract_setup(a)
      if (port = smth.to_i) > 0
        setups[:port] = port
        string_setups << a
      else
        fail_verbosely 'Invalid port provided - %s' % smth, 'Port should be a number'
      end
    when a =~ /\Ah(ost(s)?)?:/
      if hosts = extract_setup(a)
        setups[:hosts] = hosts.split(',')
        string_setups << a
      end
    when a =~ /\Ai(nclude)?:/
      mdl = validate_constant_name extract_setup(a)
      (setups[:include] ||= []).push mdl
      string_setups << a

    # migrator
    when a =~ /\Acreate_table_for:/
      if table = extract_setup(a)
        setups[:create_table] = table
        string_setups << a
      end
    when a =~ /\Am(odel)?:/
      if table = extract_setup(a)
        setups[:update_table] = table
        string_setups << a
      end
    when a =~ /\Aa?(dd_)?c(olumn)?:/
      if column = extract_setup(a)
        (setups[:create_columns] ||= []).push column.split(':')
        string_setups << a
      end
    when a =~ /\Au(pdate_)?c?(olumn)?:/
      if column = extract_setup(a)
        (setups[:update_columns] ||= []).push column.split(':')
        string_setups << a
      end
    when a =~ /\Ar(ename_)?c?(olumn)?:/
      if column = extract_setup(a)
        (setups[:rename_columns] ||= []).push column.split(':')
        string_setups << a
      end
    else
      args.push(a) unless ORM_ASSOCIATIONS.find {|an| a =~ /#{an}/}
    end
  end
  ORM_ASSOCIATIONS.each do |a|
    input.select {|x| x =~ /\A#{a}:/}.each do |s|
      next unless v = extract_setup(s)
      (setups[a] ||= []).push v
      string_setups << s
    end
  end
  [args.freeze, setups.freeze, string_setups.join(' ').freeze]
end
valid_db_type?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 42
def valid_db_type? smth
  return unless  smth.is_a?(String) || smth.is_a?(Symbol)
  case
  when smth =~ /\Am/i
    :mysql
  when smth =~ /\Ap/i
    :postgres
  when smth =~ /\As/i
    :sqlite
  end
end
valid_engine?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 55
def valid_engine? smth
  engine = smth.to_s.to_sym
  EConstants::VIEW__ENGINE_BY_SYM.has_key?(engine) ? engine : false
end
valid_orm?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 29
def valid_orm? smth
  return unless smth.is_a?(String) || smth.is_a?(Symbol)
  case
  when smth =~ /\Aa/i
    :ActiveRecord
  when smth =~ /\Ad/i
    :DataMapper
  when smth =~ /\As/i
    :Sequel
  end
end
valid_server?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 23
def valid_server? smth
  server = smth.to_s.to_sym
  KNOWN_WEB_SERVERS.include?(server) ? server : false
end
validate_constant_name(constant) click to toggle source
# File lib/enginery/helpers/validations.rb, line 86
def validate_constant_name constant
  constant =~ /[^\w|\d|\:]/ && fail("Wrong constant name - %s, it should contain only alphanumerics" % constant)
  constant =~ /\A[0-9]/     && fail("Wrong constant name - %s, it should start with a letter" % constant)
  constant =~ /\A[A-Z]/     || fail("Wrong constant name - %s, it should start with a uppercase letter" % constant)
  constant
end

Public Instance Methods

activerecord_associations(setups = {}) click to toggle source
# File lib/enginery/helpers/orm.rb, line 4
def activerecord_associations setups = {}
  ORM_ASSOCIATIONS.inject([]) do |lines,a|
    (setups[a]||[]).each do |s|
      line, input = nil, s.split(':')
      target = input[0]
      if target =~ /\W/
        o '*** WARN: invalid association target "%s", association not added ***' % target
      else
        line = '%s :%s' % [a, target]
        if through = input[1].to_s =~ /through/ && input[2]
          if through =~ /\W/
            o '*** WARN: invalid :through option "%s", association not added ***' % through
            line = nil
          else
            line << ', through: :%s' % through
          end
        end
      end
      lines.push(line) if line
    end
    lines
  end
end
app_config() click to toggle source
# File lib/enginery/helpers/app.rb, line 70
def app_config
  pv, $VERBOSE = $VERBOSE, nil
  load dst_path.config_rb
  Cfg
ensure
  $VERBOSE = pv
end
app_controllers() click to toggle source
# File lib/enginery/helpers/app.rb, line 21
def app_controllers
  App.mounted_controllers.select {|c| controller_exists?(c.name)}
end
app_models() click to toggle source
# File lib/enginery/helpers/app.rb, line 41
def app_models
  load_boot_rb
  identity_methods = ORM_IDENTITY_METHODS[Cfg[:orm].to_s.to_sym]
  return [] unless identity_methods
  ObjectSpace.each_object(Class).select do |o|
    identity_methods.all? {|m| o.respond_to?(m)} && model_exists?(o.name)
  end
end
boot_app() click to toggle source
# File lib/enginery/helpers/app.rb, line 16
def boot_app
  load_boot_rb
  App.boot!
end
controller_exists?(name) click to toggle source
# File lib/enginery/helpers/app.rb, line 31
def controller_exists? name
  path = dst_path(:controllers, class_to_route(name))
  File.file?(path + CONTROLLER_SUFFIX) && path
end
datamapper_associations(setups = {}) click to toggle source
# File lib/enginery/helpers/orm.rb, line 28
def datamapper_associations setups = {}
  ORM_ASSOCIATIONS.inject([]) do |lines,a|
    (setups[a]||[]).each do |s|
      line, input = nil, s.split(':')
      target = input[0]
      if target =~ /\W/
        o '*** WARN: invalid association target "%s", association not added ***' % target
      else
        if a == :has_one
          line = 'has 1, :%s' % target
        elsif a =~ /has_(and|many)/
          line = 'has n, :%s' % target
        else
          line = '%s :%s' % [a, target]
        end
        if through = input[1].to_s =~ /through/ && input[2]
          if through =~ /\W/
            o '*** WARN: invalid :through option "%s", association not added ***' % through
            line = nil
          else
            line << ', through: :%s' % through
          end
        end
      end
      lines.push(line) if line
    end
    lines
  end
end
dst_path(*args) click to toggle source
# File lib/enginery/helpers/generic.rb, line 35
def dst_path *args
  @dst_path_map ||= begin
    paths = { :root => @dst_root.to_s.gsub(/\/+/, '/') }
    [
      :base,
      :config,
    ].each {|p| paths[p] = File.join(paths[:root], p.to_s, '')}
    [
      :controllers,
      :models,
      :views,
      :specs,
      :migrations,
      :helpers
    ].each {|d| paths[d] = File.join(paths[:base], d.to_s, '')}

    paths[:rear_controllers] = File.join(paths[:controllers], 'rear-controllers', '')
    paths[:config_rb] = File.join(paths[:base], 'config.rb')
    paths[:config_yml] = File.join(paths[:config], 'config.yml')
    paths[:database_yml] = File.join(paths[:config], 'database.yml')
    
    [
      :Rakefile,
      :Gemfile,
    ].each {|f| paths[f] = File.join(paths[:root], f.to_s)}
    [
      :boot_rb,
      :database_rb,
    ].each {|f| paths[f] = File.join(paths[:base], f.to_s.sub('_rb', '.rb'))}
    paths.values.map(&:freeze)
    [paths, Struct.new(*paths.keys).new(*paths.values)]
  end
  paths, struct = @dst_path_map
  return struct if args.empty?
  
  paths[args.first] || fail('%s is not a recognized destination path.
    Use one of %s' % [args.first.inspect, paths.map(&:inspect)*', '])
  File.join(paths[args.shift], *args.map(&:to_s)).gsub(/\/+/, '/').freeze
end
fail_unless_in_app_folder!() click to toggle source
# File lib/enginery/helpers/validations.rb, line 8
def fail_unless_in_app_folder!
  in_app_folder? || fail("Seems current folder does not contain a Espresso application")
end
in_app_folder?() click to toggle source
# File lib/enginery/helpers/validations.rb, line 4
def in_app_folder?
  File.directory?(dst_path.controllers)
end
load_boot_rb() click to toggle source
# File lib/enginery/helpers/app.rb, line 4
def load_boot_rb
  pv, $VERBOSE = $VERBOSE, nil
  orig = Array.new($:)
  # loading app
  load dst_path.boot_rb
  # for some reason, Bundler get rid of existing loadpath entries.
  # usually this will break autoloading, so storing orig paths and inserting them back
  orig.each {|p| $:.include?(p) || $: << p}
ensure
  $VERBOSE = pv
end
migrations_by_model(model) click to toggle source
# File lib/enginery/helpers/app.rb, line 50
def migrations_by_model model
  Dir[dst_path(:migrations, class_to_route(model), '*' + MIGRATION_SUFFIX)].map do |f|
    File.basename(f)
  end
end
model_exists?(name) click to toggle source
# File lib/enginery/helpers/app.rb, line 36
def model_exists? name
  path = dst_path(:models, class_to_route(name))
  File.file?(path + MODEL_SUFFIX) && path
end
routes_by_controller(controller) click to toggle source
# File lib/enginery/helpers/app.rb, line 25
def routes_by_controller controller
  Dir[dst_path(:controllers, class_to_route(controller), '*' + ROUTE_SUFFIX)].map do |f|
    File.basename(f, File.extname(f))
  end
end
sequel_associations(setups = {}) click to toggle source
# File lib/enginery/helpers/orm.rb, line 58
def sequel_associations setups = {}
  ORM_ASSOCIATIONS.inject([]) do |lines,a|
    (setups[a]||[]).each do |s|
      line, input = nil, s.split(':')
      target = input[0]
      if target =~ /\W/
        o '*** WARN: invalid association target "%s", association not added ***' % target
      else
        case a
        when :belongs_to
          line = 'many_to_one :%s' % target
        when :has_one
          line = 'one_to_one :%s' % target
        when :has_many
          line = 'one_to_many :%s' % target
        when :has_and_belongs_to_many
          line = 'many_to_many :%s' % target
        end
        if through = input[1].to_s =~ /through/ && input[2]
          o '*** INFO: Sequel does not support :through option, ignoring ***' % through
        end
      end
      lines.push(line) if line
    end
    lines
  end
end
src_path(*args) click to toggle source
# File lib/enginery/helpers/generic.rb, line 12
def src_path *args
  @src_path_map ||= begin
    paths = { :root => File.expand_path('../../../../app', __FILE__) + '/' }
    [
      :base,
      :gemfiles,
      :rakefiles,
      :specfiles,
      :database,
      :migrations,
      :layouts,
    ].each {|d| paths[d] = File.join(paths[:root], d.to_s, '')}
    paths.values.map(&:freeze)
    [paths, Struct.new(*paths.keys).new(*paths.values)]
  end
  paths, struct = @src_path_map
  return struct if  args.empty?
  
  paths[args.first] || fail('%s is not a recognized source path.
    Use one of %s' % [args.first.inspect, paths.map(&:inspect)*', '])
  File.join(paths[args.shift], *args.map(&:to_s)).gsub(/\/+/, '/').freeze
end
unrootify(path, root = nil) click to toggle source
# File lib/enginery/helpers/generic.rb, line 75
def unrootify path, root = nil
  root = (root || dst_path.root).gsub(/\/+/, '/')
  regexp = /\A#{Regexp.escape(root)}\/?/
  path.gsub(/\/+/, '/').sub(regexp, '')
end
valid_controller?(name) click to toggle source
# File lib/enginery/helpers/validations.rb, line 61
def valid_controller? name
  name.nil? || name.empty? && fail("Please provide controller name")
  ctrl_path = controller_exists?(name) || fail('"%s" controller does not exists' % name)

  ctrl = name.split('::').map(&:to_sym).inject(Object) do |ns,c|
    ctrl_dirname = unrootify(ctrl_path)
    ns.const_defined?(c) || fail("#{ctrl_dirname} exists but #{name} controller not defined.
      Please define it manually or delete #{ctrl_dirname} and start over.")
    ns.const_get(c)
  end
  [ctrl_path, ctrl]
end
valid_route?(ctrl_name, name) click to toggle source
# File lib/enginery/helpers/validations.rb, line 74
def valid_route? ctrl_name, name
  ctrl_path, ctrl = valid_controller?(ctrl_name)
  name.nil? || name.empty? && fail("Please provide route name")
  path_rules = ctrl.path_rules.inject({}) do |map,(r,s)|
    map.merge %r[#{Regexp.escape s}] => r.source
  end
  route = action_to_route(name, path_rules)
  validate_route_name(route)
  file = File.join(ctrl_path, route + '.rb')
  [file, route]
end
validate_route_name(name) click to toggle source
# File lib/enginery/helpers/validations.rb, line 94
def validate_route_name name
  name =~ /\W/ && fail("Routes may contain only alphanumerics")
  name
end
view_setups_for(ctrl, action) click to toggle source
# File lib/enginery/helpers/app.rb, line 56
def view_setups_for ctrl, action
  boot_app
  ctrl_instance = ctrl.new
  ctrl_instance.respond_to?(action.to_sym) || fail('"%s" route does not exists' % action)
  
  action_name, request_method = deRESTify_action(action)
  ctrl_instance.action_setup  = ctrl.action_setup[action_name][request_method]
  ctrl_instance.call_setups!
  [
    File.join(ctrl_instance.view_path?, ctrl_instance.view_prefix?),
    ctrl_instance.engine_ext?
  ]
end

Private Instance Methods

constant_defined?(name) click to toggle source
# File lib/enginery/helpers/generic.rb, line 132
def constant_defined? name
  return unless name
  namespace = name.to_s.strip.sub(/\A::/, '').split('::').map {|c| validate_constant_name c}
  namespace.inject(Object) do |o,c|
    o.const_defined?(c.to_sym) ? o.const_get(c) : break
  end
end
extract_setup(input) click to toggle source
# File lib/enginery/helpers/input.rb, line 117
def extract_setup input
  input.scan(/:(.+)/).flatten.last
end
fail(*failures) click to toggle source
# File lib/enginery/helpers/validations.rb, line 12
def fail *failures
  throw :enginery_failures, Failure.new(*failures)
end
fail_verbosely(*failures) click to toggle source
# File lib/enginery/helpers/validations.rb, line 17
def fail_verbosely *failures
  o *failures
  fail *failures
end
namespace_to_source_code(name) click to toggle source
# File lib/enginery/helpers/generic.rb, line 100
def namespace_to_source_code name
  names, constants = name.split('::'), []
  
  names.uniq.size == names.size ||
    fail('%s namespace constants duplicates' % name)

  names.map(&:to_sym).inject(Object) do |ns,c|
    validate_constant_name(c)
    c_class, next_ns = Module, nil

    if ns && ns.const_defined?(c)
      next_ns = ns.const_get(c)
      c_class = next_ns.class
      [Class, Module].include?(c_class) ||
        fail('%s should be a Class or a Module. It is a %s instead' % [constants.keys*'::', c_class])
    end
    
    constants << [c, c_class.name.downcase]
    next_ns
  end
  
  constant_name = constants.pop.first.to_s
  
  before, after = [], []
  constants.each do |(cn,cc)|
    i = INDENT * before.size
    before << '%s%s %s' % [i, cc, cn]
    after  << '%send'   % i
  end
  [before, constant_name, after.reverse << '']
end
o(*chunks) click to toggle source
# File lib/enginery/helpers/generic.rb, line 93
def o *chunks
  @logger ||= Logger.new(STDOUT)
  opts = chunks.last.is_a?(Hash) ? chunks.pop : {}
  @logger << "%s\n" % chunks.join(opts[:join].to_s)
end
output_source_code(source) click to toggle source
# File lib/enginery/helpers/generic.rb, line 140
def output_source_code source
  (source.is_a?(String) ? File.readlines(source) : source).each {|l| o "+ " + l.chomp}
end
parse_input(*input) click to toggle source

TODO: refactor this huge method

# File lib/enginery/helpers/input.rb, line 5
def parse_input *input
  input.flatten!
  args, setups, string_setups = [], {}, []
  input.each do |a|
    case

    # generator
    when a =~ /\Ao(rm)?:/
      orm = extract_setup(a)
      if valid_orm = valid_orm?(orm)
        setups[:orm] = valid_orm
        string_setups << a
      else
        fail_verbosely 'Invalid ORM provided - "%s"' % orm, \
          'Supported ORMs: ActiveRecord, DataMapper, Sequel'
      end
    when a =~ /\Ae(ngine)?:/
      smth = extract_setup(a)
      if engine = valid_engine?(smth)
        setups[:engine] = engine
        string_setups << a
      else
        fail_verbosely 'Invalid engine provided - %s' % smth, \
          'Supported engines(Case Sensitive): %s' % EConstants::VIEW__ENGINE_BY_SYM.keys.join(', ')
      end
    when a =~ /\Af(ormat|ile)?:/
      if format = extract_setup(a)
        # format if used by generator, file is used by migrator
        setups[:format] = setups[:file] = format
        string_setups << a
      end
    when a =~ /\Ar(oute)?:/
      if route = extract_setup(a)
        setups[:route] = route
        string_setups << a
      end
    when a =~ /\Adb/
      [:type, :host, :port, :name, :user, :pass].each do |s|
        if (a =~ /\Adb(_)?#{s}:/) && (v = extract_setup(a))
          (setups[:db] ||= {}).update s => (s == :type ? valid_db_type?(v) : v)
          string_setups << a
        end
      end
    when a =~ /\As(erver)?:/
      smth = extract_setup(a)
      if server = valid_server?(smth)
        setups[:server] = server.to_sym
        string_setups << a
      else
        fail_verbosely 'Unknown server provided - %s' % smth, \
          'It wont be added to Gemfile nor to config.yml', \
          'Known servers(Case Sensitive): %s' % KNOWN_WEB_SERVERS.join(', ')
      end
    when a =~ /\Ap(ort)?:/
      smth = extract_setup(a)
      if (port = smth.to_i) > 0
        setups[:port] = port
        string_setups << a
      else
        fail_verbosely 'Invalid port provided - %s' % smth, 'Port should be a number'
      end
    when a =~ /\Ah(ost(s)?)?:/
      if hosts = extract_setup(a)
        setups[:hosts] = hosts.split(',')
        string_setups << a
      end
    when a =~ /\Ai(nclude)?:/
      mdl = validate_constant_name extract_setup(a)
      (setups[:include] ||= []).push mdl
      string_setups << a

    # migrator
    when a =~ /\Acreate_table_for:/
      if table = extract_setup(a)
        setups[:create_table] = table
        string_setups << a
      end
    when a =~ /\Am(odel)?:/
      if table = extract_setup(a)
        setups[:update_table] = table
        string_setups << a
      end
    when a =~ /\Aa?(dd_)?c(olumn)?:/
      if column = extract_setup(a)
        (setups[:create_columns] ||= []).push column.split(':')
        string_setups << a
      end
    when a =~ /\Au(pdate_)?c?(olumn)?:/
      if column = extract_setup(a)
        (setups[:update_columns] ||= []).push column.split(':')
        string_setups << a
      end
    when a =~ /\Ar(ename_)?c?(olumn)?:/
      if column = extract_setup(a)
        (setups[:rename_columns] ||= []).push column.split(':')
        string_setups << a
      end
    else
      args.push(a) unless ORM_ASSOCIATIONS.find {|an| a =~ /#{an}/}
    end
  end
  ORM_ASSOCIATIONS.each do |a|
    input.select {|x| x =~ /\A#{a}:/}.each do |s|
      next unless v = extract_setup(s)
      (setups[a] ||= []).push v
      string_setups << s
    end
  end
  [args.freeze, setups.freeze, string_setups.join(' ').freeze]
end
update_file(file, data) click to toggle source
# File lib/enginery/helpers/generic.rb, line 88
def update_file file, data
  o '*** Updating "%s" ***' % unrootify(file)
  File.open(file, 'a+') {|f| f << (data.respond_to?(:join) ? data.join : data)}
end
valid_db_type?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 42
def valid_db_type? smth
  return unless  smth.is_a?(String) || smth.is_a?(Symbol)
  case
  when smth =~ /\Am/i
    :mysql
  when smth =~ /\Ap/i
    :postgres
  when smth =~ /\As/i
    :sqlite
  end
end
valid_engine?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 55
def valid_engine? smth
  engine = smth.to_s.to_sym
  EConstants::VIEW__ENGINE_BY_SYM.has_key?(engine) ? engine : false
end
valid_orm?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 29
def valid_orm? smth
  return unless smth.is_a?(String) || smth.is_a?(Symbol)
  case
  when smth =~ /\Aa/i
    :ActiveRecord
  when smth =~ /\Ad/i
    :DataMapper
  when smth =~ /\As/i
    :Sequel
  end
end
valid_server?(smth) click to toggle source
# File lib/enginery/helpers/validations.rb, line 23
def valid_server? smth
  server = smth.to_s.to_sym
  KNOWN_WEB_SERVERS.include?(server) ? server : false
end
validate_constant_name(constant) click to toggle source
# File lib/enginery/helpers/validations.rb, line 86
def validate_constant_name constant
  constant =~ /[^\w|\d|\:]/ && fail("Wrong constant name - %s, it should contain only alphanumerics" % constant)
  constant =~ /\A[0-9]/     && fail("Wrong constant name - %s, it should start with a letter" % constant)
  constant =~ /\A[A-Z]/     || fail("Wrong constant name - %s, it should start with a uppercase letter" % constant)
  constant
end
write_file(file, data) click to toggle source
# File lib/enginery/helpers/generic.rb, line 83
def write_file file, data
  o '***    Writing "%s" ***' % unrootify(file).gsub('::', '_')
  File.open(file, 'w') {|f| f << (data.respond_to?(:join) ? data.join : data)}
end