1defmodule Version do
2  @moduledoc ~S"""
3  Functions for parsing and matching versions against requirements.
4
5  A version is a string in a specific format or a `Version`
6  generated after parsing via `Version.parse/1`.
7
8  Although Elixir projects are not required to follow SemVer,
9  they must follow the format outlined on [SemVer 2.0 schema](https://semver.org/).
10
11  ## Versions
12
13  In a nutshell, a version is represented by three numbers:
14
15      MAJOR.MINOR.PATCH
16
17  Pre-releases are supported by optionally appending a hyphen and a series of
18  period-separated identifiers immediately following the patch version.
19  Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
20
21      "1.0.0-alpha.3"
22
23  Build information can be added by appending a plus sign and a series of
24  dot-separated identifiers immediately following the patch or pre-release version.
25  Identifiers consist of only ASCII alphanumeric characters and hyphens (`[0-9A-Za-z-]`):
26
27      "1.0.0-alpha.3+20130417140000.amd64"
28
29  ## Struct
30
31  The version is represented by the `Version` struct and fields
32  are named according to SemVer 2.0: `:major`, `:minor`, `:patch`,
33  `:pre`, and `:build`. You can read those fields but you should
34  not create a new `Version` directly via the struct syntax. Instead
35  use the functions in this module.
36
37  ## Requirements
38
39  Requirements allow you to specify which versions of a given
40  dependency you are willing to work against. Requirements support the common
41  comparison operators such as `>`, `>=`, `<`, `<=`, and `==` that work as one
42  would expect, and additionally the special operator `~>` described in detail
43  further below.
44
45      # Only version 2.0.0
46      "== 2.0.0"
47
48      # Anything later than 2.0.0
49      "> 2.0.0"
50
51  Requirements also support `and` and `or` for complex conditions:
52
53      # 2.0.0 and later until 2.1.0
54      ">= 2.0.0 and < 2.1.0"
55
56  Since the example above is such a common requirement, it can
57  be expressed as:
58
59      "~> 2.0.0"
60
61  `~>` will never include pre-release versions of its upper bound,
62  regardless of the usage of the `:allow_pre` option, or whether the operand
63  is a pre-release version. It can also be used to set an upper bound on only the major
64  version part. See the table below for `~>` requirements and
65  their corresponding translations.
66
67  `~>`           | Translation
68  :------------- | :---------------------
69  `~> 2.0.0`     | `>= 2.0.0 and < 2.1.0`
70  `~> 2.1.2`     | `>= 2.1.2 and < 2.2.0`
71  `~> 2.1.3-dev` | `>= 2.1.3-dev and < 2.2.0`
72  `~> 2.0`       | `>= 2.0.0 and < 3.0.0`
73  `~> 2.1`       | `>= 2.1.0 and < 3.0.0`
74
75  The requirement operand after the `~>` is allowed to omit the patch version,
76  allowing us to express `~> 2.1` or `~> 2.1-dev`, something that wouldn't be allowed
77  when using the common comparison operators.
78
79  When the `:allow_pre` option is set `false` in `Version.match?/3`, the requirement
80  will not match a pre-release version unless the operand is a pre-release version.
81  The default is to always allow pre-releases but note that in
82  Hex `:allow_pre` is set to `false`. See the table below for examples.
83
84  Requirement    | Version     | `:allow_pre`      | Matches
85  :------------- | :---------- | :---------------- | :------
86  `~> 2.0`       | `2.1.0`     | `true` or `false` | `true`
87  `~> 2.0`       | `3.0.0`     | `true` or `false` | `false`
88  `~> 2.0.0`     | `2.0.5`     | `true` or `false` | `true`
89  `~> 2.0.0`     | `2.1.0`     | `true` or `false` | `false`
90  `~> 2.1.2`     | `2.1.6-dev` | `true`            | `true`
91  `~> 2.1.2`     | `2.1.6-dev` | `false`           | `false`
92  `~> 2.1-dev`   | `2.2.0-dev` | `true` or `false` | `true`
93  `~> 2.1.2-dev` | `2.1.6-dev` | `true` or `false` | `true`
94  `>= 2.1.0`     | `2.2.0-dev` | `true`            | `true`
95  `>= 2.1.0`     | `2.2.0-dev` | `false`           | `false`
96  `>= 2.1.0-dev` | `2.2.6-dev` | `true` or `false` | `true`
97
98  """
99
100  import Kernel, except: [match?: 2]
101
102  @enforce_keys [:major, :minor, :patch]
103  defstruct [:major, :minor, :patch, :build, pre: []]
104
105  @type version :: String.t() | t
106  @type requirement :: String.t() | Version.Requirement.t()
107  @type major :: non_neg_integer
108  @type minor :: non_neg_integer
109  @type patch :: non_neg_integer
110  @type pre :: [String.t() | non_neg_integer]
111  @type build :: String.t() | nil
112  @type t :: %__MODULE__{major: major, minor: minor, patch: patch, pre: pre, build: build}
113
114  defmodule Requirement do
115    @moduledoc """
116    A struct that holds version requirement information.
117
118    The struct fields are private and should not be accessed.
119
120    See the "Requirements" section in the `Version` module
121    for more information.
122    """
123
124    defstruct [:source, :lexed]
125
126    @opaque t :: %__MODULE__{
127              source: String.t(),
128              lexed: [atom | matchable]
129            }
130
131    @typep matchable ::
132             {Version.major(), Version.minor(), Version.patch(), Version.pre(), Version.build()}
133
134    @compile inline: [compare: 2]
135
136    @doc false
137    @spec new(String.t(), [atom | matchable]) :: t
138    def new(source, lexed) do
139      %__MODULE__{source: source, lexed: lexed}
140    end
141
142    @doc false
143    @spec match?(t, tuple) :: boolean
144    def match?(%__MODULE__{lexed: [operator, req | rest]}, version) do
145      match_lexed?(rest, version, match_op?(operator, req, version))
146    end
147
148    defp match_lexed?([:and, operator, req | rest], version, acc),
149      do: match_lexed?(rest, version, acc and match_op?(operator, req, version))
150
151    defp match_lexed?([:or, operator, req | rest], version, acc),
152      do: acc or match_lexed?(rest, version, match_op?(operator, req, version))
153
154    defp match_lexed?([], _version, acc),
155      do: acc
156
157    defp match_op?(:==, req, version) do
158      compare(version, req) == :eq
159    end
160
161    defp match_op?(:!=, req, version) do
162      compare(version, req) != :eq
163    end
164
165    defp match_op?(:~>, {major, minor, nil, req_pre, _}, {_, _, _, pre, allow_pre} = version) do
166      compare(version, {major, minor, 0, req_pre, nil}) in [:eq, :gt] and
167        compare(version, {major + 1, 0, 0, [0], nil}) == :lt and
168        (allow_pre or req_pre != [] or pre == [])
169    end
170
171    defp match_op?(:~>, {major, minor, _, req_pre, _} = req, {_, _, _, pre, allow_pre} = version) do
172      compare(version, req) in [:eq, :gt] and
173        compare(version, {major, minor + 1, 0, [0], nil}) == :lt and
174        (allow_pre or req_pre != [] or pre == [])
175    end
176
177    defp match_op?(:>, {_, _, _, req_pre, _} = req, {_, _, _, pre, allow_pre} = version) do
178      compare(version, req) == :gt and (allow_pre or req_pre != [] or pre == [])
179    end
180
181    defp match_op?(:>=, {_, _, _, req_pre, _} = req, {_, _, _, pre, allow_pre} = version) do
182      compare(version, req) in [:eq, :gt] and (allow_pre or req_pre != [] or pre == [])
183    end
184
185    defp match_op?(:<, req, version) do
186      compare(version, req) == :lt
187    end
188
189    defp match_op?(:<=, req, version) do
190      compare(version, req) in [:eq, :lt]
191    end
192
193    defp compare({major1, minor1, patch1, pre1, _}, {major2, minor2, patch2, pre2, _}) do
194      cond do
195        major1 > major2 -> :gt
196        major1 < major2 -> :lt
197        minor1 > minor2 -> :gt
198        minor1 < minor2 -> :lt
199        patch1 > patch2 -> :gt
200        patch1 < patch2 -> :lt
201        pre1 == [] and pre2 != [] -> :gt
202        pre1 != [] and pre2 == [] -> :lt
203        pre1 > pre2 -> :gt
204        pre1 < pre2 -> :lt
205        true -> :eq
206      end
207    end
208  end
209
210  defmodule InvalidRequirementError do
211    defexception [:requirement]
212
213    @impl true
214    def exception(requirement) when is_binary(requirement) do
215      %__MODULE__{requirement: requirement}
216    end
217
218    @impl true
219    def message(%{requirement: requirement}) do
220      "invalid requirement: #{inspect(requirement)}"
221    end
222  end
223
224  defmodule InvalidVersionError do
225    defexception [:version]
226
227    @impl true
228    def exception(version) when is_binary(version) do
229      %__MODULE__{version: version}
230    end
231
232    @impl true
233    def message(%{version: version}) do
234      "invalid version: #{inspect(version)}"
235    end
236  end
237
238  @doc """
239  Checks if the given version matches the specification.
240
241  Returns `true` if `version` satisfies `requirement`, `false` otherwise.
242  Raises a `Version.InvalidRequirementError` exception if `requirement` is not
243  parsable, or a `Version.InvalidVersionError` exception if `version` is not parsable.
244  If given an already parsed version and requirement this function won't
245  raise.
246
247  ## Options
248
249    * `:allow_pre` (boolean) - when `false`, pre-release versions will not match
250      unless the operand is a pre-release version. Defaults to `true`.
251      For examples, please refer to the table above under the "Requirements" section.
252
253  ## Examples
254
255      iex> Version.match?("2.0.0", "> 1.0.0")
256      true
257
258      iex> Version.match?("2.0.0", "== 1.0.0")
259      false
260
261      iex> Version.match?("2.1.6-dev", "~> 2.1.2")
262      true
263
264      iex> Version.match?("2.1.6-dev", "~> 2.1.2", allow_pre: false)
265      false
266
267      iex> Version.match?("foo", "== 1.0.0")
268      ** (Version.InvalidVersionError) invalid version: "foo"
269
270      iex> Version.match?("2.0.0", "== == 1.0.0")
271      ** (Version.InvalidRequirementError) invalid requirement: "== == 1.0.0"
272
273  """
274  @spec match?(version, requirement, keyword) :: boolean
275  def match?(version, requirement, opts \\ [])
276
277  def match?(version, requirement, opts) when is_binary(requirement) do
278    match?(version, parse_requirement!(requirement), opts)
279  end
280
281  def match?(version, requirement, opts) do
282    allow_pre = Keyword.get(opts, :allow_pre, true)
283    matchable_pattern = to_matchable(version, allow_pre)
284
285    Requirement.match?(requirement, matchable_pattern)
286  end
287
288  @doc """
289  Compares two versions.
290
291  Returns `:gt` if the first version is greater than the second one, and `:lt`
292  for vice versa. If the two versions are equal, `:eq` is returned.
293
294  Pre-releases are strictly less than their corresponding release versions.
295
296  Patch segments are compared lexicographically if they are alphanumeric, and
297  numerically otherwise.
298
299  Build segments are ignored: if two versions differ only in their build segment
300  they are considered to be equal.
301
302  Raises a `Version.InvalidVersionError` exception if any of the two given
303  versions are not parsable. If given an already parsed version this function
304  won't raise.
305
306  ## Examples
307
308      iex> Version.compare("2.0.1-alpha1", "2.0.0")
309      :gt
310
311      iex> Version.compare("1.0.0-beta", "1.0.0-rc1")
312      :lt
313
314      iex> Version.compare("1.0.0-10", "1.0.0-2")
315      :gt
316
317      iex> Version.compare("2.0.1+build0", "2.0.1")
318      :eq
319
320      iex> Version.compare("invalid", "2.0.1")
321      ** (Version.InvalidVersionError) invalid version: "invalid"
322
323  """
324  @spec compare(version, version) :: :gt | :eq | :lt
325  def compare(version1, version2) do
326    do_compare(to_matchable(version1, true), to_matchable(version2, true))
327  end
328
329  defp do_compare({major1, minor1, patch1, pre1, _}, {major2, minor2, patch2, pre2, _}) do
330    cond do
331      major1 > major2 -> :gt
332      major1 < major2 -> :lt
333      minor1 > minor2 -> :gt
334      minor1 < minor2 -> :lt
335      patch1 > patch2 -> :gt
336      patch1 < patch2 -> :lt
337      pre1 == [] and pre2 != [] -> :gt
338      pre1 != [] and pre2 == [] -> :lt
339      pre1 > pre2 -> :gt
340      pre1 < pre2 -> :lt
341      true -> :eq
342    end
343  end
344
345  @doc """
346  Parses a version string into a `Version` struct.
347
348  ## Examples
349
350      iex> {:ok, version} = Version.parse("2.0.1-alpha1")
351      iex> version
352      #Version<2.0.1-alpha1>
353
354      iex> Version.parse("2.0-alpha1")
355      :error
356
357  """
358  @spec parse(String.t()) :: {:ok, t} | :error
359  def parse(string) when is_binary(string) do
360    case Version.Parser.parse_version(string) do
361      {:ok, {major, minor, patch, pre, build_parts}} ->
362        build = if build_parts == [], do: nil, else: Enum.join(build_parts, "")
363        version = %Version{major: major, minor: minor, patch: patch, pre: pre, build: build}
364        {:ok, version}
365
366      :error ->
367        :error
368    end
369  end
370
371  @doc """
372  Parses a version string into a `Version`.
373
374  If `string` is an invalid version, a `Version.InvalidVersionError` is raised.
375
376  ## Examples
377
378      iex> Version.parse!("2.0.1-alpha1")
379      #Version<2.0.1-alpha1>
380
381      iex> Version.parse!("2.0-alpha1")
382      ** (Version.InvalidVersionError) invalid version: "2.0-alpha1"
383
384  """
385  @spec parse!(String.t()) :: t
386  def parse!(string) when is_binary(string) do
387    case parse(string) do
388      {:ok, version} -> version
389      :error -> raise InvalidVersionError, string
390    end
391  end
392
393  @doc """
394  Parses a version requirement string into a `Version.Requirement` struct.
395
396  ## Examples
397
398      iex> {:ok, requirement} = Version.parse_requirement("== 2.0.1")
399      iex> requirement
400      #Version.Requirement<== 2.0.1>
401
402      iex> Version.parse_requirement("== == 2.0.1")
403      :error
404
405  """
406  @spec parse_requirement(String.t()) :: {:ok, Requirement.t()} | :error
407  def parse_requirement(string) when is_binary(string) do
408    case Version.Parser.parse_requirement(string) do
409      {:ok, lexed} -> {:ok, Requirement.new(string, lexed)}
410      :error -> :error
411    end
412  end
413
414  @doc """
415  Parses a version requirement string into a `Version.Requirement` struct.
416
417  If `string` is an invalid requirement, a `Version.InvalidRequirementError` is raised.
418
419  ## Examples
420
421      iex> Version.parse_requirement!("== 2.0.1")
422      #Version.Requirement<== 2.0.1>
423
424      iex> Version.parse_requirement!("== == 2.0.1")
425      ** (Version.InvalidRequirementError) invalid requirement: "== == 2.0.1"
426
427  """
428  @doc since: "1.8.0"
429  @spec parse_requirement!(String.t()) :: Requirement.t()
430  def parse_requirement!(string) when is_binary(string) do
431    case parse_requirement(string) do
432      {:ok, requirement} -> requirement
433      :error -> raise InvalidRequirementError, string
434    end
435  end
436
437  @doc """
438  Compiles a requirement to an internal representation that may optimize matching.
439
440  The internal representation is opaque.
441  """
442  @spec compile_requirement(Requirement.t()) :: Requirement.t()
443  def compile_requirement(%Requirement{} = requirement) do
444    requirement
445  end
446
447  defp to_matchable(%Version{major: major, minor: minor, patch: patch, pre: pre}, allow_pre?) do
448    {major, minor, patch, pre, allow_pre?}
449  end
450
451  defp to_matchable(string, allow_pre?) do
452    case Version.Parser.parse_version(string) do
453      {:ok, {major, minor, patch, pre, _build_parts}} ->
454        {major, minor, patch, pre, allow_pre?}
455
456      :error ->
457        raise InvalidVersionError, string
458    end
459  end
460
461  defmodule Parser do
462    @moduledoc false
463
464    operators = [
465      {">=", :>=},
466      {"<=", :<=},
467      {"~>", :~>},
468      {">", :>},
469      {"<", :<},
470      {"==", :==},
471      {" or ", :or},
472      {" and ", :and}
473    ]
474
475    def lexer(string) do
476      lexer(string, "", [])
477    end
478
479    for {string_op, atom_op} <- operators do
480      defp lexer(unquote(string_op) <> rest, buffer, acc) do
481        lexer(rest, "", [unquote(atom_op) | maybe_prepend_buffer(buffer, acc)])
482      end
483    end
484
485    defp lexer("!=" <> rest, buffer, acc) do
486      IO.warn("!= inside Version requirements is deprecated, use ~> or >= instead")
487      lexer(rest, "", [:!= | maybe_prepend_buffer(buffer, acc)])
488    end
489
490    defp lexer("!" <> rest, buffer, acc) do
491      IO.warn("! inside Version requirements is deprecated, use ~> or >= instead")
492      lexer(rest, "", [:!= | maybe_prepend_buffer(buffer, acc)])
493    end
494
495    defp lexer(" " <> rest, buffer, acc) do
496      lexer(rest, "", maybe_prepend_buffer(buffer, acc))
497    end
498
499    defp lexer(<<char::utf8, rest::binary>>, buffer, acc) do
500      lexer(rest, <<buffer::binary, char::utf8>>, acc)
501    end
502
503    defp lexer(<<>>, buffer, acc) do
504      maybe_prepend_buffer(buffer, acc)
505    end
506
507    defp maybe_prepend_buffer("", acc), do: acc
508
509    defp maybe_prepend_buffer(buffer, [head | _] = acc)
510         when is_atom(head) and head not in [:and, :or],
511         do: [buffer | acc]
512
513    defp maybe_prepend_buffer(buffer, acc),
514      do: [buffer, :== | acc]
515
516    defp revert_lexed([version, op, cond | rest], acc)
517         when is_binary(version) and is_atom(op) and cond in [:or, :and] do
518      with {:ok, version} <- validate_requirement(op, version) do
519        revert_lexed(rest, [cond, op, version | acc])
520      end
521    end
522
523    defp revert_lexed([version, op], acc) when is_binary(version) and is_atom(op) do
524      with {:ok, version} <- validate_requirement(op, version) do
525        {:ok, [op, version | acc]}
526      end
527    end
528
529    defp revert_lexed(_rest, _acc), do: :error
530
531    defp validate_requirement(op, version) do
532      case parse_version(version, true) do
533        {:ok, version} when op == :~> -> {:ok, version}
534        {:ok, {_, _, patch, _, _} = version} when is_integer(patch) -> {:ok, version}
535        _ -> :error
536      end
537    end
538
539    @spec parse_requirement(String.t()) :: {:ok, term} | :error
540    def parse_requirement(source) do
541      revert_lexed(lexer(source), [])
542    end
543
544    def parse_version(string, approximate? \\ false) when is_binary(string) do
545      destructure [version_with_pre, build], String.split(string, "+", parts: 2)
546      destructure [version, pre], String.split(version_with_pre, "-", parts: 2)
547      destructure [major, minor, patch, next], String.split(version, ".")
548
549      with nil <- next,
550           {:ok, major} <- require_digits(major),
551           {:ok, minor} <- require_digits(minor),
552           {:ok, patch} <- maybe_patch(patch, approximate?),
553           {:ok, pre_parts} <- optional_dot_separated(pre),
554           {:ok, pre_parts} <- convert_parts_to_integer(pre_parts, []),
555           {:ok, build_parts} <- optional_dot_separated(build) do
556        {:ok, {major, minor, patch, pre_parts, build_parts}}
557      else
558        _other -> :error
559      end
560    end
561
562    defp require_digits(nil), do: :error
563
564    defp require_digits(string) do
565      if leading_zero?(string), do: :error, else: parse_digits(string, "")
566    end
567
568    defp leading_zero?(<<?0, _, _::binary>>), do: true
569    defp leading_zero?(_), do: false
570
571    defp parse_digits(<<char, rest::binary>>, acc) when char in ?0..?9,
572      do: parse_digits(rest, <<acc::binary, char>>)
573
574    defp parse_digits(<<>>, acc) when byte_size(acc) > 0, do: {:ok, String.to_integer(acc)}
575    defp parse_digits(_, _acc), do: :error
576
577    defp maybe_patch(patch, approximate?)
578    defp maybe_patch(nil, true), do: {:ok, nil}
579    defp maybe_patch(patch, _), do: require_digits(patch)
580
581    defp optional_dot_separated(nil), do: {:ok, []}
582
583    defp optional_dot_separated(string) do
584      parts = String.split(string, ".")
585
586      if Enum.all?(parts, &(&1 != "" and valid_identifier?(&1))) do
587        {:ok, parts}
588      else
589        :error
590      end
591    end
592
593    defp convert_parts_to_integer([part | rest], acc) do
594      case parse_digits(part, "") do
595        {:ok, integer} ->
596          if leading_zero?(part) do
597            :error
598          else
599            convert_parts_to_integer(rest, [integer | acc])
600          end
601
602        :error ->
603          convert_parts_to_integer(rest, [part | acc])
604      end
605    end
606
607    defp convert_parts_to_integer([], acc) do
608      {:ok, Enum.reverse(acc)}
609    end
610
611    defp valid_identifier?(<<char, rest::binary>>)
612         when char in ?0..?9
613         when char in ?a..?z
614         when char in ?A..?Z
615         when char == ?- do
616      valid_identifier?(rest)
617    end
618
619    defp valid_identifier?(<<>>) do
620      true
621    end
622
623    defp valid_identifier?(_other) do
624      false
625    end
626  end
627end
628
629defimpl String.Chars, for: Version do
630  def to_string(version) do
631    pre = pre(version.pre)
632    build = if build = version.build, do: "+#{build}"
633    "#{version.major}.#{version.minor}.#{version.patch}#{pre}#{build}"
634  end
635
636  defp pre([]) do
637    ""
638  end
639
640  defp pre(pre) do
641    "-" <>
642      Enum.map_join(pre, ".", fn
643        int when is_integer(int) -> Integer.to_string(int)
644        string when is_binary(string) -> string
645      end)
646  end
647end
648
649defimpl Inspect, for: Version do
650  def inspect(self, _opts) do
651    "#Version<" <> to_string(self) <> ">"
652  end
653end
654
655defimpl String.Chars, for: Version.Requirement do
656  def to_string(%Version.Requirement{source: source}) do
657    source
658  end
659end
660
661defimpl Inspect, for: Version.Requirement do
662  def inspect(%Version.Requirement{source: source}, _opts) do
663    "#Version.Requirement<" <> source <> ">"
664  end
665end
666