Polishing makefile
[squish.git] / squish.lua
blobcb1fa41243e1690ff3f78ad09036f8926198a048
1 #!/usr/bin/env lua
3 -- Initialise LuaRocks if present
4 pcall(require, "luarocks.require");
6 local short_opts = { v = "verbose", vv = "very_verbose", o = "output", q = "quiet", qq = "very_quiet", g = "debug" }
7 local opts = { use_http = false };
9 for _, opt in ipairs(arg) do
10 if opt:match("^%-") then
11 local name = opt:match("^%-%-?([^%s=]+)()")
12 name = (short_opts[name] or name):gsub("%-+", "_");
13 if name:match("^no_") then
14 name = name:sub(4, -1);
15 opts[name] = false;
16 else
17 opts[name] = opt:match("=(.*)$") or true;
18 end
19 else
20 squishy_file = opt;
21 end
22 end
24 if opts.very_verbose then opts.verbose = true; end
25 if opts.very_quiet then opts.quiet = true; end
27 local noprint = function () end
28 local print_err, print_info, print_verbose, print_debug = noprint, noprint, noprint, noprint;
30 if not opts.very_quiet then print_err = print; end
31 if not opts.quiet then print_info = print; end
32 if opts.verbose or opts.very_verbose then print_verbose = print; end
33 if opts.very_verbose then print_debug = print; end
35 print = print_verbose;
37 local modules, main_files, resources = {}, {}, {};
39 -- Functions to be called from squishy file --
41 function Module(name)
42 if modules[name] then
43 print_verbose("Ignoring duplicate module definition for "..name);
44 return function () end
45 end
46 local i = #modules+1;
47 modules[i] = { name = name, url = ___fetch_url };
48 modules[name] = modules[i];
49 return function (path)
50 modules[i].path = path;
51 end
52 end
54 function Resource(name, path)
55 local i = #resources+1;
56 resources[i] = { name = name, path = path or name };
57 return function (path)
58 resources[i].path = path;
59 end
60 end
62 function AutoFetchURL(url)
63 ___fetch_url = url;
64 end
66 function Main(fn)
67 table.insert(main_files, fn);
68 end
70 function Output(fn)
71 if opts.output == nil then
72 out_fn = fn;
73 end
74 end
76 function Option(name)
77 name = name:gsub("%-", "_");
78 if opts[name] == nil then
79 opts[name] = true;
80 return function (value)
81 opts[name] = value;
82 end
83 else
84 return function () end;
85 end
86 end
88 function GetOption(name)
89 return opts[name:gsub('%-', '_')];
90 end
92 function Message(message)
93 if not opts.quiet then
94 print_info(message);
95 end
96 end
98 function Error(message)
99 if not opts.very_quiet then
100 print_err(message);
104 function Exit()
105 os.exit(1);
107 -- -- -- -- -- -- -- --- -- -- -- -- -- -- -- --
109 squishy_file = squishy_file or "./squishy"
110 base_path = string.match(squishy_file, "(.*/)") or "./"
111 out_fn = opts.output;
113 local ok, err = pcall(dofile, squishy_file);
115 if not ok then
116 print_err("Couldn't read squishy file: "..err);
117 os.exit(1);
120 if not out_fn then
121 print_err("No output file specified by user or squishy file");
122 os.exit(1);
123 elseif #main_files == 0 and #modules == 0 and #resources == 0 then
124 print_err("No files, modules or resources. Not going to generate an empty file.");
125 os.exit(1);
128 local fetch = {};
129 function fetch.filesystem(path)
130 local f, err = io.open(path);
131 if not f then return false, err; end
133 local data = f:read("*a");
134 f:close();
136 return data;
139 if opts.use_http then
140 function fetch.http(url)
141 local http = require "socket.http";
143 local body, status = http.request(url);
144 if status == 200 then
145 return body;
147 return false, "HTTP status code: "..tostring(status);
149 else
150 function fetch.http(url)
151 return false, "Module not found. Re-squish with --use-http option to fetch it from "..url;
155 print_verbose("Resolving modules...");
157 local LUA_DIRSEP = package.config:sub(1,1);
158 local LUA_PATH_MARK = package.config:sub(5,5);
160 local package_path = package.path:gsub("[^;]+", function (path)
161 if not path:match("^%"..LUA_DIRSEP) then
162 return base_path..path;
164 end):gsub("/%./", "/");
165 local package_cpath = package.cpath:gsub("[^;]+", function (path)
166 if not path:match("^%"..LUA_DIRSEP) then
167 return base_path..path;
169 end):gsub("/%./", "/");
171 function resolve_module(name, path)
172 name = name:gsub("%.", LUA_DIRSEP);
173 for c in path:gmatch("[^;]+") do
174 c = c:gsub("%"..LUA_PATH_MARK, name);
175 print_debug("Looking for "..c)
176 local f = io.open(c);
177 if f then
178 print_debug("Found!");
179 f:close();
180 return c;
183 return nil; -- not found
186 for i, module in ipairs(modules) do
187 if not module.path then
188 module.path = resolve_module(module.name, package_path);
189 if not module.path then
190 print_err("Couldn't resolve module: "..module.name);
191 else
192 -- Strip base_path from resolved path
193 module.path = module.path:gsub("^"..base_path:gsub("%p", "%%%1"), "");
199 for _, module in ipairs(modules) do
200 if not module.path then
201 print_err("Exiting due to missing modules without a path");
202 os.exit(1);
206 if opts.list_files or opts.list_missing_files then
207 local function write(filename)
208 if opts.list_missing_files then
209 local f = io.open(filename);
210 if f then
211 f:close();
212 return;
213 end
215 io.write(filename, "\n");
217 for _, fn in pairs(main_files) do
218 write(fn);
220 for _, module in ipairs(modules) do
221 write(module.path);
223 for _, resource in ipairs(resources) do
224 write(resource.path);
226 return;
229 print_info("Writing "..out_fn.."...");
230 local f, err = io.open(out_fn, "w+");
231 if not f then
232 print_err("Couldn't open output file: "..tostring(err));
233 os.exit(1);
236 if opts.executable then
237 if opts.executable == true then
238 f:write("#!/usr/bin/env lua\n");
239 else
240 f:write(opts.executable, "\n");
244 print_verbose("Packing modules...");
245 for _, module in ipairs(modules) do
246 local modulename, path = module.name, module.path;
247 if module.path:sub(1,1) ~= "/" then
248 path = base_path..module.path;
250 print_debug("Packing "..modulename.." ("..path..")...");
251 local data, err = fetch.filesystem(path);
252 if (not data) and module.url then
253 local url = module.url:gsub("%?", module.path);
254 print_debug("Fetching: ".. url)
255 if url:match("^https?://") then
256 data, err = fetch.http(url);
257 elseif url:match("^file://") or url:match("^[/%.]") then
258 local dataf, dataerr = io.open((url:gsub("^file://", "")));
259 if dataf then
260 data, err = dataf:read("*a");
261 dataf:close();
262 else
263 data, err = nil, dataerr;
267 if data then
268 data = data:gsub("^#[^\r\n]*\r?\n", ""); -- Remove shebang if any (or we can't concat)
269 if not opts.debug then
270 f:write("package.preload['", modulename, "'] = (function (...)\n");
271 f:write(data);
272 f:write(" end)\n");
273 else
274 f:write("package.preload['", modulename, "'] = assert(loadstring(\n");
275 f:write(("%q\n"):format(data));
276 f:write(", ", ("%q"):format("@"..path), "))\n");
278 else
279 print_err("Couldn't pack module '"..modulename.."': "..(err or "unknown error... path to module file correct?"));
280 os.exit(1);
284 if #resources > 0 then
285 print_verbose("Packing resources...")
286 f:write("do local resources = {};\n");
287 for _, resource in ipairs(resources) do
288 local name, path = resource.name, resource.path;
289 local res_file, err = io.open(base_path..path, "rb");
290 if not res_file then
291 print_err("Couldn't load resource: "..tostring(err));
292 os.exit(1);
294 local data = res_file:read("*a");
295 local maxequals = 0;
296 data:gsub("(=+)", function (equals_string) maxequals = math.max(maxequals, #equals_string); end);
298 f:write(("resources[%q] = %q"):format(name, data));
299 --[[ f:write(("resources[%q] = ["):format(name), string.rep("=", maxequals+1), "[");
300 f:write(data);
301 f:write("]", string.rep("=", maxequals+1), "];"); ]]
303 if opts.virtual_io then
304 local vio = require_resource("vio");
305 if not vio then
306 print_err("Virtual IO requested but is not enabled in this build of squish");
307 else
308 -- Insert vio library
309 f:write(vio, "\n")
310 -- Override standard functions to use vio if opening a resource
311 f:write[[local io_open, io_lines = io.open, io.lines; function io.open(fn, mode)
312 if not resources[fn] then
313 return io_open(fn, mode);
314 else
315 return vio.open(resources[fn]);
316 end end
317 function io.lines(fn)
318 if not resources[fn] then
319 return io_lines(fn);
320 else
321 return vio.open(resources[fn]):lines()
322 end end
323 local _dofile = dofile;
324 function dofile(fn)
325 if not resources[fn] then
326 return _dofile(fn);
327 else
328 return assert(loadstring(resources[fn]))();
329 end end
330 local _loadfile = loadfile;
331 function loadfile(fn)
332 if not resources[fn] then
333 return _loadfile(fn);
334 else
335 return loadstring(resources[fn], "@"..fn);
336 end end ]]
339 f:write[[function require_resource(name) return resources[name] or error("resource '"..tostring(name).."' not found"); end end ]]
342 print_debug("Finalising...")
343 for _, fn in pairs(main_files) do
344 local fin, err = io.open(base_path..fn);
345 if not fin then
346 print_err("Failed to open "..fn..": "..err);
347 os.exit(1);
348 else
349 f:write((fin:read("*a"):gsub("^#.-\n", "")));
350 fin:close();
354 f:close();
356 print_info("OK!");