mirror of
https://github.com/kristoferssolo/solorice.git
synced 2025-10-21 20:10:34 +00:00
2719 lines
71 KiB
Lua
2719 lines
71 KiB
Lua
#!/usr/bin/env lua
|
|
--=====================================================================
|
|
--
|
|
-- z.lua - a cd command that learns, by skywind 2018-2022
|
|
-- Licensed under MIT license.
|
|
--
|
|
-- Version 1.8.15, Last Modified: 2022/03/27 21:38
|
|
--
|
|
-- * 10x faster than fasd and autojump, 3x faster than z.sh
|
|
-- * available for posix shells: bash, zsh, sh, ash, dash, busybox
|
|
-- * available for fish shell, power shell and windows cmd
|
|
-- * compatible with lua 5.1, 5.2 and 5.3+
|
|
--
|
|
-- USE:
|
|
-- * z foo # cd to most frecent dir matching foo
|
|
-- * z foo bar # cd to most frecent dir matching foo and bar
|
|
-- * z -r foo # cd to highest ranked dir matching foo
|
|
-- * z -t foo # cd to most recently accessed dir matching foo
|
|
-- * z -l foo # list matches instead of cd
|
|
-- * z -c foo # restrict matches to subdirs of $PWD
|
|
-- * z -e foo # echo the best match, don't cd
|
|
-- * z -x path # remove path from history
|
|
-- * z -i foo # cd with interactive selection
|
|
-- * z -I foo # cd with interactive selection using fzf
|
|
-- * z -b foo # cd to the parent directory starting with foo
|
|
--
|
|
-- Bash Install:
|
|
-- * put something like this in your .bashrc:
|
|
-- eval "$(lua /path/to/z.lua --init bash)"
|
|
--
|
|
-- Bash Enhanced Mode:
|
|
-- * put something like this in your .bashrc:
|
|
-- eval "$(lua /path/to/z.lua --init bash enhanced)"
|
|
--
|
|
-- Bash fzf tab completion Mode:
|
|
-- * put something like this in your .bashrc:
|
|
-- eval "$(lua /path/to/z.lua --init bash fzf)"
|
|
--
|
|
-- Zsh Install:
|
|
-- * put something like this in your .zshrc:
|
|
-- eval "$(lua /path/to/z.lua --init zsh)"
|
|
--
|
|
-- Posix Shell Install:
|
|
-- * put something like this in your .profile:
|
|
-- eval "$(lua /path/to/z.lua --init posix)"
|
|
--
|
|
-- Fish Shell Install:
|
|
-- * put something like this in your config file:
|
|
-- source (lua /path/to/z.lua --init fish | psub)
|
|
--
|
|
-- Power Shell Install:
|
|
-- * put something like this in your config file:
|
|
-- Invoke-Expression (& {
|
|
-- (lua /path/to/z.lua --init powershell) -join "`n" })
|
|
--
|
|
-- Windows Install (with Clink):
|
|
-- * copy z.lua and z.cmd to clink's home directory
|
|
-- * Add clink's home to %PATH% (z.cmd can be called anywhere)
|
|
-- * Ensure that "lua" can be called in %PATH%
|
|
--
|
|
-- Windows Cmder Install:
|
|
-- * copy z.lua and z.cmd to cmder/vendor
|
|
-- * Add cmder/vendor to %PATH%
|
|
-- * Ensure that "lua" can be called in %PATH%
|
|
--
|
|
-- Windows WSL-1:
|
|
-- * Install lua-filesystem module before init z.lua:
|
|
-- sudo apt-get install lua-filesystem
|
|
--
|
|
-- Configure (optional):
|
|
-- set $_ZL_CMD in .bashrc/.zshrc to change the command (default z).
|
|
-- set $_ZL_DATA in .bashrc/.zshrc to change the datafile (default ~/.zlua).
|
|
-- set $_ZL_NO_PROMPT_COMMAND if you're handling PROMPT_COMMAND yourself.
|
|
-- set $_ZL_EXCLUDE_DIRS to a comma separated list of dirs to exclude.
|
|
-- set $_ZL_ADD_ONCE to 1 to update database only if $PWD changed.
|
|
-- set $_ZL_CD to specify your own cd command
|
|
-- set $_ZL_ECHO to 1 to display new directory name after cd.
|
|
-- set $_ZL_MAXAGE to define a aging threshold (default is 5000).
|
|
-- set $_ZL_MATCH_MODE to 1 to enable enhanced matching mode.
|
|
-- set $_ZL_NO_CHECK to 1 to disable path validation. z --purge to clear.
|
|
-- set $_ZL_USE_LFS to 1 to use lua-filesystem package
|
|
-- set $_ZL_HYPHEN to 1 to stop treating hyphen as a regexp keyword
|
|
--
|
|
--=====================================================================
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Module Header
|
|
-----------------------------------------------------------------------
|
|
local modname = 'z'
|
|
local MM = {}
|
|
_G[modname] = MM
|
|
package.loaded[modname] = MM --return modname
|
|
setmetatable(MM, { __index = _G })
|
|
|
|
if _ENV ~= nil then
|
|
_ENV[modname] = MM
|
|
else
|
|
setfenv(1, MM)
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Environment
|
|
-----------------------------------------------------------------------
|
|
local windows = package.config:sub(1, 1) ~= '/' and true or false
|
|
local in_module = pcall(debug.getlocal, 4, 1) and true or false
|
|
local utils = {}
|
|
os.path = {}
|
|
os.argv = arg ~= nil and arg or {}
|
|
os.path.sep = windows and '\\' or '/'
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Global Variable
|
|
-----------------------------------------------------------------------
|
|
MAX_AGE = 5000
|
|
DATA_FILE = '~/.config/zlua/.zlua'
|
|
PRINT_MODE = '<stdout>'
|
|
PWD = ''
|
|
Z_METHOD = 'frecent'
|
|
Z_SUBDIR = false
|
|
Z_INTERACTIVE = 0
|
|
Z_EXCLUDE = {}
|
|
Z_CMD = 'z'
|
|
Z_MATCHMODE = 0
|
|
Z_MATCHNAME = false
|
|
Z_SKIPPWD = false
|
|
Z_HYPHEN = false
|
|
|
|
os.LOG_NAME = os.getenv('_ZL_LOG_NAME')
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- string lib
|
|
-----------------------------------------------------------------------
|
|
function string:split(sSeparator, nMax, bRegexp)
|
|
assert(sSeparator ~= '')
|
|
assert(nMax == nil or nMax >= 1)
|
|
local aRecord = {}
|
|
if self:len() > 0 then
|
|
local bPlain = not bRegexp
|
|
nMax = nMax or -1
|
|
local nField, nStart = 1, 1
|
|
local nFirst, nLast = self:find(sSeparator, nStart, bPlain)
|
|
while nFirst and nMax ~= 0 do
|
|
aRecord[nField] = self:sub(nStart, nFirst - 1)
|
|
nField = nField + 1
|
|
nStart = nLast + 1
|
|
nFirst, nLast = self:find(sSeparator, nStart, bPlain)
|
|
nMax = nMax - 1
|
|
end
|
|
aRecord[nField] = self:sub(nStart)
|
|
else
|
|
aRecord[1] = ''
|
|
end
|
|
return aRecord
|
|
end
|
|
|
|
function string:startswith(text)
|
|
local size = text:len()
|
|
if self:sub(1, size) == text then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
function string:endswith(text)
|
|
return text == "" or self:sub(- #text) == text
|
|
end
|
|
|
|
function string:lstrip()
|
|
if self == nil then return nil end
|
|
local s = self:gsub('^%s+', '')
|
|
return s
|
|
end
|
|
|
|
function string:rstrip()
|
|
if self == nil then return nil end
|
|
local s = self:gsub('%s+$', '')
|
|
return s
|
|
end
|
|
|
|
function string:strip()
|
|
return self:lstrip():rstrip()
|
|
end
|
|
|
|
function string:rfind(key)
|
|
if key == '' then
|
|
return self:len(), 0
|
|
end
|
|
local length = self:len()
|
|
local start, ends = self:reverse():find(key:reverse(), 1, true)
|
|
if start == nil then
|
|
return nil
|
|
end
|
|
return (length - ends + 1), (length - start + 1)
|
|
end
|
|
|
|
function string:join(parts)
|
|
if parts == nil or #parts == 0 then
|
|
return ''
|
|
end
|
|
local size = #parts
|
|
local text = ''
|
|
local index = 1
|
|
while index <= size do
|
|
if index == 1 then
|
|
text = text .. parts[index]
|
|
else
|
|
text = text .. self .. parts[index]
|
|
end
|
|
index = index + 1
|
|
end
|
|
return text
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- table size
|
|
-----------------------------------------------------------------------
|
|
function table.length(T)
|
|
local count = 0
|
|
if T == nil then return 0 end
|
|
for _ in pairs(T) do count = count + 1 end
|
|
return count
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- print table
|
|
-----------------------------------------------------------------------
|
|
function dump(o)
|
|
if type(o) == 'table' then
|
|
local s = '{ '
|
|
for k, v in pairs(o) do
|
|
if type(k) ~= 'number' then k = '"' .. k .. '"' end
|
|
s = s .. '[' .. k .. '] = ' .. dump(v) .. ','
|
|
end
|
|
return s .. '} '
|
|
else
|
|
return tostring(o)
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- print table
|
|
-----------------------------------------------------------------------
|
|
function printT(table, level)
|
|
key = ""
|
|
local func = function(table, level) end
|
|
func = function(table, level)
|
|
level = level or 1
|
|
local indent = ""
|
|
for i = 1, level do
|
|
indent = indent .. " "
|
|
end
|
|
if key ~= "" then
|
|
print(indent .. key .. " " .. "=" .. " " .. "{")
|
|
else
|
|
print(indent .. "{")
|
|
end
|
|
|
|
key = ""
|
|
for k, v in pairs(table) do
|
|
if type(v) == "table" then
|
|
key = k
|
|
func(v, level + 1)
|
|
else
|
|
local content = string.format("%s%s = %s", indent .. " ", tostring(k), tostring(v))
|
|
print(content)
|
|
end
|
|
end
|
|
print(indent .. "}")
|
|
end
|
|
func(table, level)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- invoke command and retrive output
|
|
-----------------------------------------------------------------------
|
|
function os.call(command)
|
|
local fp = io.popen(command)
|
|
if fp == nil then
|
|
return nil
|
|
end
|
|
local line = fp:read('*l')
|
|
fp:close()
|
|
return line
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- write log
|
|
-----------------------------------------------------------------------
|
|
function os.log(text)
|
|
if not os.LOG_NAME then
|
|
return
|
|
end
|
|
local fp = io.open(os.LOG_NAME, 'a')
|
|
if not fp then
|
|
return
|
|
end
|
|
local date = "[" .. os.date('%Y-%m-%d %H:%M:%S') .. "] "
|
|
fp:write(date .. text .. "\n")
|
|
fp:close()
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- ffi optimize (luajit has builtin ffi module)
|
|
-----------------------------------------------------------------------
|
|
os.native = {}
|
|
os.native.status, os.native.ffi = pcall(require, "ffi")
|
|
if os.native.status then
|
|
local ffi = os.native.ffi
|
|
if windows then
|
|
ffi.cdef [[
|
|
int GetFullPathNameA(const char *name, uint32_t size, char *out, char **name);
|
|
int ReplaceFileA(const char *dstname, const char *srcname, void *, uint32_t, void *, void *);
|
|
uint32_t GetTickCount(void);
|
|
uint32_t GetFileAttributesA(const char *name);
|
|
uint32_t GetCurrentDirectoryA(uint32_t size, char *ptr);
|
|
uint32_t GetShortPathNameA(const char *longname, char *shortname, uint32_t size);
|
|
uint32_t GetLongPathNameA(const char *shortname, char *longname, uint32_t size);
|
|
]]
|
|
local kernel32 = ffi.load('kernel32.dll')
|
|
local buffer = ffi.new('char[?]', 4100)
|
|
local INVALID_FILE_ATTRIBUTES = 0xffffffff
|
|
local FILE_ATTRIBUTE_DIRECTORY = 0x10
|
|
os.native.kernel32 = kernel32
|
|
function os.native.GetFullPathName(name)
|
|
local hr = kernel32.GetFullPathNameA(name, 4096, buffer, nil)
|
|
return (hr > 0) and ffi.string(buffer, hr) or nil
|
|
end
|
|
|
|
function os.native.ReplaceFile(replaced, replacement)
|
|
local hr = kernel32.ReplaceFileA(replaced, replacement, nil, 2, nil, nil)
|
|
return (hr ~= 0) and true or false
|
|
end
|
|
|
|
function os.native.GetTickCount()
|
|
return kernel32.GetTickCount()
|
|
end
|
|
|
|
function os.native.GetFileAttributes(name)
|
|
return kernel32.GetFileAttributesA(name)
|
|
end
|
|
|
|
function os.native.GetLongPathName(name)
|
|
local hr = kernel32.GetLongPathNameA(name, buffer, 4096)
|
|
return (hr ~= 0) and ffi.string(buffer, hr) or nil
|
|
end
|
|
|
|
function os.native.GetShortPathName(name)
|
|
local hr = kernel32.GetShortPathNameA(name, buffer, 4096)
|
|
return (hr ~= 0) and ffi.string(buffer, hr) or nil
|
|
end
|
|
|
|
function os.native.GetRealPathName(name)
|
|
local short = os.native.GetShortPathName(name)
|
|
if short then
|
|
return os.native.GetLongPathName(short)
|
|
end
|
|
return nil
|
|
end
|
|
|
|
function os.native.exists(name)
|
|
local attr = os.native.GetFileAttributes(name)
|
|
return attr ~= INVALID_FILE_ATTRIBUTES
|
|
end
|
|
|
|
function os.native.isdir(name)
|
|
local attr = os.native.GetFileAttributes(name)
|
|
local isdir = FILE_ATTRIBUTE_DIRECTORY
|
|
if attr == INVALID_FILE_ATTRIBUTES then
|
|
return false
|
|
end
|
|
return (attr % (2 * isdir)) >= isdir
|
|
end
|
|
|
|
function os.native.getcwd()
|
|
local hr = kernel32.GetCurrentDirectoryA(4096, buffer)
|
|
if hr <= 0 then return nil end
|
|
return ffi.string(buffer, hr)
|
|
end
|
|
else
|
|
ffi.cdef [[
|
|
typedef struct { long tv_sec; long tv_usec; } timeval;
|
|
int gettimeofday(timeval *tv, void *tz);
|
|
int access(const char *name, int mode);
|
|
char *realpath(const char *path, char *resolve);
|
|
char *getcwd(char *buf, size_t size);
|
|
]]
|
|
local timeval = ffi.new('timeval[?]', 1)
|
|
local buffer = ffi.new('char[?]', 4100)
|
|
function os.native.gettimeofday()
|
|
local hr = ffi.C.gettimeofday(timeval, nil)
|
|
local sec = tonumber(timeval[0].tv_sec)
|
|
local usec = tonumber(timeval[0].tv_usec)
|
|
return sec + (usec * 0.000001)
|
|
end
|
|
|
|
function os.native.access(name, mode)
|
|
return ffi.C.access(name, mode)
|
|
end
|
|
|
|
function os.native.realpath(name)
|
|
local path = ffi.C.realpath(name, buffer)
|
|
return (path ~= nil) and ffi.string(buffer) or nil
|
|
end
|
|
|
|
function os.native.getcwd()
|
|
local hr = ffi.C.getcwd(buffer, 4099)
|
|
return hr ~= nil and ffi.string(buffer) or nil
|
|
end
|
|
end
|
|
function os.native.tickcount()
|
|
if windows then
|
|
return os.native.GetTickCount()
|
|
else
|
|
return math.floor(os.native.gettimeofday() * 1000)
|
|
end
|
|
end
|
|
|
|
os.native.init = true
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- get current path
|
|
-----------------------------------------------------------------------
|
|
function os.pwd()
|
|
if os.native and os.native.getcwd then
|
|
local hr = os.native.getcwd()
|
|
if hr then return hr end
|
|
end
|
|
if os.getcwd then
|
|
return os.getcwd()
|
|
end
|
|
if windows then
|
|
local fp = io.popen('cd')
|
|
if fp == nil then
|
|
return ''
|
|
end
|
|
local line = fp:read('*l')
|
|
fp:close()
|
|
return line
|
|
else
|
|
local fp = io.popen('pwd')
|
|
if fp == nil then
|
|
return ''
|
|
end
|
|
local line = fp:read('*l')
|
|
fp:close()
|
|
return line
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- which executable
|
|
-----------------------------------------------------------------------
|
|
function os.path.which(exename)
|
|
local path = os.getenv('PATH')
|
|
if windows then
|
|
paths = ('.;' .. path):split(';')
|
|
else
|
|
paths = path:split(':')
|
|
end
|
|
for _, path in pairs(paths) do
|
|
if not windows then
|
|
local name = path .. '/' .. exename
|
|
if os.path.exists(name) then
|
|
return name
|
|
end
|
|
else
|
|
for _, ext in pairs({ '.exe', '.cmd', '.bat' }) do
|
|
local name = path .. '\\' .. exename .. ext
|
|
if path == '.' then
|
|
name = exename .. ext
|
|
end
|
|
if os.path.exists(name) then
|
|
return name
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- absolute path (simulated)
|
|
-----------------------------------------------------------------------
|
|
function os.path.absolute(path)
|
|
local pwd = os.pwd()
|
|
return os.path.normpath(os.path.join(pwd, path))
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- absolute path (system call, can fall back to os.path.absolute)
|
|
-----------------------------------------------------------------------
|
|
function os.path.abspath(path)
|
|
if path == '' then path = '.' end
|
|
if os.native and os.native.GetFullPathName then
|
|
local test = os.native.GetFullPathName(path)
|
|
if test then return test end
|
|
end
|
|
if windows then
|
|
local script = 'FOR /f "delims=" %%i IN ("%s") DO @echo %%~fi'
|
|
local script = string.format(script, path)
|
|
local script = 'cmd.exe /C ' .. script .. ' 2> nul'
|
|
local output = os.call(script)
|
|
local test = output:gsub('%s$', '')
|
|
if test ~= nil and test ~= '' then
|
|
return test
|
|
end
|
|
else
|
|
local test = os.path.which('realpath')
|
|
if test ~= nil and test ~= '' then
|
|
test = os.call('realpath -s \'' .. path .. '\' 2> /dev/null')
|
|
if test ~= nil and test ~= '' then
|
|
return test
|
|
end
|
|
test = os.call('realpath \'' .. path .. '\' 2> /dev/null')
|
|
if test ~= nil and test ~= '' then
|
|
return test
|
|
end
|
|
end
|
|
local test = os.path.which('perl')
|
|
if test ~= nil and test ~= '' then
|
|
local s = 'perl -MCwd -e "print Cwd::realpath(\\$ARGV[0])" \'%s\''
|
|
local s = string.format(s, path)
|
|
test = os.call(s)
|
|
if test ~= nil and test ~= '' then
|
|
return test
|
|
end
|
|
end
|
|
for _, python in pairs({ 'python3', 'python2', 'python' }) do
|
|
local s = 'sys.stdout.write(os.path.abspath(sys.argv[1]))'
|
|
local s = '-c "import os, sys;' .. s .. '" \'' .. path .. '\''
|
|
local s = python .. ' ' .. s
|
|
local test = os.path.which(python)
|
|
if test ~= nil and test ~= '' then
|
|
test = os.call(s)
|
|
if test ~= nil and test ~= '' then
|
|
return test
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return os.path.absolute(path)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- dir exists
|
|
-----------------------------------------------------------------------
|
|
function os.path.isdir(pathname)
|
|
if pathname == '/' then
|
|
return true
|
|
elseif pathname == '' then
|
|
return false
|
|
elseif windows then
|
|
if pathname == '\\' then
|
|
return true
|
|
end
|
|
end
|
|
if os.native and os.native.isdir then
|
|
return os.native.isdir(pathname)
|
|
end
|
|
if clink and os.isdir then
|
|
return os.isdir(pathname)
|
|
end
|
|
local name = pathname
|
|
if (not name:endswith('/')) and (not name:endswith('\\')) then
|
|
name = name .. os.path.sep
|
|
end
|
|
return os.path.exists(name)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- file or path exists
|
|
-----------------------------------------------------------------------
|
|
function os.path.exists(name)
|
|
if name == '/' then
|
|
return true
|
|
end
|
|
if os.native and os.native.exists then
|
|
return os.native.exists(name)
|
|
end
|
|
local ok, err, code = os.rename(name, name)
|
|
if not ok then
|
|
if code == 13 or code == 17 then
|
|
return true
|
|
elseif code == 30 then
|
|
local f = io.open(name, "r")
|
|
if f ~= nil then
|
|
io.close(f)
|
|
return true
|
|
end
|
|
elseif name:sub(-1) == '/' and code == 20 and (not windows) then
|
|
local test = name .. '.'
|
|
ok, err, code = os.rename(test, test)
|
|
if code == 16 or code == 13 or code == 22 then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- is absolute path
|
|
-----------------------------------------------------------------------
|
|
function os.path.isabs(path)
|
|
if path == nil or path == '' then
|
|
return false
|
|
elseif path:sub(1, 1) == '/' then
|
|
return true
|
|
end
|
|
if windows then
|
|
local head = path:sub(1, 1)
|
|
if head == '\\' then
|
|
return true
|
|
elseif path:match('^%a:[/\\]') ~= nil then
|
|
return true
|
|
end
|
|
end
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- normalize path
|
|
-----------------------------------------------------------------------
|
|
function os.path.norm(pathname)
|
|
if windows then
|
|
pathname = pathname:gsub('\\', '/')
|
|
end
|
|
if windows then
|
|
pathname = pathname:gsub('/', '\\')
|
|
end
|
|
return pathname
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- normalize . and ..
|
|
-----------------------------------------------------------------------
|
|
function os.path.normpath(path)
|
|
if os.path.sep ~= '/' then
|
|
path = path:gsub('\\', '/')
|
|
end
|
|
path = path:gsub('/+', '/')
|
|
local srcpath = path
|
|
local basedir = ''
|
|
local isabs = false
|
|
if windows and path:sub(2, 2) == ':' then
|
|
basedir = path:sub(1, 2)
|
|
path = path:sub(3, -1)
|
|
end
|
|
if path:sub(1, 1) == '/' then
|
|
basedir = basedir .. '/'
|
|
isabs = true
|
|
path = path:sub(2, -1)
|
|
end
|
|
local parts = path:split('/')
|
|
local output = {}
|
|
for _, path in ipairs(parts) do
|
|
if path == '.' or path == '' then
|
|
elseif path == '..' then
|
|
local size = #output
|
|
if size == 0 then
|
|
if not isabs then
|
|
table.insert(output, '..')
|
|
end
|
|
elseif output[size] == '..' then
|
|
table.insert(output, '..')
|
|
else
|
|
table.remove(output, size)
|
|
end
|
|
else
|
|
table.insert(output, path)
|
|
end
|
|
end
|
|
path = basedir .. string.join('/', output)
|
|
if windows then path = path:gsub('/', '\\') end
|
|
return path == '' and '.' or path
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- join two path
|
|
-----------------------------------------------------------------------
|
|
function os.path.join(path1, path2)
|
|
if path1 == nil or path1 == '' then
|
|
if path2 == nil or path2 == '' then
|
|
return ''
|
|
else
|
|
return path2
|
|
end
|
|
elseif path2 == nil or path2 == '' then
|
|
local head = path1:sub(-1, -1)
|
|
if head == '/' or (windows and head == '\\') then
|
|
return path1
|
|
end
|
|
return path1 .. os.path.sep
|
|
elseif os.path.isabs(path2) then
|
|
if windows then
|
|
local head = path2:sub(1, 1)
|
|
if head == '/' or head == '\\' then
|
|
if path1:match('^%a:') then
|
|
return path1:sub(1, 2) .. path2
|
|
end
|
|
end
|
|
end
|
|
return path2
|
|
elseif windows then
|
|
local d1 = path1:match('^%a:') and path1:sub(1, 2) or ''
|
|
local d2 = path2:match('^%a:') and path2:sub(1, 2) or ''
|
|
if d1 ~= '' then
|
|
if d2 ~= '' then
|
|
if d1:lower() == d2:lower() then
|
|
return d2 .. os.path.join(path1:sub(3), path2:sub(3))
|
|
else
|
|
return path2
|
|
end
|
|
end
|
|
elseif d2 ~= '' then
|
|
return path2
|
|
end
|
|
end
|
|
local postsep = true
|
|
local len1 = path1:len()
|
|
local len2 = path2:len()
|
|
if path1:sub(-1, -1) == '/' then
|
|
postsep = false
|
|
elseif windows then
|
|
if path1:sub(-1, -1) == '\\' then
|
|
postsep = false
|
|
elseif len1 == 2 and path1:sub(2, 2) == ':' then
|
|
postsep = false
|
|
end
|
|
end
|
|
if postsep then
|
|
return path1 .. os.path.sep .. path2
|
|
else
|
|
return path1 .. path2
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- split
|
|
-----------------------------------------------------------------------
|
|
function os.path.split(path)
|
|
if path == '' then
|
|
return '', ''
|
|
end
|
|
local pos = path:rfind('/')
|
|
if os.path.sep == '\\' then
|
|
local p2 = path:rfind('\\')
|
|
if pos == nil and p2 ~= nil then
|
|
pos = p2
|
|
elseif pos ~= nil and p2 ~= nil then
|
|
pos = (pos < p2) and pos or p2
|
|
end
|
|
if path:match('^%a:[/\\]') and pos == nil then
|
|
return path:sub(1, 2), path:sub(3)
|
|
end
|
|
end
|
|
if pos == nil then
|
|
if windows then
|
|
local drive = path:match('^%a:') and path:sub(1, 2) or ''
|
|
if drive ~= '' then
|
|
return path:sub(1, 2), path:sub(3)
|
|
end
|
|
end
|
|
return '', path
|
|
elseif pos == 1 then
|
|
return path:sub(1, 1), path:sub(2)
|
|
elseif windows then
|
|
local drive = path:match('^%a:') and path:sub(1, 2) or ''
|
|
if pos == 3 and drive ~= '' then
|
|
return path:sub(1, 3), path:sub(4)
|
|
end
|
|
end
|
|
local head = path:sub(1, pos)
|
|
local tail = path:sub(pos + 1)
|
|
if not windows then
|
|
local test = string.rep('/', head:len())
|
|
if head ~= test then
|
|
head = head:gsub('/+$', '')
|
|
end
|
|
else
|
|
local t1 = string.rep('/', head:len())
|
|
local t2 = string.rep('\\', head:len())
|
|
if head ~= t1 and head ~= t2 then
|
|
head = head:gsub('[/\\]+$', '')
|
|
end
|
|
end
|
|
return head, tail
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- check subdir
|
|
-----------------------------------------------------------------------
|
|
function os.path.subdir(basename, subname)
|
|
if windows then
|
|
basename = basename:gsub('\\', '/')
|
|
subname = subname:gsub('\\', '/')
|
|
basename = basename:lower()
|
|
subname = subname:lower()
|
|
end
|
|
local last = basename:sub(-1, -1)
|
|
if last ~= '/' then
|
|
basename = basename .. '/'
|
|
end
|
|
if subname:find(basename, 0, true) == 1 then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- check single name element
|
|
-----------------------------------------------------------------------
|
|
function os.path.single(path)
|
|
if string.match(path, '/') then
|
|
return false
|
|
end
|
|
if windows then
|
|
if string.match(path, '\\') then
|
|
return false
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- expand user home
|
|
-----------------------------------------------------------------------
|
|
function os.path.expand(pathname)
|
|
if not pathname:find('~') then
|
|
return pathname
|
|
end
|
|
local home = ''
|
|
if windows then
|
|
home = os.getenv('USERPROFILE')
|
|
else
|
|
home = os.getenv('HOME')
|
|
end
|
|
if pathname == '~' then
|
|
return home
|
|
end
|
|
local head = pathname:sub(1, 2)
|
|
if windows then
|
|
if head == '~/' or head == '~\\' then
|
|
return home .. '\\' .. pathname:sub(3, -1)
|
|
end
|
|
elseif head == '~/' then
|
|
return home .. '/' .. pathname:sub(3, -1)
|
|
end
|
|
return pathname
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- search executable
|
|
-----------------------------------------------------------------------
|
|
function os.path.search(name)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- get lua executable
|
|
-----------------------------------------------------------------------
|
|
function os.interpreter()
|
|
if os.argv == nil then
|
|
io.stderr:write("cannot get arguments (arg), recompiled your lua\n")
|
|
return nil
|
|
end
|
|
local lua = os.argv[-1]
|
|
if lua == nil then
|
|
io.stderr:write("cannot get executable name, recompiled your lua\n")
|
|
end
|
|
if os.path.single(lua) then
|
|
local path = os.path.which(lua)
|
|
if not os.path.isabs(path) then
|
|
return os.path.abspath(path)
|
|
end
|
|
return path
|
|
end
|
|
return os.path.abspath(lua)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- get script name
|
|
-----------------------------------------------------------------------
|
|
function os.scriptname()
|
|
if os.argv == nil then
|
|
io.stderr:write("cannot get arguments (arg), recompiled your lua\n")
|
|
return nil
|
|
end
|
|
local script = os.argv[0]
|
|
if script == nil then
|
|
io.stderr:write("cannot get script name, recompiled your lua\n")
|
|
end
|
|
return os.path.abspath(script)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- get environ
|
|
-----------------------------------------------------------------------
|
|
function os.environ(name, default)
|
|
local value = os.getenv(name)
|
|
if os.envmap ~= nil and type(os.envmap) == 'table' then
|
|
local t = os.envmap[name]
|
|
value = (t ~= nil and type(t) == 'string') and t or value
|
|
end
|
|
if value == nil then
|
|
return default
|
|
elseif type(default) == 'boolean' then
|
|
value = value:lower()
|
|
if value == '0' or value == '' or value == 'no' then
|
|
return false
|
|
elseif value == 'false' or value == 'n' or value == 'f' then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
elseif type(default) == 'number' then
|
|
value = tonumber(value)
|
|
if value == nil then
|
|
return default
|
|
else
|
|
return value
|
|
end
|
|
elseif type(default) == 'string' then
|
|
return value
|
|
elseif type(default) == 'table' then
|
|
return value:sep(',')
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- parse option
|
|
-----------------------------------------------------------------------
|
|
function os.getopt(argv)
|
|
local args = {}
|
|
local options = {}
|
|
argv = argv ~= nil and argv or os.argv
|
|
if argv == nil then
|
|
return nil, nil
|
|
elseif (#argv) == 0 then
|
|
return options, args
|
|
end
|
|
local count = #argv
|
|
local index = 1
|
|
while index <= count do
|
|
local arg = argv[index]
|
|
local head = arg:sub(1, 1)
|
|
if arg ~= '' then
|
|
if head ~= '-' then
|
|
break
|
|
end
|
|
if arg == '-' then
|
|
options['-'] = ''
|
|
elseif arg == '--' then
|
|
options['-'] = '-'
|
|
elseif arg:match('^-%d+$') then
|
|
options['-'] = arg:sub(2)
|
|
else
|
|
local part = arg:split('=')
|
|
options[part[1]] = part[2] ~= nil and part[2] or ''
|
|
end
|
|
end
|
|
index = index + 1
|
|
end
|
|
while index <= count do
|
|
table.insert(args, argv[index])
|
|
index = index + 1
|
|
end
|
|
return options, args
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- generate random seed
|
|
-----------------------------------------------------------------------
|
|
function math.random_init()
|
|
-- random seed from os.time()
|
|
local seed = tostring(os.time() * 1000)
|
|
seed = seed .. tostring(math.random(99999999))
|
|
if os.argv ~= nil then
|
|
for _, key in ipairs(os.argv) do
|
|
seed = seed .. '/' .. key
|
|
end
|
|
end
|
|
local ppid = os.getenv('PPID')
|
|
seed = (ppid ~= nil) and (seed .. '/' .. ppid) or seed
|
|
-- random seed from socket.gettime()
|
|
local status, socket = pcall(require, 'socket')
|
|
if status then
|
|
seed = seed .. tostring(socket.gettime())
|
|
end
|
|
-- random seed from _ZL_RANDOM
|
|
local rnd = os.getenv('_ZL_RANDOM')
|
|
if rnd ~= nil then
|
|
seed = seed .. rnd
|
|
end
|
|
seed = seed .. tostring(os.clock() * 10000000)
|
|
if os.native and os.native.tickcount then
|
|
seed = seed .. tostring(os.native.tickcount())
|
|
end
|
|
local number = 0
|
|
for i = 1, seed:len() do
|
|
local k = string.byte(seed:sub(i, i))
|
|
number = ((number * 127) % 0x7fffffff) + k
|
|
end
|
|
math.randomseed(number)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- math random string
|
|
-----------------------------------------------------------------------
|
|
function math.random_string(N)
|
|
local text = ''
|
|
for i = 1, N do
|
|
local k = math.random(0, 26 * 2 + 10 - 1)
|
|
if k < 26 then
|
|
text = text .. string.char(0x41 + k)
|
|
elseif k < 26 * 2 then
|
|
text = text .. string.char(0x61 + k - 26)
|
|
elseif k < 26 * 2 + 10 then
|
|
text = text .. string.char(0x30 + k - 26 * 2)
|
|
else
|
|
end
|
|
end
|
|
return text
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- returns true for path is insensitive
|
|
-----------------------------------------------------------------------
|
|
function path_case_insensitive()
|
|
if windows then
|
|
return true
|
|
end
|
|
local eos = os.getenv('OS')
|
|
eos = eos ~= nil and eos or ''
|
|
eos = eos:lower()
|
|
if eos:sub(1, 7) == 'windows' then
|
|
return true
|
|
end
|
|
return false
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- load and split data
|
|
-----------------------------------------------------------------------
|
|
function data_load(filename)
|
|
local M = {}
|
|
local N = {}
|
|
local insensitive = path_case_insensitive()
|
|
local fp = io.open(os.path.expand(filename), 'r')
|
|
if fp == nil then
|
|
return {}
|
|
end
|
|
for line in fp:lines() do
|
|
local part = string.split(line, '|')
|
|
local item = {}
|
|
if part and part[1] and part[2] and part[3] then
|
|
local key = insensitive and part[1]:lower() or part[1]
|
|
item.name = part[1]
|
|
item.rank = tonumber(part[2])
|
|
item.time = tonumber(part[3]) + 0
|
|
item.frecent = item.rank
|
|
if string.len(part[3]) < 12 then
|
|
if item.rank ~= nil and item.time ~= nil then
|
|
if N[key] == nil then
|
|
table.insert(M, item)
|
|
N[key] = 1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
fp:close()
|
|
return M
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- save data
|
|
-----------------------------------------------------------------------
|
|
function data_save(filename, M)
|
|
local fp = nil
|
|
local tmpname = nil
|
|
local i
|
|
filename = os.path.expand(filename)
|
|
math.random_init()
|
|
while true do
|
|
tmpname = filename .. '.' .. tostring(os.time())
|
|
if os.native and os.native.tickcount then
|
|
local key = os.native.tickcount() % 1000
|
|
tmpname = tmpname .. string.format('%03d', key)
|
|
tmpname = tmpname .. math.random_string(5)
|
|
else
|
|
tmpname = tmpname .. math.random_string(8)
|
|
end
|
|
if not os.path.exists(tmpname) then
|
|
-- print('tmpname: '..tmpname)
|
|
break
|
|
end
|
|
end
|
|
if windows then
|
|
if os.native and os.native.ReplaceFile then
|
|
fp = io.open(tmpname, 'w')
|
|
else
|
|
fp = io.open(filename, 'w')
|
|
tmpname = nil
|
|
end
|
|
else
|
|
fp = io.open(tmpname, 'w')
|
|
end
|
|
if fp == nil then
|
|
return false
|
|
end
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
local text = item.name .. '|' .. item.rank .. '|' .. item.time
|
|
fp:write(text .. '\n')
|
|
end
|
|
fp:close()
|
|
if tmpname ~= nil then
|
|
if windows then
|
|
local ok, err, code = os.rename(tmpname, filename)
|
|
if not ok then
|
|
os.native.ReplaceFile(filename, tmpname)
|
|
end
|
|
else
|
|
os.rename(tmpname, filename)
|
|
end
|
|
os.remove(tmpname)
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- filter out bad dirname
|
|
-----------------------------------------------------------------------
|
|
function data_filter(M)
|
|
local N = {}
|
|
local i
|
|
M = M ~= nil and M or {}
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
if os.path.isdir(item.name) then
|
|
table.insert(N, item)
|
|
end
|
|
end
|
|
return N
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- insert item
|
|
-----------------------------------------------------------------------
|
|
function data_insert(M, filename)
|
|
local i = 1
|
|
local sumscore = 0
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
sumscore = sumscore + item.rank
|
|
end
|
|
if sumscore >= MAX_AGE then
|
|
local X = {}
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
item.rank = item.rank * 0.9
|
|
if item.rank >= 1.0 then
|
|
table.insert(X, item)
|
|
end
|
|
end
|
|
M = X
|
|
end
|
|
local nocase = path_case_insensitive()
|
|
local name = filename
|
|
local key = nocase and string.lower(name) or name
|
|
local find = false
|
|
local current = os.time()
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
if not nocase then
|
|
if name == item.name then
|
|
item.rank = item.rank + 1
|
|
item.time = current
|
|
find = true
|
|
break
|
|
end
|
|
else
|
|
if key == string.lower(item.name) then
|
|
item.rank = item.rank + 1
|
|
item.time = current
|
|
find = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not find then
|
|
local item = {}
|
|
item.name = name
|
|
item.rank = 1
|
|
item.time = current
|
|
item.frecent = item.rank
|
|
table.insert(M, item)
|
|
end
|
|
return M
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- change database
|
|
-----------------------------------------------------------------------
|
|
function data_file_set(name)
|
|
DATA_FILE = name
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- change pattern
|
|
-----------------------------------------------------------------------
|
|
function case_insensitive_pattern(pattern)
|
|
-- find an optional '%' (group 1) followed by any character (group 2)
|
|
local p = pattern:gsub("(%%?)(.)", function(percent, letter)
|
|
|
|
if percent ~= "" or not letter:match("%a") then
|
|
-- if the '%' matched, or `letter` is not a letter, return "as is"
|
|
return percent .. letter
|
|
else
|
|
-- else, return a case-insensitive character class of the matched letter
|
|
return string.format("[%s%s]", letter:lower(), letter:upper())
|
|
end
|
|
end)
|
|
return p
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- pathmatch
|
|
-----------------------------------------------------------------------
|
|
function path_match(pathname, patterns, matchlast)
|
|
local pos = 1
|
|
local i = 0
|
|
local matchlast = matchlast ~= nil and matchlast or false
|
|
for i = 1, #patterns do
|
|
local pat = patterns[i]
|
|
local start, endup = pathname:find(pat, pos)
|
|
if start == nil or endup == nil then
|
|
return false
|
|
end
|
|
pos = endup + 1
|
|
end
|
|
if matchlast and #patterns > 0 then
|
|
local last = ''
|
|
local index = #patterns
|
|
local pat = patterns[index]
|
|
if not windows then
|
|
last = string.match(pathname, ".*(/.*)")
|
|
else
|
|
last = string.match(pathname, ".*([/\\].*)")
|
|
end
|
|
if last then
|
|
local start, endup = last:find(pat, 1)
|
|
if start == nil or endup == nil then
|
|
return false
|
|
end
|
|
end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- select matched pathnames
|
|
-----------------------------------------------------------------------
|
|
function data_select(M, patterns, matchlast)
|
|
local N = {}
|
|
local i = 1
|
|
local pats = {}
|
|
for i = 1, #patterns do
|
|
local p = patterns[i]
|
|
if Z_HYPHEN then
|
|
p = p:gsub('-', '%%-')
|
|
end
|
|
table.insert(pats, case_insensitive_pattern(p))
|
|
end
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
if path_match(item.name, pats, matchlast) then
|
|
table.insert(N, item)
|
|
end
|
|
end
|
|
return N
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- update frecent
|
|
-----------------------------------------------------------------------
|
|
function data_update_frecent(M)
|
|
local current = os.time()
|
|
local i
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
local dx = current - item.time
|
|
if dx < 3600 then
|
|
item.frecent = item.rank * 4
|
|
elseif dx < 86400 then
|
|
item.frecent = item.rank * 2
|
|
elseif dx < 604800 then
|
|
item.frecent = item.rank * 0.5
|
|
else
|
|
item.frecent = item.rank * 0.25
|
|
end
|
|
end
|
|
return M
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- add path
|
|
-----------------------------------------------------------------------
|
|
function z_add(path)
|
|
local paths = {}
|
|
local count = 0
|
|
if type(path) == 'table' then
|
|
paths = path
|
|
elseif type(path) == 'string' then
|
|
paths[1] = path
|
|
end
|
|
if table.length(paths) == 0 then
|
|
return false
|
|
end
|
|
local H = os.getenv('HOME')
|
|
local M = data_load(DATA_FILE)
|
|
local nc = os.getenv('_ZL_NO_CHECK')
|
|
if nc == nil or nc == '' or nc == '0' then
|
|
M = data_filter(M)
|
|
end
|
|
-- insert paths
|
|
for _, path in pairs(paths) do
|
|
if os.path.isdir(path) and os.path.isabs(path) then
|
|
local skip = false
|
|
local test = path
|
|
path = os.path.norm(path)
|
|
-- check ignore
|
|
if windows then
|
|
if path:len() == 3 and path:sub(2, 2) == ':' then
|
|
local tail = path:sub(3, 3)
|
|
if tail == '/' or tail == '\\' then
|
|
skip = true
|
|
end
|
|
end
|
|
test = os.path.norm(path:lower())
|
|
else
|
|
if H == path then
|
|
skip = true
|
|
end
|
|
end
|
|
-- check exclude
|
|
if not skip then
|
|
for _, exclude in ipairs(Z_EXCLUDE) do
|
|
if test:startswith(exclude) then
|
|
skip = true
|
|
break
|
|
end
|
|
end
|
|
end
|
|
if not skip then
|
|
if windows then
|
|
if os.native and os.native.GetRealPathName then
|
|
local ts = os.native.GetRealPathName(path)
|
|
if ts then
|
|
path = ts
|
|
end
|
|
end
|
|
end
|
|
M = data_insert(M, path)
|
|
count = count + 1
|
|
end
|
|
end
|
|
end
|
|
if count > 0 then
|
|
data_save(DATA_FILE, M)
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- remove path
|
|
-----------------------------------------------------------------------
|
|
function z_remove(path)
|
|
local paths = {}
|
|
local count = 0
|
|
local remove = {}
|
|
if type(path) == 'table' then
|
|
paths = path
|
|
elseif type(path) == 'string' then
|
|
paths[1] = path
|
|
end
|
|
if table.length(paths) == 0 then
|
|
return false
|
|
end
|
|
local H = os.getenv('HOME')
|
|
local M = data_load(DATA_FILE)
|
|
local X = {}
|
|
M = data_filter(M)
|
|
local insensitive = path_case_insensitive()
|
|
for _, path in pairs(paths) do
|
|
path = os.path.abspath(path)
|
|
if not insensitive then
|
|
remove[path] = 1
|
|
else
|
|
remove[path:lower()] = 1
|
|
end
|
|
end
|
|
for i = 1, #M do
|
|
local item = M[i]
|
|
if not insensitive then
|
|
if not remove[item.name] then
|
|
table.insert(X, item)
|
|
end
|
|
else
|
|
if not remove[item.name:lower()] then
|
|
table.insert(X, item)
|
|
end
|
|
end
|
|
end
|
|
data_save(DATA_FILE, X)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- match method: frecent, rank, time
|
|
-----------------------------------------------------------------------
|
|
function z_match(patterns, method, subdir)
|
|
patterns = patterns ~= nil and patterns or {}
|
|
method = method ~= nil and method or 'frecent'
|
|
subdir = subdir ~= nil and subdir or false
|
|
local M = data_load(DATA_FILE)
|
|
M = data_select(M, patterns, false)
|
|
M = data_filter(M)
|
|
if Z_MATCHNAME then
|
|
local N = data_select(M, patterns, true)
|
|
N = data_filter(N)
|
|
if #N > 0 then
|
|
M = N
|
|
end
|
|
end
|
|
M = data_update_frecent(M)
|
|
if method == 'time' then
|
|
current = os.time()
|
|
for _, item in pairs(M) do
|
|
item.score = item.time - current
|
|
end
|
|
elseif method == 'rank' then
|
|
for _, item in pairs(M) do
|
|
item.score = item.rank
|
|
end
|
|
else
|
|
for _, item in pairs(M) do
|
|
item.score = item.frecent
|
|
end
|
|
end
|
|
table.sort(M, function(a, b) return a.score > b.score end)
|
|
local pwd = (PWD == nil or PWD == '') and os.getenv('PWD') or PWD
|
|
if pwd == nil or pwd == '' then
|
|
pwd = os.pwd()
|
|
end
|
|
if pwd ~= '' and pwd ~= nil then
|
|
if subdir then
|
|
local N = {}
|
|
for _, item in pairs(M) do
|
|
if os.path.subdir(pwd, item.name) then
|
|
table.insert(N, item)
|
|
end
|
|
end
|
|
M = N
|
|
end
|
|
if Z_SKIPPWD then
|
|
local N = {}
|
|
local key = windows and string.lower(pwd) or pwd
|
|
for _, item in pairs(M) do
|
|
local match = false
|
|
local name = windows and string.lower(item.name) or item.name
|
|
if name ~= key then
|
|
table.insert(N, item)
|
|
end
|
|
end
|
|
M = N
|
|
end
|
|
end
|
|
return M
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- pretty print
|
|
-----------------------------------------------------------------------
|
|
function z_print(M, weight, number)
|
|
local N = {}
|
|
local maxsize = 9
|
|
local numsize = string.len(tostring(#M))
|
|
for _, item in pairs(M) do
|
|
local record = {}
|
|
record.score = string.format('%.2f', item.score)
|
|
record.name = item.name
|
|
table.insert(N, record)
|
|
if record.score:len() > maxsize then
|
|
maxsize = record.score:len()
|
|
end
|
|
end
|
|
local fp = io.stdout
|
|
if PRINT_MODE == '<stdout>' then
|
|
fp = io.stdout
|
|
elseif PRINT_MODE == '<stderr>' then
|
|
fp = io.stderr
|
|
else
|
|
fp = io.open(PRINT_MODE, 'w')
|
|
end
|
|
for i = #N, 1, -1 do
|
|
local record = N[i]
|
|
local line = record.score
|
|
while true do
|
|
local tail = line:sub(-1, -1)
|
|
if tail ~= '0' and tail ~= '.' then
|
|
break
|
|
end
|
|
line = line:sub(1, -2)
|
|
if tail == '.' then
|
|
break
|
|
end
|
|
end
|
|
local dx = maxsize - line:len()
|
|
if dx > 0 then
|
|
line = line .. string.rep(' ', dx)
|
|
end
|
|
if weight then
|
|
line = line .. ' ' .. record.name
|
|
else
|
|
line = record.name
|
|
end
|
|
if number then
|
|
local head = tostring(i)
|
|
if head:len() < numsize then
|
|
head = string.rep(' ', numsize - head:len()) .. head
|
|
end
|
|
line = head .. ': ' .. line
|
|
end
|
|
if fp ~= nil then
|
|
fp:write(line .. '\n')
|
|
end
|
|
end
|
|
if PRINT_MODE:sub(1, 1) ~= '<' then
|
|
if fp ~= nil then fp:close() end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- calculate jump dir
|
|
-----------------------------------------------------------------------
|
|
function z_cd(patterns)
|
|
if patterns == nil then
|
|
return nil
|
|
end
|
|
if #patterns == 0 then
|
|
return nil
|
|
end
|
|
local last = patterns[#patterns]
|
|
if last == '~' or last == '~/' then
|
|
return os.path.expand('~')
|
|
elseif windows and last == '~\\' then
|
|
return os.path.expand('~')
|
|
end
|
|
if os.path.isabs(last) and os.path.isdir(last) then
|
|
local size = #patterns
|
|
if size <= 1 then
|
|
return os.path.norm(last)
|
|
elseif last ~= '/' and last ~= '\\' then
|
|
return os.path.norm(last)
|
|
end
|
|
end
|
|
local M = z_match(patterns, Z_METHOD, Z_SUBDIR)
|
|
if M == nil then
|
|
return nil
|
|
end
|
|
if #M == 0 then
|
|
return nil
|
|
elseif #M == 1 then
|
|
return M[1].name
|
|
elseif Z_INTERACTIVE == 0 then
|
|
return M[1].name
|
|
end
|
|
if os.environ('_ZL_INT_SORT', false) then
|
|
table.sort(M, function(a, b) return a.name < b.name end)
|
|
end
|
|
local retval = nil
|
|
if Z_INTERACTIVE == 1 then
|
|
PRINT_MODE = '<stderr>'
|
|
z_print(M, true, true)
|
|
io.stderr:write('> ')
|
|
io.stderr:flush()
|
|
local input = io.read('*l')
|
|
if input == nil or input == '' then
|
|
return nil
|
|
end
|
|
local index = tonumber(input)
|
|
if index == nil then
|
|
return nil
|
|
end
|
|
if index < 1 or index > #M then
|
|
return nil
|
|
end
|
|
retval = M[index].name
|
|
elseif Z_INTERACTIVE == 2 then
|
|
local fzf = os.environ('_ZL_FZF', 'fzf')
|
|
local tmpname = '/tmp/zlua.txt'
|
|
local cmd = '--nth 2.. --reverse --inline-info --tac '
|
|
local flag = os.environ('_ZL_FZF_FLAG', '')
|
|
flag = (flag == '' or flag == nil) and '+s -e' or flag
|
|
cmd = ((fzf == '') and 'fzf' or fzf) .. ' ' .. cmd .. ' ' .. flag
|
|
if not windows then
|
|
tmpname = os.tmpname()
|
|
local height = os.environ('_ZL_FZF_HEIGHT', '35%')
|
|
if height ~= nil and height ~= '' and height ~= '0' then
|
|
cmd = cmd .. ' --height ' .. height
|
|
end
|
|
cmd = cmd .. ' < "' .. tmpname .. '"'
|
|
else
|
|
tmpname = os.tmpname():gsub('\\', ''):gsub('%.', '')
|
|
tmpname = os.environ('TMP', '') .. '\\zlua_' .. tmpname .. '.txt'
|
|
cmd = 'type "' .. tmpname .. '" | ' .. cmd
|
|
end
|
|
PRINT_MODE = tmpname
|
|
z_print(M, true, false)
|
|
retval = os.call(cmd)
|
|
-- io.stderr:write('<'..cmd..'>\n')
|
|
os.remove(tmpname)
|
|
if retval == '' or retval == nil then
|
|
return nil
|
|
end
|
|
local pos = retval:find(' ')
|
|
if not pos then
|
|
return nil
|
|
end
|
|
retval = retval:sub(pos, -1):gsub('^%s*', '')
|
|
end
|
|
return (retval ~= '' and retval or nil)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- purge invalid paths
|
|
-----------------------------------------------------------------------
|
|
function z_purge()
|
|
local M = data_load(DATA_FILE)
|
|
local N = data_filter(M)
|
|
local x = #M
|
|
local y = #N
|
|
if x == y then
|
|
return x, y
|
|
end
|
|
data_save(DATA_FILE, N)
|
|
return x, y
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- find_vcs_root
|
|
-----------------------------------------------------------------------
|
|
function find_vcs_root(path)
|
|
local markers = os.getenv('_ZL_ROOT_MARKERS')
|
|
local markers = markers and markers or '.git,.svn,.hg,.root'
|
|
local markers = string.split(markers, ',')
|
|
path = os.path.absolute(path)
|
|
while true do
|
|
for _, marker in ipairs(markers) do
|
|
local test = os.path.join(path, marker)
|
|
if os.path.exists(test) then
|
|
return path
|
|
end
|
|
end
|
|
local parent, _ = os.path.split(path)
|
|
if path == parent then break end
|
|
path = parent
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- cd to parent directories which contains keyword
|
|
-- #args == 0 -> returns to vcs root
|
|
-- #args == 1 -> returns to parent dir starts with args[1]
|
|
-- #args == 2 -> returns string.replace($PWD, args[1], args[2])
|
|
-----------------------------------------------------------------------
|
|
function cd_backward(args, options, pwd)
|
|
local nargs = #args
|
|
local pwd = (pwd ~= nil) and pwd or os.pwd()
|
|
if nargs == 0 then
|
|
return find_vcs_root(pwd)
|
|
elseif nargs == 1 then
|
|
if args[1]:sub(1, 2) == '..' then
|
|
local size = args[1]:len() - 1
|
|
if args[1]:match('^%.%.+$') then
|
|
size = args[1]:len() - 1
|
|
elseif args[1]:match('^%.%.%d+$') then
|
|
size = tonumber(args[1]:sub(3))
|
|
else
|
|
return nil
|
|
end
|
|
local path = pwd
|
|
for index = 1, size do
|
|
path = os.path.join(path, '..')
|
|
end
|
|
return os.path.normpath(path)
|
|
else
|
|
pwd = os.path.split(pwd)
|
|
local test = windows and pwd:gsub('\\', '/') or pwd
|
|
local key = windows and args[1]:lower() or args[1]
|
|
if not key:match('%u') then
|
|
test = test:lower()
|
|
end
|
|
local pos, ends = test:rfind('/' .. key)
|
|
if pos then
|
|
ends = test:find('/', pos + key:len() + 1, true)
|
|
ends = ends and ends or test:len()
|
|
return os.path.normpath(pwd:sub(1, ends))
|
|
elseif windows and test:startswith(key) then
|
|
ends = test:find('/', key:len(), true)
|
|
ends = ends and ends or test:len()
|
|
return os.path.normpath(pwd:sub(1, ends))
|
|
end
|
|
pos = test:rfind(key)
|
|
if pos then
|
|
ends = test:find('/', pos + key:len(), true)
|
|
ends = ends and ends or test:len()
|
|
return os.path.normpath(pwd:sub(1, ends))
|
|
end
|
|
return nil
|
|
end
|
|
else
|
|
local test = windows and pwd:gsub('\\', '/') or pwd
|
|
local src = args[1]
|
|
local dst = args[2]
|
|
if not src:match('%u') then
|
|
test = test:lower()
|
|
end
|
|
local start, ends = test:rfind(src)
|
|
if not start then
|
|
return pwd
|
|
end
|
|
local lhs = pwd:sub(1, start - 1)
|
|
local rhs = pwd:sub(ends + 1)
|
|
return lhs .. dst .. rhs
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- cd minus: "z -", "z --", "z -2"
|
|
-----------------------------------------------------------------------
|
|
function cd_minus(args, options)
|
|
Z_SKIPPWD = true
|
|
local M = z_match({}, 'time', Z_SUBDIR)
|
|
local size = #M
|
|
if options['-'] == '-' then
|
|
for i, item in ipairs(M) do
|
|
if i > 10 then break end
|
|
io.stderr:write(' ' .. tostring(i - 1) .. ' ' .. item.name .. '\n')
|
|
end
|
|
else
|
|
local level = 0
|
|
local num = options['-']
|
|
if num and num ~= '' then
|
|
level = tonumber(num)
|
|
end
|
|
if level >= 0 and level < size then
|
|
return M[level + 1].name
|
|
end
|
|
end
|
|
return nil
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- cd breadcrumbs: z -b -i, z -b -I
|
|
-----------------------------------------------------------------------
|
|
function cd_breadcrumbs(pwd, interactive)
|
|
local pwd = (pwd == nil or pwd == '') and os.pwd() or pwd
|
|
local pwd = os.path.normpath(pwd)
|
|
local path, _ = os.path.split(pwd)
|
|
local elements = {}
|
|
local interactive = interactive and interactive or 1
|
|
local fullname = os.environ('_ZL_FULL_PATH', false)
|
|
while true do
|
|
local head, name = os.path.split(path)
|
|
if head == path then -- reached root
|
|
table.insert(elements, { head, head })
|
|
break
|
|
elseif name ~= '' then
|
|
table.insert(elements, { name, path })
|
|
else
|
|
break
|
|
end
|
|
path = head
|
|
end
|
|
local tmpname = '/tmp/zlua.txt'
|
|
local fp = io.stderr
|
|
if interactive == 2 then
|
|
if not windows then
|
|
tmpname = os.tmpname()
|
|
else
|
|
tmpname = os.tmpname():gsub('\\', ''):gsub('%.', '')
|
|
tmpname = os.environ('TMP', '') .. '\\zlua_' .. tmpname .. '.txt'
|
|
end
|
|
fp = io.open(tmpname, 'w')
|
|
end
|
|
-- print table
|
|
local maxsize = string.len(tostring(#elements))
|
|
for i = #elements, 1, -1 do
|
|
local item = elements[i]
|
|
local name = item[1]
|
|
local text = string.rep(' ', maxsize - string.len(i)) .. tostring(i)
|
|
text = text .. ': ' .. (fullname and item[2] or item[1])
|
|
fp:write(text .. '\n')
|
|
end
|
|
if fp ~= io.stderr then
|
|
fp:close()
|
|
end
|
|
local retval = ''
|
|
-- select from stdin or fzf
|
|
if interactive == 1 then
|
|
io.stderr:write('> ')
|
|
io.stderr:flush()
|
|
retval = io.read('*l')
|
|
elseif interactive == 2 then
|
|
local fzf = os.environ('_ZL_FZF', 'fzf')
|
|
local cmd = '--reverse --inline-info --tac '
|
|
local flag = os.environ('_ZL_FZF_FLAG', '')
|
|
flag = (flag == '' or flag == nil) and '+s -e' or flag
|
|
cmd = ((fzf == '') and 'fzf' or fzf) .. ' ' .. cmd .. ' ' .. flag
|
|
if not windows then
|
|
local height = os.environ('_ZL_FZF_HEIGHT', '35%')
|
|
if height ~= nil and height ~= '' and height ~= '0' then
|
|
cmd = cmd .. ' --height ' .. height
|
|
end
|
|
cmd = cmd .. '< "' .. tmpname .. '"'
|
|
else
|
|
cmd = 'type "' .. tmpname .. '" | ' .. cmd
|
|
end
|
|
retval = os.call(cmd)
|
|
os.remove(tmpname)
|
|
if retval == '' or retval == nil then
|
|
return nil
|
|
end
|
|
local pos = retval:find(':')
|
|
if not pos then
|
|
return nil
|
|
end
|
|
retval = retval:sub(1, pos - 1):gsub('^%s*', '')
|
|
end
|
|
local index = tonumber(retval)
|
|
if index == nil or index < 1 or index > #elements then
|
|
return nil
|
|
end
|
|
return elements[index][2]
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- main entry
|
|
-----------------------------------------------------------------------
|
|
function main(argv)
|
|
local options, args = os.getopt(argv)
|
|
os.log("main()")
|
|
if options == nil then
|
|
return false
|
|
elseif table.length(args) == 0 and table.length(options) == 0 then
|
|
print(os.argv[0] .. ': missing arguments')
|
|
help = os.argv[-1] .. ' ' .. os.argv[0] .. ' --help'
|
|
print('Try \'' .. help .. '\' for more information')
|
|
return false
|
|
end
|
|
if true then
|
|
os.log("options: " .. dump(options))
|
|
os.log("args: " .. dump(args))
|
|
end
|
|
if options['-c'] then
|
|
Z_SUBDIR = true
|
|
end
|
|
if options['-r'] then
|
|
Z_METHOD = 'rank'
|
|
elseif options['-t'] then
|
|
Z_METHOD = 'time'
|
|
end
|
|
if options['-i'] then
|
|
Z_INTERACTIVE = 1
|
|
elseif options['-I'] then
|
|
Z_INTERACTIVE = 2
|
|
end
|
|
if options['--cd'] or options['-e'] then
|
|
local path = ''
|
|
if options['-b'] then
|
|
if Z_INTERACTIVE == 0 then
|
|
path = cd_backward(args, options)
|
|
else
|
|
path = cd_breadcrumbs('', Z_INTERACTIVE)
|
|
end
|
|
elseif options['-'] then
|
|
path = cd_minus(args, options)
|
|
elseif #args == 0 then
|
|
path = nil
|
|
else
|
|
path = z_cd(args)
|
|
if path == nil and Z_MATCHMODE ~= 0 then
|
|
local last = args[#args]
|
|
if os.path.isdir(last) then
|
|
path = os.path.abspath(last)
|
|
path = os.path.norm(path)
|
|
end
|
|
end
|
|
end
|
|
if path ~= nil then
|
|
io.write(path .. (options['-e'] and "\n" or ""))
|
|
end
|
|
elseif options['--add'] then
|
|
-- print('data: ' .. DATA_FILE)
|
|
z_add(args)
|
|
elseif options['-x'] then
|
|
z_remove(args)
|
|
elseif options['--purge'] then
|
|
local src, dst = z_purge()
|
|
local fp = io.stderr
|
|
fp:write('purge: ' .. tostring(src) .. ' record(s) remaining, ')
|
|
fp:write(tostring(src - dst) .. ' invalid record(s) removed.\n')
|
|
elseif options['--init'] then
|
|
local opts = {}
|
|
for _, key in ipairs(args) do
|
|
opts[key] = 1
|
|
end
|
|
if windows then
|
|
z_windows_init(opts)
|
|
elseif opts.fish then
|
|
z_fish_init(opts)
|
|
elseif opts.powershell then
|
|
z_windows_init(opts)
|
|
else
|
|
z_shell_init(opts)
|
|
end
|
|
elseif options['-l'] then
|
|
local M = z_match(args and args or {}, Z_METHOD, Z_SUBDIR)
|
|
if options['-s'] then
|
|
z_print(M, false, false)
|
|
else
|
|
z_print(M, true, false)
|
|
end
|
|
elseif options['--complete'] then
|
|
local line = args[1] and args[1] or ''
|
|
local head = line:sub(Z_CMD:len() + 1):gsub('^%s+', '')
|
|
local M = z_match({ head }, Z_METHOD, Z_SUBDIR)
|
|
for _, item in pairs(M) do
|
|
print(item.name)
|
|
end
|
|
elseif options['--help'] or options['-h'] then
|
|
z_help()
|
|
end
|
|
return true
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- initialize from environment variable
|
|
-----------------------------------------------------------------------
|
|
function z_init()
|
|
local _zl_data = os.getenv('_ZL_DATA')
|
|
local _zl_maxage = os.getenv('_ZL_MAXAGE')
|
|
local _zl_exclude = os.getenv('_ZL_EXCLUDE_DIRS')
|
|
local _zl_cmd = os.getenv('_ZL_CMD')
|
|
local _zl_matchname = os.getenv('_ZL_MATCH_NAME')
|
|
local _zl_skippwd = os.getenv('_ZL_SKIP_PWD')
|
|
local _zl_matchmode = os.getenv('_ZL_MATCH_MODE')
|
|
local _zl_hyphen = os.getenv('_ZL_HYPHEN')
|
|
if _zl_data ~= nil and _zl_data ~= "" then
|
|
if windows then
|
|
DATA_FILE = _zl_data
|
|
else
|
|
-- avoid windows environments affect cygwin & msys
|
|
if not string.match(_zl_data, '^%a:[/\\]') then
|
|
DATA_FILE = _zl_data
|
|
end
|
|
end
|
|
end
|
|
if _zl_maxage ~= nil and _zl_maxage ~= "" then
|
|
_zl_maxage = tonumber(_zl_maxage)
|
|
if _zl_maxage ~= nil and _zl_maxage > 0 then
|
|
MAX_AGE = _zl_maxage
|
|
end
|
|
end
|
|
if _zl_exclude ~= nil and _zl_exclude ~= "" then
|
|
local part = _zl_exclude:split(',')
|
|
local insensitive = path_case_insensitive()
|
|
for _, name in ipairs(part) do
|
|
if insensitive then
|
|
name = name:lower()
|
|
end
|
|
if windows then
|
|
name = os.path.norm(name)
|
|
end
|
|
table.insert(Z_EXCLUDE, name)
|
|
end
|
|
end
|
|
if _zl_cmd ~= nil and _zl_cmd ~= '' then
|
|
Z_CMD = _zl_cmd
|
|
end
|
|
if _zl_matchname ~= nil then
|
|
local m = string.lower(_zl_matchname)
|
|
if (m == '1' or m == 'yes' or m == 'true' or m == 't') then
|
|
Z_MATCHNAME = true
|
|
end
|
|
end
|
|
if _zl_skippwd ~= nil then
|
|
local m = string.lower(_zl_skippwd)
|
|
if (m == '1' or m == 'yes' or m == 'true' or m == 't') then
|
|
Z_SKIPPWD = true
|
|
end
|
|
end
|
|
if _zl_matchmode ~= nil then
|
|
local m = tonumber(_zl_matchmode)
|
|
Z_MATCHMODE = m
|
|
if (m == 1) then
|
|
Z_MATCHNAME = true
|
|
Z_SKIPPWD = true
|
|
end
|
|
end
|
|
if _zl_hyphen ~= nil then
|
|
local m = string.lower(_zl_hyphen)
|
|
if (m == '1' or m == 'yes' or m == 'true' or m == 't') then
|
|
Z_HYPHEN = true
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- initialize clink hooks
|
|
-----------------------------------------------------------------------
|
|
function z_clink_init()
|
|
local once = os.environ("_ZL_ADD_ONCE", false)
|
|
local _zl_clink_prompt_priority = os.environ('_ZL_CLINK_PROMPT_PRIORITY', 99)
|
|
local previous = ''
|
|
function z_add_to_database()
|
|
pwd = clink.get_cwd()
|
|
if once then
|
|
if previous == pwd then
|
|
return
|
|
end
|
|
previous = pwd
|
|
end
|
|
z_add(clink.get_cwd())
|
|
end
|
|
|
|
clink.prompt.register_filter(z_add_to_database, _zl_clink_prompt_priority)
|
|
function z_match_completion(word)
|
|
local M = z_match({ word }, Z_METHOD, Z_SUBDIR)
|
|
for _, item in pairs(M) do
|
|
clink.add_match(item.name)
|
|
end
|
|
return {}
|
|
end
|
|
|
|
local z_parser = clink.arg.new_parser()
|
|
z_parser:set_arguments({ z_match_completion })
|
|
z_parser:set_flags("-c", "-r", "-i", "--cd", "-e", "-b", "--add", "-x", "--purge",
|
|
"--init", "-l", "-s", "--complete", "--help", "-h")
|
|
clink.arg.register_parser("z", z_parser)
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- shell scripts
|
|
-----------------------------------------------------------------------
|
|
local script_zlua = [[
|
|
_zlua() {
|
|
local arg_mode=""
|
|
local arg_type=""
|
|
local arg_subdir=""
|
|
local arg_inter=""
|
|
local arg_strip=""
|
|
if [ "$1" = "--add" ]; then
|
|
shift
|
|
_ZL_RANDOM="$RANDOM" "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --add "$@"
|
|
return
|
|
elif [ "$1" = "--complete" ]; then
|
|
shift
|
|
"$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete "$@"
|
|
return
|
|
fi
|
|
while [ "$1" ]; do
|
|
case "$1" in
|
|
-l) local arg_mode="-l" ;;
|
|
-e) local arg_mode="-e" ;;
|
|
-x) local arg_mode="-x" ;;
|
|
-t) local arg_type="-t" ;;
|
|
-r) local arg_type="-r" ;;
|
|
-c) local arg_subdir="-c" ;;
|
|
-s) local arg_strip="-s" ;;
|
|
-i) local arg_inter="-i" ;;
|
|
-I) local arg_inter="-I" ;;
|
|
-h|--help) local arg_mode="-h" ;;
|
|
--purge) local arg_mode="--purge" ;;
|
|
*) break ;;
|
|
esac
|
|
shift
|
|
done
|
|
if [ "$arg_mode" = "-h" ] || [ "$arg_mode" = "--purge" ]; then
|
|
"$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode
|
|
elif [ "$arg_mode" = "-l" ] || [ "$#" -eq 0 ]; then
|
|
"$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip "$@"
|
|
elif [ -n "$arg_mode" ]; then
|
|
"$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode $arg_subdir $arg_type $arg_inter "$@"
|
|
else
|
|
local zdest=$("$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --cd $arg_type $arg_subdir $arg_inter "$@")
|
|
if [ -n "$zdest" ] && [ -d "$zdest" ]; then
|
|
if [ -z "$_ZL_CD" ]; then
|
|
builtin cd "$zdest"
|
|
else
|
|
$_ZL_CD "$zdest"
|
|
fi
|
|
if [ -n "$_ZL_ECHO" ]; then pwd; fi
|
|
fi
|
|
fi
|
|
}
|
|
# alias ${_ZL_CMD:-z}='_zlua 2>&1'
|
|
alias ${_ZL_CMD:-z}='_zlua'
|
|
]]
|
|
|
|
local script_init_bash = [[
|
|
case "$PROMPT_COMMAND" in
|
|
*_zlua?--add*) ;;
|
|
*) PROMPT_COMMAND="(_zlua --add \"\$(command pwd 2>/dev/null)\" &)${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;;
|
|
esac
|
|
]]
|
|
|
|
local script_init_bash_fast = [[
|
|
case "$PROMPT_COMMAND" in
|
|
*_zlua?--add*) ;;
|
|
*) PROMPT_COMMAND="(_zlua --add \"\$PWD\" &)${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;;
|
|
esac
|
|
]]
|
|
|
|
local script_init_bash_once = [[
|
|
_zlua_precmd() {
|
|
[ "$_ZL_PREVIOUS_PWD" = "$PWD" ] && return
|
|
_ZL_PREVIOUS_PWD="$PWD"
|
|
(_zlua --add "$PWD" 2> /dev/null &)
|
|
}
|
|
case "$PROMPT_COMMAND" in
|
|
*_zlua_precmd*) ;;
|
|
*) PROMPT_COMMAND="_zlua_precmd${PROMPT_COMMAND:+;$PROMPT_COMMAND}" ;;
|
|
esac
|
|
]]
|
|
|
|
local script_init_posix = [[
|
|
case "$PS1" in
|
|
*_zlua?--add*) ;;
|
|
*) PS1="\$(_zlua --add \"\$(command pwd 2>/dev/null)\" &)$PS1"
|
|
esac
|
|
]]
|
|
|
|
local script_init_posix_once = [[
|
|
_zlua_precmd() {
|
|
[ "$_ZL_PREVIOUS_PWD" = "$PWD" ] && return
|
|
_ZL_PREVIOUS_PWD="$PWD"
|
|
(_zlua --add "$PWD" 2> /dev/null &)
|
|
}
|
|
case "$PS1" in
|
|
*_zlua_precmd*) ;;
|
|
*) PS1="\$(_zlua_precmd)$PS1"
|
|
esac
|
|
]]
|
|
|
|
local script_init_zsh = [[
|
|
_zlua_precmd() {
|
|
(_zlua --add "${PWD:a}" &)
|
|
}
|
|
typeset -ga precmd_functions
|
|
[ -n "${precmd_functions[(r)_zlua_precmd]}" ] || {
|
|
precmd_functions[$(($#precmd_functions+1))]=_zlua_precmd
|
|
}
|
|
]]
|
|
|
|
local script_init_zsh_once = [[
|
|
_zlua_precmd() {
|
|
(_zlua --add "${PWD:a}" &)
|
|
}
|
|
typeset -ga chpwd_functions
|
|
[ -n "${chpwd_functions[(r)_zlua_precmd]}" ] || {
|
|
chpwd_functions[$(($#chpwd_functions+1))]=_zlua_precmd
|
|
}
|
|
]]
|
|
|
|
local script_complete_bash = [[
|
|
if [ -n "$BASH_VERSION" ]; then
|
|
complete -o filenames -C '_zlua --complete "$COMP_LINE"' ${_ZL_CMD:-z}
|
|
fi
|
|
]]
|
|
|
|
local script_fzf_complete_bash = [[
|
|
if [ "$TERM" != "dumb" ] && command -v fzf >/dev/null 2>&1; then
|
|
# To redraw line after fzf closes (printf '\e[5n')
|
|
bind '"\e[0n": redraw-current-line'
|
|
_zlua_fzf_complete() {
|
|
local selected=$(_zlua -l "${COMP_WORDS[@]:1}" | sed "s|$HOME|\~|" | $zlua_fzf | sed 's/^[0-9,.]* *//')
|
|
if [ -n "$selected" ]; then
|
|
COMPREPLY=( "$selected" )
|
|
fi
|
|
printf '\e[5n'
|
|
}
|
|
complete -o bashdefault -o nospace -F _zlua_fzf_complete ${_ZL_CMD:-z}
|
|
fi
|
|
]]
|
|
|
|
local script_complete_zsh = [[
|
|
_zlua_zsh_tab_completion() {
|
|
# tab completion
|
|
(( $+compstate )) && compstate[insert]=menu # no expand
|
|
local -a tmp=(${(f)"$(_zlua --complete "${words/_zlua/z}")"})
|
|
_describe "directory" tmp -U
|
|
}
|
|
if [ "${+functions[compdef]}" -ne 0 ]; then
|
|
compdef _zlua_zsh_tab_completion _zlua 2> /dev/null
|
|
fi
|
|
]]
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- initialize bash/zsh
|
|
----------------------------------------------------------------------
|
|
function z_shell_init(opts)
|
|
print('ZLUA_SCRIPT="' .. os.scriptname() .. '"')
|
|
print('ZLUA_LUAEXE="' .. os.interpreter() .. '"')
|
|
print('')
|
|
if not opts.posix then
|
|
print(script_zlua)
|
|
elseif not opts.legacy then
|
|
local script = script_zlua:gsub('builtin ', '')
|
|
print(script)
|
|
else
|
|
local script = script_zlua:gsub('local ', ''):gsub('builtin ', '')
|
|
print(script)
|
|
end
|
|
|
|
local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false))
|
|
local once = os.environ("_ZL_ADD_ONCE", false) or opts.once ~= nil
|
|
|
|
if opts.clean ~= nil then
|
|
prompt_hook = false
|
|
end
|
|
|
|
if opts.bash ~= nil then
|
|
if prompt_hook then
|
|
if once then
|
|
print(script_init_bash_once)
|
|
elseif opts.fast then
|
|
print(script_init_bash_fast)
|
|
else
|
|
print(script_init_bash)
|
|
end
|
|
end
|
|
print(script_complete_bash)
|
|
if opts.fzf ~= nil then
|
|
fzf_cmd = "fzf --nth 2.. --reverse --inline-info --tac "
|
|
local height = os.environ('_ZL_FZF_HEIGHT', '35%')
|
|
if height ~= nil and height ~= '' and height ~= '0' then
|
|
fzf_cmd = fzf_cmd .. ' --height ' .. height .. ' '
|
|
end
|
|
local flag = os.environ('_ZL_FZF_FLAG', '')
|
|
flag = (flag == '' or flag == nil) and '+s -e' or flag
|
|
fzf_cmd = fzf_cmd .. ' ' .. flag .. ' '
|
|
print('zlua_fzf="' .. fzf_cmd .. '"')
|
|
print(script_fzf_complete_bash)
|
|
end
|
|
elseif opts.zsh ~= nil then
|
|
if prompt_hook then
|
|
print(once and script_init_zsh_once or script_init_zsh)
|
|
end
|
|
print(script_complete_zsh)
|
|
elseif opts.posix ~= nil then
|
|
if prompt_hook then
|
|
local script = script_init_posix
|
|
if once then script = script_init_posix_once end
|
|
if opts.legacy then
|
|
script = script:gsub('%&%)', ')')
|
|
end
|
|
print(script)
|
|
end
|
|
else
|
|
if prompt_hook then
|
|
print('if [ -n "$BASH_VERSION" ]; then')
|
|
if opts.once then
|
|
print(script_init_bash_once)
|
|
elseif opts.fast then
|
|
print(script_init_bash_fast)
|
|
else
|
|
print(script_init_bash)
|
|
end
|
|
print(script_complete_bash)
|
|
print('elif [ -n "$ZSH_VERSION" ]; then')
|
|
print(once and script_init_zsh_once or script_init_zsh)
|
|
-- print(script_complete_zsh)
|
|
print('else')
|
|
print(once and script_init_posix_once or script_init_posix)
|
|
print('builtin() { cd "$2"; }')
|
|
print('fi')
|
|
end
|
|
end
|
|
if opts.enhanced ~= nil then
|
|
print('export _ZL_MATCH_MODE=1')
|
|
end
|
|
if opts.nc then
|
|
print('export _ZL_NO_CHECK=1')
|
|
end
|
|
if opts.echo then
|
|
print('_ZL_ECHO=1')
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- Fish shell init
|
|
-----------------------------------------------------------------------
|
|
local script_zlua_fish = [[
|
|
function _zlua
|
|
set -l arg_mode ""
|
|
set -l arg_type ""
|
|
set -l arg_subdir ""
|
|
set -l arg_inter ""
|
|
set -l arg_strip ""
|
|
function _zlua_call; eval (string escape -- $argv); end
|
|
if test "$argv[1]" = "--add"
|
|
set -e argv[1]
|
|
set -x _ZL_RANDOM (random)
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --add $argv
|
|
return
|
|
else if test "$argv[1]" = "--complete"
|
|
set -e argv[1]
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --complete $argv
|
|
return
|
|
end
|
|
while true
|
|
switch "$argv[1]"
|
|
case "-l"; set arg_mode "-l"
|
|
case "-e"; set arg_mode "-e"
|
|
case "-x"; set arg_mode "-x"
|
|
case "-t"; set arg_type "-t"
|
|
case "-r"; set arg_type "-r"
|
|
case "-c"; set arg_subdir "-c"
|
|
case "-s"; set arg_strip "-s"
|
|
case "-i"; set arg_inter "-i"
|
|
case "-I"; set arg_inter "-I"
|
|
case "-h"; set arg_mode "-h"
|
|
case "--help"; set arg_mode "-h"
|
|
case "--purge"; set arg_mode "--purge"
|
|
case '*'; break
|
|
end
|
|
set -e argv[1]
|
|
end
|
|
if test "$arg_mode" = "-h"
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -h
|
|
else if test "$arg_mode" = "--purge"
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --purge
|
|
else if test "$arg_mode" = "-l"
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip $argv
|
|
else if test (count $argv) -eq 0
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" -l $arg_subdir $arg_type $arg_strip $argv
|
|
else if test -n "$arg_mode"
|
|
_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" $arg_mode $arg_subdir $arg_type $arg_inter $argv
|
|
else
|
|
set -l dest (_zlua_call "$ZLUA_LUAEXE" "$ZLUA_SCRIPT" --cd $arg_type $arg_subdir $arg_inter $argv)
|
|
if test -n "$dest" -a -d "$dest"
|
|
if test -z "$_ZL_CD"
|
|
builtin cd "$dest"
|
|
else
|
|
_zlua_call "$_ZL_CD" "$dest"
|
|
end
|
|
if test -n "$_ZL_ECHO"; pwd; end
|
|
end
|
|
end
|
|
end
|
|
|
|
if test -z "$_ZL_CMD"; set -x _ZL_CMD z; end
|
|
alias "$_ZL_CMD"=_zlua
|
|
]]
|
|
|
|
script_init_fish = [[
|
|
function _zlua_precmd --on-event fish_prompt
|
|
_zlua --add "$PWD" 2> /dev/null &
|
|
end
|
|
]]
|
|
|
|
script_init_fish_once = [[
|
|
function _zlua_precmd --on-variable PWD
|
|
_zlua --add "$PWD" 2> /dev/null &
|
|
end
|
|
]]
|
|
|
|
script_complete_fish = [[
|
|
function _z_complete
|
|
eval "$_ZL_CMD" --complete (commandline -t)
|
|
end
|
|
|
|
complete -c $_ZL_CMD -f -a '(_z_complete)'
|
|
complete -c $_ZL_CMD -s 'r' -d 'cd to highest ranked dir matching'
|
|
complete -c $_ZL_CMD -s 'i' -d 'cd with interactive selection'
|
|
complete -c $_ZL_CMD -s 'I' -d 'cd with interactive selection using fzf'
|
|
complete -c $_ZL_CMD -s 't' -d 'cd to most recently accessed dir matching'
|
|
complete -c $_ZL_CMD -s 'l' -d 'list matches instead of cd'
|
|
complete -c $_ZL_CMD -s 'c' -d 'restrict matches to subdirs of $PWD'
|
|
complete -c $_ZL_CMD -s 'e' -d 'echo the best match, don''t cd'
|
|
complete -c $_ZL_CMD -s 'b' -d 'jump backwards to given dir or to project root'
|
|
complete -c $_ZL_CMD -s 'x' -x -d 'remove path from history' -a '(_z_complete)'
|
|
]]
|
|
|
|
|
|
function z_fish_init(opts)
|
|
print('set -x ZLUA_SCRIPT "' .. os.scriptname() .. '"')
|
|
print('set -x ZLUA_LUAEXE "' .. os.interpreter() .. '"')
|
|
local once = (os.getenv("_ZL_ADD_ONCE") ~= nil) or opts.once ~= nil
|
|
local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false))
|
|
if opts.clean ~= nil then
|
|
prompt_hook = false
|
|
end
|
|
print(script_zlua_fish)
|
|
if prompt_hook then
|
|
if once then
|
|
print(script_init_fish_once)
|
|
else
|
|
print(script_init_fish)
|
|
end
|
|
end
|
|
print(script_complete_fish)
|
|
if opts.enhanced ~= nil then
|
|
print('set -x _ZL_MATCH_MODE 1')
|
|
end
|
|
if opts.echo then
|
|
print('set -g _ZL_ECHO 1')
|
|
end
|
|
if opts.nc then
|
|
print('set -x _ZL_NO_CHECK 1')
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- windows .cmd script
|
|
-----------------------------------------------------------------------
|
|
local script_init_cmd = [[
|
|
set "MatchType=-n"
|
|
set "StrictSub=-n"
|
|
set "RunMode=-n"
|
|
set "StripMode="
|
|
set "InterMode="
|
|
if /i not "%_ZL_LUA_EXE%"=="" (
|
|
set "LuaExe=%_ZL_LUA_EXE%"
|
|
)
|
|
:parse
|
|
if /i "%1"=="-r" (
|
|
set "MatchType=-r"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-t" (
|
|
set "MatchType=-t"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-c" (
|
|
set "StrictSub=-c"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-l" (
|
|
set "RunMode=-l"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-e" (
|
|
set "RunMode=-e"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-x" (
|
|
set "RunMode=-x"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="--add" (
|
|
set "RunMode=--add"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if "%1"=="-i" (
|
|
set "InterMode=-i"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if "%1"=="-I" (
|
|
set "InterMode=-I"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-s" (
|
|
set "StripMode=-s"
|
|
shift /1
|
|
goto parse
|
|
)
|
|
if /i "%1"=="-h" (
|
|
call "%LuaExe%" "%LuaScript%" -h
|
|
goto end
|
|
)
|
|
if /i "%1"=="--purge" (
|
|
call "%LuaExe%" "%LuaScript%" --purge
|
|
goto end
|
|
)
|
|
:check
|
|
if /i "%1"=="" (
|
|
set "RunMode=-l"
|
|
)
|
|
for /f "delims=" %%i in ('cd') do set "PWD=%%i"
|
|
if /i "%RunMode%"=="-n" (
|
|
for /f "delims=" %%i in ('call "%LuaExe%" "%LuaScript%" --cd %MatchType% %StrictSub% %InterMode% %*') do set "NewPath=%%i"
|
|
if not "!NewPath!"=="" (
|
|
if exist !NewPath!\nul (
|
|
if /i not "%_ZL_ECHO%"=="" (
|
|
echo !NewPath!
|
|
)
|
|
pushd !NewPath!
|
|
pushd !NewPath!
|
|
endlocal
|
|
goto popdir
|
|
)
|
|
)
|
|
) else (
|
|
call "%LuaExe%" "%LuaScript%" "%RunMode%" %MatchType% %StrictSub% %InterMode% %StripMode% %*
|
|
)
|
|
goto end
|
|
:popdir
|
|
popd
|
|
setlocal
|
|
set "NewPath=%CD%"
|
|
set "CDCmd=cd /d"
|
|
if /i not "%_ZL_CD%"=="" (
|
|
set "CDCmd=%_ZL_CD%"
|
|
)
|
|
endlocal & popd & %CDCmd% "%NewPath%"
|
|
:end
|
|
]]
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- powershell
|
|
-----------------------------------------------------------------------
|
|
local script_zlua_powershell = [[
|
|
function global:_zlua {
|
|
$arg_mode = ""
|
|
$arg_type = ""
|
|
$arg_subdir = ""
|
|
$arg_inter = ""
|
|
$arg_strip = ""
|
|
if ($args[0] -eq "--add") {
|
|
$_, $rest = $args
|
|
$env:_ZL_RANDOM = Get-Random
|
|
& $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --add $rest
|
|
return
|
|
} elseif ($args[0] -eq "--complete") {
|
|
$_, $rest = $args
|
|
& $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --complete $rest
|
|
return
|
|
} elseif ($args[0] -eq "--update") {
|
|
$str_pwd = ([string] $PWD)
|
|
if ((!$env:_ZL_ADD_ONCE) -or
|
|
($env:_ZL_ADD_ONCE -and ($script:_zlua_previous -ne $str_pwd))) {
|
|
$script:_zlua_previous = $str_pwd
|
|
_zlua --add $str_pwd
|
|
}
|
|
return
|
|
}
|
|
:loop while ($args) {
|
|
switch -casesensitive ($args[0]) {
|
|
"-l" { $arg_mode = "-l"; break }
|
|
"-e" { $arg_mode = "-e"; break }
|
|
"-x" { $arg_mode = "-x"; break }
|
|
"-t" { $arg_type = "-t"; break }
|
|
"-r" { $arg_type = "-r"; break }
|
|
"-c" { $arg_subdir="-c"; break }
|
|
"-s" { $arg_strip="-s"; break }
|
|
"-i" { $arg_inter="-i"; break }
|
|
"-I" { $arg_inter="-I"; break }
|
|
"-h" { $arg_mode="-h"; break }
|
|
"--help" { $arg_mode="-h"; break }
|
|
"--purge" { $arg_mode="--purge"; break }
|
|
Default { break loop }
|
|
}
|
|
$_, $args = $args
|
|
if (!$args) { break loop }
|
|
}
|
|
$env:PWD = ([string] $PWD)
|
|
if ($arg_mode -eq "-h" -or $arg_mode -eq "--purge") {
|
|
& $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT $arg_mode
|
|
} elseif ($arg_mode -eq "-l" -or $args.Length -eq 0) {
|
|
& $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT -l $arg_subdir $arg_type $arg_strip $args
|
|
} elseif ($arg_mode -ne "") {
|
|
& $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT $arg_mode $arg_subdir $arg_type $arg_inter $args
|
|
} else {
|
|
$dest = & $script:ZLUA_LUAEXE $script:ZLUA_SCRIPT --cd $arg_type $arg_subdir $arg_inter $args
|
|
if ($dest) {
|
|
if ($env:_ZL_CD) { & $env:_ZL_CD "$dest" }
|
|
else { & "Push-Location" "$dest" }
|
|
if ($env:_ZL_ECHO) { Write-Host $PWD }
|
|
}
|
|
}
|
|
}
|
|
|
|
if ($env:_ZL_CMD) { Set-Alias $env:_ZL_CMD _zlua -Scope Global }
|
|
else { Set-Alias z _zlua -Scope Global }
|
|
]]
|
|
|
|
local script_init_powershell = [[
|
|
if (!$env:_ZL_NO_PROMPT_COMMAND -and (!$global:_zlua_inited)) {
|
|
$script:_zlua_orig_prompt = ([ref] $function:prompt)
|
|
$global:_zlua_inited = $True
|
|
function global:prompt {
|
|
& $script:_zlua_orig_prompt.value
|
|
_zlua --update
|
|
}
|
|
}
|
|
]]
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- initialize cmd/powershell
|
|
-----------------------------------------------------------------------
|
|
function z_windows_init(opts)
|
|
local prompt_hook = (not os.environ("_ZL_NO_PROMPT_COMMAND", false))
|
|
if opts.clean ~= nil then
|
|
prompt_hook = false
|
|
end
|
|
if opts.powershell ~= nil then
|
|
print('$script:ZLUA_LUAEXE = "' .. os.interpreter() .. '"')
|
|
print('$script:ZLUA_SCRIPT = "' .. os.scriptname() .. '"')
|
|
print(script_zlua_powershell)
|
|
if opts.enhanced ~= nil then
|
|
print('$env:_ZL_MATCH_MODE = 1')
|
|
end
|
|
if opts.once ~= nil then
|
|
print('$env:_ZL_ADD_ONCE = 1')
|
|
end
|
|
if opts.echo ~= nil then
|
|
print('$env:_ZL_ECHO = 1')
|
|
end
|
|
if opts.nc ~= nil then
|
|
print('$env:_ZL_NO_CHECK = 1')
|
|
end
|
|
if prompt_hook then
|
|
print(script_init_powershell)
|
|
end
|
|
else
|
|
print('@echo off')
|
|
print('setlocal EnableDelayedExpansion')
|
|
print('set "LuaExe=' .. os.interpreter() .. '"')
|
|
print('set "LuaScript=' .. os.scriptname() .. '"')
|
|
print(script_init_cmd)
|
|
if opts.newline then
|
|
print('echo.')
|
|
end
|
|
end
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- help
|
|
-----------------------------------------------------------------------
|
|
function z_help()
|
|
local cmd = Z_CMD .. ' '
|
|
print(cmd .. 'foo # cd to most frecent dir matching foo')
|
|
print(cmd .. 'foo bar # cd to most frecent dir matching foo and bar')
|
|
print(cmd .. '-r foo # cd to highest ranked dir matching foo')
|
|
print(cmd .. '-t foo # cd to most recently accessed dir matching foo')
|
|
print(cmd .. '-l foo # list matches instead of cd')
|
|
print(cmd .. '-c foo # restrict matches to subdirs of $PWD')
|
|
print(cmd .. '-e foo # echo the best match, don\'t cd')
|
|
print(cmd .. '-x path # remove path from history')
|
|
print(cmd .. '-i foo # cd with interactive selection')
|
|
print(cmd .. '-I foo # cd with interactive selection using fzf')
|
|
print(cmd .. '-b foo # cd to the parent directory starting with foo')
|
|
end
|
|
|
|
-----------------------------------------------------------------------
|
|
-- LFS optimize
|
|
-----------------------------------------------------------------------
|
|
os.lfs = {}
|
|
os.lfs.enable = os.getenv('_ZL_USE_LFS')
|
|
os.lfs.enable = '1'
|
|
if os.lfs.enable ~= nil then
|
|
local m = string.lower(os.lfs.enable)
|
|
if (m == '1' or m == 'yes' or m == 'true' or m == 't') then
|
|
os.lfs.status, os.lfs.pkg = pcall(require, 'lfs')
|
|
if os.lfs.status then
|
|
local lfs = os.lfs.pkg
|
|
os.path.exists = function(name)
|
|
return lfs.attributes(name) and true or false
|
|
end
|
|
os.path.isdir = function(name)
|
|
local mode = lfs.attributes(name)
|
|
if not mode then
|
|
return false
|
|
end
|
|
return (mode.mode == 'directory') and true or false
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
-----------------------------------------------------------------------
|
|
-- program entry
|
|
-----------------------------------------------------------------------
|
|
if not pcall(debug.getlocal, 4, 1) then
|
|
-- main script
|
|
z_init()
|
|
if windows and type(clink) == 'table' and clink.prompt ~= nil then
|
|
z_clink_init()
|
|
else
|
|
main()
|
|
end
|
|
end
|
|
|
|
-- vim: set ts=4 sw=4 tw=0 noet :
|