Current section

Files

Jump to
igniter lib igniter util backward_compat.ex
Raw

lib/igniter/util/backward_compat.ex

defmodule Igniter.Util.BackwardsCompat do
@moduledoc "Contains functions that we need to use that were introduced in newer Elixir versions."
if Version.match?(System.version(), ">= 1.16.0") do
defdelegate relative_to_cwd(path, opts \\ []), to: Path
else
@doc """
Convenience to get the path relative to the current working
directory.
If, for some reason, the current working directory
cannot be retrieved, this function returns the given `path`.
Check `relative_to/3` for the supported options.
"""
@spec relative_to_cwd(Path.t(), keyword) :: binary
def relative_to_cwd(path, opts \\ []) when is_list(opts) do
case :file.get_cwd() do
{:ok, base} -> relative_to(path, IO.chardata_to_string(base), opts)
_ -> path
end
end
@doc """
Returns the direct relative path from `path` in relation to `cwd`.
In other words, this function attempts to return a path such that
`Path.expand(result, cwd)` points to `path`. This function aims
to return a relative path whenever possible, but that's not guaranteed:
* If both paths are relative, a relative path is always returned
* If both paths are absolute, a relative path may be returned if
they share a common prefix. You can pass the `:force` option to
force this function to traverse up, but even then a relative
path is not guaranteed (for example, if the absolute paths
belong to different drives on Windows)
* If a mixture of paths are given, the result will always match
the given `path` (the first argument)
This function expands `.` and `..` entries without traversing the
file system, so it assumes no symlinks between the paths. See
`safe_relative_to/2` for a safer alternative.
## Options
* `:force` - (boolean since v1.16.0) if `true` forces a relative
path to be returned by traversing the path up. Except if the paths
are in different volumes on Windows. Defaults to `false`.
## Examples
### With relative `cwd`
If both paths are relative, a minimum path is computed:
Path.relative_to("tmp/foo/bar", "tmp") #=> "foo/bar"
Path.relative_to("tmp/foo/bar", "tmp/foo") #=> "bar"
Path.relative_to("tmp/foo/bar", "tmp/bat") #=> "../foo/bar"
If an absolute path is given with relative `cwd`, it is returned as:
Path.relative_to("/usr/foo/bar", "tmp/bat") #=> "/usr/foo/bar"
### With absolute `cwd`
If both paths are absolute, a relative is computed if possible,
without traversing up:
Path.relative_to("/usr/local/foo", "/usr/local") #=> "foo"
Path.relative_to("/usr/local/foo", "/") #=> "usr/local/foo"
Path.relative_to("/usr/local/foo", "/etc") #=> "/usr/local/foo"
Path.relative_to("/usr/local/foo", "/usr/local/foo") #=> "."
Path.relative_to("/usr/local/../foo", "/usr/foo") #=> "."
Path.relative_to("/usr/local/../foo/bar", "/usr/foo") #=> "bar"
If `:force` is set to `true` paths are traversed up:
Path.relative_to("/usr", "/usr/local", force: true) #=> ".."
Path.relative_to("/usr/foo", "/usr/local", force: true) #=> "../foo"
Path.relative_to("/usr/../foo/bar", "/etc/foo", force: true) #=> "../../foo/bar"
If a relative path is given, it is assumed to be relative to the
given path, so the path is returned with "." and ".." expanded:
Path.relative_to(".", "/usr/local") #=> "."
Path.relative_to("foo", "/usr/local") #=> "foo"
Path.relative_to("foo/../bar", "/usr/local") #=> "bar"
Path.relative_to("foo/..", "/usr/local") #=> "."
Path.relative_to("../foo", "/usr/local") #=> "../foo"
"""
@spec relative_to(Path.t(), Path.t(), keyword) :: binary
def relative_to(path, cwd, opts \\ []) when is_list(opts) do
os_type = major_os_type()
split_path = Path.split(path)
split_cwd = Path.split(cwd)
force = Keyword.get(opts, :force, false)
case {split_absolute?(split_path, os_type), split_absolute?(split_cwd, os_type)} do
{true, true} ->
split_path = expand_split(split_path)
split_cwd = expand_split(split_cwd)
case force do
true -> relative_to_forced(split_path, split_cwd, split_path)
false -> relative_to_unforced(split_path, split_cwd, split_path)
end
{false, false} ->
split_path = expand_relative(split_path, [], [])
split_cwd = expand_relative(split_cwd, [], [])
relative_to_forced(split_path, split_cwd, [])
{_, _} ->
Path.join(expand_relative(split_path, [], []))
end
end
defp expand_relative([".." | t], [_ | acc], up), do: expand_relative(t, acc, up)
defp expand_relative([".." | t], acc, up), do: expand_relative(t, acc, [".." | up])
defp expand_relative(["." | t], acc, up), do: expand_relative(t, acc, up)
defp expand_relative([h | t], acc, up), do: expand_relative(t, [h | acc], up)
defp expand_relative([], [], []), do: ["."]
defp expand_relative([], acc, up), do: up ++ :lists.reverse(acc)
defp expand_split([head | tail]), do: expand_split(tail, [head])
defp expand_split([".." | t], [_, last | acc]), do: expand_split(t, [last | acc])
defp expand_split([".." | t], acc), do: expand_split(t, acc)
defp expand_split(["." | t], acc), do: expand_split(t, acc)
defp expand_split([h | t], acc), do: expand_split(t, [h | acc])
defp expand_split([], acc), do: :lists.reverse(acc)
defp split_absolute?(split, :win32), do: win32_split_absolute?(split)
defp split_absolute?(split, _), do: match?(["/" | _], split)
defp win32_split_absolute?(["//" | _]), do: true
defp win32_split_absolute?([<<_, ":/">> | _]), do: true
defp win32_split_absolute?(_), do: false
defp relative_to_unforced(path, path, _original), do: "."
defp relative_to_unforced([h | t1], [h | t2], original),
do: relative_to_unforced(t1, t2, original)
defp relative_to_unforced([_ | _] = l1, [], _original), do: Path.join(l1)
defp relative_to_unforced(_, _, original), do: Path.join(original)
defp relative_to_forced(path, path, _original), do: "."
defp relative_to_forced(["."], _path, _original), do: "."
defp relative_to_forced(path, ["."], _original), do: Path.join(path)
defp relative_to_forced([h | t1], [h | t2], original),
do: relative_to_forced(t1, t2, original)
# this should only happen if we have two paths on different drives on windows
defp relative_to_forced(original, _, original), do: Path.join(original)
defp relative_to_forced(l1, l2, _original) do
base = List.duplicate("..", length(l2))
Path.join(base ++ l1)
end
defp major_os_type do
:os.type() |> elem(0)
end
end
end