cover/Elixir.Resolver.html

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
Line Hits Source