Packages

Embed a safe and reproductible node environment into your Elixir application

Current section

Files

Jump to
nox lib nox.semver.ex
Raw

lib/nox.semver.ex

defmodule Nox.Semver do
@moduledoc """
Utils for semantic versions
"""
@type t :: {number, number, number, String.t | nil}
@doc """
Parse string
"""
@spec parse(String.t) :: t | :error
def parse("v" <> str), do: do_parse(str)
def parse(str), do: do_parse(str)
@doc """
Compare semantic versions:
a == b -> 0
a > b -> 1
a < b -> -1
If `until` is given, compares only until `until` level.
## Examples
iex> Nox.cmp("1.4.7beta0", "1.3.2")
1
iex> Nox.cmp({1,4,7,"beta0"}, nil)
1
iex> Nox.cmp({1,4,7,"beta0"}, {1,3,8,"beta1"})
1
iex> Nox.cmp({1,4,7,"beta0"}, {1,3,8,"beta1"}, :major)
0
iex> Nox.cmp({1,2,7,"beta0"}, {1,3,8,"beta1"}, :minor)
-1
"""
@spec cmp(a :: t | String.t, b :: t | String.t | any, until :: :major | :minor | :patch | :extra) :: -1 | 0 | 1
def cmp(a, b, until \\ :extra)
def cmp({_, _, _, _}=a, {_, _, _, _}=b, until) when until in [:major, :minor, :patch, :extra] do
do_cmp(a, b, until, :major)
end
def cmp(a, b, until) when is_binary(a), do: cmp(parse(a), b, until)
def cmp(a, b, until) when is_binary(b), do: cmp(a, parse(b), until)
def cmp(_a, _b, _), do: 1
###
### Priv
###
defp do_parse(nil), do: :error
defp do_parse(str) do
case String.split(str, ".", parts: 3) do
[s1, s2] -> do_parse_int(s1, s2, "0")
[s1, s2, s3] -> do_parse_int(s1, s2, s3)
_ -> :error
end
end
defp do_parse_int(s1, s2, s3) do
with {major, ""} <- Integer.parse(s1),
{minor, ""} <- Integer.parse(s2),
{patch, extra} <- Integer.parse(s3) do
{major, minor, patch, extra}
else _ -> :error
end
end
defp do_cmp({a, _, _, _}, {b, _, _, _}, _max, :major) when a > b, do: 1
defp do_cmp({a, _, _, _}, {b, _, _, _}, _max, :major) when a < b, do: -1
defp do_cmp({a, _, _, _}, {a, _, _, _}, :major, :major), do: 0
defp do_cmp({a, _, _, _}=v1, {a, _, _, _}=v2, max, :major), do: do_cmp(v1, v2, max, :minor)
defp do_cmp({_, a, _, _}, {_, b, _, _}, _max, :minor) when a > b, do: 1
defp do_cmp({_, a, _, _}, {_, b, _, _}, _max, :minor) when a < b, do: -1
defp do_cmp({_, a, _, _}, {_, a, _, _}, :minor, :minor), do: 0
defp do_cmp({_, a, _, _}=v1, {_, a, _, _}=v2, max, :minor), do: do_cmp(v1, v2, max, :patch)
defp do_cmp({_, _, a, _}, {_, _, b, _}, _max, :patch) when a > b, do: 1
defp do_cmp({_, _, a, _}, {_, _, b, _}, _max, :patch) when a < b, do: -1
defp do_cmp({_, _, a, _}, {_, _, a, _}, :patch, :patch), do: 0
defp do_cmp({_, _, a, _}=v1, {_, _, a, _}=v2, max, :patch), do: do_cmp(v1, v2, max, :extra)
defp do_cmp({_, _, _, a}, {_, _, _, b}, _, :extra) when a > b, do: -1
defp do_cmp({_, _, _, a}, {_, _, _, b}, _, :extra) when a < b, do: 1
defp do_cmp({_, _, _, a}, {_, _, _, a}, _, :extra), do: 0
end