Skip to content

Commit c966351

Browse files
committed
refactor(core): split header.lua and header_spec.lua into modular files
- split core logic from `header.lua` into smaller modules under `lua/header/` - separated `header_spec.lua` into `setup_spec.lua`, `add_headers_spec.lua`, `update_date_spec.lua`, and `license_spec.lua` - improves readability, isolation, and test granularity - no functional behavior changes
1 parent ff2c12e commit c966351

28 files changed

+705
-789
lines changed

lua/header.lua

Lines changed: 0 additions & 572 deletions
This file was deleted.

lua/header/commands.lua

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
local M = {}
2+
3+
function M.create_user_commands(header)
4+
vim.api.nvim_create_user_command("AddHeader", function()
5+
header.add_headers()
6+
end, {})
7+
8+
local licenses = {
9+
"agpl3",
10+
"apache",
11+
"bsd2",
12+
"bsd3",
13+
"cc0",
14+
"gpl3",
15+
"isc",
16+
"mit",
17+
"mpl",
18+
"unlicense",
19+
"wtfpl",
20+
"x11",
21+
"zlib",
22+
}
23+
24+
for _, name in ipairs(licenses) do
25+
vim.api.nvim_create_user_command("AddLicense" .. name:upper(), function()
26+
header.add_license_header(name)
27+
end, {})
28+
end
29+
30+
vim.api.nvim_create_user_command("UpdateDateModified", function()
31+
header.update_date_modified()
32+
end, {})
33+
end
34+
35+
return M

lua/header/comments.lua

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
local util = require("header.util")
2+
local M = {}
3+
4+
function M.comment_headers(headers, comments, use_block_header)
5+
local style
6+
if use_block_header and comments.block and comments.block.start then
7+
style = comments.block
8+
elseif comments.line and comments.line.line then
9+
style = comments.line
10+
else
11+
style = comments.block or comments.line
12+
end
13+
14+
local result = {}
15+
if style.start then
16+
table.insert(result, style.start)
17+
end
18+
for _, h in ipairs(headers) do
19+
if style.line then
20+
table.insert(result, style.line .. " " .. h)
21+
else
22+
table.insert(result, h)
23+
end
24+
end
25+
if style["end"] then
26+
table.insert(result, style["end"])
27+
end
28+
table.insert(result, "")
29+
return result
30+
end
31+
32+
function M.is_comment_line(line, comments)
33+
if not comments then
34+
return false
35+
end
36+
local patterns = {}
37+
38+
local function add(p)
39+
if p then
40+
table.insert(patterns, util.escape_special_characters(p))
41+
end
42+
end
43+
44+
if comments.block then
45+
add(comments.block.start)
46+
add(comments.block.line)
47+
add(comments.block["end"])
48+
end
49+
if comments.line and comments.line.line then
50+
add(comments.line.line)
51+
end
52+
53+
for _, pat in ipairs(patterns) do
54+
if pat ~= "" and line:match("^%s*" .. pat) then
55+
return true
56+
end
57+
end
58+
return false
59+
end
60+
61+
function M.find_header_end(lines, comments)
62+
local last = 0
63+
for i, line in ipairs(lines) do
64+
if M.is_comment_line(line, comments) then
65+
last = i
66+
elseif line:match("^%s*$") then
67+
last = i
68+
break
69+
else
70+
break
71+
end
72+
end
73+
return last
74+
end
75+
76+
return M

lua/header/config.lua

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
local M = {}
2+
3+
M.defaults = {
4+
allow_autocmds = true,
5+
file_name = true,
6+
author = nil,
7+
project = nil,
8+
date_created = true,
9+
date_created_fmt = "%Y-%m-%d %H:%M:%S",
10+
date_modified = true,
11+
date_modified_fmt = "%Y-%m-%d %H:%M:%S",
12+
line_separator = "------",
13+
use_block_header = true,
14+
copyright_text = nil,
15+
license_from_file = false,
16+
author_from_git = false,
17+
}
18+
19+
M.constants = {
20+
file_name = "File name:",
21+
date_created = "Date created:",
22+
author = "Author:",
23+
project = "Project:",
24+
date_modified = "Date modified:",
25+
}
26+
27+
function M.read_config_file()
28+
local f = io.open(".header.nvim", "r")
29+
if not f then
30+
return nil
31+
end
32+
local content = f:read("*a")
33+
f:close()
34+
if not content or content == "" then
35+
return nil
36+
end
37+
return vim.fn.json_decode(content)
38+
end
39+
40+
return M

lua/header/core.lua

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
local comments = require("header.comments")
2+
local util = require("header.util")
3+
local license = require("header.license")
4+
local filetypes = require("header.filetypes")
5+
6+
local M = {}
7+
8+
local function get_comment_style()
9+
local ext = vim.fn.expand("%:e")
10+
return filetypes[ext]
11+
end
12+
13+
local function remove_old_headers(comments_table)
14+
local buf = vim.api.nvim_get_current_buf()
15+
local total = vim.api.nvim_buf_line_count(buf)
16+
if total == 0 then
17+
return
18+
end
19+
20+
local limit = math.min(300, total)
21+
local lines = vim.api.nvim_buf_get_lines(buf, 0, limit, false)
22+
local header_end = comments.find_header_end(lines, comments_table)
23+
if header_end == 0 then
24+
return
25+
end
26+
27+
local next_line = vim.api.nvim_buf_get_lines(buf, header_end, header_end + 1, false)[1]
28+
if next_line and next_line:match("^%s*$") then
29+
header_end = header_end + 1
30+
end
31+
32+
vim.api.nvim_buf_set_lines(buf, 0, header_end, false, {})
33+
end
34+
35+
function M.add_headers(header)
36+
local comments_fn = get_comment_style()
37+
if not comments_fn then
38+
vim.notify_once("unsupported file type for adding header", vim.log.levels.ERROR)
39+
return
40+
end
41+
42+
local comments_table = comments_fn()
43+
44+
-- prepare headers (inline version)
45+
local function prepare(cb)
46+
if header.config.license_from_file then
47+
local files = license.scan_license_files()
48+
if #files == 0 then
49+
cb(nil)
50+
return
51+
end
52+
if #files == 1 then
53+
cb(license.read_license_file(files[1]))
54+
return
55+
end
56+
if header.selected_license_file then
57+
cb(license.read_license_file(header.selected_license_file))
58+
return
59+
end
60+
license.select_license_file(files, function(f)
61+
cb(f and license.read_license_file(f) or nil)
62+
end, header)
63+
return
64+
end
65+
66+
local file = vim.fn.expand("%:t")
67+
local created = os.date(header.config.date_created_fmt, vim.fn.getftime(vim.fn.expand("%")))
68+
if header.config.author_from_git then
69+
local gitname = vim.fn.systemlist("git config user.name")
70+
if vim.v.shell_error == 0 and #gitname > 0 then
71+
header.config.author = gitname[1]
72+
end
73+
end
74+
75+
local hdrs = {}
76+
if header.config.file_name then
77+
table.insert(hdrs, header.constants.file_name .. " " .. file)
78+
end
79+
if header.config.project then
80+
table.insert(hdrs, header.constants.project .. " " .. header.config.project)
81+
end
82+
if header.config.author then
83+
table.insert(hdrs, header.constants.author .. " " .. header.config.author)
84+
end
85+
if header.config.date_created then
86+
table.insert(hdrs, header.constants.date_created .. " " .. created)
87+
end
88+
if header.config.date_modified then
89+
table.insert(hdrs, header.constants.date_modified .. " " .. os.date(header.config.date_modified_fmt))
90+
end
91+
if header.config.line_separator then
92+
table.insert(hdrs, header.config.line_separator)
93+
end
94+
if header.config.copyright_text then
95+
if type(header.config.copyright_text) == "string" then
96+
vim.list_extend(hdrs, util.string_to_table(header.config.copyright_text))
97+
else
98+
vim.list_extend(hdrs, header.config.copyright_text)
99+
end
100+
end
101+
cb(hdrs)
102+
end
103+
104+
prepare(function(hdrs)
105+
if not hdrs then
106+
return
107+
end
108+
remove_old_headers(comments_table)
109+
local commented = comments.comment_headers(hdrs, comments_table, header.config.use_block_header)
110+
vim.api.nvim_buf_set_lines(0, 0, 0, false, commented)
111+
end)
112+
end
113+
114+
function M.add_license_header(header, opts)
115+
local comments_fn = get_comment_style()
116+
if not comments_fn then
117+
vim.notify_once("unsupported file type for adding header", vim.log.levels.ERROR)
118+
return
119+
end
120+
121+
local license_text = require("header.licenses." .. string.lower(opts))
122+
license_text = util.replace_token(license_text, "project", header.config.project)
123+
license_text = util.replace_token(license_text, "organization", header.config.author)
124+
license_text = util.replace_token(license_text, "year", os.date("%Y"))
125+
126+
local license_table = util.string_to_table(license_text)
127+
local comments_table = comments_fn()
128+
remove_old_headers(comments_table)
129+
local commented = comments.comment_headers(license_table, comments_table, header.config.use_block_header)
130+
vim.api.nvim_buf_set_lines(0, 0, 0, false, commented)
131+
end
132+
133+
function M.update_date_modified(header)
134+
local comments_fn = get_comment_style()
135+
if not comments_fn then
136+
vim.notify("File type not supported for updating header", vim.log.levels.WARN)
137+
return
138+
end
139+
140+
local buf = vim.api.nvim_get_current_buf()
141+
local comments_table = comments_fn()
142+
local lines = vim.api.nvim_buf_get_lines(buf, 0, 30, false)
143+
local header_end = comments.find_header_end(lines, comments_table)
144+
if not header_end then
145+
return
146+
end
147+
148+
local modified = os.date(header.config.date_modified_fmt)
149+
local updated = false
150+
151+
for i = 1, header_end do
152+
if lines[i]:find(header.constants.date_modified) then
153+
local prefix = (comments_table.line and comments_table.line.line)
154+
or (comments_table.block and comments_table.block.line)
155+
or ""
156+
lines[i] = prefix .. " " .. header.constants.date_modified .. " " .. modified
157+
updated = true
158+
break
159+
end
160+
end
161+
162+
if updated then
163+
vim.api.nvim_buf_set_lines(buf, 0, header_end, false, vim.list_slice(lines, 1, header_end))
164+
end
165+
end
166+
167+
return M
Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
1-
local languages = require("languages")
1+
local languages = require("header.languages")
22

3-
local filetype_table = {}
4-
5-
filetype_table = {
3+
local filetype_table = {
64
["c"] = languages.cpp,
75
["cc"] = languages.cpp,
86
["cpp"] = languages.cpp,

lua/header/init.lua

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
local config = require("header.config")
2+
local commands = require("header.commands")
3+
local core = require("header.core")
4+
5+
local header = {
6+
config = config.defaults,
7+
constants = config.constants,
8+
selected_license_file = nil,
9+
}
10+
11+
local function check_vim_version()
12+
if vim.version().minor < 8 then
13+
vim.notify_once("header.nvim: requires Neovim ≥0.8", vim.log.levels.ERROR)
14+
return false
15+
end
16+
return true
17+
end
18+
19+
header.setup = function(params)
20+
if not check_vim_version() then
21+
return
22+
end
23+
header.config = vim.tbl_extend("force", header.config, params or {})
24+
25+
local file_cfg = config.read_config_file()
26+
if file_cfg then
27+
header.config = vim.tbl_extend("force", header.config, file_cfg)
28+
end
29+
30+
commands.create_user_commands(header)
31+
end
32+
33+
header.reset = function()
34+
header.config = vim.deepcopy(config.defaults)
35+
end
36+
37+
header.add_headers = function()
38+
if check_vim_version() then
39+
core.add_headers(header)
40+
end
41+
end
42+
43+
header.add_license_header = function(opts)
44+
if check_vim_version() then
45+
core.add_license_header(header, opts)
46+
end
47+
end
48+
49+
header.update_date_modified = function()
50+
if check_vim_version() then
51+
core.update_date_modified(header)
52+
end
53+
end
54+
55+
return header

0 commit comments

Comments
 (0)