aboutsummaryrefslogtreecommitdiff
path: root/lua/muwiki/io.lua
diff options
context:
space:
mode:
authormoxie <moxie@3kgcat.fi>2026-03-14 10:28:08 +0200
committermoxie <moxie@3kgcat.fi>2026-03-14 10:29:54 +0200
commitc8dc1635f8a921269f714117f414bbc7ba24f9fd (patch)
tree336a0bb466a5e023c0a0e6472c9876ff7458de68 /lua/muwiki/io.lua
parentffc0482ab89924cb35155fa82033e3d0ddc6c93d (diff)
refactor: consolidate modules and improve structure
Diffstat (limited to 'lua/muwiki/io.lua')
-rw-r--r--lua/muwiki/io.lua161
1 files changed, 161 insertions, 0 deletions
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