From c8dc1635f8a921269f714117f414bbc7ba24f9fd Mon Sep 17 00:00:00 2001 From: moxie Date: Sat, 14 Mar 2026 10:28:08 +0200 Subject: refactor: consolidate modules and improve structure --- lua/muwiki/io.lua | 161 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 lua/muwiki/io.lua (limited to 'lua/muwiki/io.lua') diff --git a/lua/muwiki/io.lua b/lua/muwiki/io.lua new file mode 100644 index 0000000..e86fb2e --- /dev/null +++ b/lua/muwiki/io.lua @@ -0,0 +1,161 @@ +local config = require('muwiki.config') +local paths = require('muwiki.paths') + +local M = {} + +function M.exists(path) + local stat = vim.uv.fs_stat(path) + return stat and stat.type or nil +end + +function M.file_exists(path) + return M.exists(path) == 'file' +end + +function M.dir_exists(path) + return M.exists(path) == 'directory' +end + +function M.create_dir_safely(dirpath, wiki_root) + if not paths.is_within_wiki(dirpath, wiki_root) then + return false, 'Directory is outside wiki root' + end + + if M.dir_exists(dirpath) then + return true, nil + end + + if vim.fn.mkdir(dirpath, 'p') ~= 1 then + return false, 'Failed to create directory' + end + + if not paths.is_within_wiki(dirpath, wiki_root) then + vim.fn.delete(dirpath, 'd') + return false, 'Symlink attack detected' + end + + return true, nil +end + +function M.ensure_parent_dirs(filepath, wiki_root, mode, callback) + local dirpath = vim.fs.dirname(filepath) + + if M.dir_exists(dirpath) then + if callback then + callback(true) + end + return true + end + + local function do_create() + local success, err = M.create_dir_safely(dirpath, wiki_root) + if not success then + vim.notify(string.format('Cannot create directory: %s', err), vim.log.levels.ERROR) + if callback then + callback(false) + end + return false + end + + if mode == 'notify' then + vim.notify(string.format('Created directory: %s', dirpath), vim.log.levels.INFO) + end + + if callback then + callback(true) + end + return true + end + + if mode == 'prompt' then + vim.ui.select({ 'Yes', 'No' }, { + prompt = string.format('Directory does not exist. Create %s?', dirpath), + }, function(choice) + if choice == 'Yes' then + do_create() + else + vim.notify('Directory creation cancelled', vim.log.levels.INFO) + if callback then + callback(false) + end + end + end) + return nil + end + + return do_create() +end + +function M.open_in_buffer(filepath) + local bufnr = vim.fn.bufnr(filepath, true) + vim.api.nvim_win_set_buf(0, bufnr) +end + +function M.open_index(name) + local wiki_path = config.wiki_path(name) + if not wiki_path then + return + end + + local index_path = vim.fs.joinpath(wiki_path, config.options.index_file) + M.open_in_buffer(index_path) +end + +function M.resolve(filepath, wiki_root) + if not wiki_root then + error('wiki_root parameter is required for secure path resolution') + end + + local path = paths.strip_file_protocol(filepath) + local resolved = paths.resolve(path, wiki_root) + return paths.validate_within_wiki(resolved, wiki_root, filepath) +end + +function M.is_text_file(ext) + local ext_lower = ext:lower() + for _, text_ext in ipairs(config.options.text_extensions) do + if ext_lower == text_ext then + return true + end + end + return false +end + +function M.normalize_filename(text) + local normalized = text:lower() + normalized = normalized:gsub('%s+', '_') + normalized = normalized:gsub('[^%w_%-%.]', '') + return normalized +end + +function M.open_wiki_file(filepath) + local template = require('muwiki.template') + local exists = M.file_exists(filepath) + + if not exists and config.options.create_missing_dirs then + local wiki_root = config.wiki_root(0) + if wiki_root then + local mode = config.options.create_missing_dirs + + if mode == 'prompt' then + M.ensure_parent_dirs(filepath, wiki_root, mode, function(success) + if success then + M.open_in_buffer(filepath) + template.init_file(vim.api.nvim_get_current_buf(), filepath) + end + end) + return + else + local success = M.ensure_parent_dirs(filepath, wiki_root, mode) + if not success then + return + end + end + end + end + + M.open_in_buffer(filepath) + template.init_file(vim.api.nvim_get_current_buf(), filepath) +end + +return M -- cgit v1.2.3