From e60790a7d5c2b8b428dacf883cb3a7f620b72e60 Mon Sep 17 00:00:00 2001 From: Amolith Date: Sun, 18 Jan 2026 08:41:39 -0700 Subject: [PATCH] refactor(wt): extract path module Assisted-by: Claude Opus 4.5 via Amp --- dist/wt | 187 ++++++++++++++++++++++++++---------------------- src/main.lua | 92 ++---------------------- src/wt/path.lua | 93 ++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 170 deletions(-) create mode 100644 src/wt/path.lua diff --git a/dist/wt b/dist/wt index 321327ffdf9b5360dfacd37ba33fa9e28c39b48c..878afa2cbb2fcc3417f4a16ec35edd93318b4622 100755 --- a/dist/wt +++ b/dist/wt @@ -102,6 +102,101 @@ end return M ]] +_EMBEDDED_MODULES["wt.path"] = [[-- SPDX-FileCopyrightText: Amolith +-- +-- SPDX-License-Identifier: GPL-3.0-or-later + +---@class wt.path +local M = {} + +---Split path into components +---@param path string +---@return string[] +function M.split_path(path) + local parts = {} + for part in path:gmatch("[^/]+") do + table.insert(parts, part) + end + return parts +end + +---Calculate relative path from one absolute path to another +---@param from string absolute path of starting directory +---@param to string absolute path of target +---@return string relative path +function M.relative_path(from, to) + if from == to then + return "./" + end + + local from_parts = M.split_path(from) + local to_parts = M.split_path(to) + + local common = 0 + for i = 1, math.min(#from_parts, #to_parts) do + if from_parts[i] == to_parts[i] then + common = i + else + break + end + end + + local up_count = #from_parts - common + local result = {} + + for _ = 1, up_count do + table.insert(result, "..") + end + + for i = common + 1, #to_parts do + table.insert(result, to_parts[i]) + end + + if #result == 0 then + return "./" + end + + return table.concat(result, "/") +end + +---Convert branch name to worktree path based on style +---@param root string project root path +---@param branch string branch name +---@param style string "nested" or "flat" +---@param separator? string separator for flat style (default "_") +---@return string worktree path +function M.branch_to_path(root, branch, style, separator) + if style == "flat" then + local sep = separator or "_" + local escaped_sep = sep:gsub("%%", "%%%%") + local flat_name = branch:gsub("/", escaped_sep) + return root .. "/" .. flat_name + end + -- nested style (default): preserve slashes + return root .. "/" .. branch +end + +---Check if path_a is inside (or equal to) path_b +---@param path_a string the path to check +---@param path_b string the container path +---@return boolean +function M.path_inside(path_a, path_b) + -- Normalize: ensure no trailing slash for comparison + path_b = path_b:gsub("/$", "") + path_a = path_a:gsub("/$", "") + return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/" +end + +---Escape special Lua pattern characters in a string +---@param str string +---@return string +function M.escape_pattern(str) + return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1")) +end + +return M +]] + if _VERSION < "Lua 5.2" then io.stderr:write("error: wt requires Lua 5.2 or later\n") @@ -119,6 +214,13 @@ local run_cmd_silent = shell.run_cmd_silent local get_cwd = shell.get_cwd local die = shell.die +local path_mod = require("wt.path") +local branch_to_path = path_mod.branch_to_path +local split_path = path_mod.split_path +local relative_path = path_mod.relative_path +local path_inside = path_mod.path_inside +local escape_pattern = path_mod.escape_pattern + ---Walk up from cwd looking for .git file pointing to .bare, or .bare/ directory ---@return string|nil root ---@return string|nil error @@ -398,23 +500,6 @@ local function get_default_branch() return "main" end ----Convert branch name to worktree path ----@param root string ----@param branch string ----@param style string "nested" or "flat" ----@param separator? string separator for flat style ----@return string -local function branch_to_path(root, branch, style, separator) - if style == "flat" then - local sep = separator or "_" - local escaped_sep = sep:gsub("%%", "%%%%") - local flat_name = branch:gsub("/", escaped_sep) - return root .. "/" .. flat_name - end - -- nested style (default): preserve slashes - return root .. "/" .. branch -end - ---Load global config from ~/.config/wt/config.lua ---@return {branch_path_style?: string, flat_separator?: string, remotes?: table, default_remotes?: string[]|string} local function load_global_config() @@ -481,56 +566,6 @@ local function load_project_config(root) return result end ----Split path into components ----@param path string ----@return string[] -local function split_path(path) - local parts = {} - for part in path:gmatch("[^/]+") do - table.insert(parts, part) - end - return parts -end - ----Calculate relative path from one absolute path to another ----@param from string absolute path of starting directory ----@param to string absolute path of target ----@return string relative path -local function relative_path(from, to) - if from == to then - return "./" - end - - local from_parts = split_path(from) - local to_parts = split_path(to) - - local common = 0 - for i = 1, math.min(#from_parts, #to_parts) do - if from_parts[i] == to_parts[i] then - common = i - else - break - end - end - - local up_count = #from_parts - common - local result = {} - - for _ = 1, up_count do - table.insert(result, "..") - end - - for i = common + 1, #to_parts do - table.insert(result, to_parts[i]) - end - - if #result == 0 then - return "./" - end - - return table.concat(result, "/") -end - ---Check if cwd is inside a worktree (has .git file, not at project root) ---@param root string ---@return string|nil source_worktree path if inside worktree, nil if at project root @@ -571,13 +606,6 @@ local function branch_exists_local(git_dir, branch) return run_cmd_silent("GIT_DIR=" .. git_dir .. " git show-ref --verify --quiet refs/heads/" .. branch) end ----Escape special Lua pattern characters in a string ----@param str string ----@return string -local function escape_pattern(str) - return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1")) -end - ---Parse git branch -r output to extract remotes containing a branch ---@param output string git branch -r output ---@param branch string branch name to find @@ -1291,17 +1319,6 @@ local function cmd_add(args) print(target_path) end ----Check if path_a is inside (or equal to) path_b ----@param path_a string the path to check ----@param path_b string the container path ----@return boolean -local function path_inside(path_a, path_b) - -- Normalize: ensure no trailing slash for comparison - path_b = path_b:gsub("/$", "") - path_a = path_a:gsub("/$", "") - return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/" -end - ---Check if cwd is inside (or equal to) a given path ---@param target string ---@return boolean diff --git a/src/main.lua b/src/main.lua index 666f530b4fe38d66e6895a2cda25b569cf0eb6bb..f3314d56d3b3347b16bfa61628af0fc6c82ca574 100644 --- a/src/main.lua +++ b/src/main.lua @@ -20,6 +20,13 @@ local run_cmd_silent = shell.run_cmd_silent local get_cwd = shell.get_cwd local die = shell.die +local path_mod = require("wt.path") +local branch_to_path = path_mod.branch_to_path +local split_path = path_mod.split_path +local relative_path = path_mod.relative_path +local path_inside = path_mod.path_inside +local escape_pattern = path_mod.escape_pattern + ---Walk up from cwd looking for .git file pointing to .bare, or .bare/ directory ---@return string|nil root ---@return string|nil error @@ -299,23 +306,6 @@ local function get_default_branch() return "main" end ----Convert branch name to worktree path ----@param root string ----@param branch string ----@param style string "nested" or "flat" ----@param separator? string separator for flat style ----@return string -local function branch_to_path(root, branch, style, separator) - if style == "flat" then - local sep = separator or "_" - local escaped_sep = sep:gsub("%%", "%%%%") - local flat_name = branch:gsub("/", escaped_sep) - return root .. "/" .. flat_name - end - -- nested style (default): preserve slashes - return root .. "/" .. branch -end - ---Load global config from ~/.config/wt/config.lua ---@return {branch_path_style?: string, flat_separator?: string, remotes?: table, default_remotes?: string[]|string} local function load_global_config() @@ -382,56 +372,6 @@ local function load_project_config(root) return result end ----Split path into components ----@param path string ----@return string[] -local function split_path(path) - local parts = {} - for part in path:gmatch("[^/]+") do - table.insert(parts, part) - end - return parts -end - ----Calculate relative path from one absolute path to another ----@param from string absolute path of starting directory ----@param to string absolute path of target ----@return string relative path -local function relative_path(from, to) - if from == to then - return "./" - end - - local from_parts = split_path(from) - local to_parts = split_path(to) - - local common = 0 - for i = 1, math.min(#from_parts, #to_parts) do - if from_parts[i] == to_parts[i] then - common = i - else - break - end - end - - local up_count = #from_parts - common - local result = {} - - for _ = 1, up_count do - table.insert(result, "..") - end - - for i = common + 1, #to_parts do - table.insert(result, to_parts[i]) - end - - if #result == 0 then - return "./" - end - - return table.concat(result, "/") -end - ---Check if cwd is inside a worktree (has .git file, not at project root) ---@param root string ---@return string|nil source_worktree path if inside worktree, nil if at project root @@ -472,13 +412,6 @@ local function branch_exists_local(git_dir, branch) return run_cmd_silent("GIT_DIR=" .. git_dir .. " git show-ref --verify --quiet refs/heads/" .. branch) end ----Escape special Lua pattern characters in a string ----@param str string ----@return string -local function escape_pattern(str) - return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1")) -end - ---Parse git branch -r output to extract remotes containing a branch ---@param output string git branch -r output ---@param branch string branch name to find @@ -1192,17 +1125,6 @@ local function cmd_add(args) print(target_path) end ----Check if path_a is inside (or equal to) path_b ----@param path_a string the path to check ----@param path_b string the container path ----@return boolean -local function path_inside(path_a, path_b) - -- Normalize: ensure no trailing slash for comparison - path_b = path_b:gsub("/$", "") - path_a = path_a:gsub("/$", "") - return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/" -end - ---Check if cwd is inside (or equal to) a given path ---@param target string ---@return boolean diff --git a/src/wt/path.lua b/src/wt/path.lua new file mode 100644 index 0000000000000000000000000000000000000000..d21093e80f255ac2284c82c7a529a3051cb6ba86 --- /dev/null +++ b/src/wt/path.lua @@ -0,0 +1,93 @@ +-- SPDX-FileCopyrightText: Amolith +-- +-- SPDX-License-Identifier: GPL-3.0-or-later + +---@class wt.path +local M = {} + +---Split path into components +---@param path string +---@return string[] +function M.split_path(path) + local parts = {} + for part in path:gmatch("[^/]+") do + table.insert(parts, part) + end + return parts +end + +---Calculate relative path from one absolute path to another +---@param from string absolute path of starting directory +---@param to string absolute path of target +---@return string relative path +function M.relative_path(from, to) + if from == to then + return "./" + end + + local from_parts = M.split_path(from) + local to_parts = M.split_path(to) + + local common = 0 + for i = 1, math.min(#from_parts, #to_parts) do + if from_parts[i] == to_parts[i] then + common = i + else + break + end + end + + local up_count = #from_parts - common + local result = {} + + for _ = 1, up_count do + table.insert(result, "..") + end + + for i = common + 1, #to_parts do + table.insert(result, to_parts[i]) + end + + if #result == 0 then + return "./" + end + + return table.concat(result, "/") +end + +---Convert branch name to worktree path based on style +---@param root string project root path +---@param branch string branch name +---@param style string "nested" or "flat" +---@param separator? string separator for flat style (default "_") +---@return string worktree path +function M.branch_to_path(root, branch, style, separator) + if style == "flat" then + local sep = separator or "_" + local escaped_sep = sep:gsub("%%", "%%%%") + local flat_name = branch:gsub("/", escaped_sep) + return root .. "/" .. flat_name + end + -- nested style (default): preserve slashes + return root .. "/" .. branch +end + +---Check if path_a is inside (or equal to) path_b +---@param path_a string the path to check +---@param path_b string the container path +---@return boolean +function M.path_inside(path_a, path_b) + -- Normalize: ensure no trailing slash for comparison + path_b = path_b:gsub("/$", "") + path_a = path_a:gsub("/$", "") + return path_a == path_b or path_a:sub(1, #path_b + 1) == path_b .. "/" +end + +---Escape special Lua pattern characters in a string +---@param str string +---@return string +function M.escape_pattern(str) + return (str:gsub("([%%%-%+%[%]%(%)%.%^%$%*%?])", "%%%1")) +end + +return M