1#!/usr/bin/env lua 2 3local short_opts = { v = "verbose", vv = "very_verbose", o = "output", q = "quiet", qq = "very_quiet", g = "debug" } 4local opts = { use_http = false }; 5 6for _, opt in ipairs(arg) do 7 if opt:match("^%-") then 8 local name = opt:match("^%-%-?([^%s=]+)()") 9 name = (short_opts[name] or name):gsub("%-+", "_"); 10 if name:match("^no_") then 11 name = name:sub(4, -1); 12 opts[name] = false; 13 else 14 opts[name] = opt:match("=(.*)$") or true; 15 end 16 else 17 base_path = opt; 18 end 19end 20 21if opts.very_verbose then opts.verbose = true; end 22if opts.very_quiet then opts.quiet = true; end 23 24local noprint = function () end 25local print_err, print_info, print_verbose, print_debug = noprint, noprint, noprint, noprint; 26 27if not opts.very_quiet then print_err = print; end 28if not opts.quiet then print_info = print; end 29if opts.verbose or opts.very_verbose then print_verbose = print; end 30if opts.very_verbose then print_debug = print; end 31 32print = print_verbose; 33 34local modules, main_files, resources = {}, {}, {}; 35 36-- Functions to be called from squishy file -- 37 38function Module(name) 39 if modules[name] then 40 print_verbose("Ignoring duplicate module definition for "..name); 41 return function () end 42 end 43 local i = #modules+1; 44 modules[i] = { name = name, url = ___fetch_url }; 45 modules[name] = modules[i]; 46 return function (path) 47 modules[i].path = path; 48 end 49end 50 51function Resource(name, path) 52 local i = #resources+1; 53 resources[i] = { name = name, path = path or name }; 54 return function (path) 55 resources[i].path = path; 56 end 57end 58 59function AutoFetchURL(url) 60 ___fetch_url = url; 61end 62 63function Main(fn) 64 table.insert(main_files, fn); 65end 66 67function Output(fn) 68 if opts.output == nil then 69 out_fn = fn; 70 end 71end 72 73function Option(name) 74 name = name:gsub("%-", "_"); 75 if opts[name] == nil then 76 opts[name] = true; 77 return function (value) 78 opts[name] = value; 79 end 80 else 81 return function () end; 82 end 83end 84 85function GetOption(name) 86 return opts[name:gsub('%-', '_')]; 87end 88 89function Message(message) 90 if not opts.quiet then 91 print_info(message); 92 end 93end 94 95function Error(message) 96 if not opts.very_quiet then 97 print_err(message); 98 end 99end 100 101function Exit() 102 os.exit(1); 103end 104-- -- -- -- -- -- -- --- -- -- -- -- -- -- -- -- 105 106base_path = (base_path or "."):gsub("/$", "").."/" 107squishy_file = base_path .. "squishy"; 108out_fn = opts.output; 109 110local ok, err = pcall(dofile, squishy_file); 111 112if not ok then 113 print_err("Couldn't read squishy file: "..err); 114 os.exit(1); 115end 116 117if not out_fn then 118 print_err("No output file specified by user or squishy file"); 119 os.exit(1); 120elseif #main_files == 0 and #modules == 0 and #resources == 0 then 121 print_err("No files, modules or resources. Not going to generate an empty file."); 122 os.exit(1); 123end 124 125local fetch = {}; 126function fetch.filesystem(path) 127 local f, err = io.open(path); 128 if not f then return false, err; end 129 130 local data = f:read("*a"); 131 f:close(); 132 133 return data; 134end 135 136if opts.use_http then 137 function fetch.http(url) 138 local http = require "socket.http"; 139 140 local body, status = http.request(url); 141 if status == 200 then 142 return body; 143 end 144 return false, "HTTP status code: "..tostring(status); 145 end 146else 147 function fetch.http(url) 148 return false, "Module not found. Re-squish with --use-http option to fetch it from "..url; 149 end 150end 151 152print_info("Writing "..out_fn.."..."); 153local f, err = io.open(out_fn, "w+"); 154if not f then 155 print_err("Couldn't open output file: "..tostring(err)); 156 os.exit(1); 157end 158 159if opts.executable then 160 if opts.executable == true then 161 f:write("#!/usr/bin/env lua\n"); 162 else 163 f:write(opts.executable, "\n"); 164 end 165end 166 167if opts.debug then 168 f:write(require_resource("squish.debug")); 169end 170 171print_verbose("Resolving modules..."); 172do 173 local LUA_DIRSEP = package.config:sub(1,1); 174 local LUA_PATH_MARK = package.config:sub(5,5); 175 176 local package_path = package.path:gsub("[^;]+", function (path) 177 if not path:match("^%"..LUA_DIRSEP) then 178 return base_path..path; 179 end 180 end):gsub("/%./", "/"); 181 local package_cpath = package.cpath:gsub("[^;]+", function (path) 182 if not path:match("^%"..LUA_DIRSEP) then 183 return base_path..path; 184 end 185 end):gsub("/%./", "/"); 186 187 function resolve_module(name, path) 188 name = name:gsub("%.", LUA_DIRSEP); 189 for c in path:gmatch("[^;]+") do 190 c = c:gsub("%"..LUA_PATH_MARK, name); 191 print_debug("Looking for "..c) 192 local f = io.open(c); 193 if f then 194 print_debug("Found!"); 195 f:close(); 196 return c; 197 end 198 end 199 return nil; -- not found 200 end 201 202 for i, module in ipairs(modules) do 203 if not module.path then 204 module.path = resolve_module(module.name, package_path); 205 if not module.path then 206 print_err("Couldn't resolve module: "..module.name); 207 else 208 -- Strip base_path from resolved path 209 module.path = module.path:gsub("^"..base_path:gsub("%p", "%%%1"), ""); 210 end 211 end 212 end 213end 214 215 216print_verbose("Packing modules..."); 217for _, module in ipairs(modules) do 218 local modulename, path = module.name, module.path; 219 if module.path:sub(1,1) ~= "/" then 220 path = base_path..module.path; 221 end 222 print_debug("Packing "..modulename.." ("..path..")..."); 223 local data, err = fetch.filesystem(path); 224 if (not data) and module.url then 225 print_debug("Fetching: ".. module.url:gsub("%?", module.path)) 226 data, err = fetch.http(module.url:gsub("%?", module.path)); 227 end 228 if data then 229 f:write("package.preload['", modulename, "'] = (function (...)\n"); 230 f:write(data); 231 f:write(" end)\n"); 232 if opts.debug then 233 f:write(string.format("package.preload[%q] = ___adjust_chunk(package.preload[%q], %q);\n\n", 234 modulename, modulename, "@"..path)); 235 end 236 else 237 print_err("Couldn't pack module '"..modulename.."': "..(err or "unknown error... path to module file correct?")); 238 os.exit(1); 239 end 240end 241 242if #resources > 0 then 243 print_verbose("Packing resources...") 244 f:write("do local resources = {};\n"); 245 for _, resource in ipairs(resources) do 246 local name, path = resource.name, resource.path; 247 local res_file, err = io.open(base_path..path, "rb"); 248 if not res_file then 249 print_err("Couldn't load resource: "..tostring(err)); 250 os.exit(1); 251 end 252 local data = res_file:read("*a"); 253 local maxequals = 0; 254 data:gsub("(=+)", function (equals_string) maxequals = math.max(maxequals, #equals_string); end); 255 256 f:write(("resources[%q] = %q"):format(name, data)); 257--[[ f:write(("resources[%q] = ["):format(name), string.rep("=", maxequals+1), "["); 258 f:write(data); 259 f:write("]", string.rep("=", maxequals+1), "];"); ]] 260 end 261 if opts.virtual_io then 262 local vio = require_resource("vio"); 263 if not vio then 264 print_err("Virtual IO requested but is not enabled in this build of squish"); 265 else 266 -- Insert vio library 267 f:write(vio, "\n") 268 -- Override standard functions to use vio if opening a resource 269 f:write[[local io_open, io_lines = io.open, io.lines; function io.open(fn, mode) 270 if not resources[fn] then 271 return io_open(fn, mode); 272 else 273 return vio.open(resources[fn]); 274 end end 275 function io.lines(fn) 276 if not resources[fn] then 277 return io_lines(fn); 278 else 279 return vio.open(resources[fn]):lines() 280 end end 281 local _dofile = dofile; 282 function dofile(fn) 283 if not resources[fn] then 284 return _dofile(fn); 285 else 286 return assert(loadstring(resources[fn]))(); 287 end end 288 local _loadfile = loadfile; 289 function loadfile(fn) 290 if not resources[fn] then 291 return _loadfile(fn); 292 else 293 return loadstring(resources[fn], "@"..fn); 294 end end ]] 295 end 296 end 297 f:write[[function require_resource(name) return resources[name] or error("resource '"..tostring(name).."' not found"); end end ]] 298end 299 300print_debug("Finalising...") 301for _, fn in pairs(main_files) do 302 local fin, err = io.open(base_path..fn); 303 if not fin then 304 print_err("Failed to open "..fn..": "..err); 305 os.exit(1); 306 else 307 f:write((fin:read("*a"):gsub("^#.-\n", ""))); 308 fin:close(); 309 end 310end 311 312f:close(); 313 314print_info("OK!"); 315