Initial commit

This commit is contained in:
Oliver Hartmann
2022-02-28 20:15:23 +01:00
commit b925fbc378
17 changed files with 4268 additions and 0 deletions

1724
modules/JSON.lua Normal file

File diff suppressed because it is too large Load Diff

13
modules/clink_version.lua Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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