#!/usr/bin/env lua local short_opts = { v = "verbose", vv = "very_verbose", o = "output", q = "quiet", qq = "very_quiet", g = "debug" } local opts = { use_http = false }; for _, opt in ipairs(arg) do if opt:match("^%-") then local name = opt:match("^%-%-?([^%s=]+)()") name = (short_opts[name] or name):gsub("%-+", "_"); if name:match("^no_") then name = name:sub(4, -1); opts[name] = false; else opts[name] = opt:match("=(.*)$") or true; end else base_path = opt; end end if opts.very_verbose then opts.verbose = true; end if opts.very_quiet then opts.quiet = true; end local noprint = function () end local print_err, print_info, print_verbose, print_debug = noprint, noprint, noprint, noprint; if not opts.very_quiet then print_err = print; end if not opts.quiet then print_info = print; end if opts.verbose or opts.very_verbose then print_verbose = print; end if opts.very_verbose then print_debug = print; end print = print_verbose; local modules, main_files, resources = {}, {}, {}; -- Functions to be called from squishy file -- function Module(name) if modules[name] then print_verbose("Ignoring duplicate module definition for "..name); return function () end end local i = #modules+1; modules[i] = { name = name, url = ___fetch_url }; modules[name] = modules[i]; return function (path) modules[i].path = path; end end function Resource(name, path) local i = #resources+1; resources[i] = { name = name, path = path or name }; return function (path) resources[i].path = path; end end function AutoFetchURL(url) ___fetch_url = url; end function Main(fn) table.insert(main_files, fn); end function Output(fn) if opts.output == nil then out_fn = fn; end end function Option(name) name = name:gsub("%-", "_"); if opts[name] == nil then opts[name] = true; return function (value) opts[name] = value; end else return function () end; end end function GetOption(name) return opts[name:gsub('%-', '_')]; end function Message(message) if not opts.quiet then print_info(message); end end function Error(message) if not opts.very_quiet then print_err(message); end end function Exit() os.exit(1); end -- -- -- -- -- -- -- --- -- -- -- -- -- -- -- -- base_path = (base_path or "."):gsub("/$", "").."/" squishy_file = base_path .. "squishy"; out_fn = opts.output; local ok, err = pcall(dofile, squishy_file); if not ok then print_err("Couldn't read squishy file: "..err); os.exit(1); end if not out_fn then print_err("No output file specified by user or squishy file"); os.exit(1); elseif #main_files == 0 and #modules == 0 and #resources == 0 then print_err("No files, modules or resources. Not going to generate an empty file."); os.exit(1); end local fetch = {}; function fetch.filesystem(path) local f, err = io.open(path); if not f then return false, err; end local data = f:read("*a"); f:close(); return data; end if opts.use_http then function fetch.http(url) local http = require "socket.http"; local body, status = http.request(url); if status == 200 then return body; end return false, "HTTP status code: "..tostring(status); end else function fetch.http(url) return false, "Module not found. Re-squish with --use-http option to fetch it from "..url; end end print_info("Writing "..out_fn.."..."); local f, err = io.open(out_fn, "w+"); if not f then print_err("Couldn't open output file: "..tostring(err)); os.exit(1); end if opts.executable then if opts.executable == true then f:write("#!/usr/bin/env lua\n"); else f:write(opts.executable, "\n"); end end if opts.debug then f:write(require_resource("squish.debug")); end print_verbose("Resolving modules..."); do local LUA_DIRSEP = package.config:sub(1,1); local LUA_PATH_MARK = package.config:sub(5,5); local package_path = package.path:gsub("[^;]+", function (path) if not path:match("^%"..LUA_DIRSEP) then return base_path..path; end end):gsub("/%./", "/"); local package_cpath = package.cpath:gsub("[^;]+", function (path) if not path:match("^%"..LUA_DIRSEP) then return base_path..path; end end):gsub("/%./", "/"); function resolve_module(name, path) name = name:gsub("%.", LUA_DIRSEP); for c in path:gmatch("[^;]+") do c = c:gsub("%"..LUA_PATH_MARK, name); print_debug("Looking for "..c) local f = io.open(c); if f then print_debug("Found!"); f:close(); return c; end end return nil; -- not found end for i, module in ipairs(modules) do if not module.path then module.path = resolve_module(module.name, package_path); if not module.path then print_err("Couldn't resolve module: "..module.name); else -- Strip base_path from resolved path module.path = module.path:gsub("^"..base_path:gsub("%p", "%%%1"), ""); end end end end print_verbose("Packing modules..."); for _, module in ipairs(modules) do local modulename, path = module.name, module.path; if module.path:sub(1,1) ~= "/" then path = base_path..module.path; end print_debug("Packing "..modulename.." ("..path..")..."); local data, err = fetch.filesystem(path); if (not data) and module.url then print_debug("Fetching: ".. module.url:gsub("%?", module.path)) data, err = fetch.http(module.url:gsub("%?", module.path)); end if data then f:write("package.preload['", modulename, "'] = (function (...)\n"); f:write(data); f:write(" end)\n"); if opts.debug then f:write(string.format("package.preload[%q] = ___adjust_chunk(package.preload[%q], %q);\n\n", modulename, modulename, "@"..path)); end else print_err("Couldn't pack module '"..modulename.."': "..(err or "unknown error... path to module file correct?")); os.exit(1); end end if #resources > 0 then print_verbose("Packing resources...") f:write("do local resources = {};\n"); for _, resource in ipairs(resources) do local name, path = resource.name, resource.path; local res_file, err = io.open(base_path..path, "rb"); if not res_file then print_err("Couldn't load resource: "..tostring(err)); os.exit(1); end local data = res_file:read("*a"); local maxequals = 0; data:gsub("(=+)", function (equals_string) maxequals = math.max(maxequals, #equals_string); end); f:write(("resources[%q] = %q"):format(name, data)); --[[ f:write(("resources[%q] = ["):format(name), string.rep("=", maxequals+1), "["); f:write(data); f:write("]", string.rep("=", maxequals+1), "];"); ]] end if opts.virtual_io then local vio = require_resource("vio"); if not vio then print_err("Virtual IO requested but is not enabled in this build of squish"); else -- Insert vio library f:write(vio, "\n") -- Override standard functions to use vio if opening a resource f:write[[local io_open, io_lines = io.open, io.lines; function io.open(fn, mode) if not resources[fn] then return io_open(fn, mode); else return vio.open(resources[fn]); end end function io.lines(fn) if not resources[fn] then return io_lines(fn); else return vio.open(resources[fn]):lines() end end local _dofile = dofile; function dofile(fn) if not resources[fn] then return _dofile(fn); else return assert(loadstring(resources[fn]))(); end end local _loadfile = loadfile; function loadfile(fn) if not resources[fn] then return _loadfile(fn); else return loadstring(resources[fn], "@"..fn); end end ]] end end f:write[[function require_resource(name) return resources[name] or error("resource '"..tostring(name).."' not found"); end end ]] end print_debug("Finalising...") for _, fn in pairs(main_files) do local fin, err = io.open(base_path..fn); if not fin then print_err("Failed to open "..fn..": "..err); os.exit(1); else f:write((fin:read("*a"):gsub("^#.-\n", ""))); fin:close(); end end f:close(); print_info("OK!");