Skip to content

Commit

Permalink
Adds 42 Header integration (#7)
Browse files Browse the repository at this point in the history
* feat(header): Adds header.insert logic

* feat(header): Adds update logic

* feat(header): Adds update autocmd

* fix(header): Adds creted_by and updated_by fields

* feat(header): Adds FtHeader command

* chore(docs): Adds 42 header documentation
  • Loading branch information
vinicius507 authored Jul 12, 2024
1 parent 055e171 commit 09ad21d
Show file tree
Hide file tree
Showing 6 changed files with 336 additions and 1 deletion.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ You can install the plugin using your favorite package manager:
---@type LazySpec
return {
"vinicius507/ft_nvim",
cmd = { "FtHeader", "Norme" }, -- Lazy load the commands.
ft = { "c", "cpp" }, -- Lazy load for .c and .h files.
---@type ft_nvim.Config
opts = {},
Expand All @@ -35,6 +36,7 @@ return {
```lua
use({
"vinicius507/ft_nvim",
cmd = { "FtHeader", "Norme" }, -- Lazy load the commands.
ft = { "c", "cpp" }, -- Lazy load for .c and .h files.
config = function()
require("ft_nvim").setup()
Expand All @@ -57,6 +59,15 @@ The following options are available:

```lua
require("ft_nvim").setup({
-- Configures the 42 Header integration
header = {
-- Enable the 42 Header integration (default: true).
enable = true,
-- Your Intranet username (default: "marvin").
username = "marvin",
-- Your Intranet email (default: "[email protected]").
email = "[email protected]",
},
-- Configures the norminette integration.
norminette = {
-- Enable the norminette integration (default: true).
Expand Down
10 changes: 10 additions & 0 deletions doc/ft_nvim.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@
README.md
```

## Header

42 Nvim provides a 42 header integration for various filetypes.

### Commands

:FtHeader

: Inserts/updates the 42 header for the current file.

## Norminette

42 Nvim provides integration with [norminette](https://github.com/42School/norminette) as a diagnostics source using [nvim-lint](https://github.com/mfussenegger/nvim-lint).
Expand Down
69 changes: 69 additions & 0 deletions lua/ft_nvim/header/delimeters.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
-- ************************************************************************** --
-- --
-- ::: :::::::: --
-- delimeters.lua :+: :+: :+: --
-- +:+ +:+ +:+ --
-- By: vgoncalv <[email protected]> +#+ +:+ +#+ --
-- +#+#+#+#+#+ +#+ --
-- Created: 2023/01/13 10:12:09 by vgoncalv #+# #+# --
-- Updated: 2023/01/13 10:12:09 by vgoncalv ### ########.fr --
-- --
-- ************************************************************************** --

local delimeters = {
HASHES = { "#", "#" },
SLASHES = { "/*", "*/" },
SEMICOLONS = { ";;", ";;" },
PARENS = { "(*", "*)" },
DASHES = { "--", "--" },
PERCENTS = { "%%", "%%" },
}

---@alias ft_nvim.Delimeters
---|`delimeters.HASHES`
---|`delimeters.SLASHES`
---|`delimeters.SEMICOLONS`
---|`delimeters.PARENS`
---|`delimeters.DASHES`
---|`delimeters.PERCENTS`
---Common header delimeters between filetypes

---Language delimeters
---@type table<string, ft_nvim.Delimeters>
local languages_delimeters = {
default = delimeters.HASHES,
c = delimeters.SLASHES,
cpp = delimeters.SLASHES,
css = delimeters.SLASHES,
dockerfile = delimeters.HASHES,
go = delimeters.SLASHES,
groovy = delimeters.SLASHES,
haskell = delimeters.DASHES,
dosini = delimeters.SEMICOLONS,
java = delimeters.SLASHES,
javascript = delimeters.SLASHES,
javascriptreact = delimeters.SLASHES,
latex = delimeters.PERCENTS,
less = delimeters.SLASHES,
lua = delimeters.DASHES,
make = delimeters.HASHES,
objc = delimeters.SLASHES,
ocaml = delimeters.PARENS,
perl = delimeters.HASHES,
php = delimeters.SLASHES,
text = delimeters.HASHES,
python = delimeters.HASHES,
r = delimeters.HASHES,
ruby = delimeters.HASHES,
rust = delimeters.SLASHES,
scss = delimeters.SLASHES,
sh = delimeters.HASHES,
bash = delimeters.HASHES,
sql = delimeters.HASHES,
swift = delimeters.SLASHES,
typescript = delimeters.SLASHES,
typescriptreact = delimeters.SLASHES,
yaml = delimeters.HASHES,
}

return languages_delimeters
170 changes: 170 additions & 0 deletions lua/ft_nvim/header/header.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---@class ft_nvim.Header
---@field filename string
---@field username string
---@field email string
---@field created_at string
---@field created_by string
---@field updated_at string
---@field updated_by string

local delimeters = require("ft_nvim.header.delimeters")
local TEMPLATE = {
"********************************************************************************",
"* *",
"* ::: :::::::: *",
"* @FILENAME.................................. :+: :+: :+: *",
"* +:+ +:+ +:+ *",
"* By: @AUTHOR................................ +#+ +:+ +#+ *",
"* +#+#+#+#+#+ +#+ *",
"* Created: @CREATED_AT........ by @CREATED_BY #+# #+# *",
"* Updated: @UPDATED_AT........ by @UPDATED_BY ### ########.fr *",
"* *",
"********************************************************************************",
}

---@alias ft_nvim.FieldKey "filename" | "author" | "created_at" | "updated_at" | "created_by" | "updated_by"

---@type table<ft_nvim.FieldKey, fun(header: ft_nvim.Header): string>
local fields = {
filename = function(header)
return header.filename
end,
author = function(header)
return string.format("%s <%s>", header.username, header.email)
end,
created_at = function(header)
return header.created_at
end,
updated_at = function(header)
return header.updated_at
end,
created_by = function(header)
return header.created_by
end,
updated_by = function(header)
return header.updated_by
end,
}

---@type fun(delims: ft_nvim.Delimeters[]): fun(line: string): string
local function set_delimeters(delims)
return function(line)
local start_delim, end_delim = delims[1], delims[2]
line = string.sub(line, string.len(start_delim) + 2, line:len() - string.len(end_delim) - 1)

return string.format("%s %s %s", start_delim, line, end_delim)
end
end

---@type fun(header: ft_nvim.Header): string[]
local function serialize(header)
local lines = TEMPLATE
local annotated_lines = { 4, 6, 8, 9 }
local delims = delimeters[vim.bo.filetype] or delimeters.default

for _, lineno in ipairs(annotated_lines) do
lines[lineno] = string.gsub(lines[lineno], "(@([%w_]*)%.*)", function(match, key)
key = key:lower()
local data = fields[key] and fields[key](header)

if not data then
return
end

return string.format(
"%s%s",
data,
string.rep(" ", match:len() > data:len() and match:len() - data:len() or 0)
)
end)
end
lines = vim.tbl_map(set_delimeters(delims), lines)
table.insert(lines, "")
return lines
end

---@type fun(lines: string[]): boolean
local function lines_are_header(lines)
if #lines < 11 then
return false
end

local annotated_lines = { 4, 6, 8, 9 }
local lines_delimeters = { string.match(lines[1], "^([^%s]+) .* ([^%s]+)$") }

if #lines_delimeters == 0 then
return false
end

for lineno, template_line in ipairs(TEMPLATE) do
local line = lines[lineno]
template_line = set_delimeters(lines_delimeters)(template_line)

if vim.tbl_contains(annotated_lines, lineno) then
template_line = string.gsub(template_line, "@[%w_]+%.*", function(match)
local range = { string.find(template_line, match, 1, true) }
return string.sub(line, unpack(range))
end)
end

template_line = string.gsub(template_line, "[+*]", "%%%1")

if not string.match(line, template_line) then
return false
end
end

return true
end

return {
---@type fun(bufnr: number): boolean
buf_has_header = function(bufnr)
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, #TEMPLATE, false)
return lines_are_header(lines)
end,
---@type fun(bufnr: number, opts: ft_nvim.HeaderConfig)
insert = function(bufnr, opts)
local header = {
filename = vim.fn.expand("%:t"),
username = opts.username,
email = opts.email,
created_at = vim.fn.strftime("%Y/%m/%d %H:%M:%S"),
created_by = opts.username,
updated_at = vim.fn.strftime("%Y/%m/%d %H:%M:%S"),
updated_by = opts.username,
}
local lines = serialize(header)

vim.api.nvim_buf_set_lines(bufnr, 0, 0, false, lines)
end,
---@type fun(bufnr: number, opts: ft_nvim.HeaderConfig)
update = function(bufnr, opts)
local header = {}
local lines = vim.api.nvim_buf_get_lines(bufnr, 0, #TEMPLATE, false)

if not lines_are_header(lines) then
return
end

for lineno, template_line in ipairs(TEMPLATE) do
local line = lines[lineno]

for annotation, key in string.gmatch(template_line, "(@([%w_]+)%.*)") do
key = key:lower()
local range = { string.find(template_line, annotation) }
if key == "author" then
header.username = string.match(line, "(%w+) <.*>")
header.email = string.match(line, "%w+ <(.*)>")
else
---@diagnostic disable-next-line: param-type-mismatch
header[key] = vim.fn.trim(string.sub(line, unpack(range)))
end
end
end
header.updated_at = vim.fn.strftime("%Y/%m/%d %H:%M:%S")
header.updated_by = opts.username
lines = serialize(header)
vim.api.nvim_buf_set_lines(bufnr, 0, 12, false, lines)
end,
}
58 changes: 58 additions & 0 deletions lua/ft_nvim/header/init.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---@class ft_nvim.HeaderConfig
---@field enabled boolean
---@field username string?
---@field email string?
---
---Example:
---@code
---require("ft_nvim.header").setup({
--- enabled = true,
--- username = "marvin",
--- email = "[email protected]",
---})
---@endcode

local header = require("ft_nvim.header.header")

return {
---@type fun(opts: ft_nvim.HeaderConfig)
setup = function(opts)
if not opts.enabled then
return
end

vim.api.nvim_create_user_command("FtHeader", function()
if header.buf_has_header(0) then
header.update(0, opts)
end
header.insert(0, opts)
end, { desc = "Upsert École 42 header" })

vim.api.nvim_create_autocmd("BufWritePre", {
group = vim.api.nvim_create_augroup("FtHeader", { clear = true }),
callback = function(ctx)
header.update(ctx.buf, opts)
end,
})
end,
---@type fun(opts: unknown): boolean
validate = function(opts)
if type(opts) ~= "table" then
return false
end

if opts.enabled ~= nil and type(opts.enabled) ~= "boolean" then
return false
end

if opts.username ~= nil and type(opts.username) ~= "string" then
return false
end

if opts.email ~= nil and type(opts.email) ~= "string" then
return false
end

return true
end,
}
19 changes: 18 additions & 1 deletion lua/ft_nvim/init.lua
Original file line number Diff line number Diff line change
@@ -1,17 +1,30 @@
---@class ft_nvim.Config
---@field norminette? ft_nvim.NorminetteConfig
---@field header? ft_nvim.HeaderConfig

---@type ft_nvim.Config
local default_opts = {
header = {
enabled = true,
username = "marvin",
email = "[email protected]",
},
norminette = {
enabled = true,
cmd = "norminette",
},
}
local header = require("ft_nvim.header")
local norminette = require("ft_nvim.norminette")

---@type fun(opts: ft_nvim.Config)
local function validate(opts)
vim.validate({
header = {
opts.header,
header.validate,
"ft_nvim.HeaderConfig",
},
norminette = {
opts.norminette,
norminette.validate,
Expand All @@ -23,11 +36,15 @@ end
return {
---@param opts ft_nvim.Config
setup = function(opts)
opts = vim.tbl_deep_extend("force", default_opts, opts or {})
validate(opts)
opts = vim.tbl_deep_extend("force", default_opts, opts)

if opts.norminette.enabled then
norminette.setup(opts.norminette)
end

if opts.header.enabled then
header.setup(opts.header)
end
end,
}

0 comments on commit 09ad21d

Please sign in to comment.