diff --git a/shell_hook.sh b/shell_hook.sh index 9edade6..8335573 100755 --- a/shell_hook.sh +++ b/shell_hook.sh @@ -34,58 +34,61 @@ TOOL_FOLDER=".luca" # It is designed to be idempotent and prevent PATH duplication. # When navigating to subdirectories of a project, it keeps the project's tool path in PATH. update_path() { - # Look for the active tools directory in the current location - local tool_bin_dir="$(pwd)/$TOOL_FOLDER/tools" - local current_pwd="$(pwd)" + # Walk up the directory tree to find the nearest ancestor with .luca/tools. + # This allows navigating directly into a project subdirectory and still + # picking up the project's tools without having to visit the root first. + # Stop at $HOME (exclusive): $HOME/.luca/tools is the global Luca installation + # and must not be treated as a project-specific tools directory. + local tool_bin_dir="" + local dir="$(pwd)" + while [ "$dir" != "/" ] && [ "$dir" != "$HOME" ]; do + if [ -d "$dir/$TOOL_FOLDER/tools" ]; then + tool_bin_dir="$dir/$TOOL_FOLDER/tools" + break + fi + dir="$(dirname "$dir")" + done + + # Always clean up stale .luca/tools entries from PATH first. + # Keep only the entry that matches the one we just resolved (if any). + if [[ ":$PATH:" == *"/$TOOL_FOLDER/tools:"* ]]; then + local p + local new_path="" + local current_path="$PATH" + + while [ -n "$current_path" ]; do + p="${current_path%%:*}" + + if [ "$current_path" = "$p" ]; then + current_path="" + else + current_path="${current_path#*:}" + fi - # Only proceed if the tool directory exists in the current directory - if [ -d "$tool_bin_dir" ]; then - # Check if tool_bin_dir is already in PATH to avoid duplicates + if [[ "$p" == *"/$TOOL_FOLDER/tools" ]]; then + # Keep only the entry for the currently active project + if [ "$p" = "$tool_bin_dir" ]; then + new_path="${new_path:+$new_path:}$p" + fi + else + new_path="${new_path:+$new_path:}$p" + fi + done + export PATH="$new_path" + fi + + # Add the resolved tool_bin_dir if it isn't already in PATH + if [ -n "$tool_bin_dir" ]; then case ":$PATH:" in - *":$tool_bin_dir:"*) + *":$tool_bin_dir:"*) # Already in PATH, do nothing (idempotent behavior) - return 0 ;; *) - # Not in PATH, add it to the front for priority export PATH="$tool_bin_dir:$PATH" # Optional: Uncomment the line below for debugging # echo "$TOOL_NAME: Added $tool_bin_dir to PATH" ;; esac - else - # Current directory doesn't have .luca/tools - # Check if there are any .luca/tools entries in PATH that should be cleaned up - if [[ ":$PATH:" == *"/$TOOL_FOLDER/tools:"* ]]; then - local p - local new_path="" - local current_path="$PATH" - - while [ -n "$current_path" ]; do - p="${current_path%%:*}" - - if [ "$current_path" = "$p" ]; then - current_path="" - else - current_path="${current_path#*:}" - fi - - if [[ "$p" == *"/$TOOL_FOLDER/tools" ]]; then - # This is a tool directory entry - check if we should keep it - # Get the project root (parent of .luca/tools) - local project_root="${p%/$TOOL_FOLDER/tools}" - # Keep this entry if current directory is the project root or a subdirectory of it - if [[ "$current_pwd" == "$project_root" || "$current_pwd" == "$project_root/"* ]]; then - new_path="${new_path:+$new_path:}$p" - fi - # Otherwise, skip this entry (effectively removing it from PATH) - else - # Not a tool directory, keep it in PATH - new_path="${new_path:+$new_path:}$p" - fi - done - export PATH="$new_path" - fi fi } diff --git a/tests/shell_hook.bats b/tests/shell_hook.bats index 2c0bff6..b2e6ba4 100644 --- a/tests/shell_hook.bats +++ b/tests/shell_hook.bats @@ -86,7 +86,7 @@ setup() { refute_output --partial "$project_a/.luca/tools" } -@test "update_path: keeps .luca/tools entry when in subdirectory of project" { +@test "update_path: keeps .luca/tools entry when navigating into a project subdirectory" { local project="$BATS_TEST_TMPDIR/myproject" local subdir="$project/src/lib" mkdir -p "$project/.luca/tools" "$subdir" @@ -105,6 +105,97 @@ setup() { assert_output --partial "$project/.luca/tools" } +@test "update_path: adds .luca/tools when entering a project subdirectory directly" { + local project="$BATS_TEST_TMPDIR/myproject" + local subdir="$project/src/lib" + mkdir -p "$project/.luca/tools" "$subdir" + + run bash -c " + export HOME='$TEST_HOME' + export SHELL=/bin/bash + source '$REPO_ROOT/shell_hook.sh' + # Navigate directly into a subdir without visiting the project root first + cd '$subdir' + update_path + echo \"\$PATH\" + " + + assert_output --partial "$project/.luca/tools" +} + +@test "update_path: replaces entry when switching between two projects with .luca/tools" { + local project_a="$BATS_TEST_TMPDIR/project_a" + local project_b="$BATS_TEST_TMPDIR/project_b" + mkdir -p "$project_a/.luca/tools" "$project_b/.luca/tools" + + run bash -c " + export HOME='$TEST_HOME' + export SHELL=/bin/bash + source '$REPO_ROOT/shell_hook.sh' + cd '$project_a' && update_path + cd '$project_b' && update_path + echo \"\$PATH\" + " + + assert_output --partial "$project_b/.luca/tools" + refute_output --partial "$project_a/.luca/tools" +} + +@test "update_path: switching back to first project removes second project entry" { + local project_a="$BATS_TEST_TMPDIR/project_a" + local project_b="$BATS_TEST_TMPDIR/project_b" + mkdir -p "$project_a/.luca/tools" "$project_b/.luca/tools" + + run bash -c " + export HOME='$TEST_HOME' + export SHELL=/bin/bash + source '$REPO_ROOT/shell_hook.sh' + cd '$project_a' && update_path + cd '$project_b' && update_path + cd '$project_a' && update_path + echo \"\$PATH\" + " + + assert_output --partial "$project_a/.luca/tools" + refute_output --partial "$project_b/.luca/tools" +} + +@test "update_path: does not add HOME/.luca/tools when in a non-project directory under HOME" { + # $HOME/.luca/tools is always present (global Luca installation) and must be ignored + mkdir -p "$TEST_HOME/.luca/tools" + local plain_dir="$TEST_HOME/documents" + mkdir -p "$plain_dir" + + run bash -c " + export HOME='$TEST_HOME' + export SHELL=/bin/bash + source '$REPO_ROOT/shell_hook.sh' + cd '$plain_dir' + update_path + echo \"\$PATH\" + " + + refute_output --partial "$TEST_HOME/.luca/tools" +} + +@test "update_path: project inside HOME is found but HOME/.luca/tools is not added" { + mkdir -p "$TEST_HOME/.luca/tools" + local project="$TEST_HOME/projects/myapp" + mkdir -p "$project/.luca/tools" "$project/src" + + run bash -c " + export HOME='$TEST_HOME' + export SHELL=/bin/bash + source '$REPO_ROOT/shell_hook.sh' + cd '$project/src' + update_path + echo \"\$PATH\" + " + + assert_output --partial "$project/.luca/tools" + refute_output --partial "$TEST_HOME/.luca/tools" +} + @test "update_path: non-luca PATH entries are never removed" { local empty_dir="$BATS_TEST_TMPDIR/empty" mkdir -p "$empty_dir"