package semantic_version

  1. Overview
  2. Docs

Source file semantic_version.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
module Stable = struct
  open Core.Core_stable

  module V1 = struct
    module Pre_release_tags = struct
      type tag = string [@@deriving bin_io]
      type t = tag list [@@deriving bin_io]

      let compare_tag t1 t2 =
        let maybe_int x = Core.(Option.try_with (fun () -> Int.of_string x)) in
        match maybe_int t1, maybe_int t2 with
        | None, None -> [%compare: string] t1 t2
        | Some x, Some y -> [%compare: int] x y
        | None, Some _ -> 1
        | Some _, None -> -1
      ;;

      let compare t1 t2 =
        (* The pre-release tag list comparison is a bit quirky. An empty list is
           greater than a non-empty list, but a longer list is greater than a shorter
           list if all preceding tags are equal. *)
        match t1, t2 with
        | [], [] -> 0
        | [], _ :: _ -> 1
        | _ :: _, [] -> -1
        | _ :: _, _ :: _ -> [%compare: tag list] t1 t2
      ;;
    end

    type t =
      { major : int
      ; minor : int
      ; patch : int
      ; pre_release_tags : Pre_release_tags.t
      ; build_metadata : string list [@compare.ignore]
      }
    [@@deriving bin_io, compare, fields]

    let to_string { major; minor; patch; pre_release_tags; build_metadata } =
      let version = Printf.sprintf "%d.%d.%d" major minor patch in
      let version =
        match pre_release_tags with
        | [] -> version
        | _ :: _ -> version ^ "-" ^ Core.String.concat ~sep:"." pre_release_tags
      in
      match build_metadata with
      | [] -> version
      | _ :: _ -> version ^ "+" ^ Core.String.concat ~sep:"." build_metadata
    ;;

    let regex =
      lazy
        (let open Re in
         let zero = char '0' in
         let dot = char '.' in
         let dash = char '-' in
         let plus = char '+' in
         let positive_digit = diff digit zero in
         let numeric_id = alt [ zero; seq [ positive_digit; rep digit ] ] in
         let non_digit = alt [ dash; inter [ alpha; ascii ] ] in
         let ident = alt [ digit; non_digit ] in
         let idents = rep1 ident in
         let alnum_id =
           (* An alphanumeric identifier must have a non-digit. Unlike a numeric
              identifier, it can have leading zeroes. *)
           seq [ rep digit; non_digit; rep ident ]
         in
         let major = group numeric_id in
         let minor = group numeric_id in
         let patch = group numeric_id in
         let dot_separated pattern = seq [ pattern; rep (seq [ dot; pattern ]) ] in
         let pre_release_tags = group (dot_separated (alt [ numeric_id; alnum_id ])) in
         let build_metadata = group (dot_separated idents) in
         seq
           [ bol
           ; major
           ; dot
           ; minor
           ; dot
           ; patch
           ; opt (seq [ dash; pre_release_tags ])
           ; opt (seq [ plus; build_metadata ])
           ; eol
           ]
         |> compile)
    ;;

    let of_string str =
      let get_int groups nth = Re.Group.get groups nth |> Core.Int.of_string in
      let get_opt_list groups nth =
        match Re.Group.test groups nth with
        | false -> []
        | true -> Re.Group.get groups nth |> Core.String.split ~on:'.'
      in
      let groups = Re.exec (Core.force regex) str in
      let major = get_int groups 1 in
      let minor = get_int groups 2 in
      let patch = get_int groups 3 in
      let pre_release_tags = get_opt_list groups 4 in
      let build_metadata = get_opt_list groups 5 in
      { major; minor; patch; pre_release_tags; build_metadata }
    ;;

    let sexp_of_t t = t |> to_string |> [%sexp_of: string]
    let t_of_sexp sexp = sexp |> [%of_sexp: string] |> of_string

    include (val Comparator.V1.make ~compare ~sexp_of_t)
  end

  module Latest = V1
end

open Core
include Semantic_version_intf

module Make () = struct
  module Stable = Stable
  include Stable.Latest
  include Comparable.Make_using_comparator (Stable.Latest)

  let initial_dev_release =
    { major = 0; minor = 1; patch = 0; pre_release_tags = []; build_metadata = [] }
  ;;

  let next_patch_release t =
    { t with patch = t.patch + 1; pre_release_tags = []; build_metadata = [] }
  ;;

  let next_minor_release t =
    { t with minor = t.minor + 1; patch = 0; pre_release_tags = []; build_metadata = [] }
  ;;

  let next_major_release t =
    { major = t.major + 1
    ; minor = 0
    ; patch = 0
    ; pre_release_tags = []
    ; build_metadata = []
    }
  ;;

  let arg_type = Command.Arg_type.create of_string
end