Packages

fnord

0.8.59

AI code archaeology

Current section

Files

Jump to
fnord lib resolve_project.ex
Raw

lib/resolve_project.ex

defmodule ResolveProject do
@moduledoc """
Project resolution from cwd or git worktree root.
Normalizes paths, applies robust containment, and chooses the deepest matching
project when roots nest. Returns {:ok, project_name} or {:error, :not_in_project}.
"""
@type project_name :: binary
@spec resolve_from_cwd(cwd :: binary | nil) :: {:ok, project_name} | {:error, :not_in_project}
def resolve_from_cwd(cwd \\ nil) do
projects =
Settings.new()
|> Settings.get_projects()
|> Enum.flat_map(fn {name, %{"root" => root}} ->
case root do
nil -> []
root_str -> [{Path.absname(root_str), name}]
end
end)
cwd_abs =
case cwd do
nil -> Path.absname(File.cwd!())
dir -> Path.absname(dir)
end
matching_projects =
projects
|> Enum.filter(fn {root_abs, _name} -> path_contains?(root_abs, cwd_abs) end)
case matching_projects do
[] ->
{:error, :not_in_project}
_ ->
{_, project_name} =
Enum.max_by(matching_projects, fn {root_abs, _} -> String.length(root_abs) end)
{:ok, project_name}
end
end
@spec resolve_from_worktree() :: {:ok, project_name} | {:error, :not_in_project}
def resolve_from_worktree() do
# Gather possible roots from git worktree and repo
candidates =
[GitCli.worktree_root(), GitCli.repo_root()]
|> Enum.filter(& &1)
|> Enum.map(&Path.absname/1)
|> Enum.uniq()
# Load configured project roots
projects =
Settings.new()
|> Settings.get_projects()
|> Enum.flat_map(fn {name, %{"root" => root}} ->
case root do
nil -> []
root_str -> [{Path.absname(root_str), name}]
end
end)
# Attempt direct match of candidate to configured roots
case Enum.find(candidates, fn candidate ->
Enum.any?(projects, fn {root_abs, _} -> root_abs == candidate end)
end) do
nil ->
# Fall back to resolve_from_cwd for each candidate
candidates
|> Enum.reduce_while(nil, fn cand, _ ->
case resolve_from_cwd(cand) do
{:ok, name} -> {:halt, {:ok, name}}
_ -> {:cont, nil}
end
end)
|> case do
{:ok, _} = ok -> ok
_ -> {:error, :not_in_project}
end
matched ->
# Return the project matching the candidate root
{_, name} = Enum.find(projects, fn {root_abs, _} -> root_abs == matched end)
{:ok, name}
end
end
# Determines if `child` is equal to or contained within `parent`.
defp path_contains?(parent, child) do
parent_sep =
parent
|> Path.expand()
|> Path.join("")
child_sep =
child
|> Path.expand()
|> Path.join("")
String.starts_with?(child_sep, parent_sep)
end
end