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