Initial commit
This commit is contained in:
1724
modules/JSON.lua
Normal file
1724
modules/JSON.lua
Normal file
File diff suppressed because it is too large
Load Diff
13
modules/clink_version.lua
Normal file
13
modules/clink_version.lua
Normal file
@@ -0,0 +1,13 @@
|
||||
local exports = {}
|
||||
|
||||
-- Busted runs these modules scripts *outside* of Clink.
|
||||
-- So these Clink scripts have to work without any Clink APIs being available.
|
||||
clink = clink or {}
|
||||
|
||||
local clink_version_encoded = clink.version_encoded or 0
|
||||
|
||||
exports.supports_display_filter_description = (clink_version_encoded >= 10010012)
|
||||
exports.supports_color_settings = (clink_version_encoded >= 10010009)
|
||||
exports.supports_query_rl_var = (clink_version_encoded >= 10010009)
|
||||
|
||||
return exports
|
||||
41
modules/color.lua
Normal file
41
modules/color.lua
Normal file
@@ -0,0 +1,41 @@
|
||||
local clink_version = require('clink_version')
|
||||
|
||||
local exports = {}
|
||||
|
||||
exports.BLACK = 0
|
||||
exports.RED = 1
|
||||
exports.GREEN = 2
|
||||
exports.YELLOW = 3
|
||||
exports.BLUE = 4
|
||||
exports.MAGENTA = 5
|
||||
exports.CYAN = 6
|
||||
exports.WHITE = 7
|
||||
exports.DEFAULT = 9
|
||||
exports.BOLD = 1
|
||||
|
||||
exports.set_color = function (fore, back, bold)
|
||||
local err_message = "All arguments must be either nil or numbers between 0-9"
|
||||
assert(fore == nil or (type(fore) == "number" and fore >= 0 and fore <=9), err_message)
|
||||
assert(back == nil or (type(back) == "number" and back >= 0 and back <=9), err_message)
|
||||
|
||||
fore = fore or exports.DEFAULT
|
||||
back = back or exports.DEFAULT
|
||||
bold = bold and exports.BOLD or 22
|
||||
|
||||
return "\x1b[3"..fore..";"..bold..";".."4"..back.."m"
|
||||
end
|
||||
|
||||
exports.get_clink_color = function (setting_name)
|
||||
-- Clink's settings.get() returns SGR parameters for a CSI SGR escape code.
|
||||
local sgr = clink_version.supports_color_settings and settings.get(setting_name) or ""
|
||||
if sgr ~= "" then
|
||||
sgr = "\x1b["..sgr.."m"
|
||||
end
|
||||
return sgr
|
||||
end
|
||||
|
||||
exports.color_text = function (text, fore, back, bold)
|
||||
return exports.set_color(fore, back, bold)..text..exports.set_color()
|
||||
end
|
||||
|
||||
return exports
|
||||
100
modules/funclib.lua
Normal file
100
modules/funclib.lua
Normal file
@@ -0,0 +1,100 @@
|
||||
|
||||
local exports = {}
|
||||
|
||||
--- Implementation of table.filter function. Applies filter function to each
|
||||
-- element of table and returns a new table with values for which filter
|
||||
-- returns 'true'.
|
||||
--
|
||||
-- @param tbl a table to filter. Default is an empty table.
|
||||
-- @param filter function that accepts an element of table, specified in the
|
||||
-- first argument and returns either 'true' or 'false'. If not specified,
|
||||
-- then default function is used that returns its argument.
|
||||
--
|
||||
-- @return a new table with values that are not filtered out by 'filter' function.
|
||||
exports.filter = function (tbl, filter)
|
||||
if not tbl then return {} end
|
||||
if not filter then filter = function(v) return v end end
|
||||
local ret = {}
|
||||
for _,v in ipairs(tbl) do
|
||||
if filter(v) then table.insert(ret, v) end
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Implementation of table.map function. Applies filter function to each
|
||||
-- element of table and returns a new table with values returned by mapper
|
||||
-- function.
|
||||
--
|
||||
-- @param tbl a table to filter. Default is an empty table.
|
||||
-- @param map_func function that accepts an element of table, specified in the
|
||||
-- first argument and returns a new value for resultant table. If not
|
||||
-- specified, then 'map' function returns it input table.
|
||||
--
|
||||
-- @return a new table with values produced by 'map_func'.
|
||||
exports.map = function (tbl, map_func)
|
||||
assert(tbl == nil or type(tbl) == "table",
|
||||
"First argument must be either table or nil")
|
||||
|
||||
assert(map_func == nil or type(map_func) == "function",
|
||||
"Second argument must be either function or nil")
|
||||
|
||||
if tbl == nil then return {} end
|
||||
if not map_func then return tbl end
|
||||
local ret = {}
|
||||
for _,v in ipairs(tbl) do
|
||||
table.insert(ret, map_func(v))
|
||||
end
|
||||
return ret
|
||||
end
|
||||
|
||||
--- Implementation of table.reduce function. Iterates through table and calls
|
||||
-- 'func' function passing an accumulator and an entry from the original
|
||||
-- table. The result of table is stored in accumulator and passed to next
|
||||
-- 'func' call.
|
||||
--
|
||||
-- @param accum an accumulator, initial value that will be passed to first
|
||||
-- 'func' call.
|
||||
-- @param tbl a table to reduce. Default is an empty table.
|
||||
-- @param func function that accepts two params: an accumulator and an element
|
||||
-- of table, specified in the first argument and returns a new value for
|
||||
-- accumulator.
|
||||
--
|
||||
-- @return a resultant accumulator value.
|
||||
exports.reduce = function (accum, tbl, func)
|
||||
assert(type(func) == "function",
|
||||
"Third argument must be a function")
|
||||
|
||||
if not tbl then return accum end
|
||||
for _,v in ipairs(tbl) do
|
||||
accum = func(accum, v)
|
||||
end
|
||||
return accum
|
||||
end
|
||||
|
||||
--- Concatenates any number of input values into one table. If input parameter is
|
||||
-- a table then its values is copied to the end of resultant table. If the
|
||||
-- parameter is single value, then it is appended to the resultant table. If
|
||||
-- the input value is 'nil', then it is omitted.
|
||||
--
|
||||
-- @return a result of concatenation. The result is always a table.
|
||||
exports.concat = function (...)
|
||||
local input = {...}
|
||||
local ret = {}
|
||||
local i = 1
|
||||
|
||||
while i <= #input do
|
||||
local arg = input[i]
|
||||
if type(arg) == 'table' then
|
||||
for _,v in ipairs(arg) do
|
||||
table.insert(ret, v)
|
||||
end
|
||||
elseif arg ~= nil then
|
||||
table.insert(ret, arg)
|
||||
end
|
||||
i = i + 1
|
||||
end
|
||||
|
||||
return ret
|
||||
end
|
||||
|
||||
return exports
|
||||
83
modules/gitutil.lua
Normal file
83
modules/gitutil.lua
Normal file
@@ -0,0 +1,83 @@
|
||||
local path = require('path')
|
||||
|
||||
local exports = {}
|
||||
|
||||
---
|
||||
-- Resolves closest .git directory location.
|
||||
-- Navigates subsequently up one level and tries to find .git directory
|
||||
-- @param {string} path Path to directory will be checked. If not provided
|
||||
-- current directory will be used
|
||||
-- @return {string} Path to .git directory or nil if such dir not found
|
||||
exports.get_git_dir = function (start_dir)
|
||||
|
||||
-- Checks if provided directory contains '.git' directory
|
||||
-- and returns path to that directory
|
||||
local function has_git_dir(dir)
|
||||
return #clink.find_dirs(dir..'/.git') > 0 and dir..'/.git'
|
||||
end
|
||||
|
||||
-- checks if directory contains '.git' _file_ and if it does
|
||||
-- parses it and returns a path to git directory from that file
|
||||
local function has_git_file(dir)
|
||||
local gitfile = io.open(dir..'/.git')
|
||||
if not gitfile then return false end
|
||||
|
||||
local git_dir = gitfile:read():match('gitdir: (.*)')
|
||||
gitfile:close()
|
||||
|
||||
if not git_dir then return false end
|
||||
-- If found path is absolute don't prepend initial
|
||||
-- directory - return absolute path value
|
||||
return path.is_absolute(git_dir) and git_dir
|
||||
or dir..'/'..git_dir
|
||||
end
|
||||
|
||||
-- Set default path to current directory
|
||||
if not start_dir or start_dir == '.' then start_dir = clink.get_cwd() end
|
||||
|
||||
-- Calculate parent path now otherwise we won't be
|
||||
-- able to do that inside of logical operator
|
||||
local parent_path = path.pathname(start_dir)
|
||||
|
||||
return has_git_dir(start_dir)
|
||||
or has_git_file(start_dir)
|
||||
-- Otherwise go up one level and make a recursive call
|
||||
or (parent_path ~= start_dir and exports.get_git_dir(parent_path) or nil)
|
||||
end
|
||||
|
||||
exports.get_git_common_dir = function (start_dir)
|
||||
local git_dir = exports.get_git_dir(start_dir)
|
||||
if not git_dir then return git_dir end
|
||||
local commondirfile = io.open(git_dir..'/commondir')
|
||||
if commondirfile then
|
||||
-- If there's a commondir file, we're in a git worktree
|
||||
local commondir = commondirfile:read()
|
||||
commondirfile.close()
|
||||
return path.is_absolute(commondir) and commondir
|
||||
or git_dir..'/'..commondir
|
||||
end
|
||||
return git_dir
|
||||
end
|
||||
|
||||
---
|
||||
-- Find out current branch
|
||||
-- @return {nil|git branch name}
|
||||
---
|
||||
exports.get_git_branch = function (dir)
|
||||
local git_dir = dir or exports.get_git_dir()
|
||||
|
||||
-- If git directory not found then we're probably outside of repo
|
||||
-- or something went wrong. The same is when head_file is nil
|
||||
local head_file = git_dir and io.open(git_dir..'/HEAD')
|
||||
if not head_file then return end
|
||||
|
||||
local HEAD = head_file:read()
|
||||
head_file:close()
|
||||
|
||||
-- if HEAD matches branch expression, then we're on named branch
|
||||
-- otherwise it is a detached commit
|
||||
local branch_name = HEAD:match('ref: refs/heads/(.+)')
|
||||
return branch_name or 'HEAD detached at '..HEAD:sub(1, 7)
|
||||
end
|
||||
|
||||
return exports
|
||||
80
modules/matchers.lua
Normal file
80
modules/matchers.lua
Normal file
@@ -0,0 +1,80 @@
|
||||
|
||||
local exports = {}
|
||||
|
||||
local path = require('path')
|
||||
local w = require('tables').wrap
|
||||
|
||||
exports.dirs = function(word)
|
||||
-- Strip off any path components that may be on text.
|
||||
local prefix = ""
|
||||
local i = word:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = word:sub(1, i)
|
||||
end
|
||||
local include_dots = word:find("%.+$") ~= nil
|
||||
|
||||
-- Find matches.
|
||||
local matches = w(clink.find_dirs(word.."*", true))
|
||||
:filter(function (dir)
|
||||
return clink.is_match(word, prefix..dir) and
|
||||
(include_dots or path.is_real_dir(dir))
|
||||
end)
|
||||
:map(function(dir)
|
||||
return prefix..dir
|
||||
end)
|
||||
|
||||
-- If there was no matches but word is a dir then use it as the single match.
|
||||
-- Otherwise tell readline that matches are files and it will do magic.
|
||||
if #matches == 0 and clink.is_dir(rl_state.text) then
|
||||
return {rl_state.text}
|
||||
end
|
||||
|
||||
clink.matches_are_files()
|
||||
return matches
|
||||
end
|
||||
|
||||
exports.files = function (word)
|
||||
-- Strip off any path components that may be on text.
|
||||
local prefix = ""
|
||||
local i = word:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = word:sub(1, i)
|
||||
end
|
||||
|
||||
-- Find matches.
|
||||
local matches = w(clink.find_files(word.."*", true))
|
||||
:filter(function (file)
|
||||
return clink.is_match(word, prefix..file)
|
||||
end)
|
||||
:map(function(file)
|
||||
return prefix..file
|
||||
end)
|
||||
|
||||
-- Tell readline that matches are files and it will do magic.
|
||||
if #matches ~= 0 then
|
||||
clink.matches_are_files()
|
||||
end
|
||||
|
||||
return matches
|
||||
end
|
||||
|
||||
exports.create_dirs_matcher = function (dir_pattern, show_dotfiles)
|
||||
return function (token)
|
||||
return w(clink.find_dirs(dir_pattern))
|
||||
:filter(function(dir)
|
||||
return clink.is_match(token, dir) and (path.is_real_dir(dir) or show_dotfiles)
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
exports.create_files_matcher = function (file_pattern)
|
||||
return function (token)
|
||||
return w(clink.find_files(file_pattern))
|
||||
:filter(function(file)
|
||||
-- Filter out '.' and '..' entries as well
|
||||
return clink.is_match(token, file) and path.is_real_dir(file)
|
||||
end )
|
||||
end
|
||||
end
|
||||
|
||||
return exports
|
||||
69
modules/path.lua
Normal file
69
modules/path.lua
Normal file
@@ -0,0 +1,69 @@
|
||||
local exports = {}
|
||||
|
||||
local w = require('tables').wrap
|
||||
|
||||
exports.list_files = function (base_path, glob, recursive, reverse_separator)
|
||||
local mask = glob or '/*'
|
||||
|
||||
local entries = w(clink.find_files(base_path..mask))
|
||||
:filter(function(entry)
|
||||
return exports.is_real_dir(entry)
|
||||
end)
|
||||
|
||||
local files = entries:filter(function(entry)
|
||||
return not clink.is_dir(base_path..'/'..entry)
|
||||
end)
|
||||
|
||||
-- if 'recursive' flag is not set, we don't need to iterate
|
||||
-- through directories, so just return files found
|
||||
if not recursive then return files end
|
||||
|
||||
local sep = reverse_separator and '/' or '\\'
|
||||
|
||||
return entries
|
||||
:filter(function(entry)
|
||||
return clink.is_dir(base_path..'/'..entry)
|
||||
end)
|
||||
:reduce(files, function(accum, dir)
|
||||
-- iterate through directories and call list_files recursively
|
||||
return exports.list_files(base_path..'/'..dir, mask, recursive, reverse_separator)
|
||||
:map(function(entry)
|
||||
return dir..sep..entry
|
||||
end)
|
||||
:concat(accum)
|
||||
end)
|
||||
end
|
||||
|
||||
exports.basename = function (path)
|
||||
local prefix = path
|
||||
local i = path:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = path:sub(i + 1)
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
exports.pathname = function (path)
|
||||
local prefix = ""
|
||||
local i = path:find("[\\/:][^\\/:]*$")
|
||||
if i then
|
||||
prefix = path:sub(1, i-1)
|
||||
end
|
||||
return prefix
|
||||
end
|
||||
|
||||
exports.is_absolute = function (path)
|
||||
local drive = path:find("^%s?[%l%a]:[\\/]")
|
||||
if drive then return true else return false end
|
||||
end
|
||||
|
||||
exports.is_metadir = function (dirname)
|
||||
return exports.basename(dirname) == '.'
|
||||
or exports.basename(dirname) == '..'
|
||||
end
|
||||
|
||||
exports.is_real_dir = function (dirname)
|
||||
return not exports.is_metadir(dirname)
|
||||
end
|
||||
|
||||
return exports
|
||||
74
modules/tables.lua
Normal file
74
modules/tables.lua
Normal file
@@ -0,0 +1,74 @@
|
||||
local concat = require('funclib').concat
|
||||
local filter = require('funclib').filter
|
||||
local map = require('funclib').map
|
||||
local reduce = require('funclib').reduce
|
||||
|
||||
local exports = {}
|
||||
|
||||
local wrap_filter = function (tbl, filter_func)
|
||||
return exports.wrap(filter(tbl, filter_func))
|
||||
end
|
||||
|
||||
local wrap_map = function (tbl, map_func)
|
||||
return exports.wrap(map(tbl, map_func))
|
||||
end
|
||||
|
||||
local wrap_reduce = function (tbl, accum, reduce_func)
|
||||
local res = reduce(accum, tbl, reduce_func)
|
||||
return (type(res) == "table" and exports.wrap(res) or res)
|
||||
end
|
||||
|
||||
local wrap_concat = function (tbl, ...)
|
||||
return exports.wrap(concat(tbl, ...))
|
||||
end
|
||||
|
||||
local wrap_print = function (tbl)
|
||||
return exports.wrap(filter(tbl, function (item)
|
||||
print(item)
|
||||
return true
|
||||
end))
|
||||
end
|
||||
|
||||
exports.wrap = function (tbl)
|
||||
if tbl == nil then tbl = {} end
|
||||
if type(tbl) ~= "table" then tbl = {tbl} end
|
||||
|
||||
local mt = getmetatable(tbl) or {}
|
||||
mt.__index = mt.__index or {}
|
||||
mt.__index.filter = wrap_filter
|
||||
mt.__index.map = wrap_map
|
||||
mt.__index.reduce = wrap_reduce
|
||||
mt.__index.concat = wrap_concat
|
||||
mt.__index.print = wrap_print
|
||||
mt.__index.keys = function (arg)
|
||||
local res = {}
|
||||
for k,_ in pairs(arg) do
|
||||
table.insert(res, k)
|
||||
end
|
||||
return exports.wrap(res)
|
||||
end
|
||||
mt.__index.sort = function (arg)
|
||||
table.sort(arg)
|
||||
return arg
|
||||
end
|
||||
mt.__index.dedupe = function (arg)
|
||||
local res, hash = {}, {}
|
||||
for _,v in ipairs(arg) do
|
||||
if not hash[v] then
|
||||
hash[v] = true
|
||||
table.insert(res, v)
|
||||
end
|
||||
end
|
||||
return exports.wrap(res)
|
||||
end
|
||||
mt.__index.contains = function (arg, value)
|
||||
for _,v in ipairs(arg) do
|
||||
if v == value then return true, _ end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
return setmetatable(tbl, mt)
|
||||
end
|
||||
|
||||
return exports
|
||||
Reference in New Issue
Block a user