diff --git a/.github/workflows/documentation.yml b/.github/workflows/documentation.yml index 71b6d26..9df74dc 100644 --- a/.github/workflows/documentation.yml +++ b/.github/workflows/documentation.yml @@ -1,11 +1,12 @@ +name: documentation + on: push: branches: - main -name: documentation jobs: - api_documentation: + documentation: runs-on: ubuntu-latest steps: - name: Checkout repository @@ -20,10 +21,7 @@ jobs: run: | nvim --version make api_documentation - user_documentation: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 + - name: Create User Documentation uses: kdheepak/panvimdoc@main with: @@ -31,29 +29,11 @@ jobs: version: "Neovim >= 0.8.0" demojify: true treesitter: true - tags: - runs-on: ubuntu-latest - needs: [api_documentation, user_documentation] - steps: - - name: Setup Neovim - uses: rhysd/action-setup-vim@v1 - with: - neovim: true - version: stable - - - name: Checkout plugin-template - uses: actions/checkout@v4 - with: - repository: ColinKennedy/nvim-best-practices-plugin-template - path: neovim_plugin - name: Generate Tags run: | - # nvim -c 'helptags doc' -c 'quit' - # push: - runs-on: ubuntu-latest - needs: [tags] - steps: + nvim -c 'helptags doc' -c 'quit' + - name: Push Changes uses: stefanzweifel/git-auto-commit-action@v4 with: diff --git a/.github/workflows/luacheck.yml b/.github/workflows/luacheck.yml index 97d19fb..7f7c52b 100644 --- a/.github/workflows/luacheck.yml +++ b/.github/workflows/luacheck.yml @@ -1,7 +1,7 @@ ---- -on: [pull_request] name: luacheck +on: [pull_request] + jobs: luacheck: name: Luacheck diff --git a/.github/workflows/news.yml b/.github/workflows/news.yml index 1803852..9ce9e0b 100644 --- a/.github/workflows/news.yml +++ b/.github/workflows/news.yml @@ -1,10 +1,12 @@ # Reference: https://github.com/neovim/neovim/blob/9762c5e3406cab8152d8dd161c0178965d841676/.github/workflows/news.yml name: "news.txt" + on: pull_request: types: [opened, synchronize, reopened, ready_for_review, labeled, unlabeled] branches: - main + jobs: check: runs-on: ubuntu-latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5acb227..661073f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,9 +1,11 @@ # Reference: https://github.com/ellisonleao/nvim-plugin-template/blob/922c0d5249076416c5d84e7c0504f1154225a7ab/.github/workflows/release.yml name: release + on: push: tags: - 'v*' + jobs: luarocks-upload: runs-on: ubuntu-22.04 diff --git a/.github/workflows/stylua.yml b/.github/workflows/stylua.yml index 1d80d78..8fdba55 100644 --- a/.github/workflows/stylua.yml +++ b/.github/workflows/stylua.yml @@ -1,7 +1,10 @@ ---- -on: [pull_request] name: stylua +on: + pull_request: + branches: + - main + jobs: stylua: name: stylua diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..71a2b27 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,45 @@ +name: test + +on: + pull_request: + types: [opened, synchronize, reopened, ready_for_review] + branches: + - main + +jobs: + test: + strategy: + matrix: + os: [ubuntu-latest, macos-latest] + neovim: [v0.10.0, stable, nightly] + + runs-on: ${{ matrix.os }} + name: "OS: ${{ matrix.os }} - Neovim: ${{ matrix.neovim }}" + + steps: + - uses: actions/checkout@master + + - uses: leafo/gh-actions-lua@v10 + with: + # Neovim is compiled with LuaJIT so we might as well match. But it + # doesn't look like we can match it exactly. + # + # Reference: + # https://github.com/leafo/gh-actions-lua/issues/49#issuecomment-2295071198 + # + luaVersion: "luajit-openresty" + + - uses: leafo/gh-actions-luarocks@v4 + + - uses: rhysd/action-setup-vim@v1 + with: + neovim: true + version: ${{ matrix.neovim }} + + - name: build + run: | + luarocks test plugin-template-scm-1.rockspec --prepare + + - name: test + run: | + luarocks test --test-type busted diff --git a/README.md b/README.md index 7d269ac..39ade90 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,16 @@ -# 🚧 Under Construction 🚧 - -This repository doesn't have all GitHub CI actions working yet but is available -as an early preview. We will update docs/news.txt once it's ready. - -Add https://github.com/ColinKennedy/nvim-best-practices-plugin-template/commits/main/doc/news.txt.atom -to your RSS feed so you don't miss it! - # A Neovim Plugin Template -![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/test.yml?branch=main&style=for-the-badge) -![Lua](https://img.shields.io/badge/Made%20with%20Lua-blueviolet.svg?style=for-the-badge&logo=lua) +| | | +|--------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Build Status | [![Unittests](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/test.yml?branch=fix_todo_notes_005&style=for-the-badge&label=Unittests)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/test.yml) [![Documentation](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/documentation.yml?branch=fix_todo_notes_005&style=for-the-badge&label=Documentation)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/documentation.yml) [![Luacheck](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/luacheck.yml?branch=fix_todo_notes_005&style=for-the-badge&label=Luacheck)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/luacheck.yml) [![Stylua](https://img.shields.io/github/actions/workflow/status/ColinKennedy/nvim-best-practices-plugin-template/stylua.yml?branch=fix_todo_notes_005&style=for-the-badge&label=Stylua)](https://github.com/ColinKennedy/nvim-best-practices-plugin-template/actions/workflows/stylua.yml) \| | +| License | ![License-MIT](https://img.shields.io/badge/License-MIT-blue?style=for-the-badge) | -A template repository for Neovim plugins. +A template repository used to create Neovim plugins. # Features - Follows [nvim-best-practices](https://github.com/nvim-neorocks/nvim-best-practices) -- Fast start-up (the plugin is super defer-loaded. < 1 ms guarantee) +- Fast start-up (~1 ms) - Auto-release to [luarocks](https://luarocks.org) - Automated user documentation (using [panvimdoc](https://github.com/kdheepak/panvimdoc)) - Automated API documentation (using [mini.doc](https://github.com/echasnovski/mini.doc)) @@ -29,6 +23,9 @@ A template repository for Neovim plugins. - [RSS feed support](tracking-updates) - Built-in logging to stdout / files - Unittests use the full power of native [busted](https://olivinelabs.com/busted) +- Automated testing matrix supports 6 Neovim/OS combinations + - neovim: `[v0.10.0, stable, nightly]` + - os: `[ubuntu-latest, macos-latest]` - 100% Lua - Uses [Semantic Versioning](https://semver.org) - Integrations diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 02ed077..0000000 --- a/TODO.md +++ /dev/null @@ -1,32 +0,0 @@ -## Round 2 -- Document the old manual parser way -- Fix the main commit history (force push master) - - -## Round 3 -- Make sure the CI works - - llscheck - -- Add badges to the README.md - - RSS - - Stylua - - etc - - Passing workflow badges - - Static badges - -Consider more badges -https://github.com/azratul/live-share.nvim?tab=readme-ov-file - - -## Round 4 -- Do a round of finishing TODO notes - - -## Extra Features -- Check files for syntax errors using tree-sitter parsers (via Neovim)? - - -## Last Round -- Make sure PR linters work (release (post-merge) should also work) - -- Make sure to increment the version to v1.0.0 diff --git a/doc/news.txt b/doc/news.txt index 90f6cd8..9c3d8a5 100644 --- a/doc/news.txt +++ b/doc/news.txt @@ -1,59 +1,18 @@ *plugin-template-news.txt* -Notable changes since PluginTemplate 0.1 +Notable changes since PluginTemplate 1.0 =============================================================================== NEW FEATURES *plugin-template-new-features* -GENERAL - -- 100% Lua -- LuaCATS annotations and type-hints, everywhere -- No external dependencies - -CI - -- Github actions for: - - PR reviews - Reminds users to update `doc/news.txt` - - `StyLua` - - `llscheck` - - `luacheck` - - `luarocks` - - `panvimdoc` -- RSS feed support - -CLI - -- Added nargs support to `cmdparse` and other features - -START UP - -- Fast start-up (the plugin is defer-loaded) -- Follows nvim-best-practices - -COMMANDS - -- Built-in Vim commands with auto-completion - -TESTS - -- Unittests use the full power of native busted - -TOOLS - -Integrates with - -- lualine.nvim -- telescope.nvim -- `:checkhealth` +We're live! =============================================================================== BREAKING CHANGES *plugin-template-new-breaking* -CLI +n/a -- Replaced the old raw-lua-table-based API with a class-based API. vim:tw=78:ts=8:noet:ft=help:norl: diff --git a/doc/plugin-template.txt b/doc/plugin-template.txt index a8cc91b..aa402a3 100644 --- a/doc/plugin-template.txt +++ b/doc/plugin-template.txt @@ -45,7 +45,7 @@ A template repository for Neovim plugins. - Follows nvim-best-practices - Fast start-up (the plugin is super defer-loaded. < 1 ms guarantee) - Auto-release to luarocks -- Automated User documentation (using panvimdoc ) +- Automated user documentation (using panvimdoc ) - Automated API documentation (using mini.doc ) - Vimtags generation - Built-in Vim commands @@ -53,7 +53,7 @@ A template repository for Neovim plugins. - Auto-completes your commands at any cursor position - No external dependencies - LuaCATS annotations and type-hints, everywhere -- RSS feed support +- RSS feed support - Built-in logging to stdout / files - Unittests use the full power of native busted - 100% Lua diff --git a/lua/plugin_template/_cli/cli_subcommand.lua b/lua/plugin_template/_cli/cli_subcommand.lua index c484c6a..9038cab 100644 --- a/lua/plugin_template/_cli/cli_subcommand.lua +++ b/lua/plugin_template/_cli/cli_subcommand.lua @@ -3,8 +3,6 @@ ---@module 'plugin_template._cli.cli_subcommand' --- -local help_message = require("plugin_template._cli.cmdparse.help_message") - local M = {} ---@class argparse.SubcommandRunnerOptions @@ -261,17 +259,15 @@ end --- function M.make_parser_completer(parser_creator) local function runner(_, all_text, _) + local configuration = require("plugin_template._core.configuration") + configuration.initialize_data_if_needed() + local parser = parser_creator() local column = vim.fn.getcmdpos() return parser:get_completion(all_text, column) end - -- NOTE: Initialize only once - local configuration = require("plugin_template._core.configuration") - - configuration.initialize_data_if_needed() - return runner end @@ -285,6 +281,7 @@ function M.make_subcommand_completer(prefix, subcommands) local function runner(latest_text, all_text, _) local configuration = require("plugin_template._core.configuration") configuration.initialize_data_if_needed() + local completion = _get_subcommand_completion(all_text, prefix, subcommands) if completion then @@ -381,6 +378,11 @@ function M.make_subcommand_triager(subcommands) ---@param opts argparse.SubcommandRunnerOptions The parsed user options. --- local function runner(opts) + local configuration = require("plugin_template._core.configuration") + configuration.initialize_data_if_needed() + + local help_message = require("plugin_template._cli.cmdparse.help_message") + local success, result = pcall(function() _runner(opts) end) @@ -398,11 +400,6 @@ function M.make_subcommand_triager(subcommands) end end - -- NOTE: Initialize only once - local configuration = require("plugin_template._core.configuration") - - configuration.initialize_data_if_needed() - return runner end diff --git a/lua/plugin_template/_core/tabler.lua b/lua/plugin_template/_core/tabler.lua index ecba861..50a52cb 100644 --- a/lua/plugin_template/_core/tabler.lua +++ b/lua/plugin_template/_core/tabler.lua @@ -38,13 +38,24 @@ end --- function M.get_value(data, items) local current = data + local found = {} + local count = #items - for _, item in ipairs(items) do + for index = 1, count do + local item = items[index] current = current[item] if current == nil then return nil end + + table.insert(found, item) + + local type_ = type(current) + + if index < count and type_ ~= "table" then + error(string.format("%s: expected table, got %s", vim.fn.join(found, "."), type_), 0) + end end return current diff --git a/lua/plugin_template/_vendors/vlog.lua b/lua/plugin_template/_vendors/vlog.lua index b5533bb..1ce8794 100644 --- a/lua/plugin_template/_vendors/vlog.lua +++ b/lua/plugin_template/_vendors/vlog.lua @@ -33,6 +33,9 @@ local default_config = { { name = "fatal", hl = "ErrorMsg" }, }, + -- Define this path to redirect the log file to wherever you need it to go + outfile = nil, + -- Can limit the number of decimals displayed for floats float_precision = 0.01, } @@ -64,8 +67,8 @@ log.new = function(config, standalone) obj._is_logging_to_file_enabled = config.use_file - obj._outfile = - vim.fs.joinpath(vim.api.nvim_call_function("stdpath", { "data" }), string.format("%s.log", config.plugin)) + obj._outfile = config.outfile + or vim.fs.joinpath(vim.api.nvim_call_function("stdpath", { "data" }), string.format("%s.log", config.plugin)) local levels = {} for i, v in ipairs(config.modes) do diff --git a/lua/plugin_template/health.lua b/lua/plugin_template/health.lua index 666266e..3e88454 100644 --- a/lua/plugin_template/health.lua +++ b/lua/plugin_template/health.lua @@ -55,6 +55,45 @@ local function _is_hex_color(text) return text:match("^#%x%x%x%x%x%x$") ~= nil end +--- Add issues to `array` if there are errors. +--- +--- Todo: +--- Once Neovim 0.10 is dropped, use the new function signature +--- for vim.validate to make this function cleaner. +--- +---@param array string[] +--- All of the cumulated errors, if any. +---@param name string +--- The key to check for. +---@param value_creator fun(): any +--- A function that generates the value. +---@param expected string | fun(value: any): boolean +--- If `value_creator()` does not match `expected`, this error message is +--- shown to the user. +---@param message (string | boolean)? +--- If it's a string, it's the error message when +--- `value_creator()` does not match `expected`. When it's +--- `true`, it means it's okay for `value_creator()` not to match `expected`. +--- +local function _append_validated(array, name, value_creator, expected, message) + local success, value = pcall(value_creator) + + if not success then + table.insert(array, value) + + return + end + + local validated + success, validated = pcall(vim.validate, { + [name] = { value, expected, message }, + }) + + if not success then + table.insert(array, validated) + end +end + --- Check if `data` is a boolean under `key`. --- ---@param key string The configuration value that we are checking. @@ -84,54 +123,46 @@ local function _get_boolean_issue(key, data) return message end ---- Check all "commands" values for issues. +--- Check all "cmdparse" values for issues. --- ---@param data plugin_template.Configuration All of the user's fallback settings. ---@return string[] # All found issues, if any. --- -local function _get_command_issues(data) +local function _get_cmdparse_issues(data) local output = {} - local success, message = pcall( - vim.validate, - "commands.goodnight_moon.read.phrase", - tabler.get_value(data, { "commands", "goodnight_moon", "read", "phrase" }), - "string" - ) + _append_validated(output, "cmdparse.auto_complete.display.help_flag", function() + return tabler.get_value(data, { "cmdparse", "auto_complete", "display", "help_flag" }) + end, "boolean", true) - if not success then - table.insert(output, message) - end + return output +end - success, message = pcall(vim.validate, { - ["commands.hello_world.say.repeat"] = { - tabler.get_value(data, { "commands", "hello_world", "say", "repeat" }), - function(value) - return type(value) == "number" and value > 0 - end, - "a number (value must be 1-or-more)", - }, - }) +--- Check all "commands" values for issues. +--- +---@param data plugin_template.Configuration All of the user's fallback settings. +---@return string[] # All found issues, if any. +--- +local function _get_command_issues(data) + local output = {} - if not success then - table.insert(output, message) - end + _append_validated(output, "commands.goodnight_moon.read.phrase", function() + return tabler.get_value(data, { "commands", "goodnight_moon", "read", "phrase" }) + end, "string") - success, message = pcall(vim.validate, { - ["commands.hello_world.say.style"] = { - tabler.get_value(data, { "commands", "hello_world", "say", "style" }), - function(value) - local choices = vim.tbl_keys(say_constant.Keyword.style) + _append_validated(output, "commands.hello_world.say.repeat", function() + return tabler.get_value(data, { "commands", "hello_world", "say", "repeat" }) + end, function(value) + return type(value) == "number" and value > 0 + end, "a number (value must be 1-or-more)") - return vim.tbl_contains(choices, value) - end, - '"lowercase" or "uppercase"', - }, - }) + _append_validated(output, "commands.hello_world.say.style", function() + return tabler.get_value(data, { "commands", "hello_world", "say", "style" }) + end, function(value) + local choices = vim.tbl_keys(say_constant.Keyword.style) - if not success then - table.insert(output, message) - end + return vim.tbl_contains(choices, value) + end, '"lowercase" or "uppercase"') return output end @@ -147,93 +178,73 @@ end ---@return string[] # All found issues, if any. --- local function _get_lualine_command_issues(command, data) - local success, message = pcall(vim.validate, { - [string.format("tools.lualine.%s", command)] = { - data, - function(value) - if type(value) ~= "table" then - return false - end - - return true - end, - 'a table. e.g. { text="some text here" }', - }, - }) - - if not success then - return { message } - end - local output = {} - success, message = pcall(vim.validate, { - [string.format("tools.lualine.%s.text", command)] = { - tabler.get_value(data, { "text" }), - function(value) - if type(value) ~= "string" then - return false - end + _append_validated(output, string.format("tools.lualine.%s", command), function() + return data + end, function(value) + if type(value) ~= "table" then + return false + end - return true - end, - 'a string. e.g. "some text here"', - }, - }) + return true + end, 'a table. e.g. { text="some text here" }') - if not success then - table.insert(output, message) + if not vim.tbl_isempty(output) then + return output end - success, message = pcall(vim.validate, { - [string.format("tools.lualine.%s.color", command)] = { - tabler.get_value(data, { "color" }), - function(value) - if value == nil then - -- NOTE: It's okay for this value to be undefined because - -- we define a fallback for the user. - -- - return true - end + _append_validated(output, string.format("tools.lualine.%s.text", command), function() + return tabler.get_value(data, { "text" }) + end, function(value) + if type(value) ~= "string" then + return false + end - local type_ = type(value) + return true + end, 'a string. e.g. "some text here"') - if type_ == "string" then - -- NOTE: We assume that there is a linkable highlight group - -- with the name of `value` already or one that will exist. - -- - return true - end + _append_validated(output, string.format("tools.lualine.%s.color", command), function() + return tabler.get_value(data, { "color" }) + end, function(value) + if value == nil then + -- NOTE: It's okay for this value to be undefined because + -- we define a fallback for the user. + -- + return true + end - if type_ == "table" then - if value.bg ~= nil and not _is_hex_color(value.bg) then - return false - end + local type_ = type(value) - if value.fg ~= nil and not _is_hex_color(value.fg) then - return false - end + if type_ == "string" then + -- NOTE: We assume that there is a linkable highlight group + -- with the name of `value` already or one that will exist. + -- + return true + end - if value.gui ~= nil and type(value.gui) ~= "string" then - return false - end + if type_ == "table" then + if value.bg ~= nil and not _is_hex_color(value.bg) then + return false + end - if _has_extra_color_keys(value) then - return false - end + if value.fg ~= nil and not _is_hex_color(value.fg) then + return false + end - return true - end + if value.gui ~= nil and type(value.gui) ~= "string" then + return false + end + if _has_extra_color_keys(value) then return false - end, - 'a table. e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}', - }, - }) + end - if not success then - table.insert(output, message) - end + return true + end + + return false + end, 'a table. e.g. {fg="#000000", bg="#FFFFFF", gui="effect"}') return output end @@ -248,27 +259,17 @@ local function _get_lualine_issues(data) local lualine = tabler.get_value(data, { "tools", "lualine" }) - local success, message = pcall(vim.validate, { - ["tools.lualine"] = { - lualine, - function(value) - if type(value) ~= "table" then - return false - end - - return true - end, - "a table. e.g. { goodnight_moon = {...}, hello_world = {...} }", - }, - }) + _append_validated(output, "tools.lualine", function() + return lualine + end, function(value) + if type(value) ~= "table" then + return false + end - if not success then - table.insert(output, message) + return true + end, "a table. e.g. { goodnight_moon = {...}, hello_world = {...} }") - return output - end - - if not lualine or vim.tbl_isempty(lualine) then + if not vim.tbl_isempty(output) then return output end @@ -292,49 +293,37 @@ end ---@return string[] # All of the found issues, if any. --- local function _get_logging_issues(data) - local success, message = pcall(vim.validate, { - ["logging"] = { - data, - function(value) - if type(value) ~= "table" then - return false - end - - return true - end, - 'a table. e.g. { level = "info", ... }', - }, - }) + local output = {} - if not success then - return { message } - end + _append_validated(output, "logging", function() + return data + end, function(value) + if type(value) ~= "table" then + return false + end - local output = {} + return true + end, 'a table. e.g. { level = "info", ... }') - success, message = pcall(vim.validate, { - ["logging.level"] = { - data.level, - function(value) - if type(value) ~= "string" then - return false - end + if not vim.tbl_isempty(output) then + return output + end - if not vim.tbl_contains({ "trace", "debug", "info", "warn", "error", "fatal" }, value) then - return false - end + _append_validated(output, "logging.level", function() + return data.level + end, function(value) + if type(value) ~= "string" then + return false + end - return true - end, - 'an enum. e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal"', - }, - }) + if not vim.tbl_contains({ "trace", "debug", "info", "warn", "error", "fatal" }, value) then + return false + end - if not success then - table.insert(output, message) - end + return true + end, 'an enum. e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal"') - message = _get_boolean_issue("logging.use_console", data.use_console) + local message = _get_boolean_issue("logging.use_console", data.use_console) if message ~= nil then table.insert(output, message) @@ -359,87 +348,57 @@ local function _get_telescope_issues(data) local telescope = tabler.get_value(data, { "tools", "telescope" }) - if not telescope then - return {} - end - - local success, message = pcall(vim.validate, { - ["tools.telescope"] = { - telescope, - function(value) - if type(value) ~= "table" then - return false - end - - return true - end, - "a table. e.g. { goodnight_moon = {...}, hello_world = {...}}", - }, - }) + _append_validated(output, "tools.telescope", function() + return telescope + end, function(value) + if type(value) ~= "table" then + return false + end - if not success then - table.insert(output, message) + return true + end, "a table. e.g. { goodnight_moon = {...}, hello_world = {...}}") + if not vim.tbl_isempty(output) then return output end - success, message = pcall(vim.validate, { - ["tools.telescope.goodnight_moon"] = { - telescope.goodnight_moon, - function(value) - if value == nil then - return true - end - - if type(value) ~= "table" then - return false - end - - for _, item in ipairs(value) do - if not texter.is_string_list(item) then - return false - end - - if #item ~= 2 then - return false - end - end - - return true - end, - 'a table. e.g. { {"Book", "Author"} }', - }, - }) + _append_validated(output, "tools.telescope.goodnight_moon", function() + return telescope.goodnight_moon + end, function(value) + if value == nil then + return true + end - if not success then - table.insert(output, message) + if type(value) ~= "table" then + return false + end - return output - end + for _, item in ipairs(value) do + if not texter.is_string_list(item) then + return false + end - success, message = pcall(vim.validate, { - ["tools.telescope.hello_world"] = { - telescope.hello_world, - function(value) - if value == nil then - return true - end + if #item ~= 2 then + return false + end + end - if type(value) ~= "table" then - return false - end + return true + end, 'a table. e.g. { {"Book", "Author"} }') - return texter.is_string_list(value) - end, - 'a table. e.g. { "Hello", "Hi", ...} }', - }, - }) + _append_validated(output, "tools.telescope.hello_world", function() + return telescope.hello_world + end, function(value) + if value == nil then + return true + end - if not success then - table.insert(output, message) + if type(value) ~= "table" then + return false + end - return output - end + return texter.is_string_list(value) + end, 'a table. e.g. { "Hello", "Hi", ...} }') return output end @@ -455,6 +414,7 @@ function M.get_issues(data) end local output = {} + vim.list_extend(output, _get_cmdparse_issues(data)) vim.list_extend(output, _get_command_issues(data)) local logging = data.logging diff --git a/spec/plugin_template/configuration_spec.lua b/spec/plugin_template/configuration_spec.lua index e200f5c..f326ca5 100644 --- a/spec/plugin_template/configuration_spec.lua +++ b/spec/plugin_template/configuration_spec.lua @@ -86,11 +86,41 @@ end) ---@diagnostic disable: assign-type-mismatch ---@diagnostic disable: missing-fields +describe("bad configuration - cmdparse", function() + it("happens with a bad type for #cmdparse.auto_complete.display.help_flag", function() + _assert_bad({ + cmdparse = { auto_complete = { display = { help_flag = "aaa" } } }, + }, { "cmdparse.auto_complete.display.help_flag: expected boolean, got string" }) + + _assert_bad({ + cmdparse = { auto_complete = { display = 123 } }, + }, { "cmdparse.auto_complete.display: expected table, got number" }) + + _assert_bad({ cmdparse = { auto_complete = "bnb" } }, { "cmdparse.auto_complete: expected table, got string" }) + + _assert_bad({ cmdparse = 123 }, { "cmdparse: expected table, got number" }) + end) + + it("works with an #empty configuration", function() + _assert_good({ + cmdparse = { auto_complete = { display = { help_flag = true } } }, + }) + + _assert_good({ + cmdparse = { auto_complete = { display = { help_flag = false } } }, + }) + + _assert_good({ + cmdparse = { auto_complete = { display = { help_flag = nil } } }, + }) + end) +end) + describe("bad configuration - commands", function() it("happens with a bad type for #commands.goodnight_moon.phrase", function() _assert_bad( { commands = { goodnight_moon = { read = { phrase = 10 } } } }, - { "commands.goodnight_moon.read.phrase: expected string, got number (10)" } + { "commands.goodnight_moon.read.phrase: expected string, got number" } ) end) @@ -144,11 +174,11 @@ describe("bad configuration - logging", function() end) it("happens with a bad value for #logging.use_console", function() - _assert_bad({ logging = { use_console = "asdf" } }, { "logging.use_console: expected a boolean, got asdf" }) + _assert_bad({ logging = { use_console = "aaa" } }, { "logging.use_console: expected a boolean, got aaa" }) end) it("happens with a bad value for #logging.use_file", function() - _assert_bad({ logging = { use_file = "asdf" } }, { "logging.use_file: expected a boolean, got asdf" }) + _assert_bad({ logging = { use_file = "aaa" } }, { "logging.use_file: expected a boolean, got aaa" }) end) end) ---@diagnostic enable: assign-type-mismatch @@ -171,11 +201,11 @@ describe("health.check", function() health.check({ commands = { goodnight_moon = { read = { phrase = 123 } }, - hello_world = { say = { ["repeat"] = "asdf", style = 789 } }, + hello_world = { say = { ["repeat"] = "aaa", style = 789 } }, }, logging = { level = false, - use_console = "asdf", + use_console = "aaa", use_file = "fdas", }, tools = { @@ -190,11 +220,11 @@ describe("health.check", function() local issues = tabler.get_slice(found, 1, #found - 1) assert.same({ - "commands.goodnight_moon.read.phrase: expected string, got number (123)", - "commands.hello_world.say.repeat: expected a number (value must be 1-or-more), got asdf", + "commands.goodnight_moon.read.phrase: expected string, got number", + "commands.hello_world.say.repeat: expected a number (value must be 1-or-more), got aaa", 'commands.hello_world.say.style: expected "lowercase" or "uppercase", got 789', 'logging.level: expected an enum. e.g. "trace" | "debug" | "info" | "warn" | "error" | "fatal", got false', - "logging.use_console: expected a boolean, got asdf", + "logging.use_console: expected a boolean, got aaa", "logging.use_file: expected a boolean, got fdas", 'tools.lualine.goodnight_moon: expected a table. e.g. { text="some text here" }, got false', }, issues) diff --git a/spec/plugin_template/plugin_template_spec.lua b/spec/plugin_template/plugin_template_spec.lua index 8305e28..598d3a8 100644 --- a/spec/plugin_template/plugin_template_spec.lua +++ b/spec/plugin_template/plugin_template_spec.lua @@ -7,8 +7,13 @@ ---@module 'plugin_template.plugin_template_spec' --- +local configuration = require("plugin_template._core.configuration") local copy_logs_runner = require("plugin_template._commands.copy_logs.runner") local plugin_template = require("plugin_template") +local vlog = require("plugin_template._vendors.vlog") + +---@class plugin_template.Configuration +local _CONFIGURATION_DATA ---@type string[] local _DATA = {} @@ -35,6 +40,7 @@ local function _initialize_copy_log() _DATA = { path } end + _CONFIGURATION_DATA = vim.deepcopy(configuration.DATA) copy_logs_runner._read_file = _save_path end @@ -54,6 +60,7 @@ end local function _reset_copy_log() copy_logs_runner._read_file = _ORIGINAL_COPY_LOGS_READ_FILE + configuration.DATA = _CONFIGURATION_DATA _DATA = {} end @@ -117,7 +124,7 @@ describe("copy logs API", function() after_each(_reset_copy_log) it("runs with an explicit file path", function() - local path = vim.fn.tempname() .. "copy_logs_test.txt" + local path = vim.fn.tempname() .. "copy_logs_test.log" _make_fake_log(path) plugin_template.run_copy_logs(path) @@ -127,11 +134,14 @@ describe("copy logs API", function() end) it("runs with default arguments", function() + local expected = vim.fn.tempname() .. "copy_logs_default_test.log" + configuration.DATA.logging.outfile = expected + vlog.new(configuration.DATA.logging or {}, true) + _make_fake_log(expected) + plugin_template.run_copy_logs() _wait_for_result() - local expected = vim.fs.joinpath(vim.fn.expand("~"), ".local", "share", "nvim", "plugin_template.log") - assert.same({ expected }, _DATA) end) end) @@ -141,7 +151,7 @@ describe("copy logs command", function() after_each(_reset_copy_log) it("runs with an explicit file path", function() - local path = vim.fn.tempname() .. "copy_logs_test.txt" + local path = vim.fn.tempname() .. "copy_logs_test.log" _make_fake_log(path) vim.cmd(string.format('PluginTemplate copy-logs "%s"', path)) @@ -151,10 +161,14 @@ describe("copy logs command", function() end) it("runs with default arguments", function() + local expected = vim.fn.tempname() .. "copy_logs_default_test.log" + configuration.DATA.logging.outfile = expected + vlog.new(configuration.DATA.logging or {}, true) + _make_fake_log(expected) + vim.cmd([[PluginTemplate copy-logs]]) - _wait_for_result() - local expected = vim.fs.joinpath(vim.fn.expand("~"), ".local", "share", "nvim", "plugin_template.log") + _wait_for_result() assert.same({ expected }, _DATA) end)