#!/System/Library/Frameworks/Ruby.framework/Versions/Current/usr/bin/ruby
require 'optparse'
require 'shellwords'

def parse_variables(path)
  res = { }
  data = File.read(path)
  assignments = data.scan(/^(\w+)\s*(=)[ \t]*(.*)$/)
  assignments.each do |arr|
    key, op, value = *arr
    res[key] = Shellwords.shellwords(value)
  end
  res
end

variables = { }
outfile   = nil
OptionParser.new do |opts|
  opts.banner = "Usage: expand_variables [options]"
  opts.separator "Synopsis:"
  opts.separator "expand_variables: substitute ${VARIABLES} in text files"
  opts.separator "Options:"
  opts.on("-h","--help", "show help.") do |v|
    puts opts
    exit
  end

  opts.on("-v","--variables FILE", "read variables from FILE.") do |arg|
    variables.merge!(parse_variables(arg))
  end

  opts.on("-d", "--define NAME=VALUE", "set variable NAME to VALUE.") do |arg|
    if arg =~ /^(\w+)\s*(=)[ \t]*(.*)$/
      variables[$1] = Shellwords.shellwords($3)
    end
  end

  opts.on("-o", "--output FILE", "write output to FILE.") do |arg|
    outfile = arg
  end
end.parse!

data, enc = ARGF.binmode.read, Encoding::UTF_8
if data.start_with?("\xFE\xFF".b)
  enc = Encoding::UTF_16BE
elsif data.start_with?("\xFF\xFE".b)
  enc = Encoding::UTF_16LE
end

data.force_encoding(enc)
data.encode!(Encoding::UTF_8) if enc != Encoding::UTF_8

data.gsub!(/\$\{(.*?)\}/) do
  value = nil
  if variables.include?($1)
    value = variables[$1]
  elsif ENV.include?($1)
    value = ENV[$1]
  else
    abort "*** unknown variable: ‘#$1’"
  end
  value.kind_of?(Array) ? value.join(' ') : value
end

io = outfile ? open(outfile, 'w') : STDOUT
io.set_encoding(enc)
io << data
io.close
