#!/bin/sh
SH=--[[                                             # -*- mode: lua; -*-
## Slingshot rockspec generator.
##
## This file is distributed with Slingshot, and licensed under the
## terms of the MIT license reproduced below.

## ====================================================================
## Copyright (C) 2013-2015 Gary V. Vaughan
##
## Permission is hereby granted, free of charge, to any person
## obtaining a copy of this software and associated documentation
## files (the "Software"), to deal in the Software without restriction,
## including without limitation the rights to use, copy, modify, merge,
## publish, distribute, sublicense, and/or sell copies of the Software,
## and to permit persons to whom the Software is furnished to do so,
## subject to the following conditions:
##
## The above copyright notice and this permission notice shall be
## included in  all copies or substantial portions of the Software.
##
## THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
## EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
## MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGE-
## MENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
## FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
## CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
## WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
## ====================================================================


_lua_version_re='"Lua 5."[123]*'
_lua_binaries='lua lua5.3 lua53 lua5.2 lua52 luajit lua5.1 lua51'

export LUA
export LUA_INIT
export LUA_INIT_5_2
export LUA_INIT_5_3
export LUA_PATH
export LUA_CPATH

# Be Bourne compatible
if test -n "${ZSH_VERSION+set}" && (emulate sh) >/dev/null 2>&1; then
  emulate sh
  NULLCMD=:
  # Zsh 3.x and 4.x performs word splitting on ${1+"$@"}, which
  # is contrary to our usage.  Disable this feature.
  alias -g '${1+"$@"}'='"$@"'
  setopt NO_GLOB_SUBST
else
  case `(set -o) 2>/dev/null` in *posix*) set -o posix;; esac
fi

# If LUA is not set, search PATH for something suitable.
test -n "$LUA" || {
  # Check that the supplied binary is executable and returns a compatible
  # Lua version number.
  func_vercheck ()
  {
    test -x "$1" && {
      eval 'case `'$1' -e "print (_VERSION)" 2>/dev/null` in
        '"$_lua_version_re"') LUA='$1' ;;
      esac'
    }
  }

  progname=`echo "$0" |${SED-sed} 's|.*/||'`

  save_IFS="$IFS"
  LUA=
  for x in $_lua_binaries; do
    IFS=:
    for dir in $PATH; do
      IFS="$save_IFS"
      func_vercheck "$dir/$x"
      test -n "$LUA" && break
    done
    IFS="$save_IFS"
    test -n "$LUA" && break
    e="${e+$e\n}$progname: command not found on PATH: $x"
  done
}

test -n "$LUA" || {
  printf "${e+$e\n}$progname: retry after 'export LUA=/path/to/lua'\n" >&2
  exit 1
}

LUA_INIT=
LUA_INIT_5_2=
LUA_INIT_5_3=

# Reexecute using the interpreter suppiled in LUA, or found above.
exec "$LUA" "$0" "$@"
]]SH


--[[ ============== ]]--
--[[ Parse options. ]]--
--[[ ============== ]]--

local usage = 'Usage: mkrockspecs [OPTIONS] PACKAGE VERSION [REVISION] [FILE]\n'

prog = {
  name = arg[0] and arg[0]:gsub (".*/", "") or "mkrockspecs",

  opts = {},
}

-- Print an argument processing error message, and return non-zero exit
-- status.
local function opterr (msg)
  io.stderr:write (usage)
  io.stderr:write (prog.name .. ": error: " .. msg .. ".\n")
  io.stderr:write (prog.name .. ": Try '" .. prog.name .. " --help' for help,\n")
  os.exit (2)
end

local function setopt (optname, arglist, i)
  local opt = arglist[i]
  if i + 1 > #arglist then
    opterr ("option '" .. opt .. "' requires an argument")
  end
  prog.opts[optname] = arglist[i + 1]
  return i + 1
end

local function die (msg)
  msg:gsub ("([^\n]+)\n?",
            function ()
              io.stderr:write (prog.name .. ": error: " .. msg.. "\n")
	    end)
  os.exit (1)
end

prog["--help"] = function ()
  print (usage .. [[

Convert a YAML configuration file into a full rockspec.

If FILE is provided, load it as the base configuration, otherwise if
there is a 'rockspec.conf' in the current directory use that, or else
wait for input on stdin.  If FILE is '-', force reading base config-
uration from stdin.

PACKAGE and VERSION are the package name and version number as defined
by 'configure.ac' or similar. REVISION is only required for a revised
rockspec if the default "-1" revision was released with errors.

  -b, --branch=BRANCH    make git rockspec use BRANCH
  -m, --module-dir=ROOT  directory of lua-files for builtin build type
  -r, --repository=REPO  set the repository name (default=PACKAGE)
      --help             print this help, then exit
      --version          print version number, then exit

Report bugs to http://github.com/gvvaughan/slingshot/issues.]])
    os.exit (0)
end

prog["--version"] = function ()
  print [[mkrockspecs (slingshot) 8.0.0
Written by Gary V. Vaughan <gary@gnu.org>, 2013

Copyright (C) 2013, Gary V. Vaughan
Slingshot comes with ABSOLUTELY NO WARRANTY.
See source files for individual license conditions.]]
  os.exit (0)
end

prog["-b"] = function (argl, i) return setopt ("branch", argl, i) end
prog["--branch"] = prog["-b"]

prog["-m"] = function (argl, i) return setopt ("module_root", argl, i) end
prog["--module-dir"] = prog["-m"]

prog["-r"] = function (argl, i) return setopt ("repository", argl, i) end
prog["--repository"] = prog["-r"]

local nonopts
local i = 0
while i < #arg do
  i = i + 1
  local opt = arg[i]

  -- Collect remaining arguments not nonopts to save back into _G.arg later.
  if type (nonopts) == "table" then
    table.insert (nonopts, opt)

  -- Run prog.option handler.
  elseif opt:sub (1,1) == "-" and type (prog[opt]) == "function" then
    i = prog[opt] (arg, i)

  -- End of option arguments.
  elseif opt == "--" then
    nonopts = {}

  -- Diagnose unknown command line options.
  elseif opt:sub (1, 1) == "-" then
    opterr ("unrecognized option '" .. opt .. "'")

  -- First non-option argument marks the end of options.
  else
    nonopts = { opt }
  end
end

-- put non-option args back into global arg table.
nonopts = nonopts or {}
nonopts[0] = arg[0]
_G.arg = nonopts

if select ("#", ...) < 2 then
  opterr ("only " .. select ("#", ...) .. " arguments provided")
end

local package  = arg[1]
local version  = arg[2]
local revision = arg[3] or "1"
local conf     = arg[4] or "rockspec.conf"

-- Unless set explicity, assume the repo is named after the package.
if prog.opts.repository == nil then
  prog.opts.repository = package
end


--[[ ================= ]]--
--[[ Helper functions. ]]--
--[[ ================= ]]--

local ok, posix = pcall (require, "posix")

files = {}

if ok then
  -- faster version if luaposix is available
  function tree (root)
    for f in posix.files (root) do
      local path = root .. "/" .. f
      if f:match ("%.lua$") then
        table.insert (files, path)
      elseif f == "." or f == ".." then
        -- don't go into a loop
      elseif posix.stat (path, "type") == "directory" then
        tree (path)
      end
    end
  end
else
  -- fallback version that executes ls in subshells
  function tree (root)
    local p = io.popen ("ls -1 " .. root .. " 2>/dev/null")

    if p ~= nil then
      local f = p:read "*l"
      while f ~= nil do
        if f:match ("%.lua$") then
          table.insert (files, root .. "/" .. f)
        else
          tree (root .. "/" .. f)
        end
        f = p:read "*l"
      end
    end
  end
end

local function escape_pattern (s)
  return (string.gsub (s, "[%^%$%(%)%%%.%[%]%*%+%-%?]", "%%%0"))
end

local function loadmap (root)
  local map = {}
  tree (root)
  for _, f in ipairs (files) do
    local m = f:match ("^" .. escape_pattern (root) .. "/(.*)%.lua")
    map [m:gsub ("/", "."):gsub ("%.init$", "")] = f:gsub ("^%./", "")
  end
  return map
end


--[[ =================== ]]--
--[[ Load configuration. ]]--
--[[ =================== ]]--

local yaml = require "lyaml"

-- Slurp io.input ().
local function slurp ()
  h = io.input ()
  if h then
    local s = h:read "*a"
    h:close ()
    return s
  end
end

if conf == "-" then
  io.input (io.stdin)
else
  local h = io.open (conf)
  if h then
    io.input (conf)
    h:close ()
  else
    io.input (io.stdin)
  end
end

local spec = yaml.load (slurp ())
local default = { source = {} }

-- url needn't be given if it is identical to homepage.
local url
if spec.source ~= nil then
  url = spec.source.url
elseif spec.description ~= nil then
  url = spec.description.homepage
else
  die (conf .. ": could not find source.url or description.homepage")
end
url = url:gsub ("^[a-z]*://", ""):gsub ("%.git$", "")

-- Interpolate default values.
default.package = package
default.version = version .. "-" .. revision

configure_flags = ""
if type (spec.external_dependencies) == "table" then
  CPPFLAGS, LDFLAGS = "", ""
  for name, vars in pairs (spec.external_dependencies) do
    if vars.library then
      CPPFLAGS = CPPFLAGS .. " -I$(" .. name .. "_INCDIR)"
      LDFLAGS  = LDFLAGS  .. " -L$(" .. name .. "_LIBDIR)"
    end
  end

  if string.len (CPPFLAGS) > 0 then
    configure_flags = configure_flags ..
        "CPPFLAGS='" .. CPPFLAGS:gsub ("^%s", "") .. "'" ..
        " LDFLAGS='" ..  LDFLAGS:gsub ("^%s", "") .. "'" ..
        " "
  end
end

-- If we have a module root, use the luarocks "builtin" type.
if version ~= "scm" and version ~= "git" then
  if prog.opts.module_root ~= nil then
    default.build = {
      type = "builtin",
      modules = loadmap (prog.opts.module_root),
    }
  elseif spec.build ~= nil and spec.build.modules ~= nil then
    default.build = { type = "builtin" }
  end
end

default.build = default.build or {
  type = "command",
  build_command = "./configure " ..
    "LUA='$(LUA)' LUA_INCLUDE='-I$(LUA_INCDIR)' " .. configure_flags ..
    "--prefix='$(PREFIX)' --libdir='$(LIBDIR)' --datadir='$(LUADIR)' " ..
    "--datarootdir='$(PREFIX)' && make clean all",
  install_command = "make install luadir='$(LUADIR)' luaexecdir='$(LIBDIR)'",
  copy_directories = {},
}

-- Additional spec-type dependent values.
spec.source = spec.source or {}
spec.build  = spec.build  or {}
if version ~= "scm" and version ~= "git" then
  spec.source.url = "http://" .. url .. "/archive/release-v" .. version .. ".zip"
  spec.source.dir = prog.opts.repository .. "-release-v" .. version
else
  spec.source.url = "git://" .. url .. ".git"
  spec.source.branch = prog.opts.branch
  spec.build.modules = nil
  default.build.build_command =  "./bootstrap && " .. default.build.build_command
end


-- Recursive merge, settings from spec take precedence.  Elements of src
-- overwrite equivalent keys in dest.
local function merge (dest, src)
  for k, v in pairs (src) do
    if type (v) == "table" then
      dest[k] = merge (dest[k] or {}, src[k])
    else
      dest[k] = src[k]
    end
  end
  return dest
end

spec = merge (default, spec)


--[[ ======= ]]--
--[[ Output. ]]--
--[[ ======= ]]--

-- Recursively format X, with pretty printing.
local function format (x, indent)
  indent = indent or ""
  if type (x) == "table" then
    if next (x) == nil then
      return "{}"
    else
      local s = "{\n"

      -- Collect and sort non-numeric keys first.
      keys = {}
      for k in pairs (x) do
        if type (k) ~= "number" then table.insert (keys, k) end
      end
      table.sort (keys, function (a, b) return tostring (a) < tostring (b) end)

      -- Display non-numeric key pairs in sort order.
      for _, k in ipairs (keys) do
        s = s .. indent
        if k:match ("[^_%w]") then
          -- wrap keys with non-%w chars in square brackets
          s = s .. '["' .. k .. '"]'
        else
          s = s .. k
        end
        s = s .. " = " .. format (x[k], indent .. "  ") .. ",\n"
      end

      -- And numeric key pairs last.
      for i, v in ipairs (x) do
        s = s .. indent .. format (v, indent .. "  ") .. ",\n"
      end
      return s..indent:sub (1, -3).."}"
    end
  elseif type (x) == "string" then
    return string.format ("%q", x)
  else
    return tostring (x)
  end
end

-- Use the standard order for known keys.
for _, k in ipairs {
  "package",
  "version",
  "description",
  "source",
  "dependencies",
  "external_dependencies",
  "build",
} do
  print (k .. " = " .. format (spec[k], "  "))
  spec[k] = nil
end

-- Output anything left in the table at the end.
for i, v in pairs (spec) do
  print (i .. " = " .. format (v, "  "))
end

os.exit (0)
