| jerome@65 | 1 local M, assert, getenv = {}, assert, os.getenv | 
| jerome@65 | 2 | 
| jerome@65 | 3 local function maybe(m) | 
| jerome@65 | 4 	local ok, v = pcall(require, m) | 
| jerome@65 | 5 	return ok and v | 
| jerome@65 | 6 end | 
| jerome@65 | 7 local lfs = maybe("lfs") -- LuaFileSystem; http://keplerproject.github.io/luafilesystem/ | 
| jerome@65 | 8 local zlib = maybe("zlib") -- lzlib; https://github.com/LuaDist/lzlib | 
| jerome@65 | 9 local bit = maybe("bit") -- Lua BitOp; http://bitop.luajit.org | 
| jerome@65 | 10 local socket = maybe("socket.http") -- LuaSocket; http://w3.impa.br/~diego/software/luasocket/home.html | 
| jerome@65 | 11 | 
| jerome@65 | 12 local function shellEscape(s) | 
| jerome@65 | 13 	return '"' .. s:gsub('"', '"\\\\""') .. '"' | 
| jerome@65 | 14 end | 
| jerome@65 | 15 local function readAndDeleteFile(path) | 
| jerome@65 | 16 	local h, err = io.open(path, "rb") | 
| jerome@65 | 17 	if h then | 
| jerome@65 | 18 		local c = h:read("*a") | 
| jerome@65 | 19 		h:close() | 
| jerome@65 | 20 		h, err = c, nil | 
| jerome@65 | 21 	end | 
| jerome@65 | 22 	os.remove(path) | 
| jerome@65 | 23 	return h, err | 
| jerome@65 | 24 end | 
| jerome@65 | 25 | 
| jerome@65 | 26 local dir_sep = package and package.config and package.config:sub(1,1) or "/" | 
| jerome@65 | 27 do -- M.path(a, b, ...) | 
| jerome@65 | 28 	M.path = function(a, b, ...) | 
| jerome@65 | 29 		if a and b then | 
| jerome@65 | 30 			return M.path(a .. (a:sub(-1) ~= dir_sep and dir_sep or "") .. b, ...) | 
| jerome@65 | 31 		end | 
| jerome@65 | 32 		return a | 
| jerome@65 | 33 	end | 
| jerome@65 | 34 end | 
| jerome@65 | 35 M.url = function(a, b, ...) | 
| jerome@65 | 36 	if a and b then | 
| jerome@65 | 37 		return M.url(a .. ((a:sub(-1) == "/" or b:sub(1,1) == "/") and "" or "/") .. b, ...) | 
| jerome@65 | 38 	end | 
| jerome@65 | 39 	return a | 
| jerome@65 | 40 end | 
| jerome@65 | 41 | 
| jerome@65 | 42 M.commands = | 
| jerome@65 | 43 	dir_sep == '/' and {toDevNull=' 2>/dev/null', ls='ls %s', mkdir='mkdir -p %s', gzip='gzip -dcq %s'} or | 
| jerome@65 | 44 	dir_sep == '\\' and {toDevNull=' 2>NUL', ls='(for %%a in (%s) do @echo %%~fa)', mkdir='mkdir %s', gzip='gzip -dcq %s', TMP=os.getenv('TMP') or os.getenv('TEMP')} | 
| jerome@65 | 45 | 
| jerome@65 | 46 M.tmpname = function() | 
| jerome@65 | 47 	local tn = os.tmpname() | 
| jerome@65 | 48 	return (M.commands and M.commands.TMP or "") .. tn | 
| jerome@65 | 49 end | 
| jerome@65 | 50 | 
| jerome@65 | 51 M.decompress = zlib and zlib.decompress or function(compressed) | 
| jerome@65 | 52 	assert(type(compressed) == "string", 'Syntax: casc.platform.decompress("compressed")') | 
| jerome@65 | 53 	assert(M.commands and M.commands.gzip and M.commands.toDevNull, 'unsupported platform') | 
| jerome@65 | 54 | 
| jerome@65 | 55 	local f, f2 = M.tmpname(), M.tmpname() | 
| jerome@65 | 56 	local h = io.open(f, "wb") | 
| jerome@65 | 57 	h:write('\31\139\8\0\0\0\0\0') | 
| jerome@65 | 58 	h:write(compressed) | 
| jerome@65 | 59 	h:close() | 
| jerome@65 | 60 | 
| jerome@65 | 61 	os.execute(M.commands.gzip:format(shellEscape(f)) .. " 1>" .. f2 .. " " .. M.commands.toDevNull) | 
| jerome@65 | 62 	os.remove(f) | 
| jerome@65 | 63 | 
| jerome@65 | 64 	return readAndDeleteFile(f2) | 
| jerome@65 | 65 end | 
| jerome@65 | 66 | 
| jerome@65 | 67 M.mkdir = lfs and lfs.mkdir or function(path) | 
| jerome@65 | 68 	assert(type(path) == 'string', 'Syntax: casc.platform.mkdir("path")') | 
| jerome@65 | 69 	assert(M.commands and M.commands.mkdir, 'unsupported platform') | 
| jerome@65 | 70 | 
| jerome@65 | 71 	return os.execute(M.commands.mkdir:format(shellEscape(path))) | 
| jerome@65 | 72 end | 
| jerome@65 | 73 | 
| jerome@65 | 74 M.files = lfs and function(dir, glob) | 
| jerome@65 | 75 	assert(type(dir) == "string" and type(glob) == 'string', 'Syntax: casc.platform.files("dir", "glob")') | 
| jerome@65 | 76 	local pat = "^" .. glob:gsub("%.%-%+", "%%%0"):gsub("%*", ".*") .. "$" | 
| jerome@65 | 77 	local t, ni = {}, 1 | 
| jerome@65 | 78 	for f in lfs.dir(dir) do | 
| jerome@65 | 79 		if f ~= "." and f ~= ".." and f:match(pat) then | 
| jerome@65 | 80 			t[ni], ni = M.path(dir, f), ni + 1 | 
| jerome@65 | 81 		end | 
| jerome@65 | 82 	end | 
| jerome@65 | 83 	return pairs(t) | 
| jerome@65 | 84 end or function(dir, glob) | 
| jerome@65 | 85 	assert(type(dir) == "string" and type(glob) == 'string', 'Syntax: casc.platform.files("dir", "glob")') | 
| jerome@65 | 86 	assert(M.commands and M.commands.ls, 'unsupported platform') | 
| jerome@65 | 87 | 
| jerome@65 | 88 	local dir, files = glob:match("^(.-)([^" .. dir_sep .. "]+)$") | 
| jerome@65 | 89 	local t, ni, h = {}, 1, io.popen(M.commands.ls:format(shellEscape(dir) .. files), "r") | 
| jerome@65 | 90 	for l in h:lines() do | 
| jerome@65 | 91 		t[ni], ni = l, ni + 1 | 
| jerome@65 | 92 	end | 
| jerome@65 | 93 	h:close() | 
| jerome@65 | 94 	return pairs(t) | 
| jerome@65 | 95 end | 
| jerome@65 | 96 | 
| jerome@65 | 97 local floor = math.floor | 
| jerome@65 | 98 M.rol = bit and bit.rol or function(n, b) | 
| jerome@65 | 99 	local n, e2 = n % 2^32, 2^(32-b) | 
| jerome@65 | 100 	return ((n % e2) * 2^b + floor(n/e2)) % 2^32 | 
| jerome@65 | 101 end | 
| jerome@65 | 102 | 
| jerome@65 | 103 M.bxor = bit and bit.bxor or function(a, b) | 
| jerome@65 | 104    local out, m, lm = 0, 1 | 
| jerome@65 | 105 	for i=1,32 do | 
| jerome@65 | 106 		m, lm = m+m, m | 
| jerome@65 | 107 		local am, bm = a % m, b % m | 
| jerome@65 | 108 		out, a, b = out + (am == bm and 0 or lm), a-am, b-bm | 
| jerome@65 | 109 		if a == 0 or b == 0 then | 
| jerome@65 | 110 			return (out + b + a) % 2^32 | 
| jerome@65 | 111 		end | 
| jerome@65 | 112 	end | 
| jerome@65 | 113 	return out | 
| jerome@65 | 114 end | 
| jerome@65 | 115 | 
| jerome@65 | 116 if socket then | 
| jerome@65 | 117 	socket.USERAGENT, socket.TIMEOUT = "lcasc/1.1", 5 | 
| jerome@65 | 118 	local ltn12, RETRIES = require("ltn12"), 3 | 
| jerome@65 | 119 	M.http = function(url, h) | 
| jerome@65 | 120 		for i=1,RETRIES do | 
| jerome@65 | 121 			local sink = {} | 
| jerome@65 | 122 			local ok, status, head = socket.request({url=url, sink=ltn12.sink.table(sink), headers=h}) | 
| jerome@65 | 123 			if ok then | 
| jerome@65 | 124 				local cnt = table.concat(sink, "") | 
| jerome@65 | 125 				return status >= 200 and status < 300 and cnt, status, head, cnt | 
| jerome@65 | 126 			elseif i == RETRIES then | 
| jerome@65 | 127 				error("HTTP request failed: " .. tostring(status) .. "\nURL: " .. tostring(url)) | 
| jerome@65 | 128 			end | 
| jerome@65 | 129 		end | 
| jerome@65 | 130 	end | 
| jerome@65 | 131 else | 
| jerome@65 | 132 	M.http = function(url, h) | 
| jerome@65 | 133 		local c, of = "curl -s -S -A 'luacasc/1.1+curl'", M.tmpname() | 
| jerome@65 | 134 		if type(h) == "table" then | 
| jerome@65 | 135 			for k,v in pairs(h) do | 
| jerome@65 | 136 				c = c .. ' -H ' .. shellEscape(k .. ": " .. v) | 
| jerome@65 | 137 			end | 
| jerome@65 | 138 		end | 
| jerome@65 | 139 		c = c .. ' -o ' .. shellEscape(of) | 
| jerome@65 | 140 		c = c .. ' ' .. shellEscape(url) | 
| jerome@65 | 141 		if os.execute(c) == 0 then | 
| jerome@65 | 142 			return readAndDeleteFile(of) | 
| jerome@65 | 143 		end | 
| jerome@65 | 144 		os.remove(of) | 
| jerome@65 | 145 	end | 
| jerome@65 | 146 end | 
| jerome@65 | 147 | 
| jerome@65 | 148 return M |