From c90f4512b9ab037bdb7b829f3d57b7600d7b6873 Mon Sep 17 00:00:00 2001 From: Simi Falaye Date: Fri, 25 Oct 2024 08:04:39 -0600 Subject: [PATCH] feat(config): `import` option for splitting rocks toml configs (#565) * feat: Add support for config imports * Add tests for import feature * Use newer version of toml-edit and add more tests * Update flake.lock * Update README.md * Update lua/rocks/operations/helpers/multi_mut_rocks_toml_wrapper.lua --------- Co-authored-by: Marc Jakobi Co-authored-by: Marc Jakobi --- .github/workflows/luarocks.yml | 2 +- README.md | 22 +++ doc/rocks.txt | 1 + flake.lock | 18 +- lua/rocks/api/init.lua | 1 + lua/rocks/config/internal.lua | 80 +++++++-- lua/rocks/fs.lua | 33 +++- lua/rocks/operations/add.lua | 6 +- .../{helpers.lua => helpers/init.lua} | 15 +- .../helpers/multi_mut_rocks_toml_wrapper.lua | 75 ++++++++ lua/rocks/operations/pin.lua | 4 +- lua/rocks/operations/prune.lua | 3 +- lua/rocks/operations/unpin.lua | 4 +- lua/rocks/operations/update.lua | 3 +- rocks.nvim-scm-1.rockspec | 4 +- spec/config_spec.lua | 98 +++++++++++ spec/fs_spec.lua | 10 ++ spec/operations/helpers_spec.lua | 165 +++++++++++++++++- 18 files changed, 499 insertions(+), 45 deletions(-) rename lua/rocks/operations/{helpers.lua => helpers/init.lua} (95%) create mode 100644 lua/rocks/operations/helpers/multi_mut_rocks_toml_wrapper.lua diff --git a/.github/workflows/luarocks.yml b/.github/workflows/luarocks.yml index e3f35018..b81baa96 100644 --- a/.github/workflows/luarocks.yml +++ b/.github/workflows/luarocks.yml @@ -32,7 +32,7 @@ jobs: test_interpreters: "" dependencies: | luarocks >= 3.11.1, < 4.0.0 - toml-edit >= 0.3.6 + toml-edit >= 0.5.0 fidget.nvim >= 1.1.0 fzy nvim-nio diff --git a/README.md b/README.md index 82c40770..0b650022 100644 --- a/README.md +++ b/README.md @@ -454,6 +454,28 @@ You can also pin/unpin installed plugins with: :Rocks [pin|unpin] {rock} ``` +### Importing rocks toml files + +You can break up your rocks configuration into different modules that +can then be imported into your main configuration. This can be useful +for modularity or simply for the purpose of supporting local +configuration files that you can keep outside of version control. + +For example: + +```toml +import = [ + "rocks-local.toml", # Paths are relative to the rocks.toml file directory by default + "~/my-rocks.toml", # Path expansion is supported through vim.fn.expand + "/home/user/my-rocks.toml", # Absolute paths are supported +] + +``` + +> [!NOTE] +> +> - Imported config will have higher priority + ## :calendar: User events For `:h User` events that rocks.nvim will trigger, see `:h rocks.user-event`. diff --git a/doc/rocks.txt b/doc/rocks.txt index 9d5c2263..a17d2158 100644 --- a/doc/rocks.txt +++ b/doc/rocks.txt @@ -330,6 +330,7 @@ RocksToml *RocksToml* {plugins?} (table) The `[plugins]` entries {servers?} (string[]) {dev_servers?} (string[]) + {import?} (string[]) {string} (unknown) Fields that can be added by external modules diff --git a/flake.lock b/flake.lock index fd1be4be..24dd06f9 100644 --- a/flake.lock +++ b/flake.lock @@ -508,11 +508,11 @@ "nixpkgs": "nixpkgs_3" }, "locked": { - "lastModified": 1729315397, - "narHash": "sha256-j/juRj9MDCOMYcCGXTogUigGYDPjwwYuxnuyBul78tk=", + "lastModified": 1729747489, + "narHash": "sha256-bRXRU+I+pI3lOmt5Z6r1SKJAFDOsKYxY/c6Fu/vWtVg=", "owner": "nvim-neorocks", "repo": "neorocks", - "rev": "bd4927a47d96bc2eb88f11079075ce4db5be8a4f", + "rev": "6add7e713a0291194d8dca54552dd645ab49e42f", "type": "github" }, "original": { @@ -706,11 +706,11 @@ }, "nixpkgs_3": { "locked": { - "lastModified": 1729265718, - "narHash": "sha256-4HQI+6LsO3kpWTYuVGIzhJs1cetFcwT7quWCk/6rqeo=", + "lastModified": 1729658218, + "narHash": "sha256-9Rg+AqLqvqqJniP/OQB3GtgXoAd8IlazsHp97va042Y=", "owner": "nixos", "repo": "nixpkgs", - "rev": "ccc0c2126893dd20963580b6478d1a10a4512185", + "rev": "dfffb2e7a52d29a0ef8e21ec8a0f30487b227f1a", "type": "github" }, "original": { @@ -722,11 +722,11 @@ }, "nixpkgs_4": { "locked": { - "lastModified": 1729381852, - "narHash": "sha256-Tl9738VMXlVjv7EVq3znrsodyUy0w1ekD0wsouzDybg=", + "lastModified": 1729802817, + "narHash": "sha256-yuJ1akClIezXyZyJYL3irOzjbwGquZpI5SDG7WCc2rA=", "owner": "nixos", "repo": "nixpkgs", - "rev": "75d8eea61e93c46fed1919f9ec798b7911378b22", + "rev": "a378c720993acbe2dc771f96a6ca271d82eb2a04", "type": "github" }, "original": { diff --git a/lua/rocks/api/init.lua b/lua/rocks/api/init.lua index fa73d979..a4783475 100644 --- a/lua/rocks/api/init.lua +++ b/lua/rocks/api/init.lua @@ -117,6 +117,7 @@ end ---@field plugins? table The `[plugins]` entries ---@field servers? string[] ---@field dev_servers? string[] +---@field import? string[] ---@field [string] unknown Fields that can be added by external modules ---Returns a table with the parsed rocks.toml file. diff --git a/lua/rocks/config/internal.lua b/lua/rocks/config/internal.lua index decf6198..3c9f42fc 100644 --- a/lua/rocks/config/internal.lua +++ b/lua/rocks/config/internal.lua @@ -38,6 +38,8 @@ end local default_luarocks_binary = get_default_luarocks_binary(default_rocks_path) +local notified_recursive_imports = {} + --- rocks.nvim default configuration ---@class RocksConfig local default_config = { @@ -70,26 +72,74 @@ local default_config = { ---@type string[] unrecognized_configs = {}, }, + ---@type fun(parse_func: (fun(file_str: string, file_path: string): table), process_func: fun(config: table, file_path)) + read_rocks_toml = function(parse_func, process_func) + local visited = {} + + local function parse(file_path, default) + -- Don't allow recursive includes + if visited[file_path] then + if not notified_recursive_imports[file_path] then + vim.defer_fn(function() + vim.notify("Recursive import detected: " .. file_path, vim.log.levels.WARN) + end, 1000) + notified_recursive_imports[file_path] = true + end + return nil + end + visited[file_path] = true + + -- Read config + local file_str = fs.read_or_create(file_path, default) + -- Parse + local rocks_toml = parse_func(file_str, file_path) + -- Follow import paths (giving preference to imported config) + if rocks_toml.import then + -- NOTE: using a while loop as the imports may be a metatable + local i, import_path = 0, nil + while true do + i = i + 1 + import_path = rocks_toml.import[i] + if import_path == nil then + break + end + parse(fs.get_absolute_path(vim.fs.dirname(config.config_path), import_path), "") + end + end + -- Process result + process_func(rocks_toml, file_path) + end + parse(config.config_path, constants.DEFAULT_CONFIG) + end, ---@type fun():RocksToml get_rocks_toml = function() - local config_file = fs.read_or_create(config.config_path, constants.DEFAULT_CONFIG) - local rocks_toml = require("toml_edit").parse_as_tbl(config_file) - for key, tbl in pairs(rocks_toml) do - if key == "rocks" or key == "plugins" then - for name, data in pairs(tbl) do - if type(data) == "string" then - ---@type RockSpec - rocks_toml[key][name] = { - name = name, - version = data, - } - else - rocks_toml[key][name].name = name + local rocks_toml_merged = {} + config.read_rocks_toml(function(file_str, _) + -- Parse + return require("toml_edit").parse_as_tbl(file_str) + end, function(rocks_toml, _) + -- Setup rockspec for rocks/plugins + for key, tbl in pairs(rocks_toml) do + if key == "rocks" or key == "plugins" then + for name, data in pairs(tbl) do + if type(data) == "string" then + ---@type RockSpec + rocks_toml[key][name] = { + name = name, + version = data, + } + else + rocks_toml[key][name].name = name + end end end end - end - return rocks_toml + -- Merge into configuration, in the order of preference returned by the read function + rocks_toml_merged = vim.tbl_deep_extend("keep", rocks_toml_merged, rocks_toml) + end) + rocks_toml_merged.import = nil -- Remove import field since we merged + + return rocks_toml_merged end, ---@return server_url[] get_servers = function() diff --git a/lua/rocks/fs.lua b/lua/rocks/fs.lua index 4f991274..187c03b8 100644 --- a/lua/rocks/fs.lua +++ b/lua/rocks/fs.lua @@ -36,6 +36,35 @@ function fs.file_exists(location) return false end +--- Expand environment variables and tilde in the path string +---@param path_str string +---@return string +function fs.expand_path(path_str) + -- Expand environment variables + local path = path_str:gsub("%$([%w_]+)", function(var) + return os.getenv(var) or "" + end) + -- Expand tilde to home directory + local home = os.getenv("HOME") + if home then + path = path:gsub("^~", home) + end + return path +end + +--- Expand path string and get the absolute path if it a relative path string +---@param base_path string base directory path to use if path_str is relative +---@param path_str string the path string to expand +---@return string +function fs.get_absolute_path(base_path, path_str) + local path = fs.expand_path(path_str) + -- If path is not an absolute path, set it relative to the base + if path:sub(1, 1) ~= "/" then + path = vim.fs.joinpath(fs.expand_path(base_path), path) + end + return path +end + --- Write `contents` to a file asynchronously ---@param location string file path ---@param mode string mode to open the file for @@ -66,7 +95,9 @@ function fs.write_file(location, mode, contents, callback) else local msg = ("Error opening %s for writing: %s"):format(location, err) log.error(msg) - vim.notify(msg, vim.log.levels.ERROR) + vim.schedule(function() + vim.notify(msg, vim.log.levels.ERROR) + end) if callback then callback() end diff --git a/lua/rocks/operations/add.lua b/lua/rocks/operations/add.lua index bb5ec860..47206bc5 100644 --- a/lua/rocks/operations/add.lua +++ b/lua/rocks/operations/add.lua @@ -18,8 +18,6 @@ local add = {} local constants = require("rocks.constants") local log = require("rocks.log") -local fs = require("rocks.fs") -local config = require("rocks.config.internal") local cache = require("rocks.cache") local helpers = require("rocks.operations.helpers") local handlers = require("rocks.operations.handlers") @@ -98,7 +96,7 @@ add.add = function(arg_list, callback, opts) }) end handler(report_progress, report_error, helpers.manage_rock_stub) - fs.write_file_await(config.config_path, "w", tostring(user_rocks)) + user_rocks:write() nio.scheduler() progress_handle:finish() return @@ -217,7 +215,7 @@ Use 'Rocks %s {rock_name}' or install rocks-git.nvim. else user_rocks.plugins[rock_name] = installed_rock.version end - fs.write_file_await(config.config_path, "w", tostring(user_rocks)) + user_rocks:write() cache.populate_all_rocks_state_caches() vim.schedule(function() helpers.postInstall() diff --git a/lua/rocks/operations/helpers.lua b/lua/rocks/operations/helpers/init.lua similarity index 95% rename from lua/rocks/operations/helpers.lua rename to lua/rocks/operations/helpers/init.lua index f98e2c27..cec5cfa3 100644 --- a/lua/rocks/operations/helpers.lua +++ b/lua/rocks/operations/helpers/init.lua @@ -24,6 +24,7 @@ local state = require("rocks.state") local log = require("rocks.log") local cache = require("rocks.cache") local nio = require("nio") +local multi_mut_rocks_toml_wrapper = require("rocks.operations.helpers.multi_mut_rocks_toml_wrapper") local helpers = {} @@ -32,8 +33,18 @@ helpers.semaphore = nio.control.semaphore(1) ---Decode the user rocks from rocks.toml, creating a default config file if it does not exist ---@return MutRocksTomlRef function helpers.parse_rocks_toml() - local config_file = fs.read_or_create(config.config_path, constants.DEFAULT_CONFIG) - return require("toml_edit").parse(config_file) + local rocks_toml_configs = {} + config.read_rocks_toml(function(file_str, file_path) + -- Parse + return require("toml_edit").parse(file_str) + end, function(rocks_toml, file_path) + ---@type MutRocksTomlRefWithPath + local rocks_toml_config = { config = rocks_toml, path = file_path } + -- Append to config list in order of preference returned by the read function + table.insert(rocks_toml_configs, rocks_toml_config) + end) + + return multi_mut_rocks_toml_wrapper.new(rocks_toml_configs) --[[@as MutRocksTomlRef]] end ---@param rocks_toml MutRocksTomlRef diff --git a/lua/rocks/operations/helpers/multi_mut_rocks_toml_wrapper.lua b/lua/rocks/operations/helpers/multi_mut_rocks_toml_wrapper.lua new file mode 100644 index 00000000..d61ddf73 --- /dev/null +++ b/lua/rocks/operations/helpers/multi_mut_rocks_toml_wrapper.lua @@ -0,0 +1,75 @@ +local config = require("rocks.config.internal") +local fs = require("rocks.fs") + +---@class MutRocksTomlRefWithPath +---@field config MutRocksTomlRef Config metatable +---@field path? string The path to the configuration + +---@class MultiMutRocksTomlWrapper +---@field cache table Cache for nested metatables +---@field configs MutRocksTomlRefWithPath[] A list of rocks toml configs +local MultiMutRocksTomlWrapper = {} +MultiMutRocksTomlWrapper.__index = function(self, key) + -- Give preference to class methods/fields + if MultiMutRocksTomlWrapper[key] then + return MultiMutRocksTomlWrapper[key] + end + -- Find the key within the config tables + for _, tbl in ipairs(self.configs) do + if tbl.config[key] ~= nil then + if type(tbl.config[key]) == "table" then + if not self.cache[key] then + self.cache[key] = MultiMutRocksTomlWrapper.new(vim.iter(self.configs) + :filter(function(v) + return type(v.config[key]) == "table" + end) + :fold({}, function(acc, v) + table.insert(acc, { config = v.config[key], path = v.path }) + return acc + end)) + end + return self.cache[key] + else + return tbl.config[key] + end + end + end + return nil +end +MultiMutRocksTomlWrapper.__newindex = function(self, key, value) + local insert_index = 1 + for i, tbl in ipairs(self.configs) do + -- Insert into base config by default + if tbl.path == config.config_path then + insert_index = i + end + if tbl.config[key] ~= nil then + tbl.config[key] = value + return + end + end + -- If key not found in any table, add it to the first table + self.configs[insert_index].config[key] = value +end + +--- Write to all rocks toml config files in an async context +---@type async fun(self: MultiMutRocksTomlWrapper) +function MultiMutRocksTomlWrapper:write() + for _, tbl in ipairs(self.configs) do + if tbl.path ~= nil then + fs.write_file_await(tbl.path, "w", tostring(tbl.config)) + end + end +end + +--- Function to create a new wrapper +---@param configs MutRocksTomlRefWithPath[] A list of rocks toml configs +---@return MultiMutRocksTomlWrapper +function MultiMutRocksTomlWrapper.new(configs) + assert(#configs > 0, "Must provide at least one rocks toml config") + local self = { cache = {}, configs = configs } + setmetatable(self, MultiMutRocksTomlWrapper) + return self +end + +return MultiMutRocksTomlWrapper diff --git a/lua/rocks/operations/pin.lua b/lua/rocks/operations/pin.lua index a69835bc..eae513fc 100644 --- a/lua/rocks/operations/pin.lua +++ b/lua/rocks/operations/pin.lua @@ -16,8 +16,6 @@ local pin = {} -local fs = require("rocks.fs") -local config = require("rocks.config.internal") local helpers = require("rocks.operations.helpers") local nio = require("nio") @@ -40,7 +38,7 @@ pin.pin = function(rock_name) end user_config[rocks_key][rock_name].pin = true local version = user_config[rocks_key][rock_name].version - fs.write_file_await(config.config_path, "w", tostring(user_config)) + user_config:write() vim.schedule(function() vim.notify(("%s pinned to version %s"):format(rock_name, version), vim.log.levels.INFO) end) diff --git a/lua/rocks/operations/prune.lua b/lua/rocks/operations/prune.lua index c6905809..4a42f080 100644 --- a/lua/rocks/operations/prune.lua +++ b/lua/rocks/operations/prune.lua @@ -18,7 +18,6 @@ local prune = {} local constants = require("rocks.constants") local log = require("rocks.log") -local fs = require("rocks.fs") local config = require("rocks.config.internal") local cache = require("rocks.cache") local helpers = require("rocks.operations.helpers") @@ -56,7 +55,7 @@ prune.prune = function(rock_name) progress_handle:report({ message = message, title = "Error" }) success = false end - fs.write_file_await(config.config_path, "w", tostring(user_config)) + user_config:write() local user_rocks = config.get_user_rocks() handlers.prune_user_rocks(user_rocks, report_progress, report_error) adapter.synchronise_site_symlinks() diff --git a/lua/rocks/operations/unpin.lua b/lua/rocks/operations/unpin.lua index 3a16fac9..27217fe2 100644 --- a/lua/rocks/operations/unpin.lua +++ b/lua/rocks/operations/unpin.lua @@ -16,8 +16,6 @@ local unpin = {} -local fs = require("rocks.fs") -local config = require("rocks.config.internal") local helpers = require("rocks.operations.helpers") local nio = require("nio") @@ -41,7 +39,7 @@ unpin.unpin = function(rock_name) else user_config[rocks_key][rock_name].pin = nil end - fs.write_file_await(config.config_path, "w", tostring(user_config)) + user_config:write() vim.schedule(function() vim.notify(("%s unpinned"):format(rock_name), vim.log.levels.INFO) end) diff --git a/lua/rocks/operations/update.lua b/lua/rocks/operations/update.lua index 21fea8b6..e762166a 100644 --- a/lua/rocks/operations/update.lua +++ b/lua/rocks/operations/update.lua @@ -18,7 +18,6 @@ local update = {} local constants = require("rocks.constants") local log = require("rocks.log") -local fs = require("rocks.fs") local config = require("rocks.config.internal") local state = require("rocks.state") local cache = require("rocks.cache") @@ -170,7 +169,7 @@ update.update = function(on_complete, opts) user_rocks[rocks_key][rock_name] = installed_rock.version end end - fs.write_file_await(config.config_path, "w", tostring(user_rocks)) + user_rocks:write() nio.scheduler() if not vim.tbl_isempty(error_handles) then local message = "Update completed with errors! Run ':Rocks log' for details." diff --git a/rocks.nvim-scm-1.rockspec b/rocks.nvim-scm-1.rockspec index 3f76513c..57199aa2 100644 --- a/rocks.nvim-scm-1.rockspec +++ b/rocks.nvim-scm-1.rockspec @@ -9,7 +9,7 @@ version = _MODREV .. _SPECREV dependencies = { "lua >= 5.1", "luarocks >= 3.11.1, < 4.0.0", - "toml-edit >= 0.3.6", + "toml-edit >= 0.5.0", "fidget.nvim >= 1.1.0", "fzy", "nvim-nio", @@ -19,7 +19,7 @@ dependencies = { test_dependencies = { "lua >= 5.1", "luarocks >= 3.11.1, < 4.0.0", - "toml-edit >= 0.3.6", + "toml-edit >= 0.5.0", "fidget.nvim >= 1.1.0", "fzy", "nvim-nio", diff --git a/spec/config_spec.lua b/spec/config_spec.lua index 7056e952..c24a21fc 100644 --- a/spec/config_spec.lua +++ b/spec/config_spec.lua @@ -45,4 +45,102 @@ dev_servers = [] fh:close() assert.same({}, config.get_dev_servers()) end) + it("get config basic", function() + local config_content = [[ +[rocks] +myrock = "1.0.0" + +[plugins] +myplugin = "1.0.0" + +[plugins."myotherplugin"] +version = "2.0.0" +pin = true + +[luarocks] +servers = [] +]] + local fh = assert(io.open(config.config_path, "w"), "Could not open rocks.toml for writing") + fh:write(config_content) + fh:close() + local rocks_toml = config.get_rocks_toml() + assert.same({ + rocks = { + myrock = { + name = "myrock", + version = "1.0.0", + }, + }, + plugins = { + myplugin = { + name = "myplugin", + version = "1.0.0", + }, + myotherplugin = { + name = "myotherplugin", + version = "2.0.0", + pin = true, + }, + }, + luarocks = { + servers = {}, + }, + }, rocks_toml) + end) + it("get config with imports", function() + local config_content = [[ +import = [ + "local-rocks.toml", +] +[rocks] +myrock = "1.0.0" + +[plugins] +myplugin = "1.0.0" + +[luarocks] +servers = [] +]] + local config_content2 = [[ +import = [ + "rocks.toml", # SHOULD IGNORE CIRCULAR IMPORT +] +[plugins."myotherplugin"] +version = "2.0.0" +pin = true +]] + + local fh = assert(io.open(config.config_path, "w"), "Could not open rocks.toml for writing") + fh:write(config_content) + fh:close() + fh = assert( + io.open(vim.fs.joinpath(tempdir, "local-rocks.toml"), "w"), + "Could not open local rocks.toml for writing" + ) + fh:write(config_content2) + fh:close() + local rocks_toml = config.get_rocks_toml() + assert.same({ + rocks = { + myrock = { + name = "myrock", + version = "1.0.0", + }, + }, + plugins = { + myplugin = { + name = "myplugin", + version = "1.0.0", + }, + myotherplugin = { + name = "myotherplugin", + version = "2.0.0", + pin = true, + }, + }, + luarocks = { + servers = {}, + }, + }, rocks_toml) + end) end) diff --git a/spec/fs_spec.lua b/spec/fs_spec.lua index b2a548c0..e1b79269 100644 --- a/spec/fs_spec.lua +++ b/spec/fs_spec.lua @@ -6,4 +6,14 @@ describe("rocks.fs", function() fs.mkdir_p(dir) assert.is_not_nil(vim.uv.fs_stat(dir)) end) + it("expand_path", function() + vim.env.EXPAND_PATH_ENV = "/home/user/config" + assert.same(fs.expand_path("myfile.txt"), "myfile.txt") + assert.same(fs.expand_path("$EXPAND_PATH_ENV/mydir"), "/home/user/config/mydir") + end) + it("get_absolute_path", function() + vim.env.EXPAND_PATH_ENV = "/home/user" + assert.same(fs.get_absolute_path("$EXPAND_PATH_ENV/mybase", "$EXPAND_PATH_ENV/mydir"), "/home/user/mydir") + assert.same(fs.get_absolute_path("$EXPAND_PATH_ENV/mybase", "myfile.txt"), "/home/user/mybase/myfile.txt") + end) end) diff --git a/spec/operations/helpers_spec.lua b/spec/operations/helpers_spec.lua index 20a5d18a..20c5054c 100644 --- a/spec/operations/helpers_spec.lua +++ b/spec/operations/helpers_spec.lua @@ -1,9 +1,11 @@ local tempdir = vim.fn.tempname() -vim.fn.mkdir(tempdir, "p") +vim.system({ "rm", "-r", tempdir }):wait() +vim.system({ "mkdir", "-p", tempdir }):wait() vim.g.rocks_nvim = { luarocks_binary = "luarocks", rocks_path = tempdir, experimental_features = { "ext_module_dependency_stubs" }, + config_path = vim.fs.joinpath(tempdir, "rocks.toml"), } local nio = require("nio") vim.env.PLENARY_TEST_TIMEOUT = 60000 @@ -75,4 +77,165 @@ describe("operations.helpers", function() assert.is_nil(installed_rocks["stub.nvim"]) assert.is_nil(installed_rocks["pathlib.nvim"]) end) + it("Parse rocks toml", function() + local config_content = [[ +import = [ + "local-rocks.toml", +] +[rocks] +myrock = "1.0.0" + +[plugins] +myplugin = "1.0.0" + +[luarocks] +servers = ["server1", "server2"] +]] + local config_content2 = [[ +import = [ + "rocks.toml", # SHOULD IGNORE CIRCULAR IMPORT +] +[plugins."myplugin"] +version = "2.0.0" +pin = true +]] + + local fh = assert(io.open(config.config_path, "w"), "Could not open rocks.toml for writing") + fh:write(config_content) + fh:close() + fh = assert( + io.open(vim.fs.joinpath(tempdir, "local-rocks.toml"), "w"), + "Could not open local rocks.toml for writing" + ) + fh:write(config_content2) + fh:close() + + local rocks_toml = helpers.parse_rocks_toml() + assert.is_not_nil(rocks_toml.rocks) + assert.same("1.0.0", rocks_toml.rocks.myrock) + assert.is_not_nil(rocks_toml.plugins) + assert.same("2.0.0", rocks_toml.plugins.myplugin.version) -- local overrides base + assert.same(true, rocks_toml.plugins.myplugin.pin) + assert.is_not_nil(rocks_toml.luarocks) + assert.same("server1", rocks_toml.luarocks.servers[1]) + assert.same("server2", rocks_toml.luarocks.servers[2]) + assert.same(nil, rocks_toml.luarocks.servers[3]) + assert.is_not_nil(rocks_toml.import) + end) +end) + +describe("operations.helpers.multi_mut_rocks_toml_wrapper", function() + local multi_mut_rocks_toml_wrapper = require("rocks.operations.helpers.multi_mut_rocks_toml_wrapper") + it("Create new with no config", function() + assert.error(function() + local _ = multi_mut_rocks_toml_wrapper.new({}) + end) + end) + it("Item retrival", function() + local table1 = { + a = "table1_a", + b = "table1_b", + c = { + a = "table1_c_a", + b = "table1_c_b", + c = "table1_c_c", + }, + } + local table2 = { + b = "table2_b", + c = { + d = "table2_c_d", + }, + d = "table2_d", + } + local m = multi_mut_rocks_toml_wrapper.new({ + { + config = table1, + path = "path1", + }, + { + config = table2, + path = "path2", + }, + }) + assert.same("table1_a", m.a) -- Only in table1 + assert.same("table1_b", m.b) -- Prefer table1 since it is first + local c = m.c -- Nested table, prefer table1 values since first + assert.same("table1_c_a", c.a) + assert.same("table1_c_b", c.b) + assert.same("table1_c_c", c.c) + assert.same("table2_c_d", m.c.d) -- Nested table value, only in table2 + assert.same("table2_d", m.d) -- Only in table2 + end) + it("Item modification", function() + local table1 = { + a = "table1_a", + b = "table1_b", + c = { + a = "table1_c_a", + b = "table1_c_b", + c = "table1_c_c", + }, + } + local table2 = { + b = "table2_b", + c = { + d = "table2_c_d", + }, + d = "table2_d", + } + local m = multi_mut_rocks_toml_wrapper.new({ + { + config = table1, + path = "path1", + }, + { + config = table2, + path = "path2", + }, + }) + + -- Table1 modified + m.a = "foo" + assert.same("foo", table1.a) + assert.same(nil, table2.a) + + -- Table1 modified since first + m.b = "foo" + assert.same("foo", table1.b) + assert.same("table2_b", table2.b) + end) + it("Item insertion", function() + local table1 = { + a = "table1_a", + b = "table1_b", + c = { + a = "table1_c_a", + b = "table1_c_b", + c = "table1_c_c", + }, + } + local table2 = { + b = "table2_b", + c = { + d = "table2_c_d", + }, + d = "table2_d", + } + local m = multi_mut_rocks_toml_wrapper.new({ + { + config = table1, + path = "path1", + }, + { + config = table2, + path = "path2", + }, + }) + + -- Table1 modified since first + m.z = "new_z_value" + assert.same("new_z_value", table1.z) + assert.same(nil, table2.z) + end) end)