| 1 |
|
defmodule Resolver do |
| 2 |
|
@moduledoc """ |
| 3 |
|
Pure address resolver for agent discovery. |
| 4 |
|
|
| 5 |
|
Matches addresses against a tree snapshot with support for |
| 6 |
|
bare names, paths, globs, and fanout patterns. |
| 7 |
|
""" |
| 8 |
|
|
| 9 |
|
import Enum, only: [filter: 2, group_by: 2, flat_map: 2] |
| 10 |
|
import Path, only: [join: 2, expand: 1] |
| 11 |
|
import String, only: [split: 3] |
| 12 |
|
import Glob, only: [wild?: 1, hits?: 2] |
| 13 |
|
|
| 14 |
|
def resolve(address, world, cwd) do |
| 15 |
|
world |
| 16 |
924 |
|> filter(&(&1.kind == :node)) |
| 17 |
33 |
|> filter(&(&1.name == address)) |
| 18 |
33 |
|> find(address, world, cwd) |
| 19 |
|
end |
| 20 |
|
|
| 21 |
:-( |
defp find([entry], _address, _world, _cwd), do: {:ok, entry} |
| 22 |
|
|
| 23 |
|
defp find([], address, world, cwd) do |
| 24 |
33 |
{name, path, fanout} = unpack(address) |
| 25 |
33 |
world |> path(normalize(path, cwd)) |> named(name, fanout) |> rank() |
| 26 |
|
end |
| 27 |
|
|
| 28 |
:-( |
defp find(entries, _address, _world, _cwd), do: {:many, entries} |
| 29 |
|
|
| 30 |
33 |
defp unpack([name]), do: {name, nil, false} |
| 31 |
:-( |
defp unpack(["", path]), do: {nil, path, true} |
| 32 |
:-( |
defp unpack([name, path]), do: {name, path, false} |
| 33 |
|
|
| 34 |
|
defp unpack(address) do |
| 35 |
33 |
address |> split("@", parts: 2) |> unpack() |
| 36 |
|
end |
| 37 |
|
|
| 38 |
33 |
def normalize(nil, _cwd), do: nil |
| 39 |
:-( |
def normalize("/" <> _ = path, _cwd), do: path |
| 40 |
:-( |
def normalize(path, cwd), do: cwd |> join(path) |> expand() |
| 41 |
|
|
| 42 |
|
defp path(world, search), |
| 43 |
33 |
do: filter(world, &fits?(&1, search)) |
| 44 |
|
|
| 45 |
924 |
defp fits?(_entry, nil), do: true |
| 46 |
:-( |
defp fits?(%{path: p}, s), do: same?(p, s) |
| 47 |
|
|
| 48 |
:-( |
defp same?(p, p), do: true |
| 49 |
:-( |
defp same?(p, s), do: glob(wild?(s), p, s) |
| 50 |
|
|
| 51 |
:-( |
defp glob(true, p, s), do: hits?(p, s) |
| 52 |
:-( |
defp glob(false, _p, _s), do: false |
| 53 |
|
|
| 54 |
:-( |
defp named(entries, nil, _fanout), do: entries |
| 55 |
33 |
defp named(entries, name, false), do: filter(entries, &(&1.name == name)) |
| 56 |
:-( |
defp named(entries, _name, true), do: entries |
| 57 |
|
|
| 58 |
:-( |
defp rank([]), do: {:error, :unknown} |
| 59 |
30 |
defp rank([entry]), do: {:ok, entry} |
| 60 |
|
|
| 61 |
|
defp rank(entries) do |
| 62 |
|
entries |
| 63 |
6 |
|> group_by(&{&1.name, &1.path}) |
| 64 |
|
|> flat_map(&prefer/1) |
| 65 |
3 |
|> result() |
| 66 |
|
end |
| 67 |
|
|
| 68 |
:-( |
defp prefer({_key, [single]}), do: [single] |
| 69 |
3 |
defp prefer({_key, multiple}), do: prefer(multiple) |
| 70 |
|
|
| 71 |
|
defp prefer(entries) do |
| 72 |
3 |
files = filter(entries, &(&1.kind == :file)) |
| 73 |
3 |
pick(files, entries) |
| 74 |
|
end |
| 75 |
|
|
| 76 |
3 |
defp pick([_ | _] = files, _entries), do: files |
| 77 |
:-( |
defp pick([], entries), do: entries |
| 78 |
|
|
| 79 |
3 |
defp result([entry]), do: {:ok, entry} |
| 80 |
:-( |
defp result(entries), do: {:many, entries} |
| 81 |
|
end |