path.lua

 1-- SPDX-FileCopyrightText: Amolith <amolith@secluded.site>
 2--
 3-- SPDX-License-Identifier: GPL-3.0-or-later
 4
 5---@class wt.path
 6local M = {}
 7
 8---Split path into components
 9---@param path string
10---@return string[]
11function M.split_path(path)
12	local parts = {}
13	for part in path:gmatch("[^/]+") do
14		table.insert(parts, part)
15	end
16	return parts
17end
18
19---Calculate relative path from one absolute path to another
20---@param from string absolute path of starting directory
21---@param to string absolute path of target
22---@return string relative path
23function M.relative_path(from, to)
24	if from == to then
25		return "./"
26	end
27
28	local from_parts = M.split_path(from)
29	local to_parts = M.split_path(to)
30
31	local common = 0
32	for i = 1, math.min(#from_parts, #to_parts) do
33		if from_parts[i] == to_parts[i] then
34			common = i
35		else
36			break
37		end
38	end
39
40	local up_count = #from_parts - common
41	local result = {}
42
43	for _ = 1, up_count do
44		table.insert(result, "..")
45	end
46
47	for i = common + 1, #to_parts do
48		table.insert(result, to_parts[i])
49	end
50
51	if #result == 0 then
52		return "./"
53	end
54
55	return table.concat(result, "/")
56end
57
58---Convert branch name to worktree path based on style
59---@param root string project root path
60---@param branch string branch name
61---@param style string "nested" or "flat"
62---@param separator? string separator for flat style (default "_")
63---@return string worktree path
64function M.branch_to_path(root, branch, style, separator)
65	if style == "flat" then
66		local sep = separator or "_"
67		local escaped_sep = sep:gsub("%%", "%%%%")
68		local flat_name = branch:gsub("/", escaped_sep)
69		return root .. "/" .. flat_name
70	end
71	-- nested style (default): preserve slashes
72	return root .. "/" .. branch
73end
74
75---Check if path_a is inside (or equal to) path_b
76---@param path_a string the path to check
77---@param path_b string the container path
78---@return boolean
79function M.path_inside(path_a, path_b)
80	-- Normalize: ensure no trailing slash for comparison
81	path_b = path_b:gsub("/$", "")
82	path_a = path_a:gsub("/$", "")
83	return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/"
84end
85
86---Escape special Lua pattern characters in a string
87---@param str string
88---@return string
89function M.escape_pattern(str)
90	return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1"))
91end
92
93return M