From 33303f69f41fd668f5945179159c3ad7c1129f4b Mon Sep 17 00:00:00 2001 From: Kristofers Solo Date: Wed, 11 May 2022 15:49:53 +0300 Subject: [PATCH] Added zlua --- .config/zlua/z.lua | 2757 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2757 insertions(+) create mode 100644 .config/zlua/z.lua diff --git a/.config/zlua/z.lua b/.config/zlua/z.lua new file mode 100644 index 00000000..d0aa0ad9 --- /dev/null +++ b/.config/zlua/z.lua @@ -0,0 +1,2757 @@ +#! /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 = '' +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 == '' then + fp = io.stdout + elseif PRINT_MODE == '' 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 = '' + 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 :