Skip to content

Commit 490a021

Browse files
committed
Merge branch 'test-add-unit-testing'
2 parents 0f79cca + 1cdaec9 commit 490a021

File tree

20 files changed

+901
-6
lines changed

20 files changed

+901
-6
lines changed

.github/workflows/ci.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Run tests
2+
3+
on: [push, pull_request]
4+
5+
jobs:
6+
unit_tests:
7+
name: unit tests
8+
runs-on: ${{ matrix.os }}
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
os: [ubuntu-22.04]
13+
rev: [nightly]
14+
15+
steps:
16+
- uses: actions/checkout@v4
17+
18+
- uses: rhysd/action-setup-vim@v1
19+
with:
20+
neovim: true
21+
version: ${{ matrix.rev }}
22+
23+
- name: Download requirements
24+
run: |
25+
git clone --depth 1 https://github.com/nvim-lua/plenary.nvim ../plenary.nvim
26+
27+
- name: Run tests
28+
run: |
29+
nvim --version
30+
make test

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
.DS_Store
2+
.tests

.luarc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"diagnostics.globals": [
3+
"it",
4+
"describe"
5+
]
6+
}

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
.PHONY: test
2+
3+
test:
4+
nvim --headless --noplugin -u scripts/minimal_init.vim -c "PlenaryBustedDirectory tests/ { minimal_init = './scripts/minimal_init.vim' }"
5+
6+
test_local:
7+
nvim --headless -u scripts/minimal_init.lua -c "PlenaryBustedDirectory tests/ { minimal_init = './scripts/minimal_init.lua' }"

lua/esqueleto/autocmd.lua

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
local utils = require("esqueleto.utils")
1+
local utils = require("esqueleto.core")
22

33
local M = {}
44

@@ -20,6 +20,18 @@ M.createautocmd = function(opts)
2020
end
2121
end
2222

23+
-- Skip if (i) no patterns where found or (ii) trying to run always.
24+
-- NOTE: This patterns are incompatible with the plugin in it's current state, since it
25+
-- doesn't have a way to merge templates from different patterns.
26+
if
27+
type(opts.patterns) == "table" and next(opts.patterns --[[@as table]]) == nil
28+
then
29+
error("Empty pattern (`pattern={}`) is incompatible with esqueleto.nvim")
30+
end
31+
if opts.patterns == "*" then
32+
error("Global pattern (`pattern=\"*\"`) is incompatible with esqueleto.nvim")
33+
end
34+
2335
vim.api.nvim_create_autocmd({ "BufNewFile", "BufReadPost", "FileType" }, {
2436
group = group,
2537
desc = "esqueleto.nvim :: Insert template",

lua/esqueleto/config.lua

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
---@author Carlos Vigil-Vásquez
33
---@license MIT
44

5-
local utils = require("esqueleto.utils")
5+
local utils = require("esqueleto.core")
66

77
---@class Esqueleto.Config
88
---@field autouse boolean Automatically use templates if its the only one available

lua/esqueleto/core.lua

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
local wildcards = require("esqueleto.helpers.wildcards")
2+
3+
local M = {}
4+
5+
--- Capture output of command
6+
---@param cmd string Command to run
7+
---@param raw boolean Whether the function returns the raw string
8+
---@return string output Command standard output
9+
M.capture = function(cmd, raw)
10+
local f = assert(io.popen(cmd, "r"))
11+
local s = assert(f:read("*a"))
12+
f:close()
13+
if raw then return s end
14+
s = string.gsub(s, "^%s+", "")
15+
s = string.gsub(s, "%s+$", "")
16+
s = string.gsub(s, "[\n\r]+", "")
17+
return s
18+
end
19+
20+
--- Map function over each entry in table
21+
---@param tbl table Table to map
22+
---@param f function Function to map
23+
---@return table mapped_tbl Function-mapped table
24+
M.map = function(tbl, f)
25+
local t = {}
26+
for k, v in pairs(tbl) do
27+
t[k] = f(v)
28+
end
29+
return t
30+
end
31+
32+
--- Write template contents to current buffer
33+
---@param file string Template file path
34+
---@param opts Esqueleto.Config Plugin configuration table
35+
M.writetemplate = function(file, opts)
36+
if file == nil then
37+
-- Do an early return if no files are specified
38+
return
39+
end
40+
41+
local uv = vim.uv or vim.loop
42+
---@diagnostic disable-next-line: undefined-field
43+
local handler, message = io.open(uv.fs_realpath(file), "r")
44+
if handler == nil then
45+
-- Print error message and abort if no file handlers are created
46+
vim.notify(message --[[@as string]], vim.log.levels.ERROR)
47+
return
48+
end
49+
50+
-- Read the file, convert EOL to LF and remove the new line at EOF
51+
local content = handler:read("*a"):gsub("\r\n?", "\n"):gsub("\n$", "")
52+
53+
local lines, cursor_pos
54+
if opts.wildcards.expand then
55+
-- Place the contents of the file with the wildcards expanded
56+
lines, cursor_pos = wildcards.parse(content, opts.wildcards.lookup)
57+
else
58+
-- ... or place them directly
59+
lines = vim.split(content, "\n", { plain = true })
60+
end
61+
62+
-- Replace the buffer with the given lines
63+
vim.api.nvim_buf_set_lines(0, 0, -1, true, lines)
64+
65+
if cursor_pos ~= nil then
66+
-- If a cursor wildcard was found, place the cursor there
67+
vim.api.nvim_win_set_cursor(0, cursor_pos)
68+
else
69+
-- If not, move the cursor to the last line
70+
vim.cmd("norm! G")
71+
end
72+
end
73+
74+
-- List ignored files under a directory, given a list of glob patterns
75+
local listignored = function(dir, ignored_patterns)
76+
return vim
77+
.iter(ignored_patterns)
78+
:map(function(patterns) return vim.fn.globpath(dir, patterns, true, true, true) end)
79+
end
80+
81+
-- Returns a ignore checker
82+
local getignorechecker = function(opts)
83+
local os_ignore_pats = opts.advanced.ignore_os_files
84+
and require("esqueleto.helpers.constants").ignored_os_patterns
85+
or {}
86+
local extra = opts.advanced.ignored
87+
local extra_ignore_pats, extra_ignore_func = (function()
88+
if type(extra) == "function" then
89+
return {}, extra
90+
else
91+
assert(type(extra) == "table")
92+
return extra, function(_) return false end
93+
end
94+
end)()
95+
96+
return function(filepath)
97+
local dir = vim.fn.fnamemodify(filepath, ":p:h")
98+
return extra_ignore_func(dir)
99+
or vim.tbl_contains(listignored(dir, os_ignore_pats), filepath)
100+
or vim.tbl_contains(listignored(dir, extra_ignore_pats), filepath)
101+
end
102+
end
103+
104+
--- Get available templates for current buffer
105+
---@param pattern string Pattern to use to find templates
106+
---@param opts Esqueleto.Config Plugin configuration table
107+
---@return table templates Available templates for current buffer
108+
M.gettemplates = function(pattern, opts)
109+
local templates = {}
110+
local isignored = getignorechecker(opts)
111+
112+
local alldirectories = vim.tbl_map(
113+
function(f) return vim.fn.fnamemodify(f, ":p") end,
114+
opts.directories --[[@as table<string>]]
115+
)
116+
117+
-- Count directories that contain templates for pattern
118+
local ndirs = 0
119+
for _, directory in pairs(alldirectories) do
120+
ndirs = ndirs + vim.fn.isdirectory(directory .. pattern .. "/")
121+
end
122+
123+
-- Get templates for pattern
124+
for _, directory in ipairs(alldirectories) do
125+
local pattern_dir = directory .. pattern .. "/"
126+
local exists_dir = vim.fn.isdirectory(pattern_dir) == 1
127+
if exists_dir then
128+
for basename in vim.fs.dir(pattern_dir) do
129+
local filepath = vim.fs.normalize(pattern_dir .. basename)
130+
-- Check if pattern is ignored
131+
if not isignored(filepath) then
132+
local name = vim.fs.basename(filepath)
133+
if ndirs > 1 then name = vim.fn.simplify(directory) .. " :: " .. name end
134+
templates[name] = filepath
135+
end
136+
end
137+
end
138+
end
139+
140+
return templates
141+
end
142+
143+
--- Select template to insert on current buffer
144+
---@param templates table Available template table
145+
---@param opts Esqueleto.Config Plugin configuration table
146+
M.selecttemplate = function(templates, opts)
147+
-- Check if templates exist
148+
if vim.tbl_isempty(templates) then
149+
vim.notify(
150+
"[WARNING] No templates found for this file! Pattern is known by `esqueleto` but could not find any template file",
151+
vim.log.levels.WARN
152+
)
153+
return nil
154+
end
155+
156+
-- Alphabetically sort template names for a more pleasing experience
157+
local templatenames = vim.tbl_keys(templates)
158+
table.sort(templatenames, function(a, b) return a:lower() < b:lower() end)
159+
160+
-- If only one template, write and return early
161+
if #templatenames == 1 and opts.autouse then
162+
M.writetemplate(templates[templatenames[1]], opts)
163+
return nil
164+
end
165+
166+
-- Select template
167+
vim.ui.select(templatenames, { prompt = "Select skeleton to use:" }, function(choice)
168+
if templates[choice] then
169+
---@diagnostic disable-next-line: undefined-field
170+
M.writetemplate(vim.loop.fs_realpath(templates[choice]), opts)
171+
else
172+
vim.notify("[esqueleto] No template selected, leaving buffer empty", vim.log.levels.INFO)
173+
end
174+
end)
175+
end
176+
177+
--- Insert template on current buffer
178+
---@param opts Esqueleto.Config Plugin configuration table
179+
M.inserttemplate = function(opts)
180+
-- Get pattern alternatives for current file
181+
local filepath = vim.fn.expand("%:p")
182+
local filename = vim.fn.expand("%:t")
183+
local filetype = vim.bo.filetype
184+
185+
-- Identify if pattern matches user configuration
186+
local pattern
187+
if not _G.esqueleto_inserted[filepath] then
188+
-- match either filename or extension. Filename has priority
189+
if
190+
vim.tbl_contains(opts.patterns --[[@as table]], filename)
191+
then
192+
pattern = filename
193+
elseif
194+
vim.tbl_contains(opts.patterns --[[@as table]], filetype)
195+
then
196+
pattern = filetype
197+
end
198+
199+
-- Get templates for selected pattern
200+
local templates = M.gettemplates(pattern, opts)
201+
202+
-- Pop-up selection UI
203+
M.selecttemplate(templates, opts)
204+
_G.esqueleto_inserted[filepath] = true
205+
end
206+
end
207+
208+
return M

lua/esqueleto/excmd.lua

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
local M = {}
22

3-
local utils = require("esqueleto.utils")
3+
local utils = require("esqueleto.core")
44

55
---Creates a new template based on user input and current buffer state.
66
---@param opts Esqueleto.Config Configuration options for template creation
77
---@return nil
8-
M.create_template = function(opts)
8+
local function create_template(opts)
99
vim.notify("\nesqueleto :: Entering template creation!", vim.log.levels.WARN)
1010
local state = {}
1111

@@ -100,7 +100,7 @@ M.createexcmd = function(opts)
100100
utils.inserttemplate(opts)
101101
end, {})
102102

103-
vim.api.nvim_create_user_command("EsqueletoNew", function() M.create_template(opts) end, {})
103+
vim.api.nvim_create_user_command("EsqueletoNew", function() create_template(opts) end, {})
104104
end
105105

106106
return M

0 commit comments

Comments
 (0)