-
-
Notifications
You must be signed in to change notification settings - Fork 1
/
vcomp.el
210 lines (176 loc) · 7.62 KB
/
vcomp.el
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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
;;; vcomp.el --- Compare version strings -*- lexical-binding:t -*-
;; Copyright (C) 2008-2024 Jonas Bernoulli
;; Copyright (C) 2007-2008 Free Software Foundation, Inc.
;; Author: Jonas Bernoulli <jonas@bernoul.li>
;; Homepage: https://github.com/tarsius/vcomp
;; Keywords: versions
;; Package-Requires: ((emacs "25.1"))
;; SPDX-License-Identifier: GPL-3.0-or-later
;; This file is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published
;; by the Free Software Foundation, either version 3 of the License,
;; or (at your option) any later version.
;;
;; This file is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;;
;; You should have received a copy of the GNU General Public License
;; along with this file. If not, see <https://www.gnu.org/licenses/>.
;;; Commentary:
;; Compare version strings.
;; See README.org for more information.
;;; Code:
(defconst vcomp--regexp
(concat "^\\("
"\\([0-9]+\\(?:[-_.][0-9]+\\)*\\)"
"\\([a-z]\\)?"
"\\(?:[-_]?\\(alpha\\|beta\\|pre\\|rc\\|p\\)\\([0-9]+\\)?\\)?"
"\\(?:-r\\([0-9]+\\)\\)?"
"\\)$")
"The regular expression used to compare version strings.")
(defvar vcomp--fill-number -1
"Integer used for missing positions in numeric part of versions.
Either -1 or 0. See the library header of `vcomp.el' for more
information.")
(defun vcomp-version-p (string)
"Return t if STRING is a valid version string."
(and (string-match-p vcomp--regexp string) t))
(defun vcomp--intern (version &optional prefix noerror)
"Convert version string VERSION to the internal format.
If optional PREFIX is non-nil it is a partial regular expression which
matches a prefix VERSION may (but does not need to) begin with, like e.g.
a package name. PREFIX must not begin with ^ (unless you want to
literally match it) or contain any non-shy grouping constructs.
If VERSION cannot be converted an error is raised unless optional NOERROR
is non-nil in which case nil is returned.
See the library header of `vcomp.el' for more information about
the internal format."
(if (string-match (if prefix
(concat "^" prefix (substring vcomp--regexp 1))
vcomp--regexp)
version)
(let ((num (mapcar #'string-to-number
(save-match-data
(split-string (match-string 2 version) "[-_.]"))))
(alp (match-string 3 version))
(tag (match-string 4 version))
(tnm (string-to-number (or (match-string 5 version) "0")))
(rev (string-to-number (or (match-string 6 version) "0"))))
(list num (nconc (list (if (not alp)
0
(setq alp (string-to-char alp))
(if (< alp 97)
(+ alp 32)
alp)))
(cond ((equal tag "alpha")
(list 100 tnm))
((equal tag "beta")
(list 101 tnm))
((equal tag "pre")
(list 102 tnm))
((equal tag "rc")
(list 103 tnm))
((equal tag nil)
(list 104 tnm))
((equal tag "p")
(list 105 tnm)))
(list rev))))
(unless noerror
(error "%S isn't a valid version string" version))))
(defun vcomp-compare (v1 v2 pred)
"Compare version strings V1 and V2 using PRED."
(vcomp--compare-interned (vcomp--intern v1)
(vcomp--intern v2)
pred))
(defun vcomp--compare-interned (v1 v2 pred)
(let ((l1 (length (car v1)))
(l2 (length (car v2))))
(cond ((> l1 l2)
(nconc (car v2) (make-list (- l1 l2) vcomp--fill-number)))
((> l2 l1)
(nconc (car v1) (make-list (- l2 l1) vcomp--fill-number)))))
(setq v1 (nconc (car v1) (cadr v1)))
(setq v2 (nconc (car v2) (cadr v2)))
(while (and v1 v2 (= (car v1) (car v2)))
(setq v1 (cdr v1))
(setq v2 (cdr v2)))
(if v1
(if v2
(funcall pred (car v1) (car v2))
(funcall pred v1 -1))
(if v2
(funcall pred -1 v2)
(funcall pred 0 0))))
(defun vcomp< (v1 v2)
"Return t if the version string V1 is smaller than V2."
(vcomp-compare v1 v2 #'<))
(defun vcomp> (v1 v2)
"Return t if the version string V1 is greater than V2."
(vcomp-compare v1 v2 #'>))
(defun vcomp= (v1 v2)
"Return t if the version string V1 is equal to V2."
(vcomp-compare v1 v2 #'=))
(defun vcomp<= (v1 v2)
"Return t if the version string V1 is smaller than or equal to V2."
(vcomp-compare v1 v2 #'<=))
(defun vcomp>= (v1 v2)
"Return t if the version string V1 is greater than or equal to V2."
(vcomp-compare v1 v2 #'>=))
(defun vcomp-max (version &rest versions)
"Return largest of all the arguments (which must be version strings)."
(dolist (elt versions)
(when (vcomp-compare elt version #'>)
(setq version elt)))
version)
(defun vcomp-min (version &rest versions)
"Return smallest of all the arguments (which must be version strings)."
(dolist (elt versions)
(when (vcomp-compare elt version #'<)
(setq version elt)))
version)
(defun vcomp-normalize (version)
"Normalize VERSION which has to be a valid version string."
(if (string-match vcomp--regexp version)
(let ((num (match-string 2 version))
(alp (match-string 3 version))
(tag (match-string 4 version))
(tnm (match-string 5 version))
(rev (match-string 6 version)))
(concat (save-match-data
(replace-regexp-in-string "[-_]" "." num))
(and alp (downcase alp))
(and tag (concat "_" (downcase tag)))
(and tnm (not (equal tnm "0")) tnm)
(and rev (concat "-r" rev))))
(error "%S isn't a valid version string" version)))
(defun vcomp--prefix-regexp (&optional name)
(concat "^\\(?:\\(?:"
(and name (format "%s\\|" name))
"v\\(?:ersion\\)?\\|r\\(?:elease\\)"
"?\\)[-_]?\\)?"))
(defun vcomp-prefixed-version-p (string &optional prefix)
"Return non-nil if STRING is a valid but possibly prefixed version string.
The returned value is the normalized part of STRING which is a valid
version string.
If optional PREFIX is non-nil it has to be a string. If it begin with
\"^\" it is considered a partial regexp. It must not end with \"$\" and may
only contain *shy* groups. In this case STRING is matched against:
(concat PREFIX (substring vcomp--regexp 1))
Otherwise if PREFIX is nil or does not begin with \"^\" the function
`vcomp--prefix-regexp' is used to create the prefix regexp. In this
case STRING is matched against:
(concat (vcomp--prefix-regexp PREFIX) (substring vcomp--regexp 1))
This will detect common prefixes like \"v\" or \"revision-\"."
(and (string-match (concat (if (and prefix (string-match-p "^^" prefix))
prefix
(vcomp--prefix-regexp prefix))
(substring vcomp--regexp 1))
string)
(vcomp-normalize (match-string 1 string))))
(provide 'vcomp)
;; Local Variables:
;; indent-tabs-mode: nil
;; End:
;;; vcomp.el ends here