Skip to content

Instantly share code, notes, and snippets.

@WarrenBuffering
Last active April 16, 2025 18:18
Show Gist options
  • Save WarrenBuffering/fba65ab9e79d639b121f772bc441a19a to your computer and use it in GitHub Desktop.
Save WarrenBuffering/fba65ab9e79d639b121f772bc441a19a to your computer and use it in GitHub Desktop.
neovim template thing
import { useFetchStatus } from "hooks";
import { ${SERVICE_NAME} } from "services";
import type {
${PASCAL_SERVICE_NAME}Params,
${PASCAL_SERVICE_NAME}Data,
${PASCAL_SERVICE_NAME}Response,
} from "services";
import type { StoreAction } from "types";
/* ============================================================================
= Helpers
============================================================================ */
/* ============================================================================
= Types
============================================================================ */
type Params = ${PASCAL_SERVICE_NAME}Params;
type Response = ${PASCAL_SERVICE_NAME}Response;
/* ============================================================================
= Action Hook
============================================================================ */
export function use${NAME}(): StoreAction<Response, Params> {
const status = useFetchStatus();
async function request(args: Params): Response {
status.start();
const response = await ${SERVICE_NAME}(args);
if (response.data) {
status.succeed();
// TODO: do stuff
} else {
status.fail(response.error);
}
return response;
}
return {
isFetching: status.isFetching,
isError: status.isError,
isSuccess: status.isSuccess,
isUnfetched: status.isUnfetched,
errorMessage: status.errorMessage,
errorCode: status.errorCode,
request,
reset: status.reset,
};
}
local M = {}
function M.setup()
local function read_template_file(filepath)
local file = io.open(filepath, "r")
if not file then
error("Could not open template: " .. filepath)
end
local content = file:read("*all")
file:close()
return content
end
local function to_kebab_case(str)
if not str then
return ""
end
return str:gsub("(%u)", "-%1"):gsub("_", "-"):gsub("%s+", "-"):lower():gsub("^%-", "")
end
local function to_pascal_case(str)
if not str then
return ""
end
local has_mixed_case = str:match("[a-z][A-Z]")
if has_mixed_case then
return str:sub(1, 1):upper() .. str:sub(2)
else
str = str:gsub("[-_]", " ")
local result = string.gsub(str, "(%a)([%w_']*)", function(first, rest)
return first:upper() .. rest:lower()
end)
result = result:gsub("%s+", "")
return result
end
end
local function to_camel_case(str)
if not str then
return ""
end
local has_mixed_case = str:match("[a-z][A-Z]")
if has_mixed_case then
return str:sub(1, 1):lower() .. str:sub(2)
else
local pascal = to_pascal_case(str)
return pascal:sub(1, 1):lower() .. pascal:sub(2)
end
end
local template_dir = vim.fn.stdpath("config") .. "/lua/templates"
local template_definitions = {
["action-hook"] = {
pattern = function(name, _args)
return "use" .. name .. ".tsx"
end,
is_directory = false,
arguments = {
{ name = "s", description = "Service name (without 'Service' suffix)", default = "" },
{ name = "service", description = "Service name (without 'Service' suffix)", default = "" },
},
files = {
{
name = function(name, _args)
return "use" .. name .. ".tsx"
end,
template = template_dir .. "/action-hook/index.txt",
},
},
transformations = {
["SERVICE_NAME"] = function(args)
local service = args["service"] or args["s"] or ""
if service:sub(-7) == "Service" then
return service
else
return service .. "Service"
end
end,
["PASCAL_SERVICE_NAME"] = function(args)
local service = args["service"] or args["s"] or ""
if service:sub(-7) == "Service" then
service = service:sub(1, -8)
end
local has_mixed_case = service:match("[a-z][A-Z]")
if has_mixed_case then
return service:sub(1, 1):upper() .. service:sub(2)
else
return to_pascal_case(service)
end
end,
},
},
["component"] = {
pattern = "index.tsx",
is_directory = true,
arguments = {
{ name = "with-test", description = "Include test file", default = false },
{ name = "with-storybook", description = "Include storybook file", default = false },
},
files = {
{
name = "index.tsx",
template = template_dir .. "/react-component/index.txt",
},
{
name = "styles.css",
template = template_dir .. "/react-component/styles.txt",
},
{
name = function(name, args)
if args["with-test"] then
return name .. ".test.tsx"
end
return nil
end,
template = template_dir .. "/react-component/test.txt",
},
{
name = function(name, args)
if args["with-storybook"] then
return name .. ".stories.tsx"
end
return nil
end,
template = template_dir .. "/react-component/stories.txt",
},
},
},
["native-component"] = {
pattern = "index.tsx",
is_directory = true,
arguments = {},
files = {
{
name = "index.tsx",
template = template_dir .. "/react-native-component/index.txt",
},
},
},
["screen"] = {
pattern = "index.tsx",
is_directory = true,
arguments = {
{ name = "with-test", description = "Include test file", default = false },
},
files = {
{
name = "index.tsx",
template = template_dir .. "/react-screen/index.txt",
},
{
name = "styles.css",
template = template_dir .. "/react-screen/styles.txt",
},
{
name = function(name, args)
if args["with-test"] then
return name .. ".test.tsx"
end
return nil
end,
template = template_dir .. "/react-screen/test.txt",
},
},
},
["native-screen"] = {
pattern = "index.tsx",
is_directory = true,
arguments = {},
files = {
{
name = "index.tsx",
template = template_dir .. "/react-native-screen/index.txt",
},
},
},
["store"] = {
pattern = function(name, _args)
return name .. "Store.ts"
end,
is_directory = false,
arguments = {
{ name = "with-actions", description = "Include actions file", default = false },
{ name = "with-types", description = "Include types file", default = false },
},
files = {
{
name = function(name, _args)
return name .. "Store.ts"
end,
template = template_dir .. "/store/index.txt",
},
{
name = function(name, args)
if args["with-actions"] then
return name .. "Actions.ts"
end
return nil
end,
template = template_dir .. "/store/actions.txt",
},
{
name = function(name, args)
if args["with-types"] then
return name .. "Types.ts"
end
return nil
end,
template = template_dir .. "/store/types.txt",
},
},
},
["persisted-store"] = {
pattern = function(name, _args)
return name .. "Store.ts"
end,
is_directory = false,
arguments = {},
files = {
{
name = function(name, _args)
return name .. "Store.ts"
end,
template = template_dir .. "/persisted-store/index.txt",
},
},
},
["persisted-store-rn"] = {
pattern = function(name, _args)
return name .. "Store.ts"
end,
is_directory = false,
arguments = {},
files = {
{
name = function(name, _args)
return name .. "Store.ts"
end,
template = template_dir .. "/persisted-store-rn/index.txt",
},
},
},
}
local command_configurations = {
["CreateComponent"] = {
flags = {
["-r"] = "component",
["-rn"] = "native-component",
},
default_flag = "-r",
},
["CreateScreen"] = {
flags = {
["-r"] = "screen",
["-rn"] = "native-screen",
},
default_flag = "-r",
},
["CreateStore"] = {
flags = {
[""] = "store",
},
default_flag = "",
},
["CreatePersistedStore"] = {
flags = {
["-r"] = "persisted-store",
["-rn"] = "persisted-store-rn",
},
default_flag = "-r",
},
["CreateActionHook"] = {
flags = {
[""] = "action-hook",
},
default_flag = "",
},
}
local function evaluate_pattern(pattern, name, args)
if type(pattern) == "function" then
return pattern(name, args)
end
return pattern
end
local function apply_transformations(content, name, args, transformations)
local class_name = to_kebab_case(name)
content = content:gsub("${NAME}", name)
content = content:gsub("${CLASSNAME}", class_name)
if transformations then
for placeholder, transform_fn in pairs(transformations) do
local value = transform_fn(args)
content = content:gsub("${" .. placeholder .. "}", value)
end
end
for arg_name, arg_value in pairs(args) do
content = content:gsub("${" .. string.upper(arg_name) .. "}", tostring(arg_value))
end
return content
end
local function normalize_arg_name(name)
if name == "service" then
return { "service", "s" }
elseif name == "s" then
return { "s", "service" }
end
return { name }
end
local function parse_custom_args(args_str, template_config)
local result = {}
for _, arg_def in ipairs(template_config.arguments or {}) do
result[arg_def.name] = arg_def.default
end
if args_str and args_str ~= "" then
local words = vim.split(args_str, " ", { trimempty = true })
local i = 1
while i <= #words do
local current = words[i]
local prefix = current:match("^%-+")
if prefix then
local name = current:sub(#prefix + 1)
local value
-- Check for name=value format
local name_value = vim.split(name, "=", { trimempty = true })
if #name_value > 1 then
name = name_value[1]
value = name_value[2]
i = i + 1
-- Check for -s value or --service value format
elseif i < #words and not words[i + 1]:match("^%-") then
value = words[i + 1]
i = i + 2
-- Flag with no value
else
value = true
i = i + 1
end
if value == "true" then
value = true
elseif value == "false" then
value = false
end
-- Special handling for -s and --service
if name == "s" or name == "service" then
result["s"] = value
result["service"] = value
else
-- Handle other arguments normally
for _, arg_def in ipairs(template_config.arguments or {}) do
if arg_def.name == name then
result[name] = value
break
end
end
end
else
i = i + 1
end
end
end
return result
end
local function create_template(name, template_key, custom_args)
local config = template_definitions[template_key]
if not config then
vim.notify("Unknown template type: " .. template_key, vim.log.levels.ERROR)
return
end
local args = parse_custom_args(custom_args, config)
local cwd = vim.fn.expand("%:p:h")
local output_dir = config.is_directory and (cwd .. "/" .. name) or cwd
if config.is_directory then
vim.fn.mkdir(output_dir, "p")
end
local main_file_path = nil
local main_pattern = evaluate_pattern(config.pattern, name, args)
for _, file_config in ipairs(config.files) do
local filename = evaluate_pattern(file_config.name, name, args)
if filename then
local content = read_template_file(file_config.template)
content = apply_transformations(content, name, args, config.transformations)
local out_path = output_dir .. "/" .. filename
local out_file = io.open(out_path, "w")
if out_file then
out_file:write(content)
out_file:close()
if filename == main_pattern then
main_file_path = out_path
end
else
vim.notify("Failed to write file: " .. out_path, vim.log.levels.ERROR)
end
end
end
if main_file_path then
vim.cmd("edit " .. main_file_path)
end
vim.notify(template_key .. " '" .. name .. "' created!", vim.log.levels.INFO)
end
local function parse_args(args)
if not args or args == "" then
return nil, nil, nil
end
local parts = {}
local in_quotes = false
local current_part = ""
for i = 1, #args do
local char = args:sub(i, i)
if char == '"' then
in_quotes = not in_quotes
elseif char == " " and not in_quotes then
if current_part ~= "" then
table.insert(parts, current_part)
current_part = ""
end
else
current_part = current_part .. char
end
end
if current_part ~= "" then
table.insert(parts, current_part)
end
local name = parts[1]
local flag = nil
local custom_args_str = nil
local flag_index = nil
for i = 2, #parts do
if parts[i]:match("^%-r") or parts[i]:match("^%-rn") then
flag = parts[i]
flag_index = i
break
end
end
if not flag_index then
if #parts > 1 then
custom_args_str = table.concat(parts, " ", 2)
end
else
if flag_index > 2 then
local before_flag = {}
for i = 2, flag_index - 1 do
table.insert(before_flag, parts[i])
end
custom_args_str = table.concat(before_flag, " ")
end
if flag_index < #parts then
local after_flag = {}
for i = flag_index + 1, #parts do
table.insert(after_flag, parts[i])
end
if custom_args_str and custom_args_str ~= "" then
custom_args_str = custom_args_str .. " " .. table.concat(after_flag, " ")
else
custom_args_str = table.concat(after_flag, " ")
end
end
end
return name, flag, custom_args_str
end
for command, config in pairs(command_configurations) do
pcall(vim.api.nvim_del_user_command, command)
vim.api.nvim_create_user_command(command, function(opts)
local raw_args = opts.args or ""
local name, flag, custom_args = parse_args(raw_args)
if not name or name == "" then
vim.notify("Missing name for :" .. command, vim.log.levels.ERROR)
return
end
flag = flag or config.default_flag
local template_key = config.flags[flag]
if not template_key then
local available_flags = ""
for f, _ in pairs(config.flags) do
available_flags = available_flags .. " " .. f
end
vim.notify(
"Unknown flag '" .. flag .. "' for :" .. command .. ". Available flags:" .. available_flags,
vim.log.levels.ERROR
)
return
end
create_template(name, template_key, custom_args)
end, { nargs = "*" })
end
pcall(vim.api.nvim_del_user_command, "Create")
vim.api.nvim_create_user_command("Create", function(opts)
local raw_args = opts.args or ""
local parts = vim.split(raw_args, " ", { trimempty = true })
if #parts < 2 then
vim.notify("Usage: Create <template_type> <n> [args...]", vim.log.levels.ERROR)
return
end
local template_key = parts[1]
local name = parts[2]
local custom_args_str = nil
if #parts > 2 then
custom_args_str = table.concat(parts, " ", 3)
end
if not template_definitions[template_key] then
local available_templates = ""
for t, _ in pairs(template_definitions) do
available_templates = available_templates .. " " .. t
end
vim.notify(
"Unknown template type: " .. template_key .. ". Available templates:" .. available_templates,
vim.log.levels.ERROR
)
return
end
create_template(name, template_key, custom_args_str)
end, {
nargs = "*",
complete = function(_, line, _)
local words = vim.split(line, " ", { trimempty = true })
if #words == 1 then
local templates = {}
for t, _ in pairs(template_definitions) do
table.insert(templates, t)
end
return templates
end
return {}
end,
})
pcall(vim.api.nvim_del_user_command, "TemplateHelp")
vim.api.nvim_create_user_command("TemplateHelp", function(opts)
local template_key = opts.args
if not template_key or template_key == "" then
local help_text = "Available templates:\n"
for t, _ in pairs(template_definitions) do
help_text = help_text .. " " .. t .. "\n"
end
vim.notify(help_text, vim.log.levels.INFO)
return
end
local config = template_definitions[template_key]
if not config then
vim.notify("Unknown template type: " .. template_key, vim.log.levels.ERROR)
return
end
if #config.arguments == 0 then
vim.notify("Template '" .. template_key .. "' has no custom arguments.", vim.log.levels.INFO)
return
end
local help_text = "Custom arguments for '" .. template_key .. "':\n"
for _, arg in ipairs(config.arguments) do
help_text = help_text
.. " "
.. arg.name
.. ": "
.. arg.description
.. " (default: "
.. tostring(arg.default)
.. ")\n"
end
vim.notify(help_text, vim.log.levels.INFO)
end, {
nargs = "?",
complete = function(_, _, _)
local templates = {}
for t, _ in pairs(template_definitions) do
table.insert(templates, t)
end
return templates
end,
})
end
return M
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment