Skip to content

Commit

Permalink
Replace semver usage with canonicalized version comparison
Browse files Browse the repository at this point in the history
  • Loading branch information
mneudert committed Aug 18, 2024
1 parent c52201f commit d9746c7
Show file tree
Hide file tree
Showing 3 changed files with 33 additions and 137 deletions.
12 changes: 6 additions & 6 deletions lib/ua_inspector/parser.ex
Original file line number Diff line number Diff line change
Expand Up @@ -276,8 +276,8 @@ defmodule UAInspector.Parser do

defp maybe_detect_tv(result), do: result

# Android < 2.0.0 is always a smartphone
# Android == 3.* is always a tablet
# Android < 2.0 is always a smartphone
# Android == 3.* is always a tablet
# treat Android feature phones as smartphones
defp maybe_fix_android(%{os: %{version: :unknown}} = result), do: result

Expand Down Expand Up @@ -328,12 +328,12 @@ defmodule UAInspector.Parser do

defp maybe_fix_device_type(result), do: result

defp smartphone_android?(version), do: :lt == Util.Version.compare(version, "2.0.0")
defp smartphone_android?(version), do: :lt == Util.Version.compare(version, "2.0")

defp tablet_android?(version),
do:
:lt != Util.Version.compare(version, "3.0.0") &&
:lt == Util.Version.compare(version, "4.0.0")
:lt != Util.Version.compare(version, "3.0") &&
:lt == Util.Version.compare(version, "4.0")

defp maybe_fix_android_chrome(
%{
Expand Down Expand Up @@ -381,7 +381,7 @@ defmodule UAInspector.Parser do
} = result
)
when is_binary(os_version) do
with true <- :lt != Util.Version.compare(os_version, "8.0.0"),
with true <- :lt != Util.Version.compare(os_version, "8"),
true <- Regex.match?(@has_touch, ua) do
%{result | device: %{device | type: "tablet"}}
else
Expand Down
154 changes: 25 additions & 129 deletions lib/ua_inspector/util/version.ex
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ defmodule UAInspector.Util.Version do
end

@doc """
Compare two versions.
Compare two versions using canonicalized format.
## Examples
Expand All @@ -87,68 +87,55 @@ defmodule UAInspector.Util.Version do
iex> compare("1.2.3", "1.020.3")
:lt
"""
@spec compare(binary, binary) :: :eq | :gt | :lt
def compare(version1, version2) do
semver1 = to_semver_with_pre(version1)
semver2 = to_semver_with_pre(version2)
Version.compare(semver1, semver2)
end

@doc """
Compare two versions using canonicalized format.
## Examples
iex> compare_canonicalized("1.2-3+4.alpha5", "1.2-3+4.alpha1")
iex> compare("1.2-3+4.alpha5", "1.2-3+4.alpha1")
:gt
iex> compare_canonicalized("1.02-03alpha04-05+00", "1.02-03alpha04-05+99")
iex> compare("1.02-03alpha04-05+00", "1.02-03alpha04-05+99")
:lt
iex> compare_canonicalized("1dev", "1alpha")
iex> compare("1dev", "1alpha")
:lt
iex> compare_canonicalized("1alpha", "1beta")
iex> compare("1alpha", "1beta")
:lt
iex> compare_canonicalized("1beta", "1rc")
iex> compare("1beta", "1rc")
:lt
iex> compare_canonicalized("1rc", "1")
iex> compare("1rc", "1")
:lt
iex> compare_canonicalized("1", "1patch")
iex> compare("1", "1patch")
:lt
iex> compare_canonicalized("1beta", "1patch")
iex> compare("1beta", "1patch")
:lt
iex> compare_canonicalized("1.02.03.04.05.06.alpha", "1.2.3.4.5.6alpha")
iex> compare("1.02.03.04.05.06.alpha", "1.2.3.4.5.6alpha")
:eq
iex> compare_canonicalized("1", "1.0")
iex> compare("1", "1.0")
:lt
iex> compare_canonicalized("", "")
iex> compare("", "")
:eq
iex> compare_canonicalized("", "1")
iex> compare("", "1")
:lt
iex> compare_canonicalized("1", "")
iex> compare("1", "")
:gt
iex> compare_canonicalized(".", "1")
iex> compare(".", "1")
:lt
"""
@spec compare_canonicalized(binary, binary) :: :eq | :gt | :lt
def compare_canonicalized(v1, v2) when is_binary(v1) and is_binary(v2) do
@spec compare(binary, binary) :: :eq | :gt | :lt
def compare(v1, v2) when is_binary(v1) and is_binary(v2) do
c1 = v1 |> canonicalize() |> String.split(".")
c2 = v2 |> canonicalize() |> String.split(".")

do_compare_canonicalized(c1, c2)
do_compare(c1, c2)
end

@doc """
Expand Down Expand Up @@ -195,68 +182,6 @@ defmodule UAInspector.Util.Version do
|> String.trim()
end

@doc """
Converts an unknown version string to a semver-comparable format.
Missing values are filled with zeroes while empty strings are ignored.
If a non-integer value is found it is ignored and every part
including and after it will be a zero.
## Examples
iex> to_semver("15")
"15.0.0"
iex> to_semver("3.6")
"3.6.0"
iex> to_semver("8.8.8")
"8.8.8"
iex> to_semver("")
""
iex> to_semver("invalid")
"0.0.0"
iex> to_semver("3.help")
"3.0.0"
iex> to_semver("0.1.invalid")
"0.1.0"
iex> to_semver("1.2.3.4")
"1.2.3"
iex> to_semver("1.2.3.4", 4)
"1.2.3-4"
iex> to_semver("-1.2.3.4")
"0.0.0"
iex> to_semver("1.-2.3.4")
"1.0.0"
iex> to_semver("1.2.-3.4")
"1.2.0"
iex> to_semver("1.2.3.-4")
"1.2.3"
"""
@spec to_semver(version :: String.t(), parts :: integer) :: String.t()
def to_semver(version, parts \\ 3)
def to_semver("", _), do: ""

def to_semver(version, parts) do
case String.split(version, ".", parts: parts) do
[maj] -> to_semver_string(maj, "0", "0", nil)
[maj, min] -> to_semver_string(maj, min, "0", nil)
[maj, min, patch] -> to_semver_string(maj, min, patch, nil)
[maj, min, patch, pre] -> to_semver_string(maj, min, patch, pre)
end
end

defp comparison_priority("dev" <> _), do: 0
defp comparison_priority("a" <> _), do: 1
defp comparison_priority("b" <> _), do: 2
Expand All @@ -265,25 +190,25 @@ defmodule UAInspector.Util.Version do
defp comparison_priority("p" <> _), do: 5
defp comparison_priority(_), do: -1

defp do_compare_canonicalized([], []), do: :eq
defp do_compare([], []), do: :eq

defp do_compare_canonicalized([], [v2 | _]) do
defp do_compare([], [v2 | _]) do
if comparison_priority(v2) >= comparison_priority("0") do
:lt
else
:gt
end
end

defp do_compare_canonicalized([v1 | _], []) do
defp do_compare([v1 | _], []) do
if comparison_priority(v1) < comparison_priority("0") do
:lt
else
:gt
end
end

defp do_compare_canonicalized([<<p1::size(8), _::binary>> = v1 | c1], [
defp do_compare([<<p1::size(8), _::binary>> = v1 | c1], [
<<p2::size(8), _::binary>> = v2 | c2
])
when p1 in ~c'0123456789' and p2 in ~c'0123456789' do
Expand All @@ -293,47 +218,18 @@ defmodule UAInspector.Util.Version do
cond do
iv1 < iv2 -> :lt
iv1 > iv2 -> :gt
iv1 == iv2 -> do_compare_canonicalized(c1, c2)
iv1 == iv2 -> do_compare(c1, c2)
end
end

defp do_compare_canonicalized([v1 | c1], [v2 | c2]) do
defp do_compare([v1 | c1], [v2 | c2]) do
p1 = comparison_priority(v1)
p2 = comparison_priority(v2)

cond do
p1 < p2 -> :lt
p1 > p2 -> :gt
p1 == p2 -> do_compare_canonicalized(c1, c2)
end
end

defp to_semver_string(major, minor, patch, pre) do
version =
case {Integer.parse(major), Integer.parse(minor), Integer.parse(patch)} do
{:error, _, _} -> "0.0.0"
{{maj, _}, _, _} when maj < 0 -> "0.0.0"
{{maj, _}, :error, _} -> "#{maj}.0.0"
{{maj, _}, {min, _}, _} when min < 0 -> "#{maj}.0.0"
{{maj, _}, {min, _}, :error} -> "#{maj}.#{min}.0"
{{maj, _}, {min, _}, {patch, _}} when patch < 0 -> "#{maj}.#{min}.0"
{{maj, _}, {min, _}, {patch, _}} -> "#{maj}.#{min}.#{patch}"
end

if nil != pre do
version <> "-" <> pre
else
version
end
end

defp to_semver_with_pre(version) do
semver = to_semver(version, 4)

if String.contains?(semver, "-") do
semver
else
semver <> "-0"
p1 == p2 -> do_compare(c1, c2)
end
end
end
4 changes: 2 additions & 2 deletions test/ua_inspector/util/version_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ defmodule UAInspector.Util.VersionTest do

doctest Version, import: true

test "compare canonicalized" do
test "compare" do
source = Path.expand("../../fixtures/versions.txt", __DIR__)
specs = File.stream!(source)

Expand All @@ -19,7 +19,7 @@ defmodule UAInspector.Util.VersionTest do
"==" -> :eq
end

assert ^result = Version.compare_canonicalized(v1, v2)
assert ^result = Version.compare(v1, v2)
end
end
end

0 comments on commit d9746c7

Please sign in to comment.