diff options
33 files changed, 946 insertions, 629 deletions
diff --git a/lua/nvim-lsp-installer/core/functional.lua b/lua/nvim-lsp-installer/core/functional.lua deleted file mode 100644 index 8780c995..00000000 --- a/lua/nvim-lsp-installer/core/functional.lua +++ /dev/null @@ -1,259 +0,0 @@ -local functional = {} - ----@generic T : string ----@param values T[] ----@return table<T, T> -function functional.enum(values) - local result = {} - for i = 1, #values do - local v = values[i] - result[v] = v - end - return result -end - ----@generic T ----@param list T[] ----@return table<T, boolean> -function functional.set_of(list) - local set = {} - for i = 1, #list do - set[list[i]] = true - end - return set -end - ----@generic T ----@param list T[] ----@return T[] -function functional.list_reverse(list) - local result = {} - for i = #list, 1, -1 do - result[#result + 1] = list[i] - end - return result -end - ----@generic T, U ----@param fn fun(item: T): U ----@param list T[] ----@return U[] -function functional.list_map(fn, list) - local result = {} - for i = 1, #list do - result[#result + 1] = fn(list[i], i) - end - return result -end - -function functional.table_pack(...) - return { n = select("#", ...), ... } -end - -function functional.list_not_nil(...) - local result = {} - local args = functional.table_pack(...) - for i = 1, args.n do - if args[i] ~= nil then - result[#result + 1] = args[i] - end - end - return result -end - -function functional.when(condition, value) - return condition and value or nil -end - -function functional.lazy_when(condition, fn) - return condition and fn() or nil -end - -function functional.coalesce(...) - local args = functional.table_pack(...) - for i = 1, args.n do - local variable = args[i] - if variable ~= nil then - return variable - end - end -end - ----@generic T ----@param list T[] ----@return T[] @A shallow copy of the list. -function functional.list_copy(list) - local result = {} - for i = 1, #list do - result[#result + 1] = list[i] - end - return result -end - ----@generic T ----@param predicate fun(item: T): boolean ----@param list T[] ----@return T | nil -function functional.list_find_first(predicate, list) - local result - for i = 1, #list do - local entry = list[i] - if predicate(entry) then - return entry - end - end - return result -end - ----@generic T ----@param predicate fun(item: T): boolean ----@param list T[] ----@return boolean -function functional.list_any(predicate, list) - for i = 1, #list do - if predicate(list[i]) then - return true - end - end - return false -end - -function functional.identity(a) - return a -end - -function functional.always(a) - return function() - return a - end -end - ----@generic T : fun(...) ----@param fn T ----@param cache_key_generator (fun(...): string | nil)|nil ----@return T -function functional.memoize(fn, cache_key_generator) - cache_key_generator = cache_key_generator or functional.identity - local cache = {} - return function(...) - local key = cache_key_generator(...) - if not cache[key] then - cache[key] = functional.table_pack(fn(...)) - end - return unpack(cache[key], 1, cache[key].n) - end -end - ----@generic T ----@param fn fun(): T ----@return fun(): T -function functional.lazy(fn) - local memoized = functional.memoize(fn, functional.always "lazyval") - return function() - return memoized() - end -end - ----@generic T ----@param fn fun(...): T ----@return fun(...): T -function functional.partial(fn, ...) - local bound_args = functional.table_pack(...) - return function(...) - local args = functional.table_pack(...) - local merged_args = {} - for i = 1, bound_args.n do - merged_args[i] = bound_args[i] - end - for i = 1, args.n do - merged_args[bound_args.n + i] = args[i] - end - return fn(unpack(merged_args, 1, bound_args.n + args.n)) - end -end - -function functional.compose(...) - local functions = functional.table_pack(...) - assert(functions.n > 0, "compose requires at least one function") - return function(...) - local result = functional.table_pack(...) - for i = functions.n, 1, -1 do - result = functional.table_pack(functions[i](unpack(result, 1, result.n))) - end - return unpack(result, 1, result.n) - end -end - ----@generic T ----@param filter_fn fun(item: T): boolean ----@return fun(list: T[]): T[] -function functional.filter(filter_fn) - return functional.partial(vim.tbl_filter, filter_fn) -end - ----@generic T ----@param fn fun(item: T, index: integer) ----@param list T[] -function functional.each(fn, list) - for k, v in pairs(list) do - fn(v, k) - end -end - ----@generic T ----@param predicates (fun(item: T): boolean)[] ----@return fun(item: T): boolean -function functional.all_pass(predicates) - return function(item) - for i = 1, #predicates do - if not predicates[i](item) then - return false - end - end - return true - end -end - ----@generic T ----@param predicate fun(item: T): boolean ----@return fun(item: T): boolean -function functional.negate(predicate) - return function(...) - return not predicate(...) - end -end - ----@param index any ----@return fun(obj: table): any -function functional.prop(index) - return function(obj) - return obj[index] - end -end - ----@param condition fun(...): boolean ----@param a fun(...): any ----@param b fun(...): any ----@return fun(...): any -function functional.if_else(condition, a, b) - return function(...) - if condition(...) then - return a(...) - else - return b(...) - end - end -end - ----@param pattern string -function functional.matches(pattern) - ---@param str string - return function(str) - return str:match(pattern) ~= nil - end -end - -functional.T = functional.always(true) -functional.F = functional.always(false) - -return functional diff --git a/lua/nvim-lsp-installer/core/functional/data.lua b/lua/nvim-lsp-installer/core/functional/data.lua new file mode 100644 index 00000000..da6f1efd --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/data.lua @@ -0,0 +1,30 @@ +local _ = {} + +_.table_pack = function(...) + return { n = select("#", ...), ... } +end + +---@generic T : string +---@param values T[] +---@return table<T, T> +_.enum = function(values) + local result = {} + for i = 1, #values do + local v = values[i] + result[v] = v + end + return result +end + +---@generic T +---@param list T[] +---@return table<T, boolean> +_.set_of = function(list) + local set = {} + for i = 1, #list do + set[list[i]] = true + end + return set +end + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/function.lua b/lua/nvim-lsp-installer/core/functional/function.lua new file mode 100644 index 00000000..0d70aa92 --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/function.lua @@ -0,0 +1,89 @@ +local data = require "nvim-lsp-installer.core.functional.data" + +local _ = {} + +---@generic T : fun(...) +---@param fn T +---@param arity integer +---@return T +_.curryN = function(fn, arity) + return function(...) + local args = data.table_pack(...) + if args.n >= arity then + return fn(unpack(args, 1, arity)) + else + return _.curryN(_.partial(fn, unpack(args, 1, args.n)), arity - args.n) + end + end +end + +_.compose = function(...) + local functions = data.table_pack(...) + assert(functions.n > 0, "compose requires at least one function") + return function(...) + local result = data.table_pack(...) + for i = functions.n, 1, -1 do + result = data.table_pack(functions[i](unpack(result, 1, result.n))) + end + return unpack(result, 1, result.n) + end +end + +---@generic T +---@param fn fun(...): T +---@return fun(...): T +_.partial = function(fn, ...) + local bound_args = data.table_pack(...) + return function(...) + local args = data.table_pack(...) + local merged_args = {} + for i = 1, bound_args.n do + merged_args[i] = bound_args[i] + end + for i = 1, args.n do + merged_args[bound_args.n + i] = args[i] + end + return fn(unpack(merged_args, 1, bound_args.n + args.n)) + end +end + +_.identity = function(a) + return a +end + +_.always = function(a) + return function() + return a + end +end + +_.T = _.always(true) +_.F = _.always(false) + +---@generic T : fun(...) +---@param fn T +---@param cache_key_generator (fun(...): string | nil)|nil +---@return T +_.memoize = function(fn, cache_key_generator) + cache_key_generator = cache_key_generator or _.identity + local cache = {} + return function(...) + local key = cache_key_generator(...) + if not cache[key] then + cache[key] = data.table_pack(fn(...)) + end + return unpack(cache[key], 1, cache[key].n) + end +end + +---@generic T +---@param fn fun(): T +---@return fun(): T +_.lazy = function(fn) + local memoized = _.memoize(fn, _.always "lazyval") + return function() + return memoized() + end +end + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/init.lua b/lua/nvim-lsp-installer/core/functional/init.lua new file mode 100644 index 00000000..45a09ea6 --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/init.lua @@ -0,0 +1,89 @@ +local _ = {} + +-- data +local data = require "nvim-lsp-installer.core.functional.data" +_.table_pack = data.table_pack +_.enum = data.enum +_.set_of = data.set_of + +-- function +local fun = require "nvim-lsp-installer.core.functional.function" +_.curryN = fun.curryN +_.compose = fun.compose +_.partial = fun.partial +_.identity = fun.identity +_.always = fun.always +_.T = fun.T +_.F = fun.F +_.memoize = fun.memoize +_.lazy = fun.lazy + +-- list +local list = require "nvim-lsp-installer.core.functional.list" +_.reverse = list.reverse +_.list_not_nil = list.list_not_nil +_.list_copy = list.list_copy +_.find_first = list.find_first +_.any = list.any +_.filter = list.filter +_.map = list.map +_.each = list.each + +-- relation +local relation = require "nvim-lsp-installer.core.functional.relation" +_.equals = relation.equals +_.prop_eq = relation.prop_eq +_.prop_satisfies = relation.prop_satisfies + +-- logic +local logic = require "nvim-lsp-installer.core.functional.logic" +_.all_pass = logic.all_pass +_.if_else = logic.if_else +_.is_not = logic.is_not +_.cond = logic.cond + +-- number +local number = require "nvim-lsp-installer.core.functional.number" +_.negate = number.negate +_.gt = number.gt +_.gte = number.gte +_.lt = number.lt +_.lte = number.lte +_.inc = number.inc +_.dec = number.dec + +-- string +local string = require "nvim-lsp-installer.core.functional.string" +_.matches = string.matches +_.format = string.format + +-- table +local tbl = require "nvim-lsp-installer.core.functional.table" +_.prop = tbl.prop + +-- type +local typ = require "nvim-lsp-installer.core.functional.type" +_.is_nil = typ.is_nil +_.is = typ.is + +-- TODO do something else with these + +_.coalesce = function(...) + local args = _.table_pack(...) + for i = 1, args.n do + local variable = args[i] + if variable ~= nil then + return variable + end + end +end + +_.when = function(condition, value) + return condition and value or nil +end + +_.lazy_when = function(condition, value) + return condition and value() or nil +end + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/list.lua b/lua/nvim-lsp-installer/core/functional/list.lua new file mode 100644 index 00000000..666de4d3 --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/list.lua @@ -0,0 +1,80 @@ +local fun = require "nvim-lsp-installer.core.functional.function" +local data = require "nvim-lsp-installer.core.functional.data" + +local _ = {} + +---@generic T +---@param list T[] +---@return T[] +_.reverse = function(list) + local result = {} + for i = #list, 1, -1 do + result[#result + 1] = list[i] + end + return result +end + +_.list_not_nil = function(...) + local result = {} + local args = data.table_pack(...) + for i = 1, args.n do + if args[i] ~= nil then + result[#result + 1] = args[i] + end + end + return result +end + +---@generic T +---@param predicate fun(item: T): boolean +---@param list T[] +---@return T | nil +_.find_first = fun.curryN(function(predicate, list) + local result + for i = 1, #list do + local entry = list[i] + if predicate(entry) then + return entry + end + end + return result +end, 2) + +---@generic T +---@param predicate fun(item: T): boolean +---@param list T[] +---@return boolean +_.any = fun.curryN(function(predicate, list) + for i = 1, #list do + if predicate(list[i]) then + return true + end + end + return false +end, 2) + +---@generic T +---@param filter_fn fun(item: T): boolean +---@return fun(list: T[]): T[] +_.filter = fun.curryN(vim.tbl_filter, 2) + +---@generic T +---@param map_fn fun(item: T): boolean +---@return fun(list: T[]): T[] +_.map = fun.curryN(vim.tbl_map, 2) + +---@generic T +---@param fn fun(item: T, index: integer) +---@param list T[] +_.each = fun.curryN(function(fn, list) + for k, v in pairs(list) do + fn(v, k) + end +end, 2) + +---@generic T +---@param list T[] +---@return T[] @A shallow copy of the list. +_.list_copy = _.map(fun.identity) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/logic.lua b/lua/nvim-lsp-installer/core/functional/logic.lua new file mode 100644 index 00000000..262f04a8 --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/logic.lua @@ -0,0 +1,44 @@ +local fun = require "nvim-lsp-installer.core.functional.function" + +local _ = {} + +---@generic T +---@param predicates (fun(item: T): boolean)[] +---@return fun(item: T): boolean +_.all_pass = fun.curryN(function(predicates, item) + for i = 1, #predicates do + if not predicates[i](item) then + return false + end + end + return true +end, 2) + +---@generic T +---@param predicate fun(item: T): boolean +---@param a fun(item: T): any +---@param b fun(item: T): any +---@param value T +_.if_else = fun.curryN(function(predicate, a, b, value) + if predicate(value) then + return a(value) + else + return b(value) + end +end, 4) + +---@param value boolean +_.is_not = function(value) + return not value +end + +_.cond = fun.curryN(function(predicate_transformer_pairs, value) + for _, pair in ipairs(predicate_transformer_pairs) do + local predicate, transformer = pair[1], pair[2] + if predicate(value) then + return transformer(value) + end + end +end, 2) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/number.lua b/lua/nvim-lsp-installer/core/functional/number.lua new file mode 100644 index 00000000..b123eb20 --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/number.lua @@ -0,0 +1,34 @@ +local fun = require "nvim-lsp-installer.core.functional.function" + +local _ = {} + +---@param number number +_.negate = function(number) + return -number +end + +_.gt = fun.curryN(function(number, value) + return value > number +end, 2) + +_.gte = fun.curryN(function(number, value) + return value >= number +end, 2) + +_.lt = fun.curryN(function(number, value) + return value < number +end, 2) + +_.lte = fun.curryN(function(number, value) + return value <= number +end, 2) + +_.inc = fun.curryN(function(increment, value) + return value + increment +end, 2) + +_.dec = fun.curryN(function(decrement, value) + return value - decrement +end, 2) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/relation.lua b/lua/nvim-lsp-installer/core/functional/relation.lua new file mode 100644 index 00000000..d9786a6a --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/relation.lua @@ -0,0 +1,17 @@ +local fun = require "nvim-lsp-installer.core.functional.function" + +local _ = {} + +_.equals = fun.curryN(function(expected, value) + return value == expected +end, 2) + +_.prop_eq = fun.curryN(function(property, value, tbl) + return tbl[property] == value +end, 3) + +_.prop_satisfies = fun.curryN(function(predicate, property, tbl) + return predicate(tbl[property]) +end, 3) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/string.lua b/lua/nvim-lsp-installer/core/functional/string.lua new file mode 100644 index 00000000..0061a33e --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/string.lua @@ -0,0 +1,17 @@ +local fun = require "nvim-lsp-installer.core.functional.function" + +local _ = {} + +---@param pattern string +---@param str string +_.matches = fun.curryN(function(pattern, str) + return str:match(pattern) ~= nil +end, 2) + +---@param template string +---@param string string +_.format = fun.curryN(function(template, string) + return template:format(string) +end, 2) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/table.lua b/lua/nvim-lsp-installer/core/functional/table.lua new file mode 100644 index 00000000..37aee19e --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/table.lua @@ -0,0 +1,9 @@ +local fun = require "nvim-lsp-installer.core.functional.function" + +local _ = {} + +_.prop = fun.curryN(function(index, tbl) + return tbl[index] +end, 2) + +return _ diff --git a/lua/nvim-lsp-installer/core/functional/type.lua b/lua/nvim-lsp-installer/core/functional/type.lua new file mode 100644 index 00000000..23d961ba --- /dev/null +++ b/lua/nvim-lsp-installer/core/functional/type.lua @@ -0,0 +1,14 @@ +local fun = require "nvim-lsp-installer.core.functional.function" +local rel = require "nvim-lsp-installer.core.functional.relation" + +local _ = {} + +_.is_nil = rel.equals(nil) + +---@param typ type +---@param value any +_.is = fun.curryN(function(typ, value) + return type(value) == typ +end, 2) + +return _ diff --git a/lua/nvim-lsp-installer/core/managers/composer/init.lua b/lua/nvim-lsp-installer/core/managers/composer/init.lua index 338b3fef..e2330c40 100644 --- a/lua/nvim-lsp-installer/core/managers/composer/init.lua +++ b/lua/nvim-lsp-installer/core/managers/composer/init.lua @@ -1,4 +1,4 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local process = require "nvim-lsp-installer.core.process" local path = require "nvim-lsp-installer.core.path" local Result = require "nvim-lsp-installer.core.result" @@ -6,8 +6,6 @@ local spawn = require "nvim-lsp-installer.core.spawn" local Optional = require "nvim-lsp-installer.core.optional" local installer = require "nvim-lsp-installer.core.installer" -local list_copy, list_find_first = functional.list_copy, functional.list_find_first - local M = {} ---@param packages string[] @@ -34,7 +32,7 @@ end ---@param packages string[] The composer packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. function M.require(packages) local ctx = installer.context() - local pkgs = list_copy(packages) + local pkgs = _.list_copy(packages) if not ctx.fs:file_exists "composer.json" then ctx.spawn.composer { "init", "--no-interaction", "--stability=stable" } @@ -77,7 +75,7 @@ function M.check_outdated_primary_package(receipt, install_dir) cwd = install_dir, }):map_catching(function(result) local outdated_packages = vim.json.decode(result.stdout) - local outdated_package = list_find_first(function(package) + local outdated_package = _.find_first(function(package) return package.name == receipt.primary_source.package end, outdated_packages.installed) return Optional.of_nilable(outdated_package) diff --git a/lua/nvim-lsp-installer/core/managers/gem/init.lua b/lua/nvim-lsp-installer/core/managers/gem/init.lua index 56385337..e70c89c7 100644 --- a/lua/nvim-lsp-installer/core/managers/gem/init.lua +++ b/lua/nvim-lsp-installer/core/managers/gem/init.lua @@ -1,4 +1,4 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local process = require "nvim-lsp-installer.core.process" local path = require "nvim-lsp-installer.core.path" local Result = require "nvim-lsp-installer.core.result" @@ -6,8 +6,6 @@ local spawn = require "nvim-lsp-installer.core.spawn" local Optional = require "nvim-lsp-installer.core.optional" local installer = require "nvim-lsp-installer.core.installer" -local list_copy, list_find_first = functional.list_copy, functional.list_find_first - local M = {} ---@param packages string[] @@ -33,7 +31,7 @@ end ---@param packages string[] @The Gem packages to install. The first item in this list will be the recipient of the server version, should the user request a specific one. function M.install(packages) local ctx = installer.context() - local pkgs = list_copy(packages or {}) + local pkgs = _.list_copy(packages or {}) ctx.requested_version:if_present(function(version) pkgs[1] = ("%s:%s"):format(pkgs[1], version) @@ -105,7 +103,7 @@ function M.check_outdated_primary_package(receipt, install_dir) local lines = vim.split(result.stdout, "\n") local outdated_gems = vim.tbl_map(M.parse_outdated_gem, vim.tbl_filter(not_empty, lines)) - local outdated_gem = list_find_first(function(gem) + local outdated_gem = _.find_first(function(gem) return gem.name == receipt.primary_source.package and gem.current_version ~= gem.latest_version end, outdated_gems) diff --git a/lua/nvim-lsp-installer/core/managers/github/client.lua b/lua/nvim-lsp-installer/core/managers/github/client.lua index 7387723c..bf79fec6 100644 --- a/lua/nvim-lsp-installer/core/managers/github/client.lua +++ b/lua/nvim-lsp-installer/core/managers/github/client.lua @@ -1,19 +1,8 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local log = require "nvim-lsp-installer.log" local fetch = require "nvim-lsp-installer.core.fetch" local spawn = require "nvim-lsp-installer.core.spawn" -local list_find_first, all_pass, always, prop, negate, compose, if_else, matches, T = - functional.list_find_first, - functional.all_pass, - functional.always, - functional.prop, - functional.negate, - functional.compose, - functional.if_else, - functional.matches, - functional.T - local M = {} ---@alias GitHubReleaseAsset {url: string, id: integer, name: string, browser_download_url: string, created_at: string, updated_at: string, size: integer, download_count: integer} @@ -56,13 +45,13 @@ end ---@param opts {include_prerelease: boolean, tag_name_pattern: string} function M.release_predicate(opts) - local is_not_draft = negate(prop "draft") - local is_not_prerelease = negate(prop "prerelease") - local tag_name_matches = compose(matches(opts.tag_name_pattern), prop "tag_name") + local is_not_draft = _.prop_eq("draft", false) + local is_not_prerelease = _.prop_eq("prerelease", false) + local tag_name_matches = _.prop_satisfies(_.matches(opts.tag_name_pattern), "tag_name") - return all_pass { - if_else(always(opts.include_prerelease), T, is_not_prerelease), - if_else(always(opts.tag_name_pattern), tag_name_matches, T), + return _.all_pass { + _.if_else(_.always(opts.include_prerelease), _.T, is_not_prerelease), + _.if_else(_.always(opts.tag_name_pattern), tag_name_matches, _.T), is_not_draft, } end @@ -83,7 +72,7 @@ function M.fetch_latest_release(repo, opts) function(releases) local is_stable_release = M.release_predicate(opts) ---@type GitHubRelease|nil - local latest_release = list_find_first(is_stable_release, releases) + local latest_release = _.find_first(is_stable_release, releases) if not latest_release then log.fmt_info("Failed to find latest release. repo=%s, opts=%s", repo, opts) diff --git a/lua/nvim-lsp-installer/core/managers/pip3/init.lua b/lua/nvim-lsp-installer/core/managers/pip3/init.lua index 7d599411..b6a82633 100644 --- a/lua/nvim-lsp-installer/core/managers/pip3/init.lua +++ b/lua/nvim-lsp-installer/core/managers/pip3/init.lua @@ -1,4 +1,4 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local settings = require "nvim-lsp-installer.settings" local process = require "nvim-lsp-installer.core.process" local path = require "nvim-lsp-installer.core.path" @@ -8,8 +8,6 @@ local installer = require "nvim-lsp-installer.core.installer" local Result = require "nvim-lsp-installer.core.result" local spawn = require "nvim-lsp-installer.core.spawn" -local list_find_first, list_copy, list_not_nil = - functional.list_find_first, functional.list_copy, functional.list_not_nil local VENV_DIR = "venv" local M = {} @@ -37,20 +35,20 @@ end ---@param packages string[] @The pip packages to install. The first item in this list will be the recipient of the requested version, if set. function M.install(packages) local ctx = installer.context() - local pkgs = list_copy(packages) + local pkgs = _.list_copy(packages) ctx.requested_version:if_present(function(version) pkgs[1] = ("%s==%s"):format(pkgs[1], version) end) - local executables = platform.is_win and list_not_nil(vim.g.python3_host_prog, "python", "python3") - or list_not_nil(vim.g.python3_host_prog, "python3", "python") + local executables = platform.is_win and _.list_not_nil(vim.g.python3_host_prog, "python", "python3") + or _.list_not_nil(vim.g.python3_host_prog, "python3", "python") -- pip3 will hardcode the full path to venv executables, so we need to promote cwd to make sure pip uses the final destination path. ctx:promote_cwd() -- Find first executable that manages to create venv - local executable = list_find_first(function(executable) + local executable = _.find_first(function(executable) return pcall(ctx.spawn[executable], { "-m", "venv", VENV_DIR }) end, executables) @@ -102,7 +100,7 @@ function M.check_outdated_primary_package(receipt, install_dir) ---@type PipOutdatedPackage[] local packages = vim.json.decode(result.stdout) - local outdated_primary_package = list_find_first(function(outdated_package) + local outdated_primary_package = _.find_first(function(outdated_package) return outdated_package.name == normalized_package and outdated_package.version ~= outdated_package.latest_version end, packages) @@ -136,7 +134,7 @@ function M.get_installed_primary_package_version(receipt, install_dir) }):map_catching(function(result) local pip_packages = vim.json.decode(result.stdout) local normalized_pip_package = M.normalize_package(receipt.primary_source.package) - local pip_package = list_find_first(function(package) + local pip_package = _.find_first(function(package) return package.name == normalized_pip_package end, pip_packages) return Optional.of_nilable(pip_package) diff --git a/lua/nvim-lsp-installer/core/process.lua b/lua/nvim-lsp-installer/core/process.lua index 98a7e5a1..9841c11c 100644 --- a/lua/nvim-lsp-installer/core/process.lua +++ b/lua/nvim-lsp-installer/core/process.lua @@ -1,10 +1,8 @@ local log = require "nvim-lsp-installer.log" -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local platform = require "nvim-lsp-installer.core.platform" local uv = vim.loop -local list_any = functional.list_any - ---@alias luv_pipe any ---@alias luv_handle any @@ -46,7 +44,7 @@ end ---@param env table<string, string> ---@param excluded_var_names string[]|nil function M.graft_env(env, excluded_var_names) - local excluded_var_names_set = excluded_var_names and functional.set_of(excluded_var_names) or {} + local excluded_var_names_set = excluded_var_names and _.set_of(excluded_var_names) or {} local merged_env = {} for key, val in pairs(initial_environ) do if not excluded_var_names_set[key] and env[key] == nil then @@ -64,7 +62,7 @@ end ---@param env_list string[] local function sanitize_env_list(env_list) local sanitized_list = {} - for _, env in ipairs(env_list) do + for __, env in ipairs(env_list) do local safe_envs = { "GO111MODULE", "GOBIN", @@ -73,7 +71,7 @@ local function sanitize_env_list(env_list) "GEM_HOME", "GEM_PATH", } - local is_safe_env = list_any(function(safe_env) + local is_safe_env = _.any(function(safe_env) return env:find(safe_env .. "=") == 1 end, safe_envs) if is_safe_env then diff --git a/lua/nvim-lsp-installer/core/ui/init.lua b/lua/nvim-lsp-installer/core/ui/init.lua index 1f07855d..5b855949 100644 --- a/lua/nvim-lsp-installer/core/ui/init.lua +++ b/lua/nvim-lsp-installer/core/ui/init.lua @@ -1,4 +1,4 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local M = {} ---@alias NodeType @@ -35,11 +35,13 @@ function M.HlTextNode(lines_with_span_tuples) return node end +local create_unhighlighted_lines = _.map(function(line) + return { { line, "" } } +end) + ---@param lines string[] function M.Text(lines) - return M.HlTextNode(functional.list_map(function(line) - return { { line, "" } } - end, lines)) + return M.HlTextNode(create_unhighlighted_lines(lines)) end ---@alias CascadingStyle diff --git a/lua/nvim-lsp-installer/servers/angularls/init.lua b/lua/nvim-lsp-installer/servers/angularls/init.lua index 90209c3a..c10d0a97 100644 --- a/lua/nvim-lsp-installer/servers/angularls/init.lua +++ b/lua/nvim-lsp-installer/servers/angularls/init.lua @@ -1,16 +1,12 @@ local server = require "nvim-lsp-installer.server" local platform = require "nvim-lsp-installer.core.platform" local npm = require "nvim-lsp-installer.core.managers.npm" -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local path = require "nvim-lsp-installer.core.path" -local map = functional.list_map - -local function append_node_modules(dirs) - return map(function(dir) - return path.concat { dir, "node_modules" } - end, dirs) -end +local append_node_modules = _.map(function(dir) + return path.concat { dir, "node_modules" } +end) return function(name, root_dir) local function get_cmd(workspace_dir) diff --git a/lua/nvim-lsp-installer/servers/dhall_lsp_server/init.lua b/lua/nvim-lsp-installer/servers/dhall_lsp_server/init.lua index 0d35a943..484ae389 100644 --- a/lua/nvim-lsp-installer/servers/dhall_lsp_server/init.lua +++ b/lua/nvim-lsp-installer/servers/dhall_lsp_server/init.lua @@ -2,13 +2,11 @@ local server = require "nvim-lsp-installer.server" local path = require "nvim-lsp-installer.core.path" local process = require "nvim-lsp-installer.core.process" local platform = require "nvim-lsp-installer.core.platform" -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local std = require "nvim-lsp-installer.core.managers.std" local github_client = require "nvim-lsp-installer.core.managers.github.client" local Optional = require "nvim-lsp-installer.core.optional" -local coalesce, when, list_find_first = functional.coalesce, functional.when, functional.list_find_first - return function(name, root_dir) return server.Server:new { name = name, @@ -29,17 +27,14 @@ return function(name, root_dir) :get_or_throw() local asset_name_pattern = assert( - coalesce( - when(platform.is_mac, "dhall%-lsp%-server%-.+%-x86_64%-macos.tar.bz2"), - when(platform.is_linux, "dhall%-lsp%-server%-.+%-x86_64%-linux.tar.bz2"), - when(platform.is_win, "dhall%-lsp%-server%-.+%-x86_64%-windows.zip") + _.coalesce( + _.when(platform.is.mac, "dhall%-lsp%-server%-.+%-x86_64%-macos.tar.bz2"), + _.when(platform.is.linux_x64, "dhall%-lsp%-server%-.+%-x86_64%-linux.tar.bz2"), + _.when(platform.is.win_x64, "dhall%-lsp%-server%-.+%-x86_64%-windows.zip") ) ) - local dhall_lsp_server_asset = list_find_first( - ---@param asset GitHubReleaseAsset - function(asset) - return asset.name:match(asset_name_pattern) - end, + local dhall_lsp_server_asset = _.find_first( + _.prop_satisfies(_.matches(asset_name_pattern), "name"), gh_release.assets ) Optional.of_nilable(dhall_lsp_server_asset) diff --git a/lua/nvim-lsp-installer/servers/init.lua b/lua/nvim-lsp-installer/servers/init.lua index 8ff7efaf..9d78d651 100644 --- a/lua/nvim-lsp-installer/servers/init.lua +++ b/lua/nvim-lsp-installer/servers/init.lua @@ -1,4 +1,4 @@ -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local path = require "nvim-lsp-installer.core.path" local fs = require "nvim-lsp-installer.core.fs" local settings = require "nvim-lsp-installer.settings" @@ -31,7 +31,7 @@ local INSTALL_DIRS = { ["yamlls"] = "yaml", } -local CORE_SERVERS = functional.set_of { +local CORE_SERVERS = _.set_of { "angularls", "ansiblels", "arduino_language_server", @@ -175,7 +175,7 @@ local function scan_server_roots() result[#result + 1] = entry.name end end - cached_server_roots = functional.set_of(result) + cached_server_roots = _.set_of(result) vim.schedule(function() cached_server_roots = nil end) @@ -239,17 +239,14 @@ function M.get_server(server_name) ):format(server_name, "https://github.com/williamboman/nvim-lsp-installer", server_factory) end ----@param server_names string[] ----@return Server[] -local function resolve_servers(server_names) - return functional.list_map(function(server_name) - local ok, server = M.get_server(server_name) - if not ok then - error(server) - end - return server - end, server_names) -end +---@type fun(server_names: string): Server[] +local resolve_servers = _.map(function(server_name) + local ok, server = M.get_server(server_name) + if not ok then + error(server) + end + return server +end) ---@return string[] function M.get_available_server_names() diff --git a/lua/nvim-lsp-installer/ui/components/settings-schema.lua b/lua/nvim-lsp-installer/ui/components/settings-schema.lua index 4954d456..04d8e0d5 100644 --- a/lua/nvim-lsp-installer/ui/components/settings-schema.lua +++ b/lua/nvim-lsp-installer/ui/components/settings-schema.lua @@ -1,8 +1,6 @@ -- Here be dragons local Ui = require "nvim-lsp-installer.core.ui" -local functional = require "nvim-lsp-installer.core.functional" - -local list_map = functional.list_map +local _ = require "nvim-lsp-installer.core.functional" local property_type_highlights = { ["string"] = "String", @@ -135,7 +133,7 @@ local function ServerSettingsSchema(server, schema, key, level, key_width, compo heading, toggle_expand_keybind, Ui.When(node_is_expanded, function() - local description = list_map(function(line) + local description = _.map(function(line) return { { line, "Comment" } } end, vim.split(schema.description or "No description available.", "\n")) diff --git a/lua/nvim-lsp-installer/ui/init.lua b/lua/nvim-lsp-installer/ui/init.lua index 48791d2a..73548d1f 100644 --- a/lua/nvim-lsp-installer/ui/init.lua +++ b/lua/nvim-lsp-installer/ui/init.lua @@ -3,7 +3,7 @@ local Ui = require "nvim-lsp-installer.core.ui" local display = require "nvim-lsp-installer.core.ui.display" local fs = require "nvim-lsp-installer.core.fs" local log = require "nvim-lsp-installer.log" -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local settings = require "nvim-lsp-installer.settings" local lsp_servers = require "nvim-lsp-installer.servers" local JobExecutionPool = require "nvim-lsp-installer.jobs.pool" @@ -35,7 +35,7 @@ local function Indent(children) return Ui.CascadingStyleNode({ "INDENT" }, children) end -local create_vader = functional.memoize( +local create_vader = _.memoize( ---@param saber_ticks number function(saber_ticks) -- stylua: ignore start @@ -88,7 +88,7 @@ local function Help(is_current_settings_expanded, vader_saber_ticks) { "Keyboard shortcuts", "LspInstallerLabel" }, }, }, - functional.list_map(function(keymap_tuple) + _.map(function(keymap_tuple) return { { keymap_tuple[1], "LspInstallerMuted" }, { keymap_tuple[2], "LspInstallerHighlighted" } } end, keymap_tuples) )), @@ -157,7 +157,7 @@ local function Help(is_current_settings_expanded, vader_saber_ticks) Ui.Keybind("<CR>", "TOGGLE_EXPAND_CURRENT_SETTINGS", nil), Ui.When(is_current_settings_expanded, function() local settings_split_by_newline = vim.split(vim.inspect(settings.current), "\n") - local current_settings = functional.list_map(function(line) + local current_settings = _.map(function(line) return { { line, "LspInstallerMuted" } } end, settings_split_by_newline) return Ui.HlTextNode(current_settings) @@ -211,11 +211,11 @@ end ---@param server ServerState local function ServerMetadata(server) - return Ui.Node(functional.list_not_nil( - functional.lazy_when(server.is_installed and server.deprecated, function() - return Ui.Node(functional.list_not_nil( + return Ui.Node(_.list_not_nil( + _.lazy_when(server.is_installed and server.deprecated, function() + return Ui.Node(_.list_not_nil( Ui.HlTextNode { server.deprecated.message, "Comment" }, - functional.lazy_when(server.deprecated.replace_with, function() + _.lazy_when(server.deprecated.replace_with, function() return Ui.Node { Ui.HlTextNode { { @@ -229,8 +229,8 @@ local function ServerMetadata(server) end) )) end), - Ui.Table(functional.list_not_nil( - functional.lazy_when(server.is_installed, function() + Ui.Table(_.list_not_nil( + _.lazy_when(server.is_installed, function() return { { "version", "LspInstallerMuted" }, server.installed_version_err and { @@ -239,7 +239,7 @@ local function ServerMetadata(server) } or { server.installed_version or "Loading...", "" }, } end), - functional.lazy_when(#server.metadata.outdated_packages > 0, function() + _.lazy_when(#server.metadata.outdated_packages > 0, function() return { { "latest version", "LspInstallerGreen" }, { @@ -248,17 +248,17 @@ local function ServerMetadata(server) }, } end), - functional.lazy_when(server.metadata.install_timestamp_seconds, function() + _.lazy_when(server.metadata.install_timestamp_seconds, function() return { { "installed", "LspInstallerMuted" }, { format_time(server.metadata.install_timestamp_seconds), "" }, } end), - functional.when(not server.is_installed, { + _.when(not server.is_installed, { { "filetypes", "LspInstallerMuted" }, { server.metadata.filetypes, "" }, }), - functional.when(server.is_installed, { + _.when(server.is_installed, { { "path", "LspInstallerMuted" }, { server.metadata.install_dir, "String" }, }), @@ -323,17 +323,17 @@ end ---@param servers ServerState[] ---@param props ServerGroupProps local function InstalledServers(servers, props) - return Ui.Node(functional.list_map( + return Ui.Node(_.map( ---@param server ServerState function(server) local is_expanded = props.expanded_server == server.name return Ui.Node { Ui.HlTextNode { - functional.list_not_nil( + _.list_not_nil( { settings.current.ui.icons.server_installed, "LspInstallerGreen" }, { " " .. server.name .. " ", "" }, { server.hints, "Comment" }, - functional.when(server.deprecated, { " deprecated", "LspInstallerOrange" }) + _.when(server.deprecated, { " deprecated", "LspInstallerOrange" }) ), }, Ui.When( @@ -363,7 +363,7 @@ end ---@param server ServerState local function TailedOutput(server) - return Ui.HlTextNode(functional.list_map(function(line) + return Ui.HlTextNode(_.map(function(line) return { { line, "LspInstallerMuted" } } end, server.installer.tailed_output)) end @@ -382,21 +382,21 @@ end ---@param servers ServerState[] local function PendingServers(servers) - return Ui.Node(functional.list_map(function(_server) + return Ui.Node(_.map(function(_server) ---@type ServerState local server = _server local has_failed = server.installer.has_run or server.uninstaller.has_run local note = has_failed and "(failed)" or (server.installer.is_queued and "(queued)" or "(installing)") return Ui.Node { Ui.HlTextNode { - functional.list_not_nil( + _.list_not_nil( { settings.current.ui.icons.server_pending, has_failed and "LspInstallerError" or "LspInstallerOrange", }, { " " .. server.name, server.installer.is_running and "" or "LspInstallerMuted" }, { " " .. note, "Comment" }, - functional.when(not has_failed, { + _.when(not has_failed, { (" " .. get_last_non_empty_line(server.installer.tailed_output)), "Comment", }) @@ -419,22 +419,22 @@ end ---@param servers ServerState[] ---@param props ServerGroupProps local function UninstalledServers(servers, props) - return Ui.Node(functional.list_map(function(_server) + return Ui.Node(_.map(function(_server) ---@type ServerState local server = _server local is_prioritized = props.prioritized_servers[server.name] local is_expanded = props.expanded_server == server.name return Ui.Node { Ui.HlTextNode { - functional.list_not_nil( + _.list_not_nil( { settings.current.ui.icons.server_uninstalled, is_prioritized and "LspInstallerHighlighted" or "LspInstallerMuted", }, { " " .. server.name .. " ", "LspInstallerMuted" }, { server.hints, "Comment" }, - functional.when(server.uninstaller.has_run, { " (uninstalled) ", "Comment" }), - functional.when(server.deprecated, { "deprecated ", "LspInstallerOrange" }) + _.when(server.uninstaller.has_run, { " (uninstalled) ", "Comment" }), + _.when(server.deprecated, { "deprecated ", "LspInstallerOrange" }) ), }, Ui.Keybind(settings.current.ui.keymaps.toggle_server_expand, "EXPAND_SERVER", { server.name }), @@ -468,7 +468,7 @@ local function ServerGroup(props) subtitle = props.subtitle, count = total_server_count, }, - Indent(functional.list_map(function(servers) + Indent(_.map(function(servers) return props.renderer(servers, props) end, props.servers)), } @@ -954,7 +954,7 @@ local function init(all_servers) mutate_state(function(state) state.is_showing_help = false - state.prioritized_servers = functional.set_of(prioritized_servers) + state.prioritized_servers = _.set_of(prioritized_servers) end) if not has_opened then diff --git a/scripts/autogen_metadata.lua b/scripts/autogen_metadata.lua index 608555a7..137055e0 100644 --- a/scripts/autogen_metadata.lua +++ b/scripts/autogen_metadata.lua @@ -1,12 +1,10 @@ local a = require "nvim-lsp-installer.core.async" local Path = require "nvim-lsp-installer.core.path" local fetch = require "nvim-lsp-installer.core.fetch" -local functional = require "nvim-lsp-installer.core.functional" +local _ = require "nvim-lsp-installer.core.functional" local servers = require "nvim-lsp-installer.servers" local fs = require "nvim-lsp-installer.core.fs" -local coalesce = functional.coalesce - local generated_dir = Path.concat { vim.fn.getcwd(), "lua", "nvim-lsp-installer", "_generated" } local schemas_dir = Path.concat { generated_dir, "schemas" } @@ -54,7 +52,7 @@ local function get_supported_filetypes(server) end local config = get_lspconfig(server.name) local default_options = server:get_default_options() - local filetypes = coalesce( + local filetypes = _.coalesce( -- nvim-lsp-installer options has precedence default_options and default_options.filetypes, config.default_config.filetypes, @@ -99,9 +97,7 @@ local function create_autocomplete_map() local autocomplete_candidates = {} for language, language_servers in pairs(language_map) do - local non_deprecated_servers = vim.tbl_filter(function(server) - return server.deprecated == nil - end, language_servers) + local non_deprecated_servers = _.filter(_.prop_eq("deprecated", nil), language_servers) local is_candidate = #non_deprecated_servers > 0 if #non_deprecated_servers == 1 then @@ -110,9 +106,7 @@ local function create_autocomplete_map() end if is_candidate then - autocomplete_candidates[language] = vim.tbl_map(function(server) - return server.name - end, non_deprecated_servers) + autocomplete_candidates[language] = _.map(_.prop "name", non_deprecated_servers) table.sort(autocomplete_candidates[language]) end end diff --git a/tests/core/functional/data_spec.lua b/tests/core/functional/data_spec.lua new file mode 100644 index 00000000..b89176e2 --- /dev/null +++ b/tests/core/functional/data_spec.lua @@ -0,0 +1,28 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: data", function() + it("creates enums", function() + local colors = _.enum { + "BLUE", + "YELLOW", + } + assert.same({ + ["BLUE"] = "BLUE", + ["YELLOW"] = "YELLOW", + }, colors) + end) + + it("creates sets", function() + local colors = _.set_of { + "BLUE", + "YELLOW", + "BLUE", + "RED", + } + assert.same({ + ["BLUE"] = true, + ["YELLOW"] = true, + ["RED"] = true, + }, colors) + end) +end) diff --git a/tests/core/functional/function_spec.lua b/tests/core/functional/function_spec.lua new file mode 100644 index 00000000..4b9cbba6 --- /dev/null +++ b/tests/core/functional/function_spec.lua @@ -0,0 +1,142 @@ +local spy = require "luassert.spy" +local match = require "luassert.match" +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: function", function() + it("curries functions", function() + local function sum(...) + local res = 0 + for i = 1, select("#", ...) do + res = res + select(i, ...) + end + return res + end + local arity0 = _.curryN(sum, 0) + local arity1 = _.curryN(sum, 1) + local arity2 = _.curryN(sum, 2) + local arity3 = _.curryN(sum, 3) + + assert.equals(0, arity0(42)) + assert.equals(42, arity1(42)) + assert.equals(3, arity2(1)(2)) + assert.equals(3, arity2(1, 2)) + assert.equals(6, arity3(1)(2)(3)) + assert.equals(6, arity3(1, 2, 3)) + + -- should discard superfluous args + assert.equals(0, arity1(0, 10, 20, 30)) + end) + + it("coalesces first non-nil value", function() + assert.equal("Hello World!", _.coalesce(nil, nil, "Hello World!", "")) + end) + + it("should compose functions", function() + local function add(x) + return function(y) + return y + x + end + end + local function subtract(x) + return function(y) + return y - x + end + end + local function multiply(x) + return function(y) + return y * x + end + end + + local big_maths = _.compose(add(1), subtract(3), multiply(5)) + + assert.equals(23, big_maths(5)) + end) + + it("should not allow composing no functions", function() + local e = assert.error(function() + _.compose() + end) + assert.equals("compose requires at least one function", e) + end) + + it("should partially apply functions", function() + local funcy = spy.new() + local partially_funcy = _.partial(funcy, "a", "b", "c") + partially_funcy("d", "e", "f") + assert.spy(funcy).was_called_with("a", "b", "c", "d", "e", "f") + end) + + it("should partially apply functions with nil arguments", function() + local funcy = spy.new() + local partially_funcy = _.partial(funcy, "a", nil, "c") + partially_funcy("d", nil, "f") + assert.spy(funcy).was_called_with("a", nil, "c", "d", nil, "f") + end) + + it("memoizes functions with default cache mechanism", function() + local expensive_function = spy.new(function(s) + return s + end) + local memoized_fn = _.memoize(expensive_function) + assert.equal("key", memoized_fn "key") + assert.equal("key", memoized_fn "key") + assert.equal("new_key", memoized_fn "new_key") + assert.spy(expensive_function).was_called(2) + end) + + it("memoizes function with custom cache mechanism", function() + local expensive_function = spy.new(function(arg1, arg2) + return arg1 .. arg2 + end) + local memoized_fn = _.memoize(expensive_function, function(arg1, arg2) + return arg1 .. arg2 + end) + assert.equal("key1key2", memoized_fn("key1", "key2")) + assert.equal("key1key2", memoized_fn("key1", "key2")) + assert.equal("key1key3", memoized_fn("key1", "key3")) + assert.spy(expensive_function).was_called(2) + end) + + it("should evaluate functions lazily", function() + local impl = spy.new(function() + return {}, {} + end) + local lazy_fn = _.lazy(impl) + assert.spy(impl).was_called(0) + local a, b = lazy_fn() + assert.spy(impl).was_called(1) + assert.is_true(match.is_table()(a)) + assert.is_true(match.is_table()(b)) + local new_a, new_b = lazy_fn() + assert.spy(impl).was_called(1) + assert.is_true(match.is_ref(a)(new_a)) + assert.is_true(match.is_ref(b)(new_b)) + end) + + it("should support nil return values in lazy functions", function() + local lazy_fn = _.lazy(function() + return nil, 2 + end) + local a, b = lazy_fn() + assert.is_nil(a) + assert.equal(2, b) + end) + + it("should provide identity value", function() + local obj = {} + assert.equals(2, _.identity(2)) + assert.equals(obj, _.identity(obj)) + end) + + it("should always return bound value", function() + local obj = {} + assert.equals(2, _.always(2)()) + assert.equals(obj, _.always(obj)()) + end) + + it("true is true and false is false", function() + assert.is_true(_.T()) + assert.is_false(_.F()) + end) +end) diff --git a/tests/core/functional/list_spec.lua b/tests/core/functional/list_spec.lua new file mode 100644 index 00000000..c553a7d0 --- /dev/null +++ b/tests/core/functional/list_spec.lua @@ -0,0 +1,87 @@ +local spy = require "luassert.spy" +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: list", function() + it("should produce list without nils", function() + assert.same({ 1, 2, 3, 4 }, _.list_not_nil(nil, 1, 2, nil, 3, nil, 4, nil)) + end) + + it("makes a shallow copy of a list", function() + local list = { "BLUE", { nested = "TABLE" }, "RED" } + local list_copy = _.list_copy(list) + assert.same({ "BLUE", { nested = "TABLE" }, "RED" }, list_copy) + assert.is_not.is_true(list == list_copy) + assert.is_true(list[2] == list_copy[2]) + end) + + it("reverses lists", function() + local colors = { "BLUE", "YELLOW", "RED" } + assert.same({ + "RED", + "YELLOW", + "BLUE", + }, _.reverse(colors)) + -- should not modify in-place + assert.same({ "BLUE", "YELLOW", "RED" }, colors) + end) + + it("maps over list", function() + local colors = { "BLUE", "YELLOW", "RED" } + assert.same( + { + "LIGHT_BLUE", + "LIGHT_YELLOW", + "LIGHT_RED", + }, + _.map(function(color) + return "LIGHT_" .. color + end, colors) + ) + -- should not modify in-place + assert.same({ "BLUE", "YELLOW", "RED" }, colors) + end) + + it("finds first item that fulfills predicate", function() + local predicate = spy.new(function(item) + return item == "Waldo" + end) + + assert.equal( + "Waldo", + _.find_first(predicate, { + "Where", + "On Earth", + "Is", + "Waldo", + "?", + }) + ) + assert.spy(predicate).was.called(4) + end) + + it("determines whether any item in the list fulfills predicate", function() + local predicate = spy.new(function(item) + return item == "On Earth" + end) + + assert.is_true(_.any(predicate, { + "Where", + "On Earth", + "Is", + "Waldo", + "?", + })) + + assert.spy(predicate).was.called(2) + end) + + it("should iterate list in .each", function() + local list = { "BLUE", "YELLOW", "RED" } + local iterate_fn = spy.new() + _.each(iterate_fn, list) + assert.spy(iterate_fn).was_called(3) + assert.spy(iterate_fn).was_called_with("BLUE", 1) + assert.spy(iterate_fn).was_called_with("YELLOW", 2) + assert.spy(iterate_fn).was_called_with("RED", 3) + end) +end) diff --git a/tests/core/functional/logic_spec.lua b/tests/core/functional/logic_spec.lua new file mode 100644 index 00000000..19ac8bb7 --- /dev/null +++ b/tests/core/functional/logic_spec.lua @@ -0,0 +1,44 @@ +local spy = require "luassert.spy" +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: logic", function() + it("should check that all_pass checks that all predicates pass", function() + local is_waldo = function(i) + return i == "waldo" + end + assert.is_true(_.all_pass { _.T, _.T, is_waldo, _.T } "waldo") + assert.is_false(_.all_pass { _.T, _.T, is_waldo, _.F } "waldo") + assert.is_false(_.all_pass { _.T, _.T, is_waldo, _.T } "waldina") + end) + + it("should branch if_else", function() + local a = spy.new() + local b = spy.new() + _.if_else(_.T, a, b) "a" + _.if_else(_.F, a, b) "b" + assert.spy(a).was_called(1) + assert.spy(a).was_called_with "a" + assert.spy(b).was_called(1) + assert.spy(b).was_called_with "b" + end) + + it("should flip booleans", function() + assert.is_true(_.is_not(false)) + assert.is_false(_.is_not(true)) + end) + + it("should resolve correct cond", function() + local planetary_object = _.cond { + { + _.equals "Moon!", + _.format "to the %s", + }, + { + _.equals "World!", + _.format "Hello %s", + }, + } + assert.equals("Hello World!", planetary_object "World!") + assert.equals("to the Moon!", planetary_object "Moon!") + end) +end) diff --git a/tests/core/functional/number_spec.lua b/tests/core/functional/number_spec.lua new file mode 100644 index 00000000..644547fb --- /dev/null +++ b/tests/core/functional/number_spec.lua @@ -0,0 +1,50 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: number", function() + it("should negate numbers", function() + assert.equals(-42, _.negate(42)) + assert.equals(42, _.negate(-42)) + end) + + it("should check numbers greater than value", function() + local greater_than_life = _.gt(42) + assert.is_false(greater_than_life(0)) + assert.is_false(greater_than_life(42)) + assert.is_true(greater_than_life(43)) + end) + + it("should check numbers greater or equal than value", function() + local greater_or_equal_to_life = _.gte(42) + assert.is_false(greater_or_equal_to_life(0)) + assert.is_true(greater_or_equal_to_life(42)) + assert.is_true(greater_or_equal_to_life(43)) + end) + + it("should check numbers lower than value", function() + local lesser_than_life = _.lt(42) + assert.is_true(lesser_than_life(0)) + assert.is_false(lesser_than_life(42)) + assert.is_false(lesser_than_life(43)) + end) + + it("should check numbers lower or equal than value", function() + local lesser_or_equal_to_life = _.lte(42) + assert.is_true(lesser_or_equal_to_life(0)) + assert.is_true(lesser_or_equal_to_life(42)) + assert.is_false(lesser_or_equal_to_life(43)) + end) + + it("should increment numbers", function() + local add_5 = _.inc(5) + assert.equals(0, add_5(-5)) + assert.equals(5, add_5(0)) + assert.equals(7, add_5(2)) + end) + + it("should decrement numbers", function() + local subtract_5 = _.dec(5) + assert.equals(5, subtract_5(10)) + assert.equals(-5, subtract_5(0)) + assert.equals(-3, subtract_5(2)) + end) +end) diff --git a/tests/core/functional/relation_spec.lua b/tests/core/functional/relation_spec.lua new file mode 100644 index 00000000..41325cf2 --- /dev/null +++ b/tests/core/functional/relation_spec.lua @@ -0,0 +1,36 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: relation", function() + it("should check equality", function() + local tbl = {} + local is_tbl = _.equals(tbl) + local is_a = _.equals "a" + local is_42 = _.equals(42) + + assert.is_true(is_tbl(tbl)) + assert.is_true(is_a "a") + assert.is_true(is_42(42)) + assert.is_false(is_a "b") + assert.is_false(is_42(32)) + end) + + it("should check property equality", function() + local fn_key = function() end + local tbl = { a = "a", b = "b", number = 42, [fn_key] = "fun" } + assert.is_true(_.prop_eq("a", "a", tbl)) + assert.is_true(_.prop_eq(fn_key, "fun", tbl)) + assert.is_true(_.prop_eq(fn_key) "fun"(tbl)) + end) + + it("should check whether property satisfies predicate", function() + local obj = { + low = 0, + med = 10, + high = 15, + } + + assert.is_false(_.prop_satisfies(_.gt(10), "low", obj)) + assert.is_false(_.prop_satisfies(_.gt(10), "med")(obj)) + assert.is_true(_.prop_satisfies(_.gt(10)) "high"(obj)) + end) +end) diff --git a/tests/core/functional/string_spec.lua b/tests/core/functional/string_spec.lua new file mode 100644 index 00000000..53575ce4 --- /dev/null +++ b/tests/core/functional/string_spec.lua @@ -0,0 +1,17 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: string", function() + it("matches string patterns", function() + assert.is_true(_.matches("foo", "foo")) + assert.is_true(_.matches("bar", "foobarbaz")) + assert.is_true(_.matches("ba+r", "foobaaaaaaarbaz")) + + assert.is_false(_.matches("ba+r", "foobharbaz")) + assert.is_false(_.matches("bar", "foobaz")) + end) + + it("should format strings", function() + assert.equals("Hello World!", _.format("%s", "Hello World!")) + assert.equals("special manouvers", _.format("%s manouvers", "special")) + end) +end) diff --git a/tests/core/functional/table_spec.lua b/tests/core/functional/table_spec.lua new file mode 100644 index 00000000..25adff59 --- /dev/null +++ b/tests/core/functional/table_spec.lua @@ -0,0 +1,7 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: table", function() + it("retrieves property of table", function() + assert.equals("hello", _.prop("a", { a = "hello" })) + end) +end) diff --git a/tests/core/functional/type_spec.lua b/tests/core/functional/type_spec.lua new file mode 100644 index 00000000..b99262b5 --- /dev/null +++ b/tests/core/functional/type_spec.lua @@ -0,0 +1,26 @@ +local _ = require "nvim-lsp-installer.core.functional" + +describe("functional: type", function() + it("should check nil value", function() + assert.is_true(_.is_nil(nil)) + assert.is_false(_.is_nil(1)) + assert.is_false(_.is_nil {}) + assert.is_false(_.is_nil(function() end)) + end) + + it("should check types", function() + local is_fun = _.is "function" + local is_string = _.is "string" + local is_number = _.is "number" + local is_boolean = _.is "boolean" + + assert.is_true(is_fun(function() end)) + assert.is_false(is_fun(1)) + assert.is_true(is_string "") + assert.is_false(is_string(1)) + assert.is_true(is_number(1)) + assert.is_false(is_number "") + assert.is_true(is_boolean(true)) + assert.is_false(is_boolean(1)) + end) +end) diff --git a/tests/core/functional_spec.lua b/tests/core/functional_spec.lua deleted file mode 100644 index 54433629..00000000 --- a/tests/core/functional_spec.lua +++ /dev/null @@ -1,247 +0,0 @@ -local functional = require "nvim-lsp-installer.core.functional" -local spy = require "luassert.spy" -local match = require "luassert.match" - -describe("functional", function() - it("creates enums", function() - local colors = functional.enum { - "BLUE", - "YELLOW", - } - assert.same({ - ["BLUE"] = "BLUE", - ["YELLOW"] = "YELLOW", - }, colors) - end) - - it("creates sets", function() - local colors = functional.set_of { - "BLUE", - "YELLOW", - "BLUE", - "RED", - } - assert.same({ - ["BLUE"] = true, - ["YELLOW"] = true, - ["RED"] = true, - }, colors) - end) - - it("reverses lists", function() - local colors = { "BLUE", "YELLOW", "RED" } - assert.same({ - "RED", - "YELLOW", - "BLUE", - }, functional.list_reverse(colors)) - -- should not modify in-place - assert.same({ "BLUE", "YELLOW", "RED" }, colors) - end) - - it("maps over list", function() - local colors = { "BLUE", "YELLOW", "RED" } - assert.same( - { - "LIGHT_BLUE1", - "LIGHT_YELLOW2", - "LIGHT_RED3", - }, - functional.list_map(function(color, i) - return "LIGHT_" .. color .. i - end, colors) - ) - -- should not modify in-place - assert.same({ "BLUE", "YELLOW", "RED" }, colors) - end) - - it("coalesces first non-nil value", function() - assert.equal("Hello World!", functional.coalesce(nil, nil, "Hello World!", "")) - end) - - it("makes a shallow copy of a list", function() - local list = { "BLUE", { nested = "TABLE" }, "RED" } - local list_copy = functional.list_copy(list) - assert.same({ "BLUE", { nested = "TABLE" }, "RED" }, list_copy) - assert.is_not.is_true(list == list_copy) - assert.is_true(list[2] == list_copy[2]) - end) - - it("finds first item that fulfills predicate", function() - local predicate = spy.new(function(item) - return item == "Waldo" - end) - - assert.equal( - "Waldo", - functional.list_find_first(predicate, { - "Where", - "On Earth", - "Is", - "Waldo", - "?", - }) - ) - assert.spy(predicate).was.called(4) - end) - - it("determines whether any item in the list fulfills predicate", function() - local predicate = spy.new(function(item) - return item == "On Earth" - end) - - assert.is_true(functional.list_any(predicate, { - "Where", - "On Earth", - "Is", - "Waldo", - "?", - })) - - assert.spy(predicate).was.called(2) - end) - - it("memoizes functions with default cache mechanism", function() - local expensive_function = spy.new(function(s) - return s - end) - local memoized_fn = functional.memoize(expensive_function) - assert.equal("key", memoized_fn "key") - assert.equal("key", memoized_fn "key") - assert.equal("new_key", memoized_fn "new_key") - assert.spy(expensive_function).was_called(2) - end) - - it("memoizes function with custom cache mechanism", function() - local expensive_function = spy.new(function(arg1, arg2) - return arg1 .. arg2 - end) - local memoized_fn = functional.memoize(expensive_function, function(arg1, arg2) - return arg1 .. arg2 - end) - assert.equal("key1key2", memoized_fn("key1", "key2")) - assert.equal("key1key2", memoized_fn("key1", "key2")) - assert.equal("key1key3", memoized_fn("key1", "key3")) - assert.spy(expensive_function).was_called(2) - end) - - it("should evaluate functions lazily", function() - local impl = spy.new(function() - return {}, {} - end) - local lazy_fn = functional.lazy(impl) - assert.spy(impl).was_called(0) - local a, b = lazy_fn() - assert.spy(impl).was_called(1) - assert.is_true(match.is_table()(a)) - assert.is_true(match.is_table()(b)) - local new_a, new_b = lazy_fn() - assert.spy(impl).was_called(1) - assert.is_true(match.is_ref(a)(new_a)) - assert.is_true(match.is_ref(b)(new_b)) - end) - - it("should support nil return values in lazy functions", function() - local lazy_fn = functional.lazy(function() - return nil, 2 - end) - local a, b = lazy_fn() - assert.is_nil(a) - assert.equal(2, b) - end) - - it("should partially apply functions", function() - local funcy = spy.new() - local partially_funcy = functional.partial(funcy, "a", "b", "c") - partially_funcy("d", "e", "f") - assert.spy(funcy).was_called_with("a", "b", "c", "d", "e", "f") - end) - - it("should partially apply functions with nil arguments", function() - local funcy = spy.new() - local partially_funcy = functional.partial(funcy, "a", nil, "c") - partially_funcy("d", nil, "f") - assert.spy(funcy).was_called_with("a", nil, "c", "d", nil, "f") - end) - - it("should compose functions", function() - local function add(x) - return function(y) - return y + x - end - end - local function subtract(x) - return function(y) - return y - x - end - end - local function multiply(x) - return function(y) - return y * x - end - end - - local big_maths = functional.compose(add(1), subtract(3), multiply(5)) - - assert.equals(23, big_maths(5)) - end) - - it("should not allow composing no functions", function() - local e = assert.error(function() - functional.compose() - end) - assert.equals("compose requires at least one function", e) - end) - - it("should iterate list in .each", function() - local list = { "BLUE", "YELLOW", "RED" } - local iterate_fn = spy.new() - functional.each(iterate_fn, list) - assert.spy(iterate_fn).was_called(3) - assert.spy(iterate_fn).was_called_with("BLUE", 1) - assert.spy(iterate_fn).was_called_with("YELLOW", 2) - assert.spy(iterate_fn).was_called_with("RED", 3) - end) - - it("should negate predicates", function() - local predicate = spy.new(function(item) - return item == "Waldo" - end) - local negated_predicate = functional.negate(predicate) - assert.is_false(negated_predicate "Waldo") - assert.is_true(negated_predicate "Where") - assert.spy(predicate).was_called(2) - end) - - it("should check that all_pass checks that all predicates pass", function() - local t = functional.always(true) - local f = functional.always(false) - local is_waldo = function(i) - return i == "waldo" - end - assert.is_true(functional.all_pass { t, t, is_waldo, t } "waldo") - assert.is_false(functional.all_pass { t, t, is_waldo, f } "waldo") - assert.is_false(functional.all_pass { t, t, is_waldo, t } "waldina") - end) - - it("should index object by prop", function() - local waldo = functional.prop "where is he" - assert.equals("nowhere to be found", waldo { ["where is he"] = "nowhere to be found" }) - end) - - it("should branch if_else", function() - local a = spy.new() - local b = spy.new() - functional.if_else(functional.T, a, b)("a", 1) - functional.if_else(functional.F, a, b)("b", 2) - assert.spy(a).was_called(1) - assert.spy(a).was_called_with("a", 1) - assert.spy(b).was_called(1) - assert.spy(b).was_called_with("b", 2) - end) - - it("should check if string matches", function() - assert.is_false(functional.matches "a" "b") - assert.is_true(functional.matches "a" "a") - end) -end) |
