semver_dialects 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,37 @@
1
+ require_relative "version_cut"
2
+ require_relative "version_interval"
3
+
4
+ module VersionParser
5
+ def self.parse(versionstring)
6
+ if (versionstring == "=*")
7
+ # special case = All Versions
8
+ return VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), AboveAll.new())
9
+ end
10
+
11
+ version_items = versionstring.split(" ")
12
+ interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), AboveAll.new())
13
+ version_items.each do
14
+ |version_item|
15
+ matches = version_item.match /(?<op>[><=]+) *(?<version>[a-zA-Z0-9\-_\.\*]+)/
16
+ version_string = matches[:version]
17
+ case matches[:op]
18
+ when ">="
19
+ new_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_OPEN, VersionCut.new(version_string), AboveAll.new())
20
+ interval = interval.intersect(new_interval)
21
+ when "<="
22
+ new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_CLOSED, BelowAll.new(), VersionCut.new(version_string))
23
+ interval = interval.intersect(new_interval)
24
+ when "<"
25
+ new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, BelowAll.new(), VersionCut.new(version_string))
26
+ interval = interval.intersect(new_interval)
27
+ when ">"
28
+ new_interval = VersionInterval.new(IntervalType::LEFT_OPEN | IntervalType::RIGHT_OPEN, VersionCut.new(version_string), AboveAll.new())
29
+ interval = interval.intersect(new_interval)
30
+ when "=", "=="
31
+ new_interval = VersionInterval.new(IntervalType::LEFT_CLOSED | IntervalType::RIGHT_CLOSED, VersionCut.new(version_string), VersionCut.new(version_string))
32
+ interval = interval.intersect(new_interval)
33
+ end
34
+ end
35
+ interval
36
+ end
37
+ end
@@ -0,0 +1,191 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'version_parser'
4
+ require 'set'
5
+
6
+ # VersionRange is a utility class that helps managing consecutive version ranges automatically
7
+ # given that they are added in-order
8
+ # Note that join_if_possible should be only activated in case the ranges are added in consecutive order!!
9
+ class VersionRange
10
+ attr_reader :version_intervals, :join_if_possible
11
+
12
+ def initialize(join_if_possible = true)
13
+ @version_intervals = []
14
+ @version_interval_set = Set.new
15
+ @join_if_possible = join_if_possible
16
+ end
17
+
18
+ def add_all(version_range)
19
+ version_range.version_intervals.each { |interval| add(interval) }
20
+ end
21
+
22
+ def add(version_interval)
23
+ return if @version_interval_set.include?(version_interval)
24
+
25
+ if @join_if_possible
26
+ if @version_intervals.empty?
27
+ @version_intervals << version_interval
28
+ @version_interval_set.add(version_interval)
29
+ else
30
+ last = @version_intervals.last
31
+ # nothing to do
32
+ return if last.end_cut == version_interval.start_cut && last.end_cut.value == version_interval.start_cut.value
33
+
34
+ if last.joinable?(version_interval)
35
+ @version_intervals[@version_intervals.size - 1] = last.join(version_interval)
36
+ else
37
+ @version_intervals << version_interval
38
+ @version_interval_set.add(version_interval)
39
+ end
40
+ end
41
+ else
42
+ @version_intervals << version_interval
43
+ @version_interval_set.add(version_interval)
44
+ end
45
+ end
46
+
47
+ def <<(item)
48
+ add(item)
49
+ end
50
+
51
+ def size
52
+ @version_intervals.size
53
+ end
54
+
55
+ def to_s
56
+ @version_intervals.map(&:to_s).join(',')
57
+ end
58
+
59
+ def to_description_s
60
+ @version_intervals.map(&:to_description_s).join(', ').capitalize
61
+ end
62
+
63
+ def to_npm_s
64
+ @version_intervals.map(&:to_npm_s).join('||')
65
+ end
66
+
67
+ def to_conan_s
68
+ to_npm_s
69
+ end
70
+
71
+ def to_nuget_s
72
+ to_maven_s
73
+ end
74
+
75
+ def to_maven_s
76
+ @version_intervals.map(&:to_maven_s).join(',')
77
+ end
78
+
79
+ def to_gem_s
80
+ @version_intervals.map(&:to_gem_s).join('||')
81
+ end
82
+
83
+ def to_pypi_s
84
+ @version_intervals.map(&:to_pypi_s).join('||')
85
+ end
86
+
87
+ def to_go_s
88
+ @version_intervals.map(&:to_go_s).join('||')
89
+ end
90
+
91
+ def to_packagist_s
92
+ @version_intervals.map(&:to_packagist_s).join('||')
93
+ end
94
+
95
+ def to_version_s(package_type)
96
+ case package_type
97
+ when 'npm'
98
+ to_npm_s
99
+ when 'nuget'
100
+ to_nuget_s
101
+ when 'maven'
102
+ to_maven_s
103
+ when 'gem'
104
+ to_gem_s
105
+ when 'pypi'
106
+ to_pypi_s
107
+ when 'packagist'
108
+ to_packagist_s
109
+ when 'go'
110
+ to_go_s
111
+ when 'conan'
112
+ to_conan_s
113
+ else
114
+ ''
115
+ end
116
+ end
117
+
118
+ # inverts the given version interval -- note that this function amy return two version intervals
119
+ # e.g., (2,10], (12, 13], [15, +inf)
120
+ # 1) invert: (-inf, 2], (10, +inf), (-inf, 12], (13, +inf), (15)
121
+ # 2) collapse (-inf, 2], (10, 12], (13, 15)
122
+ #
123
+ # the collapsed inverted ranges can potentially contain fixed versions
124
+ def invert
125
+ inverted = @version_intervals.map(&:invert).flatten
126
+ version_intervals = collapse_intervals(inverted)
127
+ version_intervals_to_range(version_intervals)
128
+ end
129
+
130
+ def collapse
131
+ version_intervals = collapse_intervals(@version_intervals)
132
+ version_intervals_to_range(version_intervals)
133
+ end
134
+
135
+ def includes?(version_interval)
136
+ @version_interval_set.include?(version_interval)
137
+ end
138
+
139
+ def overlaps_with?(version_interval)
140
+ @version_interval_set.each do |interval|
141
+ return true unless interval.intersect(version_interval).instance_of? EmptyInterval
142
+ end
143
+ false
144
+ end
145
+
146
+ def first
147
+ @version_intervals.first
148
+ end
149
+
150
+ def empty?
151
+ @version_intervals.empty?
152
+ end
153
+
154
+ def any?
155
+ @version_intervals.any?
156
+ end
157
+
158
+ def universal?
159
+ @version_intervals.each do |version_interval|
160
+ return true if version_interval.universal?
161
+ end
162
+ false
163
+ end
164
+
165
+ private
166
+
167
+ def version_intervals_to_range(version_intervals)
168
+ ret = VersionRange.new(false)
169
+ version_intervals.each do |version_interval|
170
+ ret << version_interval
171
+ end
172
+ ret
173
+ end
174
+
175
+ def collapse_intervals(version_intervals)
176
+ interval_cp = []
177
+ interval_cp += version_intervals
178
+ ret = [interval_cp.shift]
179
+
180
+ interval_cp.each do |item|
181
+ last = ret.last
182
+ if last.intersect(item).instance_of?(EmptyInterval)
183
+ ret << item
184
+ else
185
+ ret.pop
186
+ ret << last.collapse(item)
187
+ end
188
+ end
189
+ ret
190
+ end
191
+ end
@@ -0,0 +1,172 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../utils.rb'
4
+
5
+ # A prepropcessor -- convert different version strings to something that can be digested by the
6
+ # version parser
7
+ module VersionTranslator
8
+ def self.translate_npm(version_string)
9
+ version_string.split('||').map do |item|
10
+ add_missing_operator(single_space_after_operator(item.strip.gsub(/&&/, ' ')))
11
+ end
12
+ end
13
+
14
+ def self.translate_conan(version_string)
15
+ translate_npm(version_string)
16
+ end
17
+
18
+ def self.translate_go(version_string)
19
+ translate_gem(version_string)
20
+ end
21
+
22
+ def self.translate_gem(version_string)
23
+ version_string.split('||').map do |item|
24
+ add_missing_operator(single_space_after_operator(item.strip.gsub(/\s+/, ' ')))
25
+ end
26
+ end
27
+
28
+ def self.translate_packagist(version_string)
29
+ translate_pypi(version_string)
30
+ end
31
+
32
+ def self.translate_pypi(version_string)
33
+ version_string.split('||').map do |item|
34
+ add_missing_operator(single_space_after_operator(comma_to_space(item)))
35
+ end
36
+ end
37
+
38
+ def self.translate_nuget(version_string)
39
+ translate_maven(version_string)
40
+ end
41
+
42
+ def self.translate_maven(version_string)
43
+ lexing_maven_version_string(version_string).map { |item| translate_mvn_version_item(item) }
44
+ end
45
+
46
+ def self.add_missing_operator(version_string)
47
+ starts_with_operator?(version_string) ? version_string : "=#{version_string}"
48
+ end
49
+
50
+ def self.single_space_after_operator(version_string)
51
+ version_string.gsub(/([>=<]+) +/, '\1').gsub(/\s+/, ' ')
52
+ end
53
+
54
+ def self.starts_with_operator?(version_item)
55
+ version_item.match(/^[=><]/) ? true : false
56
+ end
57
+
58
+ def self.comma_to_space(version_string)
59
+ version_string.strip.gsub(/,/, ' ')
60
+ end
61
+
62
+ def self.lexing_maven_version_string(version_string)
63
+ open = false
64
+ substring = ''
65
+ ret = []
66
+ version_string.each_char do |c|
67
+ case c
68
+ when '(', '['
69
+ if open
70
+ puts "malformed maven version string #{version_string}"
71
+ exit(-1)
72
+ else
73
+ unless substring.empty?
74
+ ret << substring
75
+ substring = ''
76
+ end
77
+ open = true
78
+ substring += c
79
+ end
80
+ when ')', ']'
81
+ if !open
82
+ puts "malformed maven version string #{version_string}"
83
+ exit(-1)
84
+ else
85
+ open = false
86
+ substring += c
87
+ ret << substring
88
+ substring = ''
89
+ end
90
+ when ','
91
+ substring += c if open
92
+ when ' '
93
+ # nothing to do
94
+ substring += ''
95
+ else
96
+ substring += c
97
+ end
98
+ end
99
+ if open
100
+ puts "malformed maven version string #{version_string}"
101
+ exit(-1)
102
+ end
103
+ ret << substring unless substring.empty?
104
+ ret
105
+ end
106
+
107
+ def self.parenthesized?(version_item)
108
+ version_item.match(/^[\(\[]/) && version_item.match(/[\]\)]$/) ? true : false
109
+ end
110
+
111
+ def self.translate_mvn_version_item(version_item)
112
+ content = ''
113
+ parens_pattern = ''
114
+ if parenthesized?(version_item)
115
+ content = version_item[1, version_item.size - 2]
116
+ parens_pattern = version_item[0] + version_item[version_item.size - 1]
117
+ # special case -- unversal version range
118
+ return '=*' if content.strip == ','
119
+ else
120
+ # according to the doc, if there is a plain version string in maven, it means 'starting from version x'
121
+ # https://docs.oracle.com/middleware/1212/core/MAVEN/maven_version.htm#MAVEN8903
122
+ content = "#{version_item},"
123
+ parens_pattern = '[)'
124
+ end
125
+
126
+ args = content.split(',')
127
+ first_non_empty_arg = args.find(&:present?)
128
+
129
+ if content.start_with?(',')
130
+ # {,y}
131
+ case parens_pattern
132
+ when '[]'
133
+ "<=#{first_non_empty_arg}"
134
+ when '()'
135
+ "<#{first_non_empty_arg}"
136
+ when '[)'
137
+ "<#{first_non_empty_arg}"
138
+ else
139
+ # par_pattern == "(]"
140
+ "<=#{first_non_empty_arg}"
141
+ end
142
+ elsif content.end_with?(',')
143
+ # {x,}
144
+ case parens_pattern
145
+ when '[]'
146
+ ">=#{first_non_empty_arg}"
147
+ when '()'
148
+ ">#{first_non_empty_arg}"
149
+ when '[)'
150
+ ">=#{first_non_empty_arg}"
151
+ else
152
+ # par_pattern == "(]"
153
+ ">#{first_non_empty_arg}"
154
+ end
155
+ elsif content[','].nil?
156
+ # [x,x]
157
+ "=#{content}"
158
+ else
159
+ case parens_pattern
160
+ when '[]'
161
+ ">=#{args[0]} <=#{args[1]}"
162
+ when '()'
163
+ ">#{args[0]} <#{args[1]}"
164
+ when '[)'
165
+ ">=#{args[0]} <#{args[1]}"
166
+ else
167
+ # par_pattern == "(]"
168
+ ">#{args[0]} <=#{args[1]}"
169
+ end
170
+ end
171
+ end
172
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SemverDialects
4
+ VERSION = '1.0.1'
5
+ end
data/lib/utils.rb ADDED
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ # monkey-patch String class
4
+ class String
5
+ def unwrap
6
+ s = self
7
+ s = s[1..s.length - 1] if s.start_with?('(')
8
+ s = s[0..s.length - 2] if s.end_with?(')')
9
+ s
10
+ end
11
+
12
+ def present?
13
+ !empty?
14
+ end
15
+
16
+ def number?
17
+ true if Integer(self)
18
+ rescue StandardError
19
+ false
20
+ end
21
+
22
+ def initial
23
+ self[0, 1]
24
+ end
25
+
26
+ def unquote
27
+ delete_suffix('"').delete_prefix('"').delete_suffix('\'').delete_prefix('\'')
28
+ end
29
+
30
+ def csv_unquote
31
+ unquote.unquote.unquote
32
+ end
33
+
34
+ def remove_trailing_number
35
+ gsub(/([^\d]*)\d+$/, '\1')
36
+ end
37
+
38
+ def chars_only
39
+ gsub(/[^0-9A-Za-z]/, '')
40
+ end
41
+ end