diff --git a/extras/hammerlua/hammer.lua b/extras/hammerlua/hammer.lua new file mode 100644 index 0000000..4a744b4 --- /dev/null +++ b/extras/hammerlua/hammer.lua @@ -0,0 +1,88 @@ +local _M = {} +local w = require'winapi' +local prevw + +do + local prevp,prevp_w + function _M.process() + if prevp_w ~= prevw then + prevp_w = prevw + if prevp then prevp:close() end + prevp = prevw and prevw:get_process() + end + return prevp + end +end + +-- returns hammer only if it is ontop +local function gethammer() + if prevw then + local proc = prevw:get_process() + local pid = proc:get_pid() + proc:close() + if pid~= 1 then return prevw end + end + + if prevw then + print("hammer died") + prevw = nil + end + + local hammer = winapi.get_foreground_window() + local proc = hammer and hammer:get_process() + local procname = proc and proc:get_process_name() + + if proc then + proc:close() + end + + if procname ~= "hammer.exe" then + + + return nil, "not hammer" + end + + for i = 1, 10 do + local newhammer = hammer:get_parent() + + if not newhammer or not newhammer:get_text() then + + + break + end + + print("parent", newhammer:get_text()) + + hammer = newhammer + end + + prevw = hammer + print("Found hammer", hammer) + + return hammer +end + +local function inhammer() + if not prevw or not prevw:is_visible() then return false end + local fg = winapi.get_foreground_window() + local isok = fg == prevw + if isok then return true end + + return false, fg +end + +local function getmap(hammer) + local txt = hammer:get_text() + txt, n = txt:gsub('^Hammer %- %[', '') + if n ~= 1 then return end + txt, n = txt:gsub('(%.vmf) %- [^%-]+%]$', '%1') + if n ~= 1 then return end + + return txt +end + +_M.map = getmap +_M.ontop = inhammer +_M.get = gethammer +_M.process = getproc +return _M \ No newline at end of file diff --git a/extras/hammerlua/lua/inspect.lua b/extras/hammerlua/lua/inspect.lua new file mode 100644 index 0000000..ae5b430 --- /dev/null +++ b/extras/hammerlua/lua/inspect.lua @@ -0,0 +1,341 @@ +local inspect ={ + _VERSION = 'inspect.lua 3.1.0', + _URL = 'http://github.com/kikito/inspect.lua', + _DESCRIPTION = 'human-readable representations of tables', + _LICENSE = [[ + MIT LICENSE + + Copyright (c) 2013 Enrique García Cota + + 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 NONINFRINGEMENT. + 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. + ]] +} + +local tostring = tostring + +inspect.KEY = setmetatable({}, {__tostring = function() return 'inspect.KEY' end}) +inspect.METATABLE = setmetatable({}, {__tostring = function() return 'inspect.METATABLE' end}) + +-- Apostrophizes the string if it has quotes, but not aphostrophes +-- Otherwise, it returns a regular quoted string +local function smartQuote(str) + if str:match('"') and not str:match("'") then + return "'" .. str .. "'" + end + return '"' .. str:gsub('"', '\\"') .. '"' +end + +-- \a => '\\a', \0 => '\\0', 31 => '\31' +local shortControlCharEscapes = { + ["\a"] = "\\a", ["\b"] = "\\b", ["\f"] = "\\f", ["\n"] = "\\n", + ["\r"] = "\\r", ["\t"] = "\\t", ["\v"] = "\\v" +} +local longControlCharEscapes = {} -- \a => nil, \0 => \000, 31 => \031 +for i=0, 31 do + local ch = string.char(i) + if not shortControlCharEscapes[ch] then + shortControlCharEscapes[ch] = "\\"..i + longControlCharEscapes[ch] = string.format("\\%03d", i) + end +end + +local function escape(str) + return (str:gsub("\\", "\\\\") + :gsub("(%c)%f[0-9]", longControlCharEscapes) + :gsub("%c", shortControlCharEscapes)) +end + +local function isIdentifier(str) + return type(str) == 'string' and str:match( "^[_%a][_%a%d]*$" ) +end + +local function isSequenceKey(k, sequenceLength) + return type(k) == 'number' + and 1 <= k + and k <= sequenceLength + and math.floor(k) == k +end + +local defaultTypeOrders = { + ['number'] = 1, ['boolean'] = 2, ['string'] = 3, ['table'] = 4, + ['function'] = 5, ['userdata'] = 6, ['thread'] = 7 +} + +local function sortKeys(a, b) + local ta, tb = type(a), type(b) + + -- strings and numbers are sorted numerically/alphabetically + if ta == tb and (ta == 'string' or ta == 'number') then return a < b end + + local dta, dtb = defaultTypeOrders[ta], defaultTypeOrders[tb] + -- Two default types are compared according to the defaultTypeOrders table + if dta and dtb then return defaultTypeOrders[ta] < defaultTypeOrders[tb] + elseif dta then return true -- default types before custom ones + elseif dtb then return false -- custom types after default ones + end + + -- custom types are sorted out alphabetically + return ta < tb +end + +-- For implementation reasons, the behavior of rawlen & # is "undefined" when +-- tables aren't pure sequences. So we implement our own # operator. +local function getSequenceLength(t) + local len = 1 + local v = rawget(t,len) + while v ~= nil do + len = len + 1 + v = rawget(t,len) + end + return len - 1 +end + +local function getNonSequentialKeys(t) + local keys = {} + local sequenceLength = getSequenceLength(t) + for k,_ in pairs(t) do + if not isSequenceKey(k, sequenceLength) then table.insert(keys, k) end + end + table.sort(keys, sortKeys) + return keys, sequenceLength +end + +local function getToStringResultSafely(t, mt) + local __tostring = type(mt) == 'table' and rawget(mt, '__tostring') + local str, ok + if type(__tostring) == 'function' then + ok, str = pcall(__tostring, t) + str = ok and str or 'error: ' .. tostring(str) + end + if type(str) == 'string' and #str > 0 then return str end +end + +local function countTableAppearances(t, tableAppearances) + tableAppearances = tableAppearances or {} + + if type(t) == 'table' then + if not tableAppearances[t] then + tableAppearances[t] = 1 + for k,v in pairs(t) do + countTableAppearances(k, tableAppearances) + countTableAppearances(v, tableAppearances) + end + countTableAppearances(getmetatable(t), tableAppearances) + else + tableAppearances[t] = tableAppearances[t] + 1 + end + end + + return tableAppearances +end + +local copySequence = function(s) + local copy, len = {}, #s + for i=1, len do copy[i] = s[i] end + return copy, len +end + +local function makePath(path, ...) + local keys = {...} + local newPath, len = copySequence(path) + for i=1, #keys do + newPath[len + i] = keys[i] + end + return newPath +end + +local function processRecursive(process, item, path, visited) + + if item == nil then return nil end + if visited[item] then return visited[item] end + + local processed = process(item, path) + if type(processed) == 'table' then + local processedCopy = {} + visited[item] = processedCopy + local processedKey + + for k,v in pairs(processed) do + processedKey = processRecursive(process, k, makePath(path, k, inspect.KEY), visited) + if processedKey ~= nil then + processedCopy[processedKey] = processRecursive(process, v, makePath(path, processedKey), visited) + end + end + + local mt = processRecursive(process, getmetatable(processed), makePath(path, inspect.METATABLE), visited) + setmetatable(processedCopy, mt) + processed = processedCopy + end + return processed +end + + + +------------------------------------------------------------------- + +local Inspector = {} +local Inspector_mt = {__index = Inspector} + +function Inspector:puts(...) + local args = {...} + local buffer = self.buffer + local len = #buffer + for i=1, #args do + len = len + 1 + buffer[len] = args[i] + end +end + +function Inspector:down(f) + self.level = self.level + 1 + f() + self.level = self.level - 1 +end + +function Inspector:tabify() + self:puts(self.newline, string.rep(self.indent, self.level)) +end + +function Inspector:alreadyVisited(v) + return self.ids[v] ~= nil +end + +function Inspector:getId(v) + local id = self.ids[v] + if not id then + local tv = type(v) + id = (self.maxIds[tv] or 0) + 1 + self.maxIds[tv] = id + self.ids[v] = id + end + return tostring(id) +end + +function Inspector:putKey(k) + if isIdentifier(k) then return self:puts(k) end + self:puts("[") + self:putValue(k) + self:puts("]") +end + +function Inspector:putTable(t) + if t == inspect.KEY or t == inspect.METATABLE then + self:puts(tostring(t)) + elseif self:alreadyVisited(t) then + self:puts('') + elseif self.level >= self.depth then + self:puts('{...}') + else + if self.tableAppearances[t] > 1 then self:puts('<', self:getId(t), '>') end + + local nonSequentialKeys, sequenceLength = getNonSequentialKeys(t) + local mt = getmetatable(t) + local toStringResult = getToStringResultSafely(t, mt) + + self:puts('{') + self:down(function() + if toStringResult then + self:puts(' -- ', escape(toStringResult)) + if sequenceLength >= 1 then self:tabify() end + end + + local count = 0 + for i=1, sequenceLength do + if count > 0 then self:puts(',') end + self:puts(' ') + self:putValue(t[i]) + count = count + 1 + end + + for _,k in ipairs(nonSequentialKeys) do + if count > 0 then self:puts(',') end + self:tabify() + self:putKey(k) + self:puts(' = ') + self:putValue(t[k]) + count = count + 1 + end + + if mt then + if count > 0 then self:puts(',') end + self:tabify() + self:puts(' = ') + self:putValue(mt) + end + end) + + if #nonSequentialKeys > 0 or mt then -- result is multi-lined. Justify closing } + self:tabify() + elseif sequenceLength > 0 then -- array tables have one extra space before closing } + self:puts(' ') + end + + self:puts('}') + end +end + +function Inspector:putValue(v) + local tv = type(v) + + if tv == 'string' then + self:puts(smartQuote(escape(v))) + elseif tv == 'number' or tv == 'boolean' or tv == 'nil' then + self:puts(tostring(v)) + elseif tv == 'table' then + self:putTable(v) + else + self:puts('<',tv,' ',self:getId(v),'>') + end +end + +------------------------------------------------------------------- + +function inspect.inspect(root, options) + options = options or {} + + local depth = options.depth or math.huge + local newline = options.newline or '\n' + local indent = options.indent or ' ' + local process = options.process + + if process then + root = processRecursive(process, root, {}, {}) + end + + local inspector = setmetatable({ + depth = depth, + level = 0, + buffer = {}, + ids = {}, + maxIds = {}, + newline = newline, + indent = indent, + tableAppearances = countTableAppearances(root) + }, Inspector_mt) + + inspector:putValue(root) + + return table.concat(inspector.buffer) +end + +setmetatable(inspect, { __call = function(_, ...) return inspect.inspect(...) end }) + +return inspect + diff --git a/extras/hammerlua/lua/path.lua b/extras/hammerlua/lua/path.lua new file mode 100644 index 0000000..6f5fde1 --- /dev/null +++ b/extras/hammerlua/lua/path.lua @@ -0,0 +1,598 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local package = require "package" +local string = require "string" +local table = require "table" +local os = require "os" +local io = require "io" + +local USE_ALIEN = true +local USE_FFI = true +local USE_AFX = true + +local DIR_SEP = package.config:sub(1,1) +local IS_WINDOWS = DIR_SEP == '\\' + +local PATH = {} + +PATH.DIR_SEP = DIR_SEP +PATH.IS_WINDOWS = IS_WINDOWS + +-- +-- PATH manipulation + +function PATH:unquote(P) + if P:sub(1,1) == '"' and P:sub(-1,-1) == '"' then + return (P:sub(2,-2)) + end + return P +end + +function PATH:quote(P) + if P:find("%s") then + return '"' .. P .. '"' + end + return P +end + +function PATH:has_dir_end(P) + return (string.find(P, '[\\/]$')) and true +end + +function PATH:remove_dir_end(P) + return (string.gsub(P, '[\\/]+$', '')) +end + +function PATH:ensure_dir_end(P) + return self:remove_dir_end(P) .. self.DIR_SEP +end + +function PATH:isunc(P) + return (string.sub(P, 1, 2) == (self.DIR_SEP .. self.DIR_SEP)) and P +end + +function PATH:normolize_sep(P) + return (string.gsub(P, '\\', self.DIR_SEP):gsub('/', self.DIR_SEP)) +end + +PATH.normalize_sep = PATH.normolize_sep + +function PATH:normolize(P) + P = self:normolize_sep(P) + local DIR_SEP = self.DIR_SEP + + local is_unc = self:isunc(P) + while true do -- `/./` => `/` + local n P,n = string.gsub(P, DIR_SEP .. '%.' .. DIR_SEP, DIR_SEP) + if n == 0 then break end + end + while true do -- `//` => `/` + local n P,n = string.gsub(P, DIR_SEP .. DIR_SEP, DIR_SEP) + if n == 0 then break end + end + P = string.gsub(P, DIR_SEP .. '%.$', '') + if (not IS_WINDOWS) and (P == '') then P = '/' end + + if is_unc then P = DIR_SEP .. P end + + local root, path = nil, P + if is_unc then + root, path = self:splitroot(P) + end + + path = self:ensure_dir_end(path) + while true do + local first, last = string.find(path, DIR_SEP .. "[^".. DIR_SEP .. "]+" .. DIR_SEP .. '%.%.' .. DIR_SEP) + if not first then break end + path = string.sub(path, 1, first) .. string.sub(path, last+1) + end + P = path + + if root then -- unc + assert(is_unc) + P = P:gsub( '%.%.?' .. DIR_SEP , '') + P = DIR_SEP .. DIR_SEP .. self:join(root, P) + elseif self.IS_WINDOWS then + -- c:\..\foo => c:\foo + -- \..\foo => \foo + local root, path = self:splitroot(P) + if root ~= '' or P:sub(1,1) == DIR_SEP then + path = path:gsub( '%.%.?' .. DIR_SEP , '') + P = self:join(root, path) + end + end + + if self.IS_WINDOWS and #P <= 3 and P:sub(2,2) == ':' then -- c: => c:\ or c:\ => c:\ + if #P == 2 then return P .. self.DIR_SEP end + return P + end + + if (not self.IS_WINDOWS) and (P == DIR_SEP) then return '/' end + return self:remove_dir_end(P) +end + +PATH.normalize = PATH.normolize + +function PATH:join_(P1, P2) + local ch = P2:sub(1,1) + if (ch == '\\') or (ch == '/') then + return self:remove_dir_end(P1) .. P2 + end + return self:ensure_dir_end(P1) .. P2 +end + +function PATH:join(...) + local t,n = {...}, select('#', ...) + local r = t[1] + for i = 2, #t do + if self:isfullpath(t[i]) then + r = t[i] + else + r = self:join_(r,t[i]) + end + end + return r +end + +function PATH:splitext(P) + local s1,s2 = string.match(P,"(.-[^\\/.])(%.[^\\/.]*)$") + if s1 then return s1,s2 end + return P, '' +end + +function PATH:splitpath(P) + return string.match(P,"^(.-)[\\/]?([^\\/]*)$") +end + +function PATH:splitroot(P) + if self.IS_WINDOWS then + if self:isunc(P) then + return string.match(P, [[^\\([^\/]+)[\]?(.*)$]]) + end + if string.sub(P,2,2) == ':' then + return string.sub(P,1,2), string.sub(P,4) + end + return '', P + else + if string.sub(P,1,1) == '/' then + return string.match(P,[[^/([^\/]+)[/]?(.*)$]]) + end + return '', P + end +end + +function PATH:splitdrive(P) + if self.IS_WINDOWS then + return self:splitroot(P) + end + return '', P +end + +function PATH:basename(P) + local s1,s2 = self:splitpath(P) + return s2 +end + +function PATH:dirname(P) + return (self:splitpath(P)) +end + +function PATH:extension(P) + local s1,s2 = self:splitext(P) + return s2 +end + +function PATH:root(P) + return (self:splitroot(P)) +end + +function PATH:isfullpath(P) + return (self:root(P) ~= '') and P +end + +function PATH:user_home() + if IS_WINDOWS then + return os.getenv('USERPROFILE') or PATH:join(os.getenv('HOMEDRIVE'), os.getenv('HOMEPATH')) + end + return os.getenv('HOME') +end + +local function prequire(m) + local ok, err = pcall(require, m) + if not ok then return nil, err end + return err +end + +local fs = prequire "path.fs" + +if fs then + +-- +-- PATH based on system + +local function assert_system(self) + if PATH.IS_WINDOWS then assert(self.IS_WINDOWS) return end + assert(not self.IS_WINDOWS) +end + +if fs.flags then + function PATH:flags(P, ...) + assert_system(self) + P = self:fullpath(P) + return fs.flags(P, ...) + end +end + +function PATH:tmpdir() + assert_system(self) + return self:remove_dir_end(fs.tmpdir()) +end + +function PATH:tmpname() + local P = os.tmpname() + if self:dirname(P) == '' then + P = self:join(self:tmpdir(), P) + end + return P +end + +function PATH:size(P) + assert_system(self) + return fs.size(P) +end + +function PATH:fullpath(P) + if not self:isfullpath(P) then + P = self:normolize_sep(P) + local ch1, ch2 = P:sub(1,1), P:sub(2,2) + if ch1 == '~' then -- ~\temp + P = self:join(self:user_home(), P:sub(2)) + elseif self.IS_WINDOWS and (ch1 == self.DIR_SEP) then -- \temp => c:\temp + local root = self:root(self:currentdir()) + P = self:join(root, P) + else + P = self:join(self:currentdir(), P) + end + end + + return self:normolize(P) +end + +function PATH:attrib(P, ...) + assert_system(self) + return fs.attributes(P, ...) +end + +function PATH:exists(P) + assert_system(self) + return fs.exists(self:fullpath(P)) +end + +function PATH:isdir(P) + assert_system(self) + return fs.isdir(self:fullpath(P)) +end + +function PATH:isfile(P) + assert_system(self) + return fs.isfile(self:fullpath(P)) +end + +function PATH:islink(P) + assert_system(self) + return fs.islink(self:fullpath(P)) +end + +function PATH:ctime(P) + assert_system(self) + return fs.ctime(self:fullpath(P)) +end + +function PATH:mtime(P) + assert_system(self) + return fs.mtime(self:fullpath(P)) +end + +function PATH:atime(P) + assert_system(self) + return fs.atime(self:fullpath(P)) +end + +function PATH:touch(P, ...) + assert_system(self) + return fs.touch(self:fullpath(P), ...) +end + +function PATH:currentdir() + assert_system(self) + return self:normolize(fs.currentdir()) +end + +function PATH:chdir(P) + assert_system(self) + return fs.chdir(self:fullpath(P)) +end + +function PATH:isempty(P) + assert_system(self) + local ok, err = fs.each_impl{ + file = self:ensure_dir_end(P), + callback = function() return 'pass' end; + } + if err then return nil, err end + return ok ~= 'pass' +end + +local date = prequire "date" +if date then + local function make_getfiletime_as_date(fn) + if date then + return function(...) + local t,e = fn(...) + if not t then return nil, e end + return date(t) + end + end + end + + PATH.cdate = make_getfiletime_as_date( PATH.ctime ); + PATH.mdate = make_getfiletime_as_date( PATH.mtime ); + PATH.adate = make_getfiletime_as_date( PATH.atime ); +end + +function PATH:mkdir(P) + assert_system(self) + local P = self:fullpath(P) + if self:exists(P) then return self:isdir(P) end + local p = '' + P = self:ensure_dir_end(P) + for str in string.gmatch(P, '.-' .. self.DIR_SEP) do + p = p .. str + if self:exists(p) then + if not self:isdir(p) then + return nil, 'can not create ' .. p + end + else + if IS_WINDOWS or p ~= DIR_SEP then + local ok, err = fs.mkdir(self:remove_dir_end(p)) + if not ok then return nil, err .. ' ' .. p end + end + end + end + return P +end + +function PATH:rmdir(P) + assert_system(self) + return fs.rmdir(self:fullpath(P)) +end + +function PATH:rename(from, to, force) + assert_system(self) + from = self:fullpath(from) + to = self:fullpath(to) + return fs.move(from, to, force) +end + +local each = require "path.findfile".load(function(opt) + local has_dir_end = PATH:has_dir_end(opt.file) + opt.file = PATH:fullpath(opt.file) + if has_dir_end then opt.file = PATH:ensure_dir_end(opt.file) end + return fs.each_impl(opt) +end) + +function PATH:each(...) + assert_system(self) + return each(...) +end + +local function copy_impl_batch(self, fs, src_dir, mask, dst_dir, opt) + if not opt then opt = {} end + + local overwrite = opt.overwrite + local accept = opt.accept + local onerror = opt.error + local chlen = #fs.DIR_SEP + local count = 0 + + local existed_dirs = {} + local ok, err = fs.each_impl{file = src_dir .. fs.DIR_SEP .. mask, + delay = opt.delay; recurse = opt.recurse; param = "pnm"; + skipdirs = opt.skipdirs; skipfiles = opt.skipfiles; + callback = function(path, name, mode) + local rel = string.sub(path, #src_dir + chlen + 1) + if #rel > 0 then rel = rel .. fs.DIR_SEP .. name else rel = name end + local dst = dst_dir .. fs.DIR_SEP .. rel + local src = path .. fs.DIR_SEP .. name + + if accept then + local ok = accept(src, dst, opt) + if not ok then return end + end + + local ok, err = true + if mode == "directory" then + if not existed_dirs[dst] then + if not fs.isdir(dst) then + ok, err = self:mkdir(dst) + end + existed_dirs[dst] = true + end + else + local dir = self:splitpath(dst) + if not existed_dirs[dir] then + if not fs.isdir(dst) then + ok, err = self:mkdir(dir) + end + existed_dirs[dir] = true + end + if ok then + ok, err = fs.copy(src, dst, not overwrite) + end + end + + if not ok and onerror then + if not onerror(err, src, dst, opt) then -- break + return true + end + else + count = count + 1 + end + end; + } + if ok or err then return ok, err end + return count +end + +local function remove_impl_batch(fs, src_dir, mask, opt) + if not opt then opt = {} end + + local overwrite = opt.overwrite + local accept = opt.accept + local onerror = opt.error + local chlen = #fs.DIR_SEP + local count = 0 + local delay = (opt.delay == nil) and true or opt.delay + + local ok, err = fs.each_impl{file = src_dir .. fs.DIR_SEP .. mask, + delay = delay; recurse = opt.recurse; reverse = true; param = "fm"; + skipdirs = opt.skipdirs; skipfiles = opt.skipfiles; + callback = function(src, mode) + if accept then + local ok = accept(src, opt) + if not ok then return end + end + + local ok, err + if mode == "directory" then ok, err = fs.rmdir(src) + else ok, err = fs.remove(src) end + + if not ok and onerror then + if not onerror(err, src, opt) then -- break + return true + end + else + count = count + 1 + end + end; + } + if ok or err then return ok, err end + return count +end + +function PATH:remove_impl(P) + if self:isdir(P) then return fs.rmdir(P) end + return fs.remove(P) +end + +function PATH:copy(from, to, opt) + from = self:fullpath(from) + to = self:fullpath(to) + + if type(opt) == "boolean" then opt = {overwrite = opt} end + + local overwrite = opt and opt.overwrite + local recurse = opt and opt.recurse + + local src_dir, src_name = self:splitpath(from) + if recurse or src_name:find("[*?]") then -- batch mode + return copy_impl_batch(self, fs, src_dir, src_name, to, opt) + end + if self.mkdir then self:mkdir(self:dirname(to)) end + return fs.copy(from, to, not not overwrite) +end + +function PATH:remove(P, opt) + assert_system(self) + local P = self:fullpath(P) + local dir, name = self:splitpath(P) + if (opt and opt.recurse) or name:find("[*?]") then -- batch mode + return remove_impl_batch(fs, dir, name, opt) + end + return self:remove_impl(P) +end + +end -- fs + +do -- Python aliases +PATH.split = PATH.splitpath +PATH.isabs = PATH.isfullpath +PATH.normpath = PATH.normolize +PATH.abspath = PATH.fullpath +PATH.getctime = PATH.ctime +PATH.getatime = PATH.atime +PATH.getmtime = PATH.mtime +PATH.getsize = PATH.size +end + +local function path_new(o) + o = o or {} + for k, f in pairs(PATH) do + if type(f) == 'function' then + o[k] = function(...) + if o == ... then return f(...) end + return f(o, ...) + end + else + o[k] = f + end + end + return o +end + +local function lock_table(t) + return setmetatable(t,{ + __newindex = function() + error("Can not change path library", 2) + end; + __metatable = "lua-path object"; + }) +end + +local M = path_new(require "path.module") + +local path_cache = setmetatable({}, {__mode='v'}) + +function M.new(DIR_SEP) + local is_win, sep + + if type(DIR_SEP) == 'string' then + sep = DIR_SEP + is_win = (DIR_SEP == '\\') + elseif DIR_SEP ~= nil then + assert(type(DIR_SEP) == 'boolean') + is_win = DIR_SEP + sep = is_win and '\\' or '/' + else + sep = M.DIR_SEP + is_win = M.IS_WINDOWS + end + + if M.DIR_SEP == sep then + assert(M.IS_WINDOWS == is_win) + return M + end + + local o = path_cache[sep] + + if not o then + o = path_new() + o.DIR_SEP = sep + o.IS_WINDOWS = is_win + path_cache[sep] = lock_table(o) + end + + return o +end + +return lock_table(M) diff --git a/extras/hammerlua/lua/path/findfile.lua b/extras/hammerlua/lua/path/findfile.lua new file mode 100644 index 0000000..54c7334 --- /dev/null +++ b/extras/hammerlua/lua/path/findfile.lua @@ -0,0 +1,81 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +--- +-- Implementation of afx.findfile + +local string = require "string" +local table = require "table" +local coroutine = require "coroutine" +local PATH = require "path.module" + +local function load(findfile_t) + + local function clone(t) local o = {} for k,v in pairs(t) do o[k] = v end return o end + + local function findfile_ssf(str_file, str_params, func_callback, tbl_option) + tbl_option = tbl_option and clone(tbl_option) or {} + tbl_option.file = assert(str_file) + tbl_option.param = assert(str_params) + tbl_option.callback = assert(func_callback) + return findfile_t(tbl_option) + end + + local function findfile_ss(str_file, str_params, tbl_option) + tbl_option = tbl_option and clone(tbl_option) or {} + tbl_option.file = assert(str_file) + tbl_option.param = assert(str_params) + return findfile_t(tbl_option) + end + + local function findfile_sf(str_file, func_callback, tbl_option) + tbl_option = tbl_option and clone(tbl_option) or {} + tbl_option.file = assert(str_file) + tbl_option.callback = assert(func_callback) + return findfile_t(tbl_option) + end + + local function findfile_s(str_file, tbl_option) + tbl_option = tbl_option and clone(tbl_option) or {} + tbl_option.file = assert(str_file) + return findfile_t(tbl_option) + end + + local function findfile_f(func_callback, tbl_option) + tbl_option = clone(assert(tbl_option)) -- need file + tbl_option.callback = assert(func_callback) + return findfile_t(tbl_option) + end + + local function findfile(p1,p2,p3,p4) + if type(p1) == 'string' then + if type(p2) == 'string' then + if type(p3) == 'function' then + return findfile_ssf(p1,p2,p3,p4) + end + return findfile_ss(p1,p2,p3) + end + if type(p2) == 'function' then + return findfile_sf(p1,p2,p3) + end + return findfile_s(p1,p2) + end + if type(p1) == 'function' then + return findfile_f(p1,p2) + end + return findfile_t(p1) + end + + return findfile +end + +return {load = load} \ No newline at end of file diff --git a/extras/hammerlua/lua/path/fs.lua b/extras/hammerlua/lua/path/fs.lua new file mode 100644 index 0000000..f968205 --- /dev/null +++ b/extras/hammerlua/lua/path/fs.lua @@ -0,0 +1,41 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local DIR_SEP = package.config:sub(1,1) +local IS_WINDOWS = DIR_SEP == '\\' + +local function prequire(m) + local ok, err = pcall(require, m) + if not ok then return nil, err end + return err +end + +local fs + +if not fs and IS_WINDOWS then + local fsload = require"path.win32.fs".load + local ok, mod = pcall(fsload, "ffi", "A") + if not ok then ok, mod = pcall(fsload, "alien", "A") end + fs = ok and mod +end + +if not fs and not IS_WINDOWS then + fs = prequire"path.syscall.fs" +end + +if not fs then + fs = prequire"path.lfs.fs" +end + +assert(fs, "you need installed LuaFileSystem or FFI/Alien (Windows only)") + +return fs diff --git a/extras/hammerlua/lua/path/lfs/fs.lua b/extras/hammerlua/lua/path/lfs/fs.lua new file mode 100644 index 0000000..a63b4ab --- /dev/null +++ b/extras/hammerlua/lua/path/lfs/fs.lua @@ -0,0 +1,14 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local lfs = require"lfs" +return require"path.lfs.impl.fs"(lfs) diff --git a/extras/hammerlua/lua/path/lfs/impl/fs.lua b/extras/hammerlua/lua/path/lfs/impl/fs.lua new file mode 100644 index 0000000..b80b683 --- /dev/null +++ b/extras/hammerlua/lua/path/lfs/impl/fs.lua @@ -0,0 +1,381 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +return function(lfs) +local os = require "os" + +local DIR_SEP = package.config:sub(1,1) +local IS_WINDOWS = DIR_SEP == '\\' + +local _M = { + DIR_SEP = DIR_SEP; +} + +_M.currentdir = lfs.currentdir + +_M.attributes = lfs.attributes + +-- function _M.flags(P) end + +local attrib = lfs.attributes + +function _M.ctime(P) return attrib(P,'change') end + +function _M.atime(P) return attrib(P,'access') end + +function _M.mtime(P) return attrib(P,'modification') end + +function _M.size(P) return attrib(P,'size') end + +function _M.exists(P) return attrib(P,'mode') ~= nil and P end + +function _M.isdir(P) return attrib(P,'mode') == 'directory' and P end + +function _M.isfile(P) return attrib(P,'mode') == 'file' and P end + +function _M.islink(P) return attrib(P,'mode') == 'link' and P end + +_M.mkdir = lfs.mkdir + +_M.rmdir = lfs.rmdir + +_M.chdir = lfs.chdir + +_M.link = lfs.link + +_M.setmode = lfs.setmode + +function _M.copy(src, dst, force) + if not IS_WINDOWS then + if _M.isdir(src) or _M.isdir(dst) then + return nil, 'can not copy directories' + end + end + local f, err = io.open(src, 'rb') + if not f then return nil, err end + + if not force then + local t, err = io.open(dst, 'rb' ) + if t then + f:close() + t:close() + return nil, "file alredy exists" + end + end + + local t, err = io.open(dst, 'w+b') + if not t then + f:close() + return nil, err + end + + local CHUNK_SIZE = 4096 + while true do + local chunk = f:read(CHUNK_SIZE) + if not chunk then break end + local ok, err = t:write(chunk) + if not ok then + t:close() + f:close() + return nil, err or "can not write" + end + end + + t:close() + f:close() + return true +end + +function _M.move(src, dst, flags) + if flags and _M.exists(dst) and _M.exists(src) then + local ok, err = _M.remove(dst) + -- do we have to remove dir? + -- if not ok then ok, err = _M.rmdir(dst) end + if not ok then return nil, err end + end + if (not IS_WINDOWS) and _M.exists(dst) then + -- on windows os.rename return error when dst exists, + -- but on linux its just replace existed file + return nil, "destination alredy exists" + end + return os.rename(src, dst) +end + +function _M.remove(P) + -- on windows os.remove can not remove dir + if (not IS_WINDOWS) and _M.isdir(P) then + return nil, "remove method can not remove dirs" + end + return os.remove(P) +end + +local function splitpath(P) return string.match(P,"^(.-)[\\/]?([^\\/]*)$") end + +function _M.tmpdir() + if IS_WINDOWS then + for _, p in ipairs{'TEMP', 'TMP'} do + local dir = os.getenv(p) + if dir and dir ~= '' then + return dir + end + end + end + return (splitpath(os.tmpname())) +end + +_M.dir = lfs.dir + +_M.touch = lfs.touch + +local function isdots(P) + return P == '.' or P == '..' +end + +local foreach_impl + +local function do_foreach_recurse(base, match, callback, option) + local dir_next, dir = lfs.dir(base) + for name in dir_next, dir do if not isdots(name) then + local path = base .. DIR_SEP .. name + if _M.attributes(path,"mode") == "directory" then + local ret, err = foreach_impl(path, match, callback, option) + if ret or err then + if dir then dir:close() end + return ret, err + end + end + end end +end + +foreach_impl = function(base, match, callback, option) + local tmp, origin_cb + if option.delay then + tmp, origin_cb, callback = {}, callback, function(base,name,fd) + table.insert(tmp, {base,name,fd}) + end; + end + + if option.recurse and option.reverse == true then + local ok, err = do_foreach_recurse(base, match, callback, option) + if ok or err then return ok, err end + end + + local dir_next, dir = lfs.dir(base) + for name in dir_next, dir do if option.skipdots == false or not isdots(name) then + local path = base .. DIR_SEP .. name + local attr = _M.attributes(path) + if not attr then return end + + if (option.skipdirs and attr.mode == "directory") + or (option.skipfiles and attr.mode == "file") + then else + if match(name) then + local ret, err = callback(base, name, attr) + if ret or err then + if dir then dir:close() end + return ret, err + end + end + end + + local can_recurse = (not option.delay) and option.recurse and (option.reverse == nil) + if can_recurse and attr.mode == "directory" and not isdots(name) then + local ret, err = foreach_impl(path, match, callback, option) + if ret or err then + if dir then dir:close() end + return ret, err + end + end + end end + + if option.delay then + for _, t in ipairs(tmp) do + local ok, err = origin_cb(t[1], t[2], t[3]) + if ok or err then return ok, err end + end + end + + if option.recurse and (not option.reverse) then + if option.delay or (option.reverse == false) then + return do_foreach_recurse(base, match, origin_cb or callback, option) + end + end +end + +local function filePat2rexPat(pat) + if pat:find("[*?]") then + local post = '$' + if pat:find("*", 1, true) then + if pat:find(".", 1, true) then post = '[^.]*$' + else post = '' end + end + pat = "^" .. pat:gsub("%.","%%."):gsub("%*",".*"):gsub("%?", ".?") .. post + else + pat = "^" .. pat:gsub("%.","%%.") .. "$" + end + if IS_WINDOWS then pat = pat:upper() end + return pat +end + +local function match_pat(pat) + pat = filePat2rexPat(pat) + return IS_WINDOWS + and function(s) return nil ~= string.find(string.upper(s), pat) end + or function(s) return nil ~= string.find(s, pat) end +end + +function _M.foreach(base, callback, option) + local base, mask = splitpath(base, DIR_SEP) + if mask ~= '' then mask = match_pat(mask) + else mask = function() return true end end + return foreach_impl(base, mask, function(base, name, fd) + return callback(base .. DIR_SEP .. name, fd) + end, option or {}) +end + +local attribs = { + f = function(base, name, fd) return base..DIR_SEP..name end; + p = function(base, name, fd) return base end; + n = function(base, name, fd) return name end; + m = function(base, name, fd) return fd.mode end; + a = function(base, name, fd) return fd end; + z = function(base, name, fd) return fd.size end; + t = function(base, name, fd) return fd.modification end; + c = function(base, name, fd) return fd.change end; + l = function(base, name, fd) return fd.access end; +} + +local function make_attrib(str) + local t = {} + for i = 1, #str do + local ch = str:sub(i,i) + local fn = attribs[ ch ] + if not fn then return nil, 'unknown file attribute: ' .. ch end + table.insert(t, fn) + end + + return function(...) + local res = {n = #t} + for i, f in ipairs(t) do + local ok, err = f(...) + if ok == nil then return nil, err end + table.insert(res, ok) + end + return res + end +end + +function _M.each_impl(option) + if not option.file then return nil, 'no file mask present' end + local base, mask = splitpath( option.file, DIR_SEP ) + if mask ~= '' then mask = match_pat(mask) + else mask = function() return true end end + + local get_params, err = make_attrib(option.param or 'f') + if not get_params then return nil, err end + local unpack = unpack or table.unpack + + local filter = option.filter + + if option.callback then + local callback = option.callback + + local function cb(base, name, fd) + local params = assert(get_params(base, name, fd)) + if filter and (not filter(unpack(params, 1, params.n))) then return end + return callback(unpack(params, 1, params.n)) + end + + return foreach_impl(base, mask, cb, option) + else + local function cb(base, name, fd) + local params = assert(get_params(base, name, fd)) + if filter and (not filter(unpack(params, 1, params.n))) then return end + coroutine.yield(params) + end + local co = coroutine.create(function() + foreach_impl(base, mask, cb, option) + end) + return function() + local status, params = coroutine.resume(co) + if status then if params then return unpack(params, 1, params.n) end + else error(params, 2) end + end + end +end + +local create_each = require "path.findfile".load + +_M.each = create_each(_M.each_impl) + +local function match_pat_selftest() + + local t = { + ["*.txt"] = { + [".txt" ] = true; + ["1.txt" ] = true; + ["1.txtdat" ] = true; + [".txtdat" ] = false; + [".txt.dat" ] = false; + [".dat.txt" ] = true; + }; + ["*.txt*"] = { + [".txt" ] = true; + ["1.txt" ] = true; + ["1.txtdat" ] = true; + [".txtdat" ] = true; + [".txt.dat" ] = true; + [".dat.txt" ] = true; + }; + ["?.txt"] = { + [".txt" ] = true; + ["1.txt" ] = true; + ["1.txtdat" ] = false; + [".txtdat" ] = false; + [".txt.dat" ] = false; + [".dat.txt" ] = false; + }; + ["1?.txt"] = { + [".txt" ] = false; + ["1.txt" ] = true; + ["1.txtdat" ] = false; + [".txtdat" ] = false; + [".txt.dat" ] = false; + [".dat.txt" ] = false; + }; + ["1*.txt"] = { + [".txt" ] = false; + ["1.txt" ] = true; + ["1.txtdat" ] = true; + [".txtdat" ] = false; + [".txt.dat" ] = false; + [".dat.txt" ] = false; + }; + } + + local function test_match(pat, t) + local cmp = match_pat(pat) + for fname, status in pairs(t) do + if status ~= cmp(fname) then + io.write("Pat: ", pat, " Name: ", fname, " Expected: ", tostring(status), " Got: ", tostring(cmp(fname)), "\n") + end + end + end + + for k, v in pairs(t) do + test_match(k,v) + end + +end + +return _M +end \ No newline at end of file diff --git a/extras/hammerlua/lua/path/module.lua b/extras/hammerlua/lua/path/module.lua new file mode 100644 index 0000000..43c4347 --- /dev/null +++ b/extras/hammerlua/lua/path/module.lua @@ -0,0 +1,18 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +return{ + _NAME = "path"; + _VERSION = "0.3.0"; + _COPYRIGHT = "Copyright (C) 2013-2016 Alexey Melnichuk"; + _LICENSE = "MIT"; +} \ No newline at end of file diff --git a/extras/hammerlua/lua/path/syscall/fs.lua b/extras/hammerlua/lua/path/syscall/fs.lua new file mode 100644 index 0000000..3c32e74 --- /dev/null +++ b/extras/hammerlua/lua/path/syscall/fs.lua @@ -0,0 +1,14 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local lfs = require"syscall.lfs" +return require"path.lfs.impl.fs"(lfs) diff --git a/extras/hammerlua/lua/path/win32/alien/fs.lua b/extras/hammerlua/lua/path/win32/alien/fs.lua new file mode 100644 index 0000000..a2fc643 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/alien/fs.lua @@ -0,0 +1,417 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local alien = require "alien" +local autil = require "path.win32.alien.utils" +local types = require "path.win32.alien.types" + +local CTYPES = types.CTYPES +local CTYPE2LUA = types.CTYPE2LUA +local DWORD = CTYPES.DWORD +local HANDLE = CTYPES.HANDLE +local FILETIME = CTYPES.FILETIME +local BOOL = "uint" +local INVALID_HANDLE = types.INVALID_HANDLE +local LPVOID = "pointer" +local LPDWORD = "ref " .. DWORD + +local NULL = nil +local WIN32_FIND_DATAA = CTYPES.WIN32_FIND_DATAA +local WIN32_FIND_DATAW = CTYPES.WIN32_FIND_DATAW + +local kernel32 = assert(alien.load("kernel32.dll")) +local GetCurrentDirectoryA = assert(kernel32.GetCurrentDirectoryA) +local GetCurrentDirectoryW = assert(kernel32.GetCurrentDirectoryW) +local SetCurrentDirectoryA = assert(kernel32.SetCurrentDirectoryA) +local SetCurrentDirectoryW = assert(kernel32.SetCurrentDirectoryW) +local GetTempPathA = assert(kernel32.GetTempPathA) +local GetTempPathW = assert(kernel32.GetTempPathW) +local GetFileAttributesExA = assert(kernel32.GetFileAttributesExA) +local GetFileAttributesExW = assert(kernel32.GetFileAttributesExW) +local CopyFileA = assert(kernel32.CopyFileA) +local CopyFileW = assert(kernel32.CopyFileW) +local GetLastError = assert(kernel32.GetLastError) +local FindFirstFileA = assert(kernel32.FindFirstFileA) +local FindFirstFileW = assert(kernel32.FindFirstFileW) +local FindNextFileA = assert(kernel32.FindNextFileA) +local FindNextFileW = assert(kernel32.FindNextFileW) +local FindClose_ = assert(kernel32.FindClose) +local RemoveDirectoryA = assert(kernel32.RemoveDirectoryA) +local RemoveDirectoryW = assert(kernel32.RemoveDirectoryW) +local CreateDirectoryA = assert(kernel32.CreateDirectoryA) +local CreateDirectoryW = assert(kernel32.CreateDirectoryW) +local MoveFileExA = assert(kernel32.MoveFileExA) +local MoveFileExW = assert(kernel32.MoveFileExW) +local DeleteFileA = assert(kernel32.DeleteFileA) +local DeleteFileW = assert(kernel32.DeleteFileW) +local CreateFileA = assert(kernel32.CreateFileA) +local CreateFileW = assert(kernel32.CreateFileW) +local CloseHandle_ = assert(kernel32.CloseHandle) +local SetFileTime_ = assert(kernel32.SetFileTime) +local FormatMessageA = assert(kernel32.FormatMessageA) +local FormatMessageW = assert(kernel32.FormatMessageW) +local LocalFree = assert(kernel32.LocalFree) +local DeviceIoControl_ = assert(kernel32.DeviceIoControl) + +GetCurrentDirectoryA:types{abi="stdcall", ret = DWORD, DWORD, LPVOID} +GetCurrentDirectoryW:types{abi="stdcall", ret = DWORD, DWORD, LPVOID} + +SetCurrentDirectoryA:types{abi="stdcall", ret = BOOL, LPVOID} +SetCurrentDirectoryW:types{abi="stdcall", ret = BOOL, LPVOID} + +CopyFileA:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID, BOOL} +CopyFileW:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID, BOOL} + +GetTempPathA:types{abi="stdcall", ret = DWORD, DWORD, LPVOID} +GetTempPathW:types{abi="stdcall", ret = DWORD, DWORD, LPVOID} + +GetFileAttributesExA:types{abi="stdcall", ret = DWORD, LPVOID, "int", LPVOID} +GetFileAttributesExW:types{abi="stdcall", ret = DWORD, LPVOID, "int", LPVOID} + +FindFirstFileA:types{abi="stdcall", ret = HANDLE, LPVOID, LPVOID} +FindFirstFileW:types{abi="stdcall", ret = HANDLE, LPVOID, LPVOID} + +FindNextFileA:types {abi="stdcall", ret = "int", HANDLE, LPVOID } +FindNextFileW:types {abi="stdcall", ret = "int", HANDLE, LPVOID } + +FindClose_:types{abi="stdcall", ret = "int", HANDLE } + +RemoveDirectoryA:types{abi="stdcall", ret = BOOL, LPVOID} +RemoveDirectoryW:types{abi="stdcall", ret = BOOL, LPVOID} + +CreateDirectoryA:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID} +CreateDirectoryW:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID} + +DeleteFileA:types{abi="stdcall", ret = BOOL, LPVOID} +DeleteFileW:types{abi="stdcall", ret = BOOL, LPVOID} + +MoveFileExA:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID, DWORD} +MoveFileExW:types{abi="stdcall", ret = BOOL, LPVOID, LPVOID, DWORD} + +CreateFileA:types {abi="stdcall", ret = HANDLE, LPVOID, DWORD, DWORD, LPVOID, DWORD, DWORD, LPVOID}; +CreateFileW:types {abi="stdcall", ret = HANDLE, LPVOID, DWORD, DWORD, LPVOID, DWORD, DWORD, LPVOID}; + +CloseHandle_:types {abi="stdcall", ret = BOOL, HANDLE}; + +SetFileTime_:types {abi="stdcall", ret = BOOL, HANDLE, LPVOID, LPVOID, LPVOID}; + +GetLastError:types{abi="stdcall", ret = DWORD} + +FormatMessageW:types{abi="stdcall", ret = DWORD, DWORD, LPVOID, DWORD, DWORD, LPVOID, DWORD, LPVOID}; +FormatMessageA:types{abi="stdcall", ret = DWORD, DWORD, LPVOID, DWORD, DWORD, LPVOID, DWORD, LPVOID}; + +LocalFree:types{abi="stdcall", ret = LPVOID, LPVOID}; + +DeviceIoControl_:types{abi="stdcall", ret = BOOL, HANDLE, DWORD, LPVOID, DWORD, LPVOID, DWORD, LPDWORD, LPVOID} + +local function GetCurrentDirectory(u) + local n = (u and GetCurrentDirectoryW or GetCurrentDirectoryA)(0, NULL) + if n == 0 then + local err = GetLastError() + return nil, err + end + local buf = alien.buffer(u and n*2 or n) + n = (u and GetCurrentDirectoryW or GetCurrentDirectoryA)(n, buf) + if n == 0 then + local err = GetLastError() + return nil, err + end + return buf:tostring(u and n*2 or n ) +end + +local function SetCurrentDirectory(u, P) + local ret + if u then ret = SetCurrentDirectoryW(P .. "\0") + else ret = SetCurrentDirectoryA(P) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function GetTempPath(u) + local n = (u and GetTempPathW or GetTempPathA)(0, NULL) + if n == 0 then + local err = GetLastError() + return nil, err + end + local buf = alien.buffer(u and n*2 or n) + n = (u and GetTempPathW or GetTempPathA)(n, buf) + if n == 0 then + local err = GetLastError() + return nil, err + end + return buf:tostring(u and n*2 or n ) +end + +local function CopyFile(u, src, dst, flag) + local ret + if u then ret = CopyFileW(src .. "\0", dst .. "\0", flag and 1 or 0) + else ret = CopyFileA(src, dst, flag and 1 or 0) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function GetFileAttributesEx(u, P) + local ret, info + if u then + info = WIN32_FIND_DATAW:new() + ret = GetFileAttributesExW(P .. "\0", 0, info()) + else + info = WIN32_FIND_DATAA:new() + ret = GetFileAttributesExA(P, 0, info()) + end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return info +end + +local function FindClose(h) + FindClose_(autil.gc_null(h)) +end + +local function FindFirstFile(u, P) + local ret, fd, err + if u then + fd = WIN32_FIND_DATAW:new() + ret = FindFirstFileW(P .. "\0", fd()) + else + fd = WIN32_FIND_DATAA:new() + ret = FindFirstFileA(P, fd()) + end + + if ret == INVALID_HANDLE then + local err = GetLastError() + return nil, err + end + + ret = autil.gc_wrap(ret, FindClose_) + return ret, fd +end + +local function FindNextFile(u, h, fd) + local ret + if u then ret = FindNextFileW(h.value, fd()) + else ret = FindNextFileA(h.value, fd()) end + return ret +end + +local function RemoveDirectory(u, src) + local ret + if u then ret = RemoveDirectoryW(src .. "\0") + else ret = RemoveDirectoryA(src) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function CreateDirectory(u, src) + local ret + if u then ret = CreateDirectoryW(src .. "\0", NULL) + else ret = CreateDirectoryA(src, NULL) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function MoveFileEx(u, src, dst, flag) + local ret + if u then ret = MoveFileExW(src .. "\0", dst .. "\0", flag and flag or 0) + else ret = MoveFileExA(src, dst, flag and flag or 0) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function DeleteFile(u, src) + local ret + if u then ret = DeleteFileW(src .. "\0") + else ret = DeleteFileA(src) end + if ret == 0 then + local err = GetLastError() + return nil, err + end + return true +end + +local function CloseHandle(h) + return CloseHandle_(autil.gc_null(h)) +end + +local function CreateFile(u, P, access, share, sec, mode, attr, template) + local p = P + if u then p = p .. "\0" end + + local h = (u and CreateFileW or CreateFileA)( + p, access, share, sec or NULL, mode, attr, template or NULL + ); + + if INVALID_HANDLE == h then + local err = GetLastError() + return nil, err + end + + return autil.gc_wrap(h, CloseHandle_) +end + +local function newft(t) + if not t then return NULL end + local v = FILETIME:new() + v.dwLowDateTime, v.dwHighDateTime = t[1], t[2] + return v +end + +local function SetFileTime(h, c, a, m) + local ctime, atime, mtime = newft(c), newft(a), newft(m) + local ret = SetFileTime_(h.value, ctime and ctime(), atime and atime(), mtime and mtime()) + if ret ~= 0 then return true end + return nil, GetLastError() +end + +local FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 +local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 +local FORMAT_MESSAGE_FROM_STRING = 0x00000400 +local FORMAT_MESSAGE_FROM_HMODULE = 0x00000800 +local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 +local FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 +local FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF + +local function ErrorMessage(u, dwErr, lang) + local lpMsgBuf = alien.array(LPVOID, 1) + lang = lang or 0 + local ret = (u and FormatMessageW or FormatMessageA)( + FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErr, lang, lpMsgBuf.buffer, 0, NULL + ); + + if ret == 0 then + local err = GetLastError() + return "", err + end + + local str = alien.tostring(lpMsgBuf[1], ret) + ret = LocalFree(lpMsgBuf[1]); + return str; +end + +local function DeviceIoControl(h, code, inBuffer, inBufferSize, outBuffer, outBufferSize) + if inBuffer == nil then inBuffer, inBufferSize = NULL, 0 end + if outBuffer == nil then outBuffer, outBufferSize = NULL, 0 end + local ret, dwTmp = DeviceIoControl_(h.value, code, + inBuffer, inBufferSize, outBuffer, outBufferSize, + 0, NULL + ) + if ret == 0 then + local err = GetLastError() + return nil, err + end + return ret, dwTmp +end + +local FILE_FLAG_NO_BUFFERING = 0x20000000 +local FILE_ATTRIBUTE_NORMAL = 0x00000080 +local FILE_SHARE_READ = 0x00000001 +local FILE_SHARE_WRITE = 0x00000002 +local OPEN_EXISTING = 3 +local IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2D1080 + +local function DiskNumber(u, P) + local p + if u then p = "\\\0\\\0.\0\\\0" .. P .. "\0" + else p = "\\\\.\\" .. P end + + -- Open partition + local hPart, err = CreateFile(u, p, 0, + FILE_SHARE_READ + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_NO_BUFFERING, NULL + ); + + if not hPart then return nil, err end + + local Info = CTYPES.STORAGE_DEVICE_NUMBER:new(); + local Info_size = CTYPES.STORAGE_DEVICE_NUMBER.size_ + + local ret, dwTmp = DeviceIoControl(hPart, IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, Info(), Info_size + ) + if not ret then err = dwTmp end + if dwTmp ~= Info_size then ret, err = nil, GetLastError() end + + CloseHandle(hPart) + if not ret then return nil, err end + return { + Info.DeviceType, + Info.DeviceNumber, + Info.PartitionNumber, + } +end + +return { + A = { + GetCurrentDirectory = function(...) return GetCurrentDirectory(false, ...) end; + SetCurrentDirectory = function(...) return SetCurrentDirectory(false, ...) end; + GetTempPath = function(...) return GetTempPath (false, ...) end; + GetFileAttributesEx = function(...) return GetFileAttributesEx(false, ...) end; + CopyFile = function(...) return CopyFile (false, ...) end; + FindFirstFile = function(...) return FindFirstFile (false, ...) end; + FindNextFile = function(...) return FindNextFile (false, ...) end; + RemoveDirectory = function(...) return RemoveDirectory (false, ...) end; + DeleteFile = function(...) return DeleteFile (false, ...) end; + CreateDirectory = function(...) return CreateDirectory (false, ...) end; + CreateFile = function(...) return CreateFile (false, ...) end; + MoveFileEx = function(...) return MoveFileEx (false, ...) end; + ErrorMessage = function(...) return ErrorMessage (false, ...) end; + DiskNumber = function(...) return DiskNumber (false, ...) end; + FindClose = FindClose; + CloseHandle = CloseHandle; + SetFileTime = SetFileTime; + DeviceIoControl = DeviceIoControl; + WIN32_FIND_DATA2TABLE = CTYPE2LUA.WIN32_FIND_DATAA; + DIR_SEP = "\\"; + ANY_MASK = "*"; + }; + W = { + GetCurrentDirectory = function(...) return GetCurrentDirectory(true, ...) end; + SetCurrentDirectory = function(...) return SetCurrentDirectory(true, ...) end; + GetTempPath = function(...) return GetTempPath (true, ...) end; + GetFileAttributesEx = function(...) return GetFileAttributesEx(true, ...) end; + CopyFile = function(...) return CopyFile (true, ...) end; + FindFirstFile = function(...) return FindFirstFile (true, ...) end; + FindNextFile = function(...) return FindNextFile (true, ...) end; + RemoveDirectory = function(...) return RemoveDirectory (true, ...) end; + DeleteFile = function(...) return DeleteFile (true, ...) end; + CreateDirectory = function(...) return CreateDirectory (true, ...) end; + CreateFile = function(...) return CreateFile (true, ...) end; + MoveFileEx = function(...) return MoveFileEx (true, ...) end; + ErrorMessage = function(...) return ErrorMessage (true, ...) end; + DiskNumber = function(...) return DiskNumber (true, ...) end; + FindClose = FindClose; + CloseHandle = CloseHandle; + SetFileTime = SetFileTime; + DeviceIoControl = DeviceIoControl; + WIN32_FIND_DATA2TABLE = CTYPE2LUA.WIN32_FIND_DATAW; + DIR_SEP = "\\\000"; + ANY_MASK = "*\000"; + }; +} \ No newline at end of file diff --git a/extras/hammerlua/lua/path/win32/alien/types.lua b/extras/hammerlua/lua/path/win32/alien/types.lua new file mode 100644 index 0000000..d362367 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/alien/types.lua @@ -0,0 +1,111 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local alien = require "alien" +local autil = require "path.win32.alien.utils" + +local function ztrim(str) + local pos = 1 + while true do + pos = string.find(str, "\000\000", pos, true) + if not pos then return str end + if 0 ~= (pos % 2) then return string.sub(str, 1, pos - 1) end + pos = pos + 1 + end +end + +assert( alien.sizeof("uint") == 4 ) +assert( alien.sizeof("pointer") == alien.sizeof("size_t") ) + +local MAX_PATH = 260 +local CHAR = function(N) return "c" .. N end +local DWORD = "I4" +local ULONG = DWORD; +local DEVICE_TYPE = DWORD; + +local FILETIME = autil.define_struct{ + {DWORD, "dwLowDateTime" }; + {DWORD, "dwHighDateTime" }; +} + +local WIN32_FIND_DATAA = autil.define_struct{ + { DWORD ,"dwFileAttributes" }; + { FILETIME ,"ftCreationTime" }; + { FILETIME ,"ftLastAccessTime" }; + { FILETIME ,"ftLastWriteTime" }; + { DWORD ,"nFileSizeHigh" }; + { DWORD ,"nFileSizeLow" }; + { DWORD ,"dwReserved0" }; + { DWORD ,"dwReserved1" }; + { CHAR(MAX_PATH) ,"cFileName" }; + { CHAR(14) ,"cAlternateFileName" }; +} + +local WIN32_FIND_DATAW = autil.define_struct{ + { DWORD ,"dwFileAttributes" }; + { FILETIME ,"ftCreationTime" }; + { FILETIME ,"ftLastAccessTime" }; + { FILETIME ,"ftLastWriteTime" }; + { DWORD ,"nFileSizeHigh" }; + { DWORD ,"nFileSizeLow" }; + { DWORD ,"dwReserved0" }; + { DWORD ,"dwReserved1" }; + { CHAR(2*MAX_PATH),"cFileName" }; + { CHAR(2*14) ,"cAlternateFileName" }; +} + +local WIN32_FILE_ATTRIBUTE_DATA = function(s) return { + dwFileAttributes = s.dwFileAttributes; + ftCreationTime = {s.ftCreationTime.dwLowDateTime, s.ftCreationTime.dwHighDateTime}; + ftLastAccessTime = {s.ftLastAccessTime.dwLowDateTime, s.ftLastAccessTime.dwHighDateTime}; + ftLastWriteTime = {s.ftLastWriteTime.dwLowDateTime, s.ftLastWriteTime.dwHighDateTime}; + nFileSize = {s.nFileSizeLow, s.nFileSizeHigh}; +}end; + +local WIN32_FIND_DATAA2LUA = function(s) + local res = WIN32_FILE_ATTRIBUTE_DATA(s) + res.cFileName = s.cFileName:gsub("%z.*$", "") + return res +end; + +local WIN32_FIND_DATAW2LUA = function(s) + local res = WIN32_FILE_ATTRIBUTE_DATA(s) + res.cFileName = ztrim(s.cFileName) + return res +end; + +local STORAGE_DEVICE_NUMBER = autil.define_struct{ + { DEVICE_TYPE , "DeviceType" }; + { ULONG , "DeviceNumber" }; + { ULONG , "PartitionNumber" }; +} + +local c2lua = { + WIN32_FIND_DATAA = WIN32_FIND_DATAA2LUA; + WIN32_FIND_DATAW = WIN32_FIND_DATAW2LUA; +} + +local CTYPES = { + HANDLE = "size_t"; + DWORD = "uint"; + + FILETIME = FILETIME; + WIN32_FIND_DATAA = WIN32_FIND_DATAA; + WIN32_FIND_DATAW = WIN32_FIND_DATAW; + STORAGE_DEVICE_NUMBER = STORAGE_DEVICE_NUMBER; +} + +return { + CTYPES = CTYPES; + CTYPE2LUA = c2lua; + INVALID_HANDLE = autil.cast(-1, CTYPES.HANDLE) +} \ No newline at end of file diff --git a/extras/hammerlua/lua/path/win32/alien/utils.lua b/extras/hammerlua/lua/path/win32/alien/utils.lua new file mode 100644 index 0000000..c473be3 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/alien/utils.lua @@ -0,0 +1,188 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local alien = require "alien" + +local STRUCT = {} +STRUCT.__index = STRUCT + +local function define_struct(opt, t) + if not t then t, opt = opt, nil end + assert(type(t) == "table") + assert(not opt or type(opt) == "table") + + local s_align = opt and opt.align or 1 + local off = 0 + local names, offsets, types,fields = {}, {}, {}, {} + local fmt = "" + + for i, field in ipairs(t) do + local ftype, fname + local align = s_align + if type(field) == "string" then + ftype, fname = field, i + elseif getmetatable(field) == STRUCT then + ftype, fname = field, i + else + ftype, fname = field[1], field[2] or i + align = field.align or align + end + off = math.ceil(off / align) * align + table.insert(names, fname) + offsets[fname] = off + types[fname] = ftype + if type(ftype) == "string" then + off = off + alien.size(ftype) + else + off = off + ftype.size_ + end + end + + return setmetatable({ + names_ = names, + offsets_ = offsets, + types_ = types, + size_ = off, + fmt_ = fmt, + }, STRUCT) +end + +function STRUCT:new(t, ptr) + local buffer_ = alien.buffer(ptr or self.size_) + + local function get(_,key) + local off = self.offsets_[key] + if not off then error("field " .. key .. " does not exist") end + local ftype = assert(self.types_[key]) + + if type(ftype) ~= "string" then + local ptr = buffer_:topointer(off + 1) + return ftype:new(nil, ptr) + end + + local size = alien.size(ftype) + local str = buffer_:tostring(size, off + 1) + return alien.unpack(ftype, str) + end + + local function set(_,key, val) + local off = self.offsets_[key] + if not off then error("field " .. key .. " does not exist") end + local ftype = assert(self.types_[key]) + local ptr = buffer_:topointer(off + 1) + local size = alien.size(ftype) + local val = alien.pack(ftype, val) + alien.memmove( ptr, val, #val ) + end + + local o = setmetatable({}, { + __index = get; __newindex = set; + __call = function () return buffer_ end + }) + + if t then for k, v in pairs(t) do + o[k] = v + end end + + return o +end + +local function self_struct_test() + + local S1 = define_struct{ + {"I4", "s1v1"}; + {"I4", "s1v2"}; + } + + local S2 = define_struct{ + {"I4", "s2v1"}; + {"I4", "s2v2"}; + } + + local SS = define_struct{ + {S1, "s1"}; + {S2, "s2"}; + } + + local s = SS:new() + alien.memset(s(),0, SS.size_) + assert(s.s1.s1v1 == 0) + assert(s.s1.s1v2 == 0) + assert(s.s2.s2v1 == 0) + assert(s.s2.s2v2 == 0) + assert(not pcall(function() return s.s1.s2v1 end)) + assert(not pcall(function() return s.s1.s1v3 end)) + assert(not pcall(function() return s.s3.s3v1 end)) + s.s1.s1v1 = 123 + s.s2.s2v1 = 456 + assert(s.s1.s1v1 == 123) + assert(s.s1.s1v2 == 0) + assert(s.s2.s2v1 == 456) + assert(s.s2.s2v2 == 0) +end + +self_struct_test() + +local function cast(v,t) + local tmp = alien.buffer(alien.sizeof(t)) + tmp:set(1, v, t) + return tmp:get(1,t) +end + +local gc_wrap, gc_null +if _VERSION >= 'Lua 5.2' then + local setmetatable = setmetatable + gc_wrap = function(v, fn) + return setmetatable({ + value = v; + }, { __gc = function() fn(v) end}) + end + + gc_null = function(h) + setmetatable(h, nil) + return h.value + end + +else + local debug = require "debug" + local newproxy = newproxy + local assert = assert + local setmetatable = setmetatable + + local function gc(fn) + local p = assert(newproxy()) + assert(debug.setmetatable(p, { __gc = fn })) + return p + end + + gc_wrap = function(v, fn) + return { + value = v; + _ = gc(function() fn(v) end); + } + end + + gc_null = function(h) + debug.setmetatable(h._, nil) + return h.value + end + +end + +local _M = { + define_struct = define_struct; + cast = cast; + gc_wrap = gc_wrap; + gc_null = gc_null; +} + +return _M \ No newline at end of file diff --git a/extras/hammerlua/lua/path/win32/alien/wcs.lua b/extras/hammerlua/lua/path/win32/alien/wcs.lua new file mode 100644 index 0000000..5c0bb37 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/alien/wcs.lua @@ -0,0 +1,134 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local alien = require "alien" +local kernel32 = assert(alien.load("kernel32.dll")) +local MultiByteToWideChar_ = assert(kernel32.MultiByteToWideChar) +local WideCharToMultiByte_ = assert(kernel32.WideCharToMultiByte) +local GetLastError = assert(kernel32.GetLastError) + +local DWORD = "uint" +local WCHAR_SIZE = 2 + +-- int __stdcall MultiByteToWideChar(UINT cp, DWORD flag, const char* src, int srclen, wchar_t* dst, int dstlen); +MultiByteToWideChar_:types{abi="stdcall", ret = "int", + "uint", -- cp + DWORD, -- flag + "string", -- src (const char*) + "int", -- srclen + "string", -- dst (wchar_t*) + "int" -- dstlen +} + +--int __stdcall WideCharToMultiByte(UINT cp, DWORD flag, const wchar_t* src, int srclen, char* dst, int dstlen, const char* defchar, int* used); +WideCharToMultiByte_:types{abi="stdcall", ret = "int", + "int", -- cp + DWORD, -- flag + "string", -- src (const wchar_t*) + "int", -- srclen + "string", -- dst (char*) + "int", -- dstlen + "pointer", -- defchar (char*) + "pointer" -- used(int*) +} + +GetLastError:types{ret = DWORD, abi='stdcall'} + +local function strnlen(data, n) + if type(data) == 'string' then + return #data + end + n = n or #data + for i = 1, n do + if data[i] == 0 then + return i + end + end + return n +end + +local function wcsnlen(data, n) + if type(data) == 'string' then + return math.ceil(#data/2) + end + n = n or #data + for i = 1, (2 * n), 2 do + if (data[i] == 0) and (data[i+1] == 0) then + return math.floor( i / 2 ) + end + end + return n +end + +local function MultiByteToWideChar(src, cp) + local flag = true + local buflen = strnlen(src) + local dst = alien.buffer( WCHAR_SIZE * (buflen + 1) ) -- eos + local ret = MultiByteToWideChar_(cp, 0, src, #src, dst, buflen) + if ret < 0 then return nil, GetLastError() end + if ret <= buflen then + dst[ret * WCHAR_SIZE ] = 0 + dst[ret * WCHAR_SIZE + 1] = 0 + return dst, ret + end + dst = alien.buffer(WCHAR_SIZE * 1) + dst[0] = 0 + dst[1] = 0 + return dst,0 +end + +local function WideCharToMultiByte(src, cp) + local srclen = wcsnlen(src) + local buflen = (srclen + 1) + while true do + local dst = alien.buffer(buflen + 1) -- eof + local ret = WideCharToMultiByte_(cp, 0, src, srclen, dst, buflen, nil, nil) + if ret <= 0 then + local err = GetLastError() + if err == 122 then -- buffer too small + buflen = math.ceil(1.5 * buflen) + else + return nil, err + end + else + if ret <= buflen then + return dst, ret + end + end + end + local dst = alien.buffer(1) + dst[0] = 0 + return dst, 0 +end + +local function LUA_M2W(src, ...) + if not src or #src == 0 then return src end + local dst, dstlen = MultiByteToWideChar(src, ...) + if not dst then return nil, dstlen end + return dst:tostring(dstlen * WCHAR_SIZE) +end + +local function LUA_W2M(src, ...) + if not src or #src == 0 then return src end + local dst, dstlen = WideCharToMultiByte(src, ...) + if not dst then return nil, dstlen end + return dst:tostring(dstlen) +end + +local _M = { + MultiByteToWideChar = MultiByteToWideChar; + WideCharToMultiByte = WideCharToMultiByte; + mbstowcs = LUA_M2W; + wcstombs = LUA_W2M; +} + +return _M \ No newline at end of file diff --git a/extras/hammerlua/lua/path/win32/ffi/fs.lua b/extras/hammerlua/lua/path/win32/ffi/fs.lua new file mode 100644 index 0000000..428917b --- /dev/null +++ b/extras/hammerlua/lua/path/win32/ffi/fs.lua @@ -0,0 +1,377 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local ffi = require "ffi" +local types = require "path.win32.ffi.types" + +ffi.cdef[[ + DWORD __stdcall GetCurrentDirectoryA( DWORD nBufferLength, CHAR* lpBuffer); + DWORD __stdcall GetCurrentDirectoryW( DWORD nBufferLength, CHAR* lpBuffer); + BOOL __stdcall SetCurrentDirectoryA(const CHAR* lpPathName); + BOOL __stdcall SetCurrentDirectoryW(const CHAR* lpPathName); + DWORD __stdcall GetTempPathA(DWORD n, CHAR* buf); + DWORD __stdcall GetTempPathW(DWORD n, CHAR* buf); + DWORD __stdcall GetFileAttributesExA(const CHAR* lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, void* lpFileInformation); + DWORD __stdcall GetFileAttributesExW(const CHAR* lpFileName, GET_FILEEX_INFO_LEVELS fInfoLevelId, void* lpFileInformation); + BOOL __stdcall CopyFileA(const CHAR* src, const CHAR* dst, BOOL flag); + BOOL __stdcall CopyFileW(const CHAR* src, const CHAR* dst, BOOL flag); + HANDLE __stdcall FindFirstFileA(const CHAR* pattern, WIN32_FIND_DATAA* fd); + HANDLE __stdcall FindFirstFileW(const CHAR* pattern, WIN32_FIND_DATAW* fd); + int __stdcall FindNextFileA(HANDLE ff, WIN32_FIND_DATAA* fd); + int __stdcall FindNextFileW(HANDLE ff, WIN32_FIND_DATAW* fd); + int __stdcall FindClose(HANDLE ff); + DWORD __stdcall GetLastError(); + BOOL __stdcall RemoveDirectoryA(const CHAR* src); + BOOL __stdcall RemoveDirectoryW(const CHAR* src); + BOOL __stdcall MoveFileExA(const CHAR* src, const CHAR* dst, DWORD flags); + BOOL __stdcall MoveFileExW(const CHAR* src, const CHAR* dst, DWORD flags); + BOOL __stdcall DeleteFileA(const CHAR* src); + BOOL __stdcall DeleteFileW(const CHAR* src); + BOOL __stdcall CreateDirectoryA(const CHAR* src, void* lpSecurityAttributes); + BOOL __stdcall CreateDirectoryW(const CHAR* src, void* lpSecurityAttributes); + + HANDLE __stdcall CreateFileA(const CHAR* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, void* lpSecurityAttributes, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile + ); + + HANDLE __stdcall CreateFileW(const CHAR* lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, void* lpSecurityAttributes, + DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile + ); + + BOOL __stdcall SetFileTime(HANDLE hFile, const FILETIME *lpCreationTime, const FILETIME *lpLastAccessTime,const FILETIME *lpLastWriteTime); + + BOOL __stdcall CloseHandle(HANDLE hObject); + + DWORD __stdcall FormatMessageA(DWORD dwFlags, void* lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPVOID lpBuffer, DWORD nSize, void *Arguments); + DWORD __stdcall FormatMessageW(DWORD dwFlags, void* lpSource, DWORD dwMessageId, DWORD dwLanguageId, LPVOID lpBuffer, DWORD nSize, void *Arguments); + HLOCAL __stdcall LocalFree( HLOCAL hMem ); + + BOOL __stdcall DeviceIoControl(HANDLE hDevice, DWORD dwIoControlCode, void* lpInBuffer, DWORD nInBufferSize, void* lpOutBuffer, DWORD nOutBufferSize, DWORD* lpBytesReturned, void* lpOverlapped); +]] + +local C = ffi.C +local CTYPES = types.CTYPES +local CTYPE2LUA = types.CTYPE2LUA +local WIN32_FIND_DATAA = CTYPES.WIN32_FIND_DATAA +local WIN32_FIND_DATAW = CTYPES.WIN32_FIND_DATAW +local FILETIME = CTYPES.FILETIME +local INVALID_HANDLE = types.INVALID_HANDLE +local NULL = ffi.cast("void*", 0) +local NULLSTR = ffi.cast("CHAR*", NULL) + +local function GetCurrentDirectory(u) + local n = (u and C.GetCurrentDirectoryW or C.GetCurrentDirectoryA)(0,NULLSTR) + if n == 0 then + local err = C.GetLastError() + return nil, err + end + + local buf = ffi.new(CTYPES.VLA_CHAR, u and 2*n or n) + + n = (u and C.GetCurrentDirectoryW or C.GetCurrentDirectoryA)(n, buf) + if n == 0 then + local err = C.GetLastError() + return nil, err + end + return ffi.string(buf, u and 2*n or n) +end + +local function SetCurrentDirectory(u, P) + local ret + if u then ret = C.SetCurrentDirectoryW(P .. "\0") + else ret = C.SetCurrentDirectoryA(P) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function GetTempPath(u) + local n = (u and C.GetTempPathW or C.GetTempPathA)(0,NULLSTR) + if n == 0 then + local err = C.GetLastError() + return nil, err + end + + local buf = ffi.new(CTYPES.VLA_CHAR, u and 2*n or n) + + n = (u and C.GetTempPathW or C.GetTempPathA)(n, buf) + if n == 0 then + local err = C.GetLastError() + return nil, err + end + return ffi.string(buf, u and 2*n or n) +end + +local function GetFileAttributesEx(u, P) + local ret, info + if u then + info = WIN32_FIND_DATAW() + ret = C.GetFileAttributesExW(P .. "\0", C.GetFileExInfoStandard, info) + else + info = WIN32_FIND_DATAA() + ret = C.GetFileAttributesExA(P, C.GetFileExInfoStandard, info) + end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return info +end + +local function CopyFile(u, src, dst, flag) + local ret + if u then ret = C.CopyFileW(src .. "\0", dst .. "\0", flag and 1 or 0) + else ret = C.CopyFileA(src, dst, flag and 1 or 0) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function FindFirstFile(u, P) + local ret, fd, err + if u then + fd = WIN32_FIND_DATAW() + ret = C.FindFirstFileW(P .. "\0", fd) + else + fd = WIN32_FIND_DATAA() + ret = C.FindFirstFileA(P, fd) + end + if ret == INVALID_HANDLE then + local err = C.GetLastError() + return nil, err + end + ffi.gc(ret, C.FindClose) + return ret, fd +end + +local function FindNextFile(u, h, fd) + local ret + if u then ret = C.FindNextFileW(h, fd) + else ret = C.FindNextFileA(h, fd) end + return ret +end + +local function FindClose(h) + C.FindClose(ffi.gc(h, nil)) +end + +local function RemoveDirectory(u, src) + local ret + if u then ret = C.RemoveDirectoryW(src .. "\0") + else ret = C.RemoveDirectoryA(src) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function DeleteFile(u, src) + local ret + if u then ret = C.DeleteFileW(src .. "\0") + else ret = C.DeleteFileA(src) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function MoveFileEx(u, src, dst, flag) + local ret + if u then ret = C.MoveFileExW(src .. "\0", dst .. "\0", flag and flag or 0) + else ret = C.MoveFileExA(src, dst, flag and flag or 0) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function CreateDirectory(u, src) + local ret + if u then ret = C.CreateDirectoryW(src .. "\0",NULL) + else ret = C.CreateDirectoryA(src,NULL) end + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + return true +end + +local function CreateFile(u, P, access, share, sec, mode, attr, template) + local p = P + if u then p = p .. "\0" end + + local h = (u and C.CreateFileW or C.CreateFileA)( + p, access, share, sec or NULL, mode, attr, template or NULL + ); + + if INVALID_HANDLE == h then + local err = C.GetLastError() + return nil, err + end + + return ffi.gc(h, C.CloseHandle) +end + +local function CloseHandle(h) + return C.CloseHandle(ffi.gc(h, nil)) +end + +local function newft(t) + if not t then return ffi.cast("FILETIME*", NULL) end + local v = FILETIME() + v.dwLowDateTime, v.dwHighDateTime = t[1], t[2] + return v +end + +local function SetFileTime(h, c, a, m) + local ctime, atime, mtime = newft(c), newft(a), newft(m) + local ret = C.SetFileTime(h, ctime, atime, mtime) + if ret ~= 0 then return true end + return nil, C.GetLastError() +end + +local FORMAT_MESSAGE_ALLOCATE_BUFFER = 0x00000100 +local FORMAT_MESSAGE_IGNORE_INSERTS = 0x00000200 +local FORMAT_MESSAGE_FROM_STRING = 0x00000400 +local FORMAT_MESSAGE_FROM_HMODULE = 0x00000800 +local FORMAT_MESSAGE_FROM_SYSTEM = 0x00001000 +local FORMAT_MESSAGE_ARGUMENT_ARRAY = 0x00002000 +local FORMAT_MESSAGE_MAX_WIDTH_MASK = 0x000000FF + +local function ErrorMessage(u, dwErr, lang) + local lpMsgBuf = ffi.new("LPVOID[1]", 0); + lang = lang or 0 + local ret = (u and C.FormatMessageW or C.FormatMessageA)( + FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM + FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, dwErr, lang, lpMsgBuf, 0, NULL + ); + + if ret == 0 then + local err = C.GetLastError() + return "", err + end + + local str = ffi.cast(CTYPES.PCHAR, lpMsgBuf[0]) + str = ffi.string(str, u and 2 * ret or ret); + ret = C.LocalFree(lpMsgBuf[0]); + return str; +end + +local function DeviceIoControl(h, code, inBuffer, inBufferSize, outBuffer, outBufferSize) + if inBuffer == nil then inBuffer, inBufferSize = NULL, 0 end + if outBuffer == nil then outBuffer, outBufferSize = NULL, 0 end + local dwTmp = ffi.new("DWORD[1]", 0) + local ret = C.DeviceIoControl(h, code, + inBuffer, inBufferSize, outBuffer, outBufferSize, + dwTmp, NULL + ) + if ret == 0 then + local err = C.GetLastError() + return nil, err + end + + return ret, dwTmp[0] +end + +local FILE_FLAG_NO_BUFFERING = 0x20000000 +local FILE_ATTRIBUTE_NORMAL = 0x00000080 +local FILE_SHARE_READ = 0x00000001 +local FILE_SHARE_WRITE = 0x00000002 +local OPEN_EXISTING = 3 +local IOCTL_STORAGE_GET_DEVICE_NUMBER = 0x2D1080 + +local function DiskNumber(u, P) + local p + if u then p = "\\\0\\\0.\0\\\0" .. P .. "\0" + else p = "\\\\.\\" .. P end + + -- Open partition + local hPart, err = CreateFile(u, p, 0, + FILE_SHARE_READ + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL + FILE_FLAG_NO_BUFFERING, NULL + ); + + if not hPart then return nil, err end + + local Info = CTYPES.STORAGE_DEVICE_NUMBER(); + local Info_size = ffi.sizeof(CTYPES.STORAGE_DEVICE_NUMBER) + + local ret, dwTmp = DeviceIoControl(hPart, IOCTL_STORAGE_GET_DEVICE_NUMBER, + NULL, 0, Info, Info_size + ) + if not ret then err = dwTmp end + if dwTmp ~= Info_size then ret, err = nil, C.GetLastError() end + + CloseHandle(hPart) + if not ret then return nil, err end + return { + Info.DeviceType, + Info.DeviceNumber, + Info.PartitionNumber, + } +end + +return { + A = { + GetCurrentDirectory = function(...) return GetCurrentDirectory(false, ...) end; + SetCurrentDirectory = function(...) return SetCurrentDirectory(false, ...) end; + GetTempPath = function(...) return GetTempPath (false, ...) end; + GetFileAttributesEx = function(...) return GetFileAttributesEx(false, ...) end; + CopyFile = function(...) return CopyFile (false, ...) end; + FindFirstFile = function(...) return FindFirstFile (false, ...) end; + FindNextFile = function(...) return FindNextFile (false, ...) end; + RemoveDirectory = function(...) return RemoveDirectory (false, ...) end; + DeleteFile = function(...) return DeleteFile (false, ...) end; + CreateDirectory = function(...) return CreateDirectory (false, ...) end; + CreateFile = function(...) return CreateFile (false, ...) end; + MoveFileEx = function(...) return MoveFileEx (false, ...) end; + ErrorMessage = function(...) return ErrorMessage (false, ...) end; + DiskNumber = function(...) return DiskNumber (false, ...) end; + FindClose = FindClose; + CloseHandle = CloseHandle; + SetFileTime = SetFileTime; + DeviceIoControl = DeviceIoControl; + WIN32_FIND_DATA2TABLE = CTYPE2LUA.WIN32_FIND_DATAA; + DIR_SEP = "\\"; + ANY_MASK = "*"; + }; + W = { + GetCurrentDirectory = function(...) return GetCurrentDirectory(true, ...) end; + SetCurrentDirectory = function(...) return SetCurrentDirectory(true, ...) end; + GetTempPath = function(...) return GetTempPath (true, ...) end; + GetFileAttributesEx = function(...) return GetFileAttributesEx(true, ...) end; + CopyFile = function(...) return CopyFile (true, ...) end; + FindFirstFile = function(...) return FindFirstFile (true, ...) end; + FindNextFile = function(...) return FindNextFile (true, ...) end; + RemoveDirectory = function(...) return RemoveDirectory (true, ...) end; + DeleteFile = function(...) return DeleteFile (true, ...) end; + CreateDirectory = function(...) return CreateDirectory (true, ...) end; + CreateFile = function(...) return CreateFile (true, ...) end; + MoveFileEx = function(...) return MoveFileEx (true, ...) end; + ErrorMessage = function(...) return ErrorMessage (true, ...) end; + DiskNumber = function(...) return DiskNumber (true, ...) end; + FindClose = FindClose; + CloseHandle = CloseHandle; + SetFileTime = SetFileTime; + DeviceIoControl = DeviceIoControl; + WIN32_FIND_DATA2TABLE = CTYPE2LUA.WIN32_FIND_DATAW; + DIR_SEP = "\\\000"; + ANY_MASK = "*\000"; + }; +} \ No newline at end of file diff --git a/extras/hammerlua/lua/path/win32/ffi/types.lua b/extras/hammerlua/lua/path/win32/ffi/types.lua new file mode 100644 index 0000000..dd41f55 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/ffi/types.lua @@ -0,0 +1,165 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local ffi = require "ffi" + +local C = ffi.C + +local function pcdef(...) + local ok, err = pcall( ffi.cdef, ... ) + if not ok then return nil, err end + return err +end + +local function pack(n, str) + return [[ + #pragma pack(push) + #pragma pack(1) + ]] .. str ..[[ + #pragma pack(pop) + ]] +end + +local function ztrim(str) + local pos = 1 + while true do + pos = string.find(str, "\000\000", pos, true) + if not pos then return str end + if 0 ~= (pos % 2) then return string.sub(str, 1, pos - 1) end + pos = pos + 1 + end +end + +ffi.cdef [[ + static const int MAX_PATH = 260; + typedef uint32_t DWORD; + typedef char CHAR; + typedef wchar_t WCHAR; + typedef uint32_t DEVICE_TYPE; + typedef uint32_t ULONG; + typedef void* HANDLE; + typedef void* HLOCAL; + typedef void* LPVOID; + typedef uint32_t BOOL; +]] + +pcdef(pack(1, [[ // GET_FILEEX_INFO_LEVELS + typedef enum _GET_FILEEX_INFO_LEVELS { + GetFileExInfoStandard, + GetFileExMaxInfoLevel + } GET_FILEEX_INFO_LEVELS; +]])) + +pcdef(pack(1, [[ // FILETIME + typedef struct _FILETIME { + DWORD dwLowDateTime; + DWORD dwHighDateTime; + } FILETIME, *PFILETIME; +]])) + +pcdef(pack(1, [[ // WIN32_FILE_ATTRIBUTE_DATA + typedef struct _WIN32_FILE_ATTRIBUTE_DATA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + } WIN32_FILE_ATTRIBUTE_DATA, *PWIN32_FILE_ATTRIBUTE_DATA; +]])) + +pcdef(pack(1, [[ // WIN32_FIND_DATAA + typedef struct _WIN32_FIND_DATAA { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + CHAR cFileName[MAX_PATH]; + CHAR cAlternateFileName[14]; + } WIN32_FIND_DATAA, *PWIN32_FIND_DATAA; +]])) + +pcdef(pack(1, [[ // WIN32_FIND_DATAW + typedef struct _WIN32_FIND_DATAW { + DWORD dwFileAttributes; + FILETIME ftCreationTime; + FILETIME ftLastAccessTime; + FILETIME ftLastWriteTime; + DWORD nFileSizeHigh; + DWORD nFileSizeLow; + DWORD dwReserved0; + DWORD dwReserved1; + WCHAR cFileName[MAX_PATH]; + WCHAR cAlternateFileName[14]; + } WIN32_FIND_DATAW, *PWIN32_FIND_DATAW; +]])) + +pcdef(pack(1, [[ // STORAGE_DEVICE_NUMBER + typedef struct _STORAGE_DEVICE_NUMBER { + DEVICE_TYPE DeviceType; + ULONG DeviceNumber; + ULONG PartitionNumber; + } STORAGE_DEVICE_NUMBER, *PSTORAGE_DEVICE_NUMBER; +]])) + +local CTYPES = { + DWORD = ffi.typeof("DWORD"); + PCHAR = ffi.typeof("CHAR*"); + PWCHAR = ffi.typeof("WCHAR*"); + VLA_CHAR = ffi.typeof("CHAR[?]"); + VLA_WCHAR = ffi.typeof("WCHAR[?]"); + + WIN32_FILE_ATTRIBUTE_DATA = ffi.typeof("WIN32_FILE_ATTRIBUTE_DATA"); + FILETIME = ffi.typeof("FILETIME"); + WIN32_FIND_DATAA = ffi.typeof("WIN32_FIND_DATAA"); + WIN32_FIND_DATAW = ffi.typeof("WIN32_FIND_DATAW"); + STORAGE_DEVICE_NUMBER = ffi.typeof("STORAGE_DEVICE_NUMBER"); +} + +local c2lua +c2lua = { + + WIN32_FILE_ATTRIBUTE_DATA = function(s) return { + dwFileAttributes = s.dwFileAttributes; + ftCreationTime = {s.ftCreationTime.dwLowDateTime, s.ftCreationTime.dwHighDateTime}; + ftLastAccessTime = {s.ftLastAccessTime.dwLowDateTime, s.ftLastAccessTime.dwHighDateTime}; + ftLastWriteTime = {s.ftLastWriteTime.dwLowDateTime, s.ftLastWriteTime.dwHighDateTime}; + nFileSize = {s.nFileSizeLow, s.nFileSizeHigh}; + }end; + + WIN32_FIND_DATAA = function(s) + local res = c2lua.WIN32_FILE_ATTRIBUTE_DATA(s) + res.cFileName = ffi.string(s.cFileName); + return res + end; + + WIN32_FIND_DATAW = function(s) + local res = c2lua.WIN32_FILE_ATTRIBUTE_DATA(s) + local pstr = ffi.cast(CTYPES.PCHAR, s.cFileName) + local str = ffi.string(pstr, C.MAX_PATH * 2) + res.cFileName = ztrim(str) + return res + end; + +} + +local _M = { + INVALID_HANDLE = ffi.cast("void*", -1); + CTYPES = CTYPES; + CTYPE2LUA = c2lua; +} + +return _M diff --git a/extras/hammerlua/lua/path/win32/ffi/wcs.lua b/extras/hammerlua/lua/path/win32/ffi/wcs.lua new file mode 100644 index 0000000..a2e8461 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/ffi/wcs.lua @@ -0,0 +1,128 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local ffi = require("ffi") +ffi.cdef[[ + int __stdcall MultiByteToWideChar(unsigned int cp, uint32_t flag, const char* src, int srclen, wchar_t* dst, int dstlen); + int __stdcall WideCharToMultiByte(unsigned int cp, uint32_t flag, const char* src, int srclen, char* dst, int dstlen, const char* defchar, int* used); + uint32_t __stdcall GetLastError(void); +]] + +local C = ffi.C +local achar_t = ffi.typeof'char[?]' +local awchar_t = ffi.typeof'wchar_t[?]' +local pchar_t = ffi.typeof'char*' +local pwchar_t = ffi.typeof'wchar_t*' +-- The data area passed to a system call is too small. +local ERROR_INSUFFICIENT_BUFFER = 122; -- 0x0000007A + +local function strnlen(data, n) + if type(data) == 'string' then + return #data + end + if not n then + if ffi.istype(pchar_t, data) then + n = math.huge + else -- assume char[?] / char&[...] + n = assert(ffi.sizeof(data)) + end + end + for i = 0, n-1 do + if data[i] == 0 then return i end + end + + return n +end + +local function wcsnlen(data, n) + if type(data) == 'string' then + return math.ceil(#data/2) + end + + if not n then + if ffi.istype(pchar_t, data) then + n = math.huge + else -- assume wchar[?] / wchar&[...] + n = math.ceil(assert(ffi.sizeof(data))/2) + end + end + for i = 0, n-1 do + if data[i] == 0 then return i end + end + + return n +end + +local function MultiByteToWideChar(src, cp) + if not src or #src == 0 then return src, 0 end + local flag = true + local buflen = strnlen(src) + local dst = ffi.new(awchar_t, buflen + 1) -- eos + local ret = C.MultiByteToWideChar(cp, 0, src, #src, dst, buflen) + if ret < 0 then return nil, C.GetLastError() end + if ret <= buflen then + dst[ret] = 0 + return dst, ret + end + dst = ffi.new(awchar_t, 1) + dst[0] = 0 + return dst,0 +end + +local function WideCharToMultiByte(src, cp) + local srclen = wcsnlen(src) + local buflen = srclen + 1 + if type(src) == 'userdata' then src = ffi.cast('const char*', src) end + while true do + local dst = ffi.new("char[?]", buflen + 1) -- eof + local ret = ffi.C.WideCharToMultiByte(cp, 0, src, srclen, dst, buflen, nil, nil) + if ret <= 0 then + local err = C.GetLastError() + if err == ERROR_INSUFFICIENT_BUFFER then + buflen = math.ceil(1.5 * buflen) + else + return nil, err + end + else + if ret <= buflen then + return dst, ret + end + end + end + local dst = ffi.new(achar_t, 1) + dst[0] = 0 + return dst,0 +end + +local function LUA_W2M(src,...) + if not src or #src == 0 then return src end + local dst, dstlen = WideCharToMultiByte(src,...) + if not dst then return nil, dstlen end + return ffi.string(dst, dstlen) +end + +local const_pchar_t = ffi.typeof'char*' +local function LUA_M2W(src,...) + if not src or #src == 0 then return src end + local dst, dstlen = MultiByteToWideChar(src,...) + if not dst then return nil, dstlen end + return ffi.string(ffi.cast(const_pchar_t, dst), dstlen*2) +end + +local _M = { + MultiByteToWideChar = MultiByteToWideChar; + WideCharToMultiByte = WideCharToMultiByte; + mbstowcs = LUA_M2W; + wcstombs = LUA_W2M; +} + +return _M diff --git a/extras/hammerlua/lua/path/win32/fs.lua b/extras/hammerlua/lua/path/win32/fs.lua new file mode 100644 index 0000000..fc482c3 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/fs.lua @@ -0,0 +1,860 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +--[[ note GetTempPath +GetTempPath() might ignore the environment variables it's supposed to use (TEMP, TMP, ...) if they are more than 130 characters or so. +http://blogs.msdn.com/b/larryosterman/archive/2010/10/19/because-if-you-do_2c00_-stuff-doesn_2700_t-work-the-way-you-intended_2e00_.aspx + +--------------- + Limit of Buffer Size for GetTempPath +[Note - this behavior does not occur with the latest versions of the OS as of Vista SP1/Windows Server 2008. If anyone has more information about when this condition occurs, please update this content.] + +[Note - this post has been edited based on, and extended by, information in the following post] + +Apparently due to the method used by GetTempPathA to translate ANSI strings to UNICODE, this function itself cannot be told that the buffer is greater than 32766 in narrow convention. Attempting to pass a larger value in nBufferLength will result in a failed RtlHeapFree call in ntdll.dll and subsequently cause your application to call DbgBreakPoint in debug compiles and simple close without warning in release compiles. + +Example: + +// Allocate a 32Ki character buffer, enough to hold even native NT paths. +LPTSTR tempPath = new TCHAR[32767]; +::GetTempPath(32767, tempPath); // Will crash in RtlHeapFree +---------------- +--]] + +local function prequire(...) + local ok, mod = pcall(require, ...) + if not ok then return nil, mod end + return mod +end + +local lua_version do + +local lua_version_t +lua_version = function() + if not lua_version_t then + local version = assert(_VERSION) + local maj, min = version:match("^Lua (%d+)%.(%d+)$") + if maj then lua_version_t = {tonumber(maj),tonumber(min)} + elseif math.type then lua_version_t = {5,3} + elseif not math.mod then lua_version_t = {5,2} + elseif table.pack and not pack then lua_version_t = {5,2} + else lua_version_t = {5,2} end + end + return lua_version_t[1], lua_version_t[2] +end + +end + +local LUA_MAJOR, LUA_MINOR = lua_version() + +local LUA_VER_NUM = LUA_MAJOR * 100 + LUA_MINOR + +local load_bit if LUA_VER_NUM < 503 then + load_bit = function() + return assert(prequire("bit32") or prequire("bit")) + end +else + load_bit = function () + local bit_loader = assert(load[[ + return { + band = function(a, b) return a & b end; + } + ]]) + return assert(bit_loader()) + end +end + +local bit = load_bit() + +local CONST = { + GENERIC_READ = 0x80000000; + GENERIC_WRITE = 0x40000000; + GENERIC_EXECUTE = 0x20000000; + GENERIC_ALL = 0x10000000; + + FILE_FLAG_WRITE_THROUGH = 0x80000000; + FILE_FLAG_NO_BUFFERING = 0x20000000; + FILE_FLAG_RANDOM_ACCESS = 0x10000000; + FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; + FILE_FLAG_DELETE_ON_CLOSE = 0x04000000; + FILE_FLAG_OVERLAPPED = 0x40000000; + + FILE_ATTRIBUTE_ARCHIVE = 0x00000020; -- A file or directory that is an archive file or directory. Applications typically use this attribute to mark files for backup or removal . + FILE_ATTRIBUTE_COMPRESSED = 0x00000800; -- A file or directory that is compressed. For a file, all of the data in the file is compressed. For a directory, compression is the default for newly created files and subdirectories. + FILE_ATTRIBUTE_DEVICE = 0x00000040; -- This value is reserved for system use. + FILE_ATTRIBUTE_DIRECTORY = 0x00000010; -- The handle that identifies a directory. + FILE_ATTRIBUTE_ENCRYPTED = 0x00004000; -- A file or directory that is encrypted. For a file, all data streams in the file are encrypted. For a directory, encryption is the default for newly created files and subdirectories. + FILE_ATTRIBUTE_HIDDEN = 0x00000002; -- The file or directory is hidden. It is not included in an ordinary directory listing. + FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000; -- The directory or user data stream is configured with integrity (only supported on ReFS volumes). It is not included in an ordinary directory listing. The integrity setting persists with the file if it's renamed. If a file is copied the destination file will have integrity set if either the source file or destination directory have integrity set. (This flag is not supported until Windows Server 2012.) + FILE_ATTRIBUTE_NORMAL = 0x00000080; -- A file that does not have other attributes set. This attribute is valid only when used alone. + FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000; -- The file or directory is not to be indexed by the content indexing service. + FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000; -- The user data stream not to be read by the background data integrity scanner (AKA scrubber). When set on a directory it only provides inheritance. This flag is only supported on Storage Spaces and ReFS volumes. It is not included in an ordinary directory listing. This flag is not supported until Windows 8 and Windows Server 2012. + FILE_ATTRIBUTE_OFFLINE = 0x00001000; -- The data of a file is not available immediately. This attribute indicates that the file data is physically moved to offline storage. This attribute is used by Remote Storage, which is the hierarchical storage management software. Applications should not arbitrarily change this attribute. + FILE_ATTRIBUTE_READONLY = 0x00000001; -- A file that is read-only. Applications can read the file, but cannot write to it or delete it. This attribute is not honored on directories. For more information, see You cannot view or change the Read-only or the System attributes of folders in Windows Server 2003, in Windows XP, in Windows Vista or in Windows 7. + FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400; -- A file or directory that has an associated reparse point, or a file that is a symbolic link. + FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200; -- A file that is a sparse file. + FILE_ATTRIBUTE_SYSTEM = 0x00000004; -- A file or directory that the operating system uses a part of, or uses exclusively. + FILE_ATTRIBUTE_TEMPORARY = 0x00000100; -- A file that is being used for temporary storage. File systems avoid writing data back to mass storage if sufficient cache memory is available, because typically, an application deletes a temporary file after the handle is closed. In that scenario, the system can entirely avoid writing the data. Otherwise, the data is written after the handle is closed. + FILE_ATTRIBUTE_VIRTUAL = 0x00010000; -- + + FILE_READ_DATA = 0x00000001; -- file & pipe + FILE_LIST_DIRECTORY = 0x00000001; -- directory + FILE_WRITE_DATA = 0x00000002; -- file & pipe + FILE_ADD_FILE = 0x00000002; -- directory + FILE_APPEND_DATA = 0x00000004; -- file + FILE_ADD_SUBDIRECTORY = 0x00000004; -- directory + FILE_CREATE_PIPE_INSTANCE = 0x00000004; -- named pipe + FILE_READ_EA = 0x00000008; -- file & directory + FILE_WRITE_EA = 0x00000010; -- file & directory + FILE_EXECUTE = 0x00000020; -- file + FILE_TRAVERSE = 0x00000020; -- directory + FILE_DELETE_CHILD = 0x00000040; -- directory + FILE_READ_ATTRIBUTES = 0x00000080; -- all + FILE_WRITE_ATTRIBUTES = 0x00000100; -- all + + FILE_SHARE_READ = 0x00000001; + FILE_SHARE_WRITE = 0x00000002; + FILE_SHARE_DELETE = 0x00000004; + + CREATE_NEW = 1; + CREATE_ALWAYS = 2; + OPEN_EXISTING = 3; + OPEN_ALWAYS = 4; + TRUNCATE_EXISTING = 5; + + FILE_DEVICE_8042_PORT = 0x00000027; + FILE_DEVICE_ACPI = 0x00000032; + FILE_DEVICE_BATTERY = 0x00000029; + FILE_DEVICE_BEEP = 0x00000001; + FILE_DEVICE_BUS_EXTENDER = 0x0000002a; + FILE_DEVICE_CD_ROM = 0x00000002; + FILE_DEVICE_CD_ROM_FILE_SYSTEM = 0x00000003; + FILE_DEVICE_CHANGER = 0x00000030; + FILE_DEVICE_CONTROLLER = 0x00000004; + FILE_DEVICE_DATALINK = 0x00000005; + FILE_DEVICE_DFS = 0x00000006; + FILE_DEVICE_DFS_FILE_SYSTEM = 0x00000035; + FILE_DEVICE_DFS_VOLUME = 0x00000036; + FILE_DEVICE_DISK = 0x00000007; + FILE_DEVICE_DISK_FILE_SYSTEM = 0x00000008; + FILE_DEVICE_DVD = 0x00000033; + FILE_DEVICE_FILE_SYSTEM = 0x00000009; + FILE_DEVICE_FIPS = 0x0000003a; + FILE_DEVICE_FULLSCREEN_VIDEO = 0x00000034; + FILE_DEVICE_INPORT_PORT = 0x0000000a; + FILE_DEVICE_KEYBOARD = 0x0000000b; + FILE_DEVICE_KS = 0x0000002f; + FILE_DEVICE_KSEC = 0x00000039; + FILE_DEVICE_MAILSLOT = 0x0000000c; + FILE_DEVICE_MASS_STORAGE = 0x0000002d; + FILE_DEVICE_MIDI_IN = 0x0000000d; + FILE_DEVICE_MIDI_OUT = 0x0000000e; + FILE_DEVICE_MODEM = 0x0000002b; + FILE_DEVICE_MOUSE = 0x0000000f; + FILE_DEVICE_MULTI_UNC_PROVIDER = 0x00000010; + FILE_DEVICE_NAMED_PIPE = 0x00000011; + FILE_DEVICE_NETWORK = 0x00000012; + FILE_DEVICE_NETWORK_BROWSER = 0x00000013; + FILE_DEVICE_NETWORK_FILE_SYSTEM = 0x00000014; + FILE_DEVICE_NETWORK_REDIRECTOR = 0x00000028; + FILE_DEVICE_NULL = 0x00000015; + FILE_DEVICE_PARALLEL_PORT = 0x00000016; + FILE_DEVICE_PHYSICAL_NETCARD = 0x00000017; + FILE_DEVICE_PRINTER = 0x00000018; + FILE_DEVICE_SCANNER = 0x00000019; + FILE_DEVICE_SCREEN = 0x0000001c; + FILE_DEVICE_SERENUM = 0x00000037; + FILE_DEVICE_SERIAL_MOUSE_PORT = 0x0000001a; + FILE_DEVICE_SERIAL_PORT = 0x0000001b; + FILE_DEVICE_SMARTCARD = 0x00000031; + FILE_DEVICE_SMB = 0x0000002e; + FILE_DEVICE_SOUND = 0x0000001d; + FILE_DEVICE_STREAMS = 0x0000001e; + FILE_DEVICE_TAPE = 0x0000001f; + FILE_DEVICE_TAPE_FILE_SYSTEM = 0x00000020; + FILE_DEVICE_TERMSRV = 0x00000038; + FILE_DEVICE_TRANSPORT = 0x00000021; + FILE_DEVICE_UNKNOWN = 0x00000022; + FILE_DEVICE_VDM = 0x0000002c; + FILE_DEVICE_VIDEO = 0x00000023; + FILE_DEVICE_VIRTUAL_DISK = 0x00000024; + FILE_DEVICE_WAVE_IN = 0x00000025; + FILE_DEVICE_WAVE_OUT = 0x00000026; + + -- If the file is to be moved to a different volume, the function simulates the move by using the CopyFile and DeleteFile functions. + -- If the file is successfully copied to a different volume and the original file is unable to be deleted, the function succeeds leaving the source file intact. + -- This value cannot be used with MOVEFILE_DELAY_UNTIL_REBOOT. + MOVEFILE_COPY_ALLOWED = 0x00000002; + + -- Reserved for future use. + MOVEFILE_CREATE_HARDLINK = 0x00000010; + + -- The system does not move the file until the operating system is restarted. The system moves the file immediately after AUTOCHK is executed, but before creating any paging files. Consequently, this parameter enables the function to delete paging files from previous startups. + -- This value can be used only if the process is in the context of a user who belongs to the administrators group or the LocalSystem account. + -- This value cannot be used with MOVEFILE_COPY_ALLOWED. + -- Windows Server 2003 and Windows XP: For information about special situations where this functionality can fail, and a suggested workaround solution, see Files are not exchanged when Windows Server 2003 restarts if you use the MoveFileEx function to schedule a replacement for some files in the Help and Support Knowledge Base. + MOVEFILE_DELAY_UNTIL_REBOOT = 0x00000004; + + + -- The function fails if the source file is a link source, but the file cannot be tracked after the move. This situation can occur if the destination is a volume formatted with the FAT file system. + MOVEFILE_FAIL_IF_NOT_TRACKABLE = 0x00000020; + + + -- If a file named lpNewFileName exists, the function replaces its contents with the contents of the lpExistingFileName file, provided that security requirements regarding access control lists (ACLs) are met. For more information, see the Remarks section of this topic. + -- This value cannot be used if lpNewFileName or lpExistingFileName names a directory. + MOVEFILE_REPLACE_EXISTING = 0x00000001; + + + -- The function does not return until the file is actually moved on the disk. + -- Setting this value guarantees that a move performed as a copy and delete operation is flushed to disk before the function returns. The flush occurs at the end of the copy operation. + -- This value has no effect if MOVEFILE_DELAY_UNTIL_REBOOT is set. + MOVEFILE_WRITE_THROUGH = 0x00000008; + + LANG_NEUTRAL = 0x00; + LANG_AFRIKAANS = 0x36; + LANG_ALBANIAN = 0x1c; + LANG_ARABIC = 0x01; + LANG_BASQUE = 0x2d; + LANG_BELARUSIAN = 0x23; + LANG_BULGARIAN = 0x02; + LANG_CATALAN = 0x03; + LANG_CHINESE = 0x04; + LANG_CROATIAN = 0x1a; + LANG_CZECH = 0x05; + LANG_DANISH = 0x06; + LANG_DUTCH = 0x13; + LANG_ENGLISH = 0x09; + LANG_ESTONIAN = 0x25; + LANG_FAEROESE = 0x38; + LANG_FARSI = 0x29; + LANG_FINNISH = 0x0b; + LANG_FRENCH = 0x0c; + LANG_GERMAN = 0x07; + LANG_GREEK = 0x08; + LANG_HEBREW = 0x0d; + LANG_HINDI = 0x39; + LANG_HUNGARIAN = 0x0e; + LANG_ICELANDIC = 0x0f; + LANG_INDONESIAN = 0x21; + LANG_ITALIAN = 0x10; + LANG_JAPANESE = 0x11; + LANG_KOREAN = 0x12; + LANG_LATVIAN = 0x26; + LANG_LITHUANIAN = 0x27; + LANG_MACEDONIAN = 0x2f; + LANG_MALAY = 0x3e; + LANG_NORWEGIAN = 0x14; + LANG_POLISH = 0x15; + LANG_PORTUGUESE = 0x16; + LANG_ROMANIAN = 0x18; + LANG_RUSSIAN = 0x19; + LANG_SERBIAN = 0x1a; + LANG_SLOVAK = 0x1b; + LANG_SLOVENIAN = 0x24; + LANG_SPANISH = 0x0a; + LANG_SWAHILI = 0x41; + LANG_SWEDISH = 0x1d; + LANG_THAI = 0x1e; + LANG_TURKISH = 0x1f; + LANG_UKRAINIAN = 0x22; + LANG_VIETNAMESE = 0x2a; + + SUBLANG_NEUTRAL = 0x00; -- language neutral + SUBLANG_DEFAULT = 0x01; -- user default + SUBLANG_SYS_DEFAULT = 0x02; -- system default + + SUBLANG_ARABIC_SAUDI_ARABIA = 0x01; -- Arabic (Saudi Arabia) + SUBLANG_ARABIC_IRAQ = 0x02; -- Arabic (Iraq) + SUBLANG_ARABIC_EGYPT = 0x03; -- Arabic (Egypt) + SUBLANG_ARABIC_LIBYA = 0x04; -- Arabic (Libya) + SUBLANG_ARABIC_ALGERIA = 0x05; -- Arabic (Algeria) + SUBLANG_ARABIC_MOROCCO = 0x06; -- Arabic (Morocco) + SUBLANG_ARABIC_TUNISIA = 0x07; -- Arabic (Tunisia) + SUBLANG_ARABIC_OMAN = 0x08; -- Arabic (Oman) + SUBLANG_ARABIC_YEMEN = 0x09; -- Arabic (Yemen) + SUBLANG_ARABIC_SYRIA = 0x0a; -- Arabic (Syria) + SUBLANG_ARABIC_JORDAN = 0x0b; -- Arabic (Jordan) + SUBLANG_ARABIC_LEBANON = 0x0c; -- Arabic (Lebanon) + SUBLANG_ARABIC_KUWAIT = 0x0d; -- Arabic (Kuwait) + SUBLANG_ARABIC_UAE = 0x0e; -- Arabic (U.A.E) + SUBLANG_ARABIC_BAHRAIN = 0x0f; -- Arabic (Bahrain) + SUBLANG_ARABIC_QATAR = 0x10; -- Arabic (Qatar) + SUBLANG_CHINESE_TRADITIONAL = 0x01; -- Chinese (Taiwan Region) + SUBLANG_CHINESE_SIMPLIFIED = 0x02; -- Chinese (PR China) + SUBLANG_CHINESE_HONGKONG = 0x03; -- Chinese (Hong Kong) + SUBLANG_CHINESE_SINGAPORE = 0x04; -- Chinese (Singapore) + SUBLANG_CHINESE_MACAU = 0x05; -- Chinese (Macau) + SUBLANG_DUTCH = 0x01; -- Dutch + SUBLANG_DUTCH_BELGIAN = 0x02; -- Dutch (Belgian) + SUBLANG_ENGLISH_US = 0x01; -- English (USA) + SUBLANG_ENGLISH_UK = 0x02; -- English (UK) + SUBLANG_ENGLISH_AUS = 0x03; -- English (Australian) + SUBLANG_ENGLISH_CAN = 0x04; -- English (Canadian) + SUBLANG_ENGLISH_NZ = 0x05; -- English (New Zealand) + SUBLANG_ENGLISH_EIRE = 0x06; -- English (Irish) + SUBLANG_ENGLISH_SOUTH_AFRICA = 0x07; -- English (South Africa) + SUBLANG_ENGLISH_JAMAICA = 0x08; -- English (Jamaica) + SUBLANG_ENGLISH_CARIBBEAN = 0x09; -- English (Caribbean) + SUBLANG_ENGLISH_BELIZE = 0x0a; -- English (Belize) + SUBLANG_ENGLISH_TRINIDAD = 0x0b; -- English (Trinidad) + SUBLANG_ENGLISH_ZIMBABWE = 0x0c; -- English (Zimbabwe) + SUBLANG_ENGLISH_PHILIPPINES = 0x0d; -- English (Philippines) + SUBLANG_FRENCH = 0x01; -- French + SUBLANG_FRENCH_BELGIAN = 0x02; -- French (Belgian) + SUBLANG_FRENCH_CANADIAN = 0x03; -- French (Canadian) + SUBLANG_FRENCH_SWISS = 0x04; -- French (Swiss) + SUBLANG_FRENCH_LUXEMBOURG = 0x05; -- French (Luxembourg) + SUBLANG_FRENCH_MONACO = 0x06; -- French (Monaco) + SUBLANG_GERMAN = 0x01; -- German + SUBLANG_GERMAN_SWISS = 0x02; -- German (Swiss) + SUBLANG_GERMAN_AUSTRIAN = 0x03; -- German (Austrian) + SUBLANG_GERMAN_LUXEMBOURG = 0x04; -- German (Luxembourg) + SUBLANG_GERMAN_LIECHTENSTEIN = 0x05; -- German (Liechtenstein) + SUBLANG_ITALIAN = 0x01; -- Italian + SUBLANG_ITALIAN_SWISS = 0x02; -- Italian (Swiss) + SUBLANG_KOREAN = 0x01; -- Korean (Extended Wansung) + SUBLANG_KOREAN_JOHAB = 0x02; -- Korean (Johab) + SUBLANG_LITHUANIAN = 0x01; -- Lithuanian + SUBLANG_LITHUANIAN_CLASSIC = 0x02; -- Lithuanian (Classic) + SUBLANG_MALAY_MALAYSIA = 0x01; -- Malay (Malaysia) + SUBLANG_MALAY_BRUNEI_DARUSSALAM = 0x02; -- Malay (Brunei Darussalam) + SUBLANG_NORWEGIAN_BOKMAL = 0x01; -- Norwegian (Bokmal) + SUBLANG_NORWEGIAN_NYNORSK = 0x02; -- Norwegian (Nynorsk) + SUBLANG_PORTUGUESE = 0x02; -- Portuguese + SUBLANG_PORTUGUESE_BRAZILIAN = 0x01; -- Portuguese (Brazilian) + SUBLANG_SERBIAN_LATIN = 0x02; -- Serbian (Latin) + SUBLANG_SERBIAN_CYRILLIC = 0x03; -- Serbian (Cyrillic) + SUBLANG_SPANISH = 0x01; -- Spanish (Castilian) + SUBLANG_SPANISH_MEXICAN = 0x02; -- Spanish (Mexican) + SUBLANG_SPANISH_MODERN = 0x03; -- Spanish (Modern) + SUBLANG_SPANISH_GUATEMALA = 0x04; -- Spanish (Guatemala) + SUBLANG_SPANISH_COSTA_RICA = 0x05; -- Spanish (Costa Rica) + SUBLANG_SPANISH_PANAMA = 0x06; -- Spanish (Panama) + SUBLANG_SPANISH_DOMINICAN_REPUBLIC = 0x07; -- Spanish (Dominican Republic) + SUBLANG_SPANISH_VENEZUELA = 0x08; -- Spanish (Venezuela) + SUBLANG_SPANISH_COLOMBIA = 0x09; -- Spanish (Colombia) + SUBLANG_SPANISH_PERU = 0x0a; -- Spanish (Peru) + SUBLANG_SPANISH_ARGENTINA = 0x0b; -- Spanish (Argentina) + SUBLANG_SPANISH_ECUADOR = 0x0c; -- Spanish (Ecuador) + SUBLANG_SPANISH_CHILE = 0x0d; -- Spanish (Chile) + SUBLANG_SPANISH_URUGUAY = 0x0e; -- Spanish (Uruguay) + SUBLANG_SPANISH_PARAGUAY = 0x0f; -- Spanish (Paraguay) + SUBLANG_SPANISH_BOLIVIA = 0x10; -- Spanish (Bolivia) + SUBLANG_SPANISH_EL_SALVADOR = 0x11; -- Spanish (El Salvador) + SUBLANG_SPANISH_HONDURAS = 0x12; -- Spanish (Honduras) + SUBLANG_SPANISH_NICARAGUA = 0x13; -- Spanish (Nicaragua) + SUBLANG_SPANISH_PUERTO_RICO = 0x14; -- Spanish (Puerto Rico) + SUBLANG_SWEDISH = 0x01; -- Swedish + SUBLANG_SWEDISH_FINLAND = 0x02; -- Swedish (Finland) + + + -- The system cannot find the file specified. + ERROR_FILE_NOT_FOUND = 2; -- 0x00000002 + -- The system cannot find the path specified. + ERROR_PATH_NOT_FOUND = 3; -- 0x00000003 + -- Cannot create a file when that file already exists. + ERROR_ALREADY_EXISTS = 183; -- 0x000000B7 +} + +local function lshift(v, n) + return math.floor(v * (2 ^ n)) +end + +local function rshift(v, n) + return math.floor(v / (2 ^ n)) +end + +local function FileTimeToTimeT(low, high) + return math.floor(low / 10000000 + high * (2^32 / 10000000)) - 11644473600; +end + +local function TimeTToFileTime(v) + v = 10000000 * (v + 11644473600) + local high = rshift(v,32) + local low = v - lshift(high, 32) + return low, high +end + +local function LargeToNumber(low, high) + return low + high * 2^32 +end + +local function TestBit(flags, flag) + return (0 ~= bit.band(flags, flag)) +end + +local function AttributesToStat(fd) + local flags = fd.dwFileAttributes; + local ctime = FileTimeToTimeT(fd.ftCreationTime.dwLowDateTime, fd.ftCreationTime.dwHighDateTime); + local atime = FileTimeToTimeT(fd.ftLastAccessTime.dwLowDateTime, fd.ftLastAccessTime.dwHighDateTime); + local mtime = FileTimeToTimeT(fd.ftLastWriteTime.dwLowDateTime, fd.ftLastWriteTime.dwHighDateTime); + local size = LargeToNumber (fd.nFileSizeLow, fd.nFileSizeHigh); + + local mode + if TestBit(flags, CONST.FILE_ATTRIBUTE_REPARSE_POINT) then mode = "link" + elseif TestBit(flags, CONST.FILE_ATTRIBUTE_DIRECTORY) then mode = "directory" + else mode = "file" end + + return{ + mode = mode; + nlink = 1; -- number of hard links to the file + uid = 0; -- user-id of owner (Unix only, always 0 on Windows) + gid = 0; -- group-id of owner (Unix only, always 0 on Windows) + ino = 0; + access = atime; + modification = mtime; + change = ctime; + size = size; + } +end + +local function FlagsToMode(flags) + if TestBit(flags, CONST.FILE_ATTRIBUTE_REPARSE_POINT) then return "link" end + if TestBit(flags, CONST.FILE_ATTRIBUTE_DIRECTORY) then return "directory" end + return "file" +end + +local function AttributesToStat2(fd) + local flags = fd.dwFileAttributes; + local ctime = FileTimeToTimeT( fd.ftCreationTime[1], fd.ftCreationTime[2] ); + local atime = FileTimeToTimeT( fd.ftLastAccessTime[1], fd.ftLastAccessTime[2] ); + local mtime = FileTimeToTimeT( fd.ftLastWriteTime[1], fd.ftLastWriteTime[2] ); + local size = LargeToNumber ( fd.nFileSize[1], fd.nFileSize[2] ); + + local mode = FlagsToMode(flags) + + return{ + mode = mode; + nlink = 1; -- number of hard links to the file + uid = 0; -- user-id of owner (Unix only, always 0 on Windows) + gid = 0; -- group-id of owner (Unix only, always 0 on Windows) + ino = 0; + access = atime; + modification = mtime; + change = ctime; + size = size; + } +end + +local function clone(t, o) + if not o then o = {} end + for k, v in pairs(t) do o[k] = v end + return o +end + +local _M = {} + +function _M.currentdir(u) + return u.GetCurrentDirectory() +end + +function _M.attributes(u, P, a) + --- @todo On Windows systems, represents the drive number of the disk containing the file + local dev = 0 + --- @todo decode only one attribute if `a` provided + local attr, err = u.GetFileAttributesEx(P) + if not attr then return nil, err end + local stat = AttributesToStat(attr) + stat.dev, stat.rdev = dev, dev + if a then return stat[a] end + return stat +end + +function _M.flags(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then return nil, err end + return fd.dwFileAttributes +end + +function _M.ctime(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then return nil, err end + return FileTimeToTimeT(fd.ftCreationTime.dwLowDateTime, fd.ftCreationTime.dwHighDateTime) +end + +function _M.atime(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then return nil, err end + return FileTimeToTimeT(fd.ftLastAccessTime.dwLowDateTime, fd.ftLastAccessTime.dwHighDateTime) +end + +function _M.mtime(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then return nil, err end + return FileTimeToTimeT(fd.ftLastWriteTime.dwLowDateTime, fd.ftLastWriteTime.dwHighDateTime) +end + +function _M.size(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then return nil, err end + return LargeToNumber (fd.nFileSizeLow, fd.nFileSizeHigh); +end + +local function file_not_found(err) + return (err == CONST.ERROR_FILE_NOT_FOUND) or (err == CONST.ERROR_PATH_NOT_FOUND) +end + +function _M.exists(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then + if file_not_found(err) then return false end + return nil, err + end + return P +end + +function _M.isdir(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then + if file_not_found(err) then return false end + return nil, err + end + if TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_REPARSE_POINT) then return false end + return TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_DIRECTORY) and P +end + +function _M.isfile(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then + if file_not_found(err) then return false end + return nil, err + end + if TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_REPARSE_POINT) then return false end + return (not TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_DIRECTORY)) and P +end + +function _M.islink(u, P) + local fd, err = u.GetFileAttributesEx(P) + if not fd then + if file_not_found(err) then return false end + return nil, err + end + return TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_REPARSE_POINT) +end + +function _M.mkdir(u, P) + local ok, err = u.CreateDirectory(P) + if not ok then + -- if err == CONST.ERROR_ALREADY_EXISTS then return false end + return ok, err + end + return ok +end + +function _M.rmdir(u, P) + return u.RemoveDirectory(P) +end + +function _M.chdir(u, P) + return u.SetCurrentDirectory(P) +end + +function _M.copy(u, src, dst, force) + return u.CopyFile(src, dst, not force) +end + +function _M.move(u, src, dst, flags) + if flags == nil then flags = CONST.MOVEFILE_COPY_ALLOWED + elseif flags == true then flags = CONST.MOVEFILE_COPY_ALLOWED + CONST.MOVEFILE_REPLACE_EXISTING end + return u.MoveFileEx(src, dst, flags) +end + +function _M.remove(u, P) + return u.DeleteFile(P) +end + +function _M.tmpdir(u) + return u.GetTempPath() +end + +function _M.link() + return nil, "make_link is not supported on Windows"; +end + +function _M.setmode() + return nil, "setmode is not supported by this implementation"; +end + +function _M.dir(u, P) + local h, fd = u.FindFirstFile(P .. u.DIR_SEP .. u.ANY_MASK) + if not h then + local closed = false + + local function close() + if not closed then + closed = true + return + end + error("calling 'next' on bad self (closed directory)", 2) + end + local next + if (fd == CONST.ERROR_FILE_NOT_FOUND) or (fd == CONST.ERROR_PATH_NOT_FOUND) then + next = function() + if closed then + error("calling 'next' on bad self (closed directory)", 2) + end + close() + end + else + next = function() + if closed then + error("calling 'next' on bad self (closed directory)", 2) + end + close() + return nil, fd + end + end + + local obj = { close = close; next = next} + + return obj.next, obj + end + + local closed = false + local obj = { + close = function(self) + if not h then return end + u.FindClose(h) + h, closed = nil, true + end; + next = function(self) + if not h then + if not closed then + closed = true + return + end + error("calling 'next' on bad self (closed directory)", 2) + end + local fname = u.WIN32_FIND_DATA2TABLE(fd).cFileName + local ret, err = u.FindNextFile(h, fd) + if ret == 0 then self:close() closed = false end + return fname + end + } + return obj.next, obj +end + +function _M.touch(u, P, at, mt) + if not at then at = os.time() end + if not mt then mt = at end + local atime = {TimeTToFileTime(at)} + local mtime = {TimeTToFileTime(mt)} + local h, err = u.CreateFile(P, + CONST.GENERIC_READ + CONST.FILE_WRITE_ATTRIBUTES, + CONST.FILE_SHARE_READ + CONST.FILE_SHARE_WRITE, nil, + CONST.OPEN_EXISTING, CONST.FILE_ATTRIBUTE_NORMAL, nil + ) + if not h then return nil, err end + + local ok, err = u.SetFileTime(h, nil, atime, mtime) + u.CloseHandle(h) + if not ok then return nil, err end + return ok +end + +local function findfile(u, P, cb) + local h, fd = u.FindFirstFile(P) + if not h then + if (fd == CONST.ERROR_FILE_NOT_FOUND) or (fd == CONST.ERROR_PATH_NOT_FOUND) then + -- this is not error but just empty result + return + end + return nil, fd + end + repeat + local ret = cb(fd) + if ret then + u.FindClose(h) + return ret + end + ret = u.FindNextFile(h, fd) + until ret == 0; + u.FindClose(h) +end + +local function isdots(P) + return P == '.' or P == '..' + or P == '.\0' or P == '.\0.\0' +end + +local function find_last(str, sub) + local pos = nil + while true do + local next_pos = string.find(str, sub, pos, true) + if not next_pos then return pos end + pos = next_pos + #sub + end +end + +local function splitpath(P, sep) + local pos = find_last(P, sep) + if not pos then return "", P end + return string.sub(P, 1, pos - #sep - 1), string.sub(P, pos) +end + +local foreach_impl +local function do_foreach_recurse(u, base, mask, callback, option) + return findfile(u, base .. u.DIR_SEP .. u.ANY_MASK, function(fd) + if not TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_DIRECTORY) then return end + if option.skiplinks and TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_REPARSE_POINT) then return end + fd = u.WIN32_FIND_DATA2TABLE(fd) + if isdots(fd.cFileName) then return end + return foreach_impl(u, base .. u.DIR_SEP .. fd.cFileName, mask, callback, option) + end) +end + +foreach_impl = function (u, base, mask, callback, option) + local path = base .. u.DIR_SEP + if option.recurse and option.reverse then + local res, err = do_foreach_recurse(u, base, mask, callback, option) + if res or err then return res, err end + end + + local tmp, origin_cb + if option.delay then + tmp, origin_cb, callback = {}, callback, function(base,name,fd) + table.insert(tmp, {base,name,fd}) + end; + end + + local ok, err = findfile(u, path .. mask, function(fd) + local isdir = TestBit(fd.dwFileAttributes, CONST.FILE_ATTRIBUTE_DIRECTORY) + if isdir then if option.skipdirs then return end + else if option.skipfiles then return end end + + fd = u.WIN32_FIND_DATA2TABLE(fd) + if isdir and option.skipdots ~= false and isdots(fd.cFileName) then + return + end + + return callback(base, fd.cFileName, fd) + end) + + if ok or err then return ok, err end + + if option.delay then + for _, t in pairs(tmp) do + local ok, err = origin_cb(t[1], t[2], t[3]) + if ok or err then return ok, err end + end + end + + if option.recurse and not option.reverse then + local res, err = do_foreach_recurse(u, base, mask, origin_cb or callback, option) + if res or err then return res, err end + end + +end + +function _M.foreach(u, base, callback, option) + local base, mask = splitpath(base, u.DIR_SEP) + if mask == '' then mask = u.ANY_MASK end + return foreach_impl(u, base, mask, function(base, name, fd) + return callback(base .. u.DIR_SEP .. name, AttributesToStat2(fd)) + end, option or {}) +end + +local attribs = { + f = function(u, base, name, fd) return base..u.DIR_SEP..name end; + p = function(u, base, name, fd) return base end; + n = function(u, base, name, fd) return name end; + m = function(u, base, name, fd) return FlagsToMode(fd.dwFileAttributes) end; + a = function(u, base, name, fd) return AttributesToStat2(fd) end; + z = function(u, base, name, fd) return LargeToNumber ( fd.nFileSize[1], fd.nFileSize[2] ) end; + t = function(u, base, name, fd) return FileTimeToTimeT( fd.ftLastWriteTime[1], fd.ftLastWriteTime[2] ) end; + c = function(u, base, name, fd) return FileTimeToTimeT( fd.ftCreationTime[1], fd.ftCreationTime[2] ) end; + l = function(u, base, name, fd) return FileTimeToTimeT( fd.ftLastAccessTime[1], fd.ftLastAccessTime[2] ) end; +} + +local function make_attrib(str) + local t = {} + for i = 1, #str do + local ch = str:sub(i,i) + local fn = attribs[ ch ] + if not fn then return nil, 'unknown file attribute: ' .. ch end + table.insert(t, fn) + end + + return function(...) + local res = {n = #t} + for i, f in ipairs(t) do + local ok, err = f(...) + if ok == nil then return nil, err end + table.insert(res, ok) + end + return res + end +end + +function _M.each_impl(u, option) + if not option.file then return nil, 'no file mask present' end + local base, mask = splitpath( option.file, u.DIR_SEP ) + if mask == '' then mask = u.ANY_MASK end + + local get_params, err = make_attrib(option.param or 'f') + if not get_params then return nil, err end + local unpack = unpack or table.unpack + + local filter = option.filter + + if option.callback then + local callback = option.callback + + local function cb(base, name, path, fd) + local params = assert(get_params(u, base, name, path, fd)) + if filter and (not filter(unpack(params, 1, params.n))) then return end + return callback(unpack(params, 1, params.n)) + end + + return foreach_impl(u, base, mask, cb, option) + else + local function cb(base, name, path, fd) + local params = assert(get_params(u, base, name, path, fd)) + if filter and (not filter(unpack(params, 1, params.n))) then return end + coroutine.yield(params) + end + local co = coroutine.create(function() + foreach_impl(u, base, mask, cb, option) + end) + return function() + local status, params = coroutine.resume(co) + if status then if params then return unpack(params, 1, params.n) end + else error(params, 2) end + end + end +end + +local create_each = require "path.findfile".load + +local LOADED = {} +local function load(ltype, sub) + local M = LOADED[ltype .. "/" .. sub] + if M then return M end + local IMPL = require("path.win32." .. ltype ..".fs")[sub] + M = { + CONST = CONST; + DIR_SEP = IMPL.DIR_SEP; + } + for k, v in pairs(_M) do + if type(v) ~= "function" then M[k] = v + else M[k] = function(...) return v(IMPL, ...) end end + end + local each_impl = _M.each_impl + M.each = create_each(function(...) return each_impl(IMPL, ...) end) + + LOADED[ltype .. "/" .. sub] = M + return M +end + +return { + load = load +} diff --git a/extras/hammerlua/lua/path/win32/wcs.lua b/extras/hammerlua/lua/path/win32/wcs.lua new file mode 100644 index 0000000..89c4745 --- /dev/null +++ b/extras/hammerlua/lua/path/win32/wcs.lua @@ -0,0 +1,65 @@ +------------------------------------------------------------------ +-- +-- Author: Alexey Melnichuk +-- +-- Copyright (C) 2013-2016 Alexey Melnichuk +-- +-- Licensed according to the included 'LICENCE' document +-- +-- This file is part of lua-path library. +-- +------------------------------------------------------------------ + +local CP_ACP = 0 -- default to ANSI code page +local CP_OEM = 1 -- default to OEM code page +local CP_MAC = 2 -- default to MAC code page +local CP_THREAD_ACP = 3 -- current thread's ANSI code page +local CP_SYMBOL = 42 -- SYMBOL translations +local CP_UTF7 = 65000 -- UTF-7 translation +local CP_UTF8 = 65001 -- UTF-8 translation + +local LOADED = {} + +local function load(type) + if LOADED[type] then return LOADED[type] end + local IMPL = require("path.win32." .. type ..".wcs") + + + local wcstoutf8 = function (str) return IMPL.wcstombs(str, CP_UTF8) end + local utf8towcs = function (str) return IMPL.mbstowcs(str, CP_UTF8) end + + local wcstoansi = function (str) return IMPL.wcstombs(str, CP_ACP) end + local ansitowcs = function (str) return IMPL.mbstowcs(str, CP_ACP) end + + local wcstooem = function (str) return IMPL.wcstombs(str, CP_OEM) end + local oemtowcs = function (str) return IMPL.mbstowcs(str, CP_OEM) end + + local _M = { + MultiByteToWideChar = IMPL.MultiByteToWideChar; + WideCharToMultiByte = IMPL.WideCharToMultiByte; + mbstowcs = IMPL.mbstowcs; + wcstombs = IMPL.wcstombs; + wcstoutf8 = wcstoutf8; + utf8towcs = utf8towcs; + wcstoansi = wcstoansi; + ansitowcs = ansitowcs; + wcstooem = wcstooem; + oemtowcs = oemtowcs; + CP = { + ACP = CP_ACP; + OEM = CP_OEM; + MAC = CP_MAC; + THREAD_ACP = CP_THREAD_ACP; + SYMBOL = CP_SYMBOL; + UTF7 = CP_UTF7; + UTF8 = CP_UTF8; + } + } + + LOADED[type] = _M + return _M +end + +return { + load = load +} \ No newline at end of file diff --git a/extras/hammerlua/lua/shell.lua b/extras/hammerlua/lua/shell.lua new file mode 100644 index 0000000..4f65f22 --- /dev/null +++ b/extras/hammerlua/lua/shell.lua @@ -0,0 +1,27 @@ + +local shell = {} + +function shell.escape(args) + local ret = {} + for _,a in pairs(args) do + s = tostring(a) + if s:match("[^A-Za-z0-9_/:=-]") then + s = '"'..s:gsub('"', "\\\"")..'"' + end + table.insert(ret,s) + end + return table.concat(ret, " ") +end + +function shell.run(args) + local h = io.popen(shell.escape(args)) + local outstr = h:read("*a") + return h:close(), outstr +end + +function shell.execute(args) + return os.execute(shell.escape(args)) +end + +return shell + diff --git a/extras/hammerlua/lua/xml2lua.lua b/extras/hammerlua/lua/xml2lua.lua new file mode 100644 index 0000000..55f3c8a --- /dev/null +++ b/extras/hammerlua/lua/xml2lua.lua @@ -0,0 +1,541 @@ +--- Overview: +-- ========= +-- +-- This module provides a non-validating XML stream parser in Lua. +-- +-- Features: +-- ========= +-- +-- * Tokenises well-formed XML (relatively robustly) +-- * Flexible handler based event API (see below) +-- * Parses all XML Infoset elements - ie. +-- - Tags +-- - Text +-- - Comments +-- - CDATA +-- - XML Decl +-- - Processing Instructions +-- - DOCTYPE declarations +-- * Provides limited well-formedness checking +-- (checks for basic syntax & balanced tags only) +-- * Flexible whitespace handling (selectable) +-- * Entity Handling (selectable) +-- +-- Limitations: +-- ============ +-- +-- * Non-validating +-- * No charset handling +-- * No namespace support +-- * Shallow well-formedness checking only (fails +-- to detect most semantic errors) +-- +-- API: +-- ==== +-- +-- The parser provides a partially object-oriented API with +-- functionality split into tokeniser and handler components. +-- +-- The handler instance is passed to the tokeniser and receives +-- callbacks for each XML element processed (if a suitable handler +-- function is defined). The API is conceptually similar to the +-- SAX API but implemented differently. +-- +-- The following events are generated by the tokeniser +-- +-- handler:start - Start Tag +-- handler:end - End Tag +-- handler:text - Text +-- handler:decl - XML Declaration +-- handler:pi - Processing Instruction +-- handler:comment - Comment +-- handler:dtd - DOCTYPE definition +-- handler:cdata - CDATA +-- +-- The function prototype for all the callback functions is +-- +-- callback(val, attrs, start, end) +-- +-- where attrs is a table and val/attrs are overloaded for +-- specific callbacks - ie. +-- +-- Callback val attrs (table) +-- -------- --- ------------- +-- start name { attributes (name=val).. } +-- end name nil +-- text nil +-- cdata nil +-- decl "xml" { attributes (name=val).. } +-- pi pi name { attributes (if present).. +-- _text = +-- } +-- comment nil +-- dtd root element { _root = , +-- _type = SYSTEM|PUBLIC, +-- _name = , +-- _uri = , +-- _internal = +-- } +-- +-- (start & end provide the character positions of the start/end +-- of the element) +-- +-- XML data is passed to the parser instance through the 'parse' +-- method (Note: must be passed a single string currently) +-- +-- License: +-- ======== +-- +-- This code is freely distributable under the terms of the [MIT license](LICENSE). +-- +-- +--@author Paul Chakravarti (paulc@passtheaardvark.com) +--@author Manoel Campos da Silva Filho + +local _G, print, string, table, pairs, type, tostring, tonumber, error, io, setmetatable, getmetatable + = + _G, print, string, table, pairs, type, tostring, tonumber, error, io, setmetatable, getmetatable + +module "xml2lua" + +---Converts the decimal code of a character to its corresponding char +--if it's a graphical char, otherwise, returns the HTML ISO code +--for that decimal value in the format &#code +--@param code the decimal value to convert to its respective character +local function decimalToHtmlChar(code) + local n = tonumber(code) + if n >= 0 and n < 256 then + return string.char(n) + else + return "&#"..code..";" + end +end + +---Converts the hexadecimal code of a character to its corresponding char +--if it's a graphical char, otherwise, returns the HTML ISO code +--for that hexadecimal value in the format ode +--@param code the hexadecimal value to convert to its respective character +local function hexadecimalToHtmlChar(code) + local n = tonumber(code, 16) + if n >= 0 and n < 256 then + return string.char(n) + else + return "&#x"..code..";" + end +end + + +---Recursivelly prints a table in an easy-to-ready format +--@param tb The table to be printed +--@param level the indentation level to start with +local function printableInternal(tb, level) + level = level or 1 + local spaces = string.rep(' ', level*2) + for k,v in pairs(tb) do + if type(v) == "table" then + print(spaces .. k) + printableInternal(v, level+1) + else + print(spaces .. k..'='..v) + end + end +end + +---Recursivelly prints a table in an easy-to-ready format +--@param tb The table to be printed +function printable(tb) + printableInternal(tb) +end + +---Handler to generate a string prepresentation of a table +--Convenience function for printHandler (Does not support recursive tables). +--@param t Table to be parsed +--@return a string representation of the table +function showTable(t) + local sep = '' + local res = '' + if type(t) ~= 'table' then + return t + end + + for k,v in pairs(t) do + if type(v) == 'table' then + v = showTable(v) + end + res = res .. sep .. string.format("%s=%s", k, v) + sep = ',' + end + res = '{'..res..'}' + + return res +end + + +---Class to parse XML +XmlParser = { + -- Available options are - + -- + -- * stripWS + -- + -- Strip non-significant whitespace (leading/trailing) + -- and do not generate events for empty text elements + -- + -- * expandEntities + -- + -- Expand entities (standard entities + single char + -- numeric entities only currently - could be extended + -- at runtime if suitable DTD parser added elements + -- to table (see obj._ENTITIES). May also be possible + -- to expand multibyre entities for UTF-8 only + -- + -- * errorHandler + -- + -- Custom error handler function + -- + -- NOTE: Boolean options must be set to 'nil' not '0' + options = {}, + + -- Private attribures/functions + _stack = {}, + _handler = {}, + + _XML = '^([^<]*)<(%/?)([^>]-)(%/?)>', + _ATTR1 = '([%w-:_]+)%s*=%s*"(.-)"', + _ATTR2 = '([%w-:_]+)%s*=%s*\'(.-)\'', + _CDATA = '<%!%[CDATA%[(.-)%]%]>', + _PI = '<%?(.-)%?>', + _COMMENT = '', + _TAG = '^(.-)%s.*', + _LEADINGWS = '^%s+', + _TRAILINGWS = '%s+$', + _WS = '^%s*$', + _DTD1 = '', + _DTD2 = '', + _DTD3 = '', + _DTD4 = '', + _DTD5 = '', + + _ATTRERR1 = '=%s*"[^"]*$', + _ATTRERR2 = '=%s*\'[^\']*$', + _TAGEXT = '(%/?)>', + + _errstr = { + xmlErr = "Error Parsing XML", + declErr = "Error Parsing XMLDecl", + declStartErr = "XMLDecl not at start of document", + declAttrErr = "Invalid XMLDecl attributes", + piErr = "Error Parsing Processing Instruction", + commentErr = "Error Parsing Comment", + cdataErr = "Error Parsing CDATA", + dtdErr = "Error Parsing DTD", + endTagErr = "End Tag Attributes Invalid", + unmatchedTagErr = "Unbalanced Tag", + incompleteXmlErr = "Incomplete XML Document", + }, + + _ENTITIES = { + ["<"] = "<", + [">"] = ">", + ["&"] = "&", + ["""] = '"', + ["'"] = "'", + ["&#(%d+);"] = decimalToHtmlChar, + ["&#x(%x+);"] = hexadecimalToHtmlChar + } +} + +---Instantiates a XmlParser object to parse a XML string +--@param handler Handler module to be used to convert the XML string +--to another formats. See the available handlers at the handler directory. +-- Usually you get an instance to a handler module using, for instance: +-- local handler = require("xml2lua/handler/tree"). +--@return a XmlParser object used to parse the XML +--@see XmlParser +function parser(handler) + if handler == _G["xml2lua"] then + error("You must call xml2lua.parse(handler) instead of xml2lua:parse(handler)") + end + + local obj = { + _handler = handler, + + -- Public attributes + options = { + --Indicates if whitespaces should be striped or not + stripWS = 1, + expandEntities = 1, + errorHandler = function(errMsg, pos) + error(string.format("%s [char=%d]\n", errMsg or "Parse Error", pos)) + end + } + } + + setmetatable(obj, XmlParser) + XmlParser.__index = XmlParser + + return obj +end + +--Main function which starts the XML parsing process +--@param str the XML string to parse +--@param parseAttributes indicates if tag attributes should be parsed or not. If omitted, the default value is true. +function XmlParser:parse(str, parseAttributes) + if type(self) ~= "table" or getmetatable(self) ~= XmlParser then + error("You must call xmlparser:parse(parametrs) instead of xmlparser.parse(parametrs)") + end + + if parseAttributes == nil then + parseAttributes = true + end + self._handler.parseAttributes = parseAttributes + + local match, endmatch, pos = 0, 0, 1 + local text, endt1, endt2, tagstr, tagname, attrs, starttext, endtext + local errstart, errend, extstart, extend + while match do + -- Get next tag (first pass - fix exceptions below) + match, endmatch, text, endt1, tagstr, endt2 = string.find(str, self._XML, pos) + if not match then + if string.find(str, self._WS, pos) then + -- No more text - check document complete + if #self._stack ~= 0 then + self:_err(self._errstr.incompleteXmlErr,pos) + else + break + end + else + -- Unparsable text + self:_err(self._errstr.xmlErr,pos) + end + end + + -- Handle leading text + starttext = match + endtext = match + string.len(text) - 1 + match = match + string.len(text) + text = self:_parseEntities(self:_stripWS(text)) + if text ~= "" and self._handler.text then + self._handler:text(text,nil,match,endtext) + end + + -- Test for tag type + if string.find(string.sub(tagstr,1,5),"?xml%s") then + -- XML Declaration + match,endmatch,text = string.find(str, self._PI, pos) + if not match then + self:_err(self._errstr.declErr,pos) + end + if match ~= 1 then + -- Must be at start of doc if present + self:_err(self._errstr.declStartErr,pos) + end + tagname,attrs = self:_parseTag(text) + -- TODO: Check attributes are valid + -- Check for version (mandatory) + if attrs.version == nil then + self:_err(self._errstr.declAttrErr,pos) + end + if self._handler.decl then + self._handler:decl(tagname,attrs,match,endmatch) + end + elseif string.sub(tagstr,1,1) == "?" then + -- Processing Instruction + match,endmatch,text = string.find(str,self._PI,pos) + if not match then + self:_err(self._errstr.piErr,pos) + end + if self._handler.pi then + -- Parse PI attributes & text + tagname,attrs = self:_parseTag(text) + local pi = string.sub(text,string.len(tagname)+1) + if pi ~= "" then + if attrs then + attrs._text = pi + else + attrs = { _text = pi } + end + end + self._handler:pi(tagname,attrs,match,endmatch) + end + elseif string.sub(tagstr,1,3) == "!--" then + -- Parse a Comment + match,endmatch,text = string.find(str,self._COMMENT,pos) + if not match then + self:_err(self._errstr.commentErr,pos) + end + if self._handler.comment then + text = self:_parseEntities(self:_stripWS(text)) + self._handler:comment(text,next,match,endmatch) + end + elseif string.sub(tagstr,1,8) == "!DOCTYPE" then + -- Parse DTD + match,endmatch,attrs = self:_parseDTD(string,pos) + if not match then + self:_err(self._errstr.dtdErr,pos) + end + if self._handler.dtd then + self._handler:dtd(attrs._root,attrs,match,endmatch) + end + elseif string.sub(tagstr,1,8) == "![CDATA[" then + -- Parse CDATA + match,endmatch,text = string.find(str,self._CDATA,pos) + if not match then + self:_err(self._errstr.cdataErr,pos) + end + if self._handler.cdata then + self._handler:cdata(text,nil,match,endmatch) + end + else + -- Parse a Normal tag + -- Need check for embedded '>' in attribute value and extend + -- match recursively if necessary eg. + while 1 do + errstart,errend = string.find(tagstr,self._ATTRERR1) + if errend == nil then + errstart,errend = string.find(tagstr,self._ATTRERR2) + if errend == nil then + break + end + end + extstart,extend,endt2 = string.find(str,self._TAGEXT,endmatch+1) + tagstr = tagstr .. string.sub(str,endmatch,extend-1) + if not match then + self:_err(self._errstr.xmlErr,pos) + end + endmatch = extend + end + + -- Extract tagname/attrs + tagname,attrs = self:_parseTag(tagstr) + + if (endt1=="/") then + -- End tag + if self._handler.endtag then + if attrs then + -- Shouldnt have any attributes in endtag + self:_err(string.format("%s (/%s)", + self._errstr.endTagErr, + tagname) + ,pos) + end + if table.remove(self._stack) ~= tagname then + self:_err(string.format("%s (/%s)", + self._errstr.unmatchedTagErr, + tagname) + ,pos) + end + self._handler:endtag(tagname,nil,match,endmatch) + end + else + -- Start Tag + table.insert(self._stack,tagname) + if self._handler.starttag then + self._handler:starttag(tagname,attrs,match,endmatch) + end + --TODO: Tags com fechamento automático estão sendo + --retornadas como uma tabela, o que complica + --para a app NCLua tratar isso. É preciso + --fazer com que seja retornado um campo string vazio. + -- Self-Closing Tag + if (endt2=="/") then + table.remove(self._stack) + if self._handler.endtag then + self._handler:endtag(tagname,nil,match,endmatch) + end + end + end + end + pos = endmatch + 1 + end +end + + +-- Private functions ---------------------------------------- + +function XmlParser:_err(err, pos) + if self.options.errorHandler then + self.options.errorHandler(err,pos) + end +end + +--Removes whitespaces +function XmlParser:_stripWS(s) + if self.options.stripWS then + s = string.gsub(s,'^%s+','') + s = string.gsub(s,'%s+$','') + end + return s +end + +function XmlParser:_parseEntities(s) + if self.options.expandEntities then + --for k,v in self._ENTITIES do + for k,v in pairs(self._ENTITIES) do + --print (k, v) + s = string.gsub(s,k,v) + end + end + + return s +end + +function XmlParser:_parseDTD(s, pos) + -- match,endmatch,root,type,name,uri,internal + local m,e,r,t,n,u,i + m,e,r,t,u,i = string.find(s,self._DTD1,pos) + if m then + return m,e,{_root=r,_type=t,_uri=u,_internal=i} + end + m,e,r,t,n,u,i = string.find(s,self._DTD2,pos) + if m then + return m,e,{_root=r,_type=t,_name=n,_uri=u,_internal=i} + end + m,e,r,i = string.find(s,self._DTD3,pos) + if m then + return m,e,{_root=r,_internal=i} + end + m,e,r,t,u = string.find(s,self._DTD4,pos) + if m then + return m,e,{_root=r,_type=t,_uri=u} + end + m,e,r,t,n,u = string.find(s,self._DTD5,pos) + if m then + return m,e,{_root=r,_type=t,_name=n,_uri=u} + end + + return nil +end + +---Parses a string representing a tag +--@param s String containing tag text +--@return Returns a string containing the tagname and a table attrs +--containing the atributtes of tag +function XmlParser:_parseTag(s) + local attrs = {} + local tagname = string.gsub(s,self._TAG,'%1') + local parseFunction = function (k,v) + attrs[string.lower(k)]=self:_parseEntities(v) + attrs._ = 1 + end + + string.gsub(s, self._ATTR1, parseFunction) + string.gsub(s, self._ATTR2, parseFunction) + + if attrs._ then + attrs._ = nil + else + attrs = nil + end + + return tagname, attrs +end + +function loadFile(xmlFilePath) + local f, e = io.open(xmlFilePath, "r") + if f then + --Gets the entire file content and stores into a string + return f:read("*a") + end + + error(e) +end diff --git a/extras/hammerlua/lua/xmlhandler/README.md b/extras/hammerlua/lua/xmlhandler/README.md new file mode 100644 index 0000000..bd0c832 --- /dev/null +++ b/extras/hammerlua/lua/xmlhandler/README.md @@ -0,0 +1,15 @@ +# XML Handlers +This directory contains a list of handlers used by the XML parser to process a XML producing different results. +There are currently 3 available handlers: + +- tree: generates a lua table from an XML content string (the most common used handler). +- print: generates a simple event trace which outputs messages to the terminal during the XML parsing, usually for debugging purposes. +- dom: generates a DOM-like node tree structure with a single ROOT node parent. + +# Usage +To get a handler instance you must call, for instance, `handler = require("xmlhandler/tree")`. +Then, you have to use one the handler instance when getting an instance of the XML parser using `parser = xml2lua.parser(handler)`. +Notice the module `xml2lua` should have been loaded before using `require("xml2lua")`. +This way, the handler is called internally when the `parser:parse(xml)` function is called. + +Check the documentation on the root directory for complete examples. \ No newline at end of file diff --git a/extras/hammerlua/lua/xmlhandler/dom.lua b/extras/hammerlua/lua/xmlhandler/dom.lua new file mode 100644 index 0000000..8c51885 --- /dev/null +++ b/extras/hammerlua/lua/xmlhandler/dom.lua @@ -0,0 +1,101 @@ +---- +-- Handler to generate a DOM-like node tree structure with +-- a single ROOT node parent - each node is a table comprising +-- fields below. +-- +-- node = { _name = , +-- _type = ROOT|ELEMENT|TEXT|COMMENT|PI|DECL|DTD, +-- _attr = { Node attributes - see callback API }, +-- _parent = +-- _children = { List of child nodes - ROOT/NODE only } +-- } +-- +-- The dom structure is capable of representing any valid XML document +-- +-- Options +-- ======= +-- options.(comment|pi|dtd|decl)Node = bool +-- - Include/exclude given node types +-- +-- License: +-- ======== +-- +-- This code is freely distributable under the terms of the [MIT license](LICENSE). +-- +--@author Paul Chakravarti (paulc@passtheaardvark.com) +--@author Manoel Campos da Silva Filho + +local _G, print, string, table, pairs, type, tostring, tonumber, error, io + = + _G, print, string, table, pairs, type, tostring, tonumber, error, io + +module "xmlhandler/dom" + +options = {commentNode=1, piNode=1, dtdNode=1, declNode=1} +root = { _children = {n=0}, _type = "ROOT" } +current = root + +function starttag(self, t, a) + local node = { _type = 'ELEMENT', + _name = t, + _attr = a, + _parent = self.current, + _children = {n=0} } + table.insert(self.current._children,node) + self.current = node +end + +function endtag(self, t, s) + if t ~= self.current._name then + error("XML Error - Unmatched Tag ["..s..":"..t.."]\n") + end + self.current = self.current._parent +end + +function text(self, t) + local node = { _type = "TEXT", + _parent = self.current, + _text = t } + table.insert(self.current._children,node) +end + +function comment(self, t) + if self.options.commentNode then + local node = { _type = "COMMENT", + _parent = self.current, + _text = t } + table.insert(self.current._children,node) + end +end + +function pi(self, t, a) + if self.options.piNode then + local node = { _type = "PI", + _name = t, + _attr = a, + _parent = self.current } + table.insert(self.current._children,node) + end +end + +function decl(self, t, a) + if self.options.declNode then + local node = { _type = "DECL", + _name = t, + _attr = a, + _parent = self.current } + table.insert(self.current._children,node) + end +end + +function dtd(self, t, a) + if self.options.dtdNode then + local node = { _type = "DTD", + _name = t, + _attr = a, + _parent = self.current } + table.insert(self.current._children,node) + end +end + +cdata = text diff --git a/extras/hammerlua/lua/xmlhandler/print.lua b/extras/hammerlua/lua/xmlhandler/print.lua new file mode 100644 index 0000000..2a68b88 --- /dev/null +++ b/extras/hammerlua/lua/xmlhandler/print.lua @@ -0,0 +1,69 @@ +---Handler to generate a simple event trace which +--outputs messages to the terminal during the XML +--parsing, usually for debugging purposes. +-- +-- License: +-- ======== +-- +-- This code is freely distributable under the terms of the [MIT license](LICENSE). +-- +--@author Paul Chakravarti (paulc@passtheaardvark.com) +--@author Manoel Campos da Silva Filho + +local _G, print, string, table, pairs, type, tostring, tonumber, error, io + = + _G, print, string, table, pairs, type, tostring, tonumber, error, io + +module "xmlhandler/print" + +function starttag(self, t, a, s, e) + io.write("Start : "..t.."\n") + if a then + for k,v in pairs(a) do + io.write(string.format(" + %s='%s'\n",k,v)) + end + end +end + +function endtag(self, t, s, e) + io.write("End : "..t.."\n") +end + +function text(self, t, s, e) + io.write("Text : "..t.."\n") +end + +function cdata(self, t, s, e) + io.write("CDATA : "..t.."\n") +end + +function comment(self, t, s, e) + io.write("Comment : "..t.."\n") +end + +function dtd(self, t, a, s, e) + io.write("DTD : "..t.."\n") + if a then + for k,v in pairs(a) do + io.write(string.format(" + %s='%s'\n",k,v)) + end + end +end + +function pi(self, t, a, s, e) + io.write("PI : "..t.."\n") + if a then + for k,v in pairs(a) do + io. write(string.format(" + %s='%s'\n",k,v)) + end + end +end + +function decl(self, t, a, s, e) + io.write("XML Decl : "..t.."\n") + if a then + for k,v in pairs(a) do + io.write(string.format(" + %s='%s'\n",k,v)) + end + end +end \ No newline at end of file diff --git a/extras/hammerlua/lua/xmlhandler/tree.lua b/extras/hammerlua/lua/xmlhandler/tree.lua new file mode 100644 index 0000000..1f5a0b7 --- /dev/null +++ b/extras/hammerlua/lua/xmlhandler/tree.lua @@ -0,0 +1,147 @@ +---Handler to generate a lua table from an XML content string. +-- It is a simplified handler which attempts +-- to generate a more 'natural' table based structure which +-- supports many common XML formats. +-- +-- The XML tree structure is mapped directly into a recursive +-- table structure with node names as keys and child elements +-- as either a table of values or directly as a string value +-- for text. Where there is only a single child element this +-- is inserted as a named key - if there are multiple +-- elements these are inserted as a vector (in some cases it +-- may be preferable to always insert elements as a vector +-- which can be specified on a per element basis in the +-- options). Attributes are inserted as a child element with +-- a key of '_attr'. +-- +-- Only Tag/Text & CDATA elements are processed - all others +-- are ignored. +-- +-- This format has some limitations - primarily +-- +-- * Mixed-Content behaves unpredictably - the relationship +-- between text elements and embedded tags is lost and +-- multiple levels of mixed content does not work +-- * If a leaf element has both a text element and attributes +-- then the text must be accessed through a vector (to +-- provide a container for the attribute) +-- +-- In general however this format is relatively useful. +-- +-- It is much easier to understand by running some test +-- data through 'textxml.lua -simpletree' than to read this) +-- +-- Options +-- ======= +-- options.noReduce = { = bool,.. } +-- - Nodes not to reduce children vector even if only +-- one child +-- +-- License: +-- ======== +-- +-- This code is freely distributable under the terms of the [MIT license](LICENSE). +-- +--@author Paul Chakravarti (paulc@passtheaardvark.com) +--@author Manoel Campos da Silva Filho + +local _G, print, string, table, pairs, type, tostring, tonumber, error, io + = + _G, print, string, table, pairs, type, tostring, tonumber, error, io + +module "xmlhandler/tree" + +root = {} +stack = {root; n=1} +options = {noreduce = {}} + +--Gets the first key of a table +--@param tb table to get its first key +--@return the table's first key, nil if the table is empty +--or the given parameter if it isn't a table +local function getFirstKey(tb) + if type(tb) == "table" then + for k, v in pairs(tb) do + return k + end + + return nil + end + + return tb +end + +function reduce(self, node, key, parent) + -- Recursively remove redundant vectors for nodes + -- with single child elements + for k,v in pairs(node) do + if type(v) == 'table' then + self:reduce(v,k,node) + end + end + if #node == 1 and not self.options.noreduce[key] and + node._attr == nil then + parent[key] = node[1] + else + node.n = nil + end +end + +---Parses a start tag +--@param t Table that represents a XML tag +--@param a Attributes table (_attr) +function starttag(self, t, a) + local node = {} + if self.parseAttributes == true then + node._attr=a + end + + local current = self.stack[#self.stack] + if current[t] then + table.insert(current[t],node) + else + current[t] = {node;n=1} + end + table.insert(self.stack,node) +end + +---Parses an end tag +--@param t Tag name +function endtag(self, t, s) + --Tabela que representa a tag atualmente sendo processada + local current = self.stack[#self.stack] + --Tabela que representa a tag na qual a tag + --atual está contida. + local prev = self.stack[#self.stack-1] + if not prev[t] then + error("XML Error - Unmatched Tag ["..s..":"..t.."]\n") + end + if prev == self.root then + -- Once parsing complete recursively reduce tree + self:reduce(prev,nil,nil) + end + + local firstKey = getFirstKey(current) + --Se a primeira chave da tabela que representa + --a tag atual não possui nenhum elemento, + --é porque não há nenhum valor associado à tag + -- (como nos casos de tags automaticamente fechadas como ). + --Assim, atribui uma string vazia a mesma para + --que seja retornado vazio no lugar da tag e não + --uma tabela. Retornando uma string vazia + --simplifica para as aplicações NCLua + --para imprimir tal valor. + if firstKey == nil then + current[t] = "" + prev[t] = "" + end + + table.remove(self.stack) +end + +function text(self, t) + local current = self.stack[#self.stack] + table.insert(current,t) +end + +cdata = text \ No newline at end of file diff --git a/extras/hammerlua/main.lua b/extras/hammerlua/main.lua new file mode 100644 index 0000000..1068f8f --- /dev/null +++ b/extras/hammerlua/main.lua @@ -0,0 +1,216 @@ +local w = require'winapi' +socket = require("socket") +local wx = require'wx' +local Hammer = require'hammer' +local svn = require'svn' +local Point = wx.wxPoint +local Size = wx.wxSize +local ffi = require'ffi' +print"\nStarting luahammer" +local Path=require'path' + + +local ID = setmetatable({}, { + __index = function(self, k) + local val = wx.wxNewId() + self[k] = val + + return val + end +}) + +local process = winapi.get_current_process() +local winapi_us_window + +function window() + if winapi_us_window then return winapi_us_window end + winapi_us_window = winapi.window_from_handle(tonumber(ffi.cast('uintptr_t', ffi.cast('void *', frame:GetHandle())))) + + return winapi_us_window +end + +function sleep(sec) + wx.wxMilliSleep(sec * 1000) +end + +local dbg = print +local printerr = print +local lx, ly, lw, lh = 0, 0, 0, 0 + +function doresize(hammer, frame) + local x, y = hammer:get_position() + local w, h = hammer:get_bounds() + local mod + + if lx ~= x then + mod = true + lx = x + end + + if ly ~= y then + mod = true + ly = y + end + + if lw ~= w then + mod = true + lw = w + end + + if lh ~= h then + mod = true + lh = h + end + + if mod then + local bw, bh = frame:GetSize() + bw, bh = bw:GetWidth(), bw:GetHeight() + frame:SetPosition(wx.wxPoint(x + w * .6 - bw * .5, y)) + end +end + +local think_hook +frameheight = 50 + +function main() + frame = wx.wxFrame(wx.NULL, wx.wxID_ANY, "Hammer Lua", wx.wxDefaultPosition, Size(500, frameheight), bit.bor(wx.wxSTAY_ON_TOP, wx.wxFRAME_TOOL_WINDOW)) + think_hook = wx.wxTimer(frame) + frame:SetCanFocus(false) + frame:SetBackgroundColour(wx.wxBLACK) + toolBar = frame:CreateToolBar(wx.wxNO_BORDER + wx.wxTB_FLAT + wx.wxTB_DOCKABLE) + local toolBmpSize = toolBar:GetToolBitmapSize() + local bmp = wx.wxArtProvider.GetBitmap(wx.wxART_NEW_DIR, wx.wxART_MENU, toolBmpSize) + toolBar:AddTool(ID.update, "Update Repo", bmp, "Update repository") + local bmp = wx.wxArtProvider.GetBitmap(wx.wxART_FILE_SAVE, wx.wxART_MENU, toolBmpSize) + toolBar:AddTool(ID.commit, "Commit map", bmp, "Commit map") + toolBar:AddSeparator() + local bmp = wx.wxArtProvider.GetBitmap(wx.wxART_FILE_OPEN, wx.wxART_MENU, toolBmpSize) + toolBar:AddTool(ID.lock, "Lock This Map", bmp, "Lock This Map") + local bmp = wx.wxArtProvider.GetBitmap(wx.wxART_UNDO, wx.wxART_MENU, toolBmpSize) + toolBar:AddTool(ID.unlock, "Unlock this Map", bmp, "Unlock this Map") + toolBar:AddSeparator() + local bmp = wx.wxArtProvider.GetBitmap(wx.wxART_QUIT, wx.wxART_MENU, toolBmpSize) + toolBar:AddTool(ID.exit, "Close", bmp, "Close") + toolBar:Realize() + frame:CreateStatusBar(1) + + frame:Connect(wx.wxID_ANY, wx.wxEVT_COMMAND_MENU_SELECTED, function(event) + local id = event:GetId() + + if id == ID.lock then + svn.lock() + elseif id == ID.unlock then + svn.unlock() + elseif id == ID.commit then + svn.commit() + elseif id == ID.update then + svn.update() + elseif id == ID.exit then + os.exit(0) + else + print("INVALID", evt, id) + end + end) + + frame:Connect(wx.wxEVT_TIMER, function(event) + local ok, err = xpcall(OnThink, debug.traceback) + + if err then + printerr(err) + os.exit(1) + think_hook:Start(1500) + end + end) + + think_hook:Start(100) + frame:Show(true) + frame:Show(false) +end + +local showing = nil + +function ShowMain(yes) + yes = not not yes + + if yes ~= showing then + showing = yes + if showing==nil then frame:Show(true) end + local w = winapi.get_foreground_window() + frame:Show(yes) + w:set_foreground() + + return true + end +end + +local lspeed + +local function setspeed(n) + if lspeed ~= n then + lspeed = n + think_hook:Start(n * 1000) + end +end + +do + function mapchange(map, lmap) + end +end + +local ltxt + +function status(txt) + if txt == ltxt then return end + ltxt = txt + dbg("Status: ",txt) + frame:SetStatusText(txt) +end + +local lmap +local wtf=true +OnThink = function() + if wtf then wtf=nil frame:Show(true)frame:Show(false) end + local hammer, err = Hammer.get() + + if not hammer then + if ShowMain(false) then + dbg("Hiding, no hammer") + end + + setspeed(1) + + return + end + + local inhammer = Hammer.ontop() + + if not inhammer then + inhammer = winapi.get_foreground_window() == window() + end + + local refresh = ShowMain(inhammer) + refresh = refresh and inhammer + + if refresh then + print("Refresh", inhammer) + end + + if inhammer then + doresize(hammer, frame) + end + + local map = Hammer.map(hammer) + + if map ~= lmap then + lmap = map + mapchange(map, lmap) + end + + status(map) + if not map then return end + setspeed(1 / 60) +end + +main() + +wx.wxGetApp():MainLoop() \ No newline at end of file diff --git a/extras/hammerlua/svn.lua b/extras/hammerlua/svn.lua new file mode 100644 index 0000000..80ae6b0 --- /dev/null +++ b/extras/hammerlua/svn.lua @@ -0,0 +1,27 @@ +local winapi = require'winapi' +local shell = require'shell' +local xml2lua = require'xml2lua' +local inspect = require'inspect' +local handler = require("xmlhandler/tree") +local lanes = require'lanes' +local svn={} + +function svn.islocked(path) + local path = [[x:\do\GMod\metastruct\mapfiles\moon.vmf]] + local ret,xml = shell.run{"svn",'status','-u','--xml',path} + + local parser = xml2lua.parser(handler) + parser:parse(xml) + + return handler.root.status.target.entry["repos-status"].lock +end + +svn.islocked() +os.exit(0) + +function svn.lock() end +function svn.unlock() end +function svn.commit() end +function svn.update() end +return svn +