dependabot-go_modules 0.87.0
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.
- checksums.yaml +7 -0
- data/helpers/Makefile +9 -0
- data/helpers/build +26 -0
- data/helpers/go.mod +13 -0
- data/helpers/go.sum +6 -0
- data/helpers/importresolver/main.go +34 -0
- data/helpers/main.go +77 -0
- data/helpers/updatechecker/main.go +107 -0
- data/helpers/updater/go.mod +3 -0
- data/helpers/updater/go.sum +2 -0
- data/helpers/updater/helpers.go +57 -0
- data/helpers/updater/main.go +48 -0
- data/lib/dependabot/go_modules.rb +11 -0
- data/lib/dependabot/go_modules/file_fetcher.rb +66 -0
- data/lib/dependabot/go_modules/file_parser.rb +131 -0
- data/lib/dependabot/go_modules/file_updater.rb +73 -0
- data/lib/dependabot/go_modules/file_updater/go_mod_updater.rb +80 -0
- data/lib/dependabot/go_modules/metadata_finder.rb +58 -0
- data/lib/dependabot/go_modules/native_helpers.rb +20 -0
- data/lib/dependabot/go_modules/path_converter.rb +72 -0
- data/lib/dependabot/go_modules/requirement.rb +148 -0
- data/lib/dependabot/go_modules/update_checker.rb +114 -0
- data/lib/dependabot/go_modules/version.rb +43 -0
- metadata +191 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "open3"
|
4
|
+
require "dependabot/dependency"
|
5
|
+
require "dependabot/file_parsers/base/dependency_set"
|
6
|
+
require "dependabot/go_modules/path_converter"
|
7
|
+
require "dependabot/errors"
|
8
|
+
require "dependabot/file_parsers"
|
9
|
+
require "dependabot/file_parsers/base"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module GoModules
|
13
|
+
class FileParser < Dependabot::FileParsers::Base
|
14
|
+
GIT_VERSION_REGEX = /^v\d+\.\d+\.\d+-.*-(?<sha>[0-9a-f]{12})$/.freeze
|
15
|
+
|
16
|
+
def parse
|
17
|
+
dependency_set = Dependabot::FileParsers::Base::DependencySet.new
|
18
|
+
|
19
|
+
i = 0
|
20
|
+
chunks = module_info(go_mod).lines.
|
21
|
+
group_by { |line| line == "{\n" ? i += 1 : i }
|
22
|
+
deps = chunks.values.map { |chunk| JSON.parse(chunk.join) }
|
23
|
+
|
24
|
+
deps.each do |dep|
|
25
|
+
# The project itself appears in this list as "Main"
|
26
|
+
next if dep["Main"]
|
27
|
+
|
28
|
+
dependency = dependency_from_details(dep)
|
29
|
+
dependency_set << dependency if dependency
|
30
|
+
end
|
31
|
+
|
32
|
+
dependency_set.dependencies
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def go_mod
|
38
|
+
@go_mod ||= get_original_file("go.mod")
|
39
|
+
end
|
40
|
+
|
41
|
+
def check_required_files
|
42
|
+
raise "No go.mod!" unless go_mod
|
43
|
+
end
|
44
|
+
|
45
|
+
def dependency_from_details(details)
|
46
|
+
source =
|
47
|
+
if rev_identifier?(details) then git_source(details)
|
48
|
+
else { type: "default", source: details["Path"] }
|
49
|
+
end
|
50
|
+
|
51
|
+
version = details["Version"]&.sub(/^v?/, "")
|
52
|
+
|
53
|
+
reqs = [{
|
54
|
+
requirement: rev_identifier?(details) ? nil : details["Version"],
|
55
|
+
file: go_mod.name,
|
56
|
+
source: source,
|
57
|
+
groups: []
|
58
|
+
}]
|
59
|
+
|
60
|
+
Dependency.new(
|
61
|
+
name: details["Path"],
|
62
|
+
version: version,
|
63
|
+
requirements: details["Indirect"] ? [] : reqs,
|
64
|
+
package_manager: "dep"
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def module_info(go_mod)
|
69
|
+
@module_info ||=
|
70
|
+
SharedHelpers.in_a_temporary_directory do |path|
|
71
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
72
|
+
File.write("go.mod", go_mod.content)
|
73
|
+
|
74
|
+
command = "GO111MODULE=on go mod edit -print > /dev/null"
|
75
|
+
command += " && GO111MODULE=on go list -m -json all"
|
76
|
+
stdout, stderr, status = Open3.capture3(command)
|
77
|
+
handle_parser_error(path, stderr) unless status.success?
|
78
|
+
stdout
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def handle_parser_error(path, stderr)
|
84
|
+
case stderr
|
85
|
+
when /go: .*: unknown revision/
|
86
|
+
line = stderr.lines.grep(/unknown revision/).first
|
87
|
+
raise Dependabot::DependencyFileNotResolvable, line.strip
|
88
|
+
when /go: .*: unrecognized import path/
|
89
|
+
line = stderr.lines.grep(/unrecognized import/).first
|
90
|
+
raise Dependabot::DependencyFileNotResolvable, line.strip
|
91
|
+
when /go: errors parsing go.mod/
|
92
|
+
msg = stderr.gsub(path.to_s, "").strip
|
93
|
+
raise Dependabot::DependencyFileNotParseable.new(go_mod.path, msg)
|
94
|
+
else
|
95
|
+
msg = stderr.gsub(path.to_s, "").strip
|
96
|
+
raise Dependabot::DependencyFileNotParseable.new(go_mod.path, msg)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def rev_identifier?(dep)
|
101
|
+
dep["Version"]&.match?(GIT_VERSION_REGEX)
|
102
|
+
end
|
103
|
+
|
104
|
+
def git_source(dep)
|
105
|
+
url = PathConverter.git_url_for_path(dep["Path"])
|
106
|
+
|
107
|
+
# Currently, we have no way of knowing whether the commit tagged
|
108
|
+
# is being used because a branch is being followed or because a
|
109
|
+
# particular ref is in use. We *assume* that a particular ref is in
|
110
|
+
# use (which means we'll only propose updates when its included in
|
111
|
+
# a release)
|
112
|
+
{
|
113
|
+
type: "git",
|
114
|
+
url: url || dep["Path"],
|
115
|
+
ref: git_revision(dep),
|
116
|
+
branch: nil
|
117
|
+
}
|
118
|
+
end
|
119
|
+
|
120
|
+
def git_revision(dep)
|
121
|
+
raw_version = dep.fetch("Version")
|
122
|
+
return raw_version unless raw_version.match?(GIT_VERSION_REGEX)
|
123
|
+
|
124
|
+
raw_version.match(GIT_VERSION_REGEX).named_captures.fetch("sha")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
Dependabot::FileParsers.
|
131
|
+
register("go_modules", Dependabot::GoModules::FileParser)
|
@@ -0,0 +1,73 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/shared_helpers"
|
4
|
+
require "dependabot/file_updaters"
|
5
|
+
require "dependabot/file_updaters/base"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class FileUpdater < Dependabot::FileUpdaters::Base
|
10
|
+
require_relative "file_updater/go_mod_updater"
|
11
|
+
|
12
|
+
def self.updated_files_regex
|
13
|
+
[
|
14
|
+
/^go\.mod$/,
|
15
|
+
/^go\.sum$/
|
16
|
+
]
|
17
|
+
end
|
18
|
+
|
19
|
+
def updated_dependency_files
|
20
|
+
updated_files = []
|
21
|
+
|
22
|
+
if go_mod && file_changed?(go_mod)
|
23
|
+
updated_files <<
|
24
|
+
updated_file(
|
25
|
+
file: go_mod,
|
26
|
+
content: file_updater.updated_go_mod_content
|
27
|
+
)
|
28
|
+
|
29
|
+
if go_sum && go_sum.content != file_updater.updated_go_sum_content
|
30
|
+
updated_files <<
|
31
|
+
updated_file(
|
32
|
+
file: go_sum,
|
33
|
+
content: file_updater.updated_go_sum_content
|
34
|
+
)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
raise "No files changed!" if updated_files.none?
|
39
|
+
|
40
|
+
updated_files
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def check_required_files
|
46
|
+
return if go_mod
|
47
|
+
|
48
|
+
raise "No go.mod!"
|
49
|
+
end
|
50
|
+
|
51
|
+
def go_mod
|
52
|
+
@go_mod ||= get_original_file("go.mod")
|
53
|
+
end
|
54
|
+
|
55
|
+
def go_sum
|
56
|
+
@go_sum ||= get_original_file("go.sum")
|
57
|
+
end
|
58
|
+
|
59
|
+
def file_updater
|
60
|
+
@file_updater ||=
|
61
|
+
GoModUpdater.new(
|
62
|
+
dependencies: dependencies,
|
63
|
+
go_mod: go_mod,
|
64
|
+
go_sum: go_sum,
|
65
|
+
credentials: credentials
|
66
|
+
)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
Dependabot::FileUpdaters.
|
73
|
+
register("go_modules", Dependabot::GoModules::FileUpdater)
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/utils/go/shared_helper"
|
4
|
+
require "dependabot/go_modules/file_updater"
|
5
|
+
require "dependabot/go_modules/native_helpers"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class FileUpdater
|
10
|
+
class GoModUpdater
|
11
|
+
def initialize(dependencies:, go_mod:, go_sum:, credentials:)
|
12
|
+
@dependencies = dependencies
|
13
|
+
@go_mod = go_mod
|
14
|
+
@go_sum = go_sum
|
15
|
+
@credentials = credentials
|
16
|
+
end
|
17
|
+
|
18
|
+
def updated_go_mod_content
|
19
|
+
@updated_go_mod_content ||=
|
20
|
+
SharedHelpers.in_a_temporary_directory do
|
21
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
22
|
+
File.write("go.mod", go_mod.content)
|
23
|
+
|
24
|
+
deps = dependencies.map do |dep|
|
25
|
+
{
|
26
|
+
name: dep.name,
|
27
|
+
version: "v" + dep.version.sub(/^v/i, ""),
|
28
|
+
indirect: dep.requirements.empty?
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
SharedHelpers.run_helper_subprocess(
|
33
|
+
command: "GO111MODULE=on #{NativeHelpers.helper_path}",
|
34
|
+
function: "updateDependencyFile",
|
35
|
+
args: { dependencies: deps }
|
36
|
+
)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def updated_go_sum_content
|
42
|
+
return nil unless go_sum
|
43
|
+
|
44
|
+
# This needs to be run separately so we don't nest subprocess calls
|
45
|
+
updated_go_mod_content
|
46
|
+
|
47
|
+
@updated_go_sum_content ||=
|
48
|
+
SharedHelpers.in_a_temporary_directory do
|
49
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
50
|
+
File.write("go.mod", updated_go_mod_content)
|
51
|
+
File.write("go.sum", go_sum.content)
|
52
|
+
File.write("main.go", dummy_main_go)
|
53
|
+
|
54
|
+
`GO111MODULE=on go get -d`
|
55
|
+
unless $CHILD_STATUS.success?
|
56
|
+
raise Dependabot::DependencyFileNotParseable, go_sum.path
|
57
|
+
end
|
58
|
+
|
59
|
+
File.read("go.sum")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def dummy_main_go
|
67
|
+
lines = ["package main", "import ("]
|
68
|
+
dependencies.each do |dep|
|
69
|
+
lines << "_ \"#{dep.name}\""
|
70
|
+
end
|
71
|
+
lines << ")"
|
72
|
+
lines << "func main() {}"
|
73
|
+
lines.join("\n")
|
74
|
+
end
|
75
|
+
|
76
|
+
attr_reader :dependencies, :go_mod, :go_sum, :credentials
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dependabot/metadata_finders"
|
4
|
+
require "dependabot/metadata_finders/base"
|
5
|
+
require "dependabot/go_modules/path_converter"
|
6
|
+
|
7
|
+
module Dependabot
|
8
|
+
module GoModules
|
9
|
+
class MetadataFinder < Dependabot::MetadataFinders::Base
|
10
|
+
private
|
11
|
+
|
12
|
+
def look_up_source
|
13
|
+
return look_up_git_dependency_source if git_dependency?
|
14
|
+
|
15
|
+
path_str = (specified_source_string || dependency.name)
|
16
|
+
url = Dependabot::GoModules::PathConverter.
|
17
|
+
git_url_for_path_without_go_helper(path_str)
|
18
|
+
Source.from_url(url) if url
|
19
|
+
end
|
20
|
+
|
21
|
+
def git_dependency?
|
22
|
+
return false unless declared_source_details
|
23
|
+
|
24
|
+
dependency_type =
|
25
|
+
declared_source_details.fetch(:type, nil) ||
|
26
|
+
declared_source_details.fetch("type")
|
27
|
+
|
28
|
+
dependency_type == "git"
|
29
|
+
end
|
30
|
+
|
31
|
+
def look_up_git_dependency_source
|
32
|
+
specified_url =
|
33
|
+
declared_source_details.fetch(:url, nil) ||
|
34
|
+
declared_source_details.fetch("url")
|
35
|
+
|
36
|
+
Source.from_url(specified_url)
|
37
|
+
end
|
38
|
+
|
39
|
+
def specified_source_string
|
40
|
+
declared_source_details&.fetch(:source, nil) ||
|
41
|
+
declared_source_details&.fetch("source", nil)
|
42
|
+
end
|
43
|
+
|
44
|
+
def declared_source_details
|
45
|
+
sources = dependency.requirements.
|
46
|
+
map { |r| r.fetch(:source) }.
|
47
|
+
uniq.compact
|
48
|
+
|
49
|
+
raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
|
50
|
+
|
51
|
+
sources.first
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
Dependabot::MetadataFinders.
|
58
|
+
register("go_modules", Dependabot::GoModules::MetadataFinder)
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dependabot
|
4
|
+
module GoModules
|
5
|
+
module NativeHelpers
|
6
|
+
def self.helper_path
|
7
|
+
clean_path(File.join(native_helpers_root, "go_modules/bin/helper"))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.native_helpers_root
|
11
|
+
default_path = File.join(__dir__, "../../../helpers/install-dir")
|
12
|
+
ENV.fetch("DEPENDABOT_NATIVE_HELPERS_PATH", default_path)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.clean_path(path)
|
16
|
+
Pathname.new(path).cleanpath.to_path
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "excon"
|
4
|
+
require "nokogiri"
|
5
|
+
|
6
|
+
require "dependabot/shared_helpers"
|
7
|
+
require "dependabot/source"
|
8
|
+
require "dependabot/go_modules/native_helpers"
|
9
|
+
|
10
|
+
module Dependabot
|
11
|
+
module GoModules
|
12
|
+
module PathConverter
|
13
|
+
def self.git_url_for_path(path)
|
14
|
+
# Save a query by manually converting golang.org/x names
|
15
|
+
import_path = path.gsub(%r{^golang\.org/x}, "github.com/golang")
|
16
|
+
|
17
|
+
SharedHelpers.run_helper_subprocess(
|
18
|
+
command: NativeHelpers.helper_path,
|
19
|
+
function: "getVcsRemoteForImport",
|
20
|
+
args: { import: import_path }
|
21
|
+
)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Used in dependabot-backend, which doesn't have access to any Go
|
25
|
+
# helpers.
|
26
|
+
# TODO: remove the need for this.
|
27
|
+
def self.git_url_for_path_without_go_helper(path)
|
28
|
+
# Save a query by manually converting golang.org/x names
|
29
|
+
tmp_path = path.gsub(%r{^golang\.org/x}, "github.com/golang")
|
30
|
+
|
31
|
+
# Currently, Dependabot::Source.new will return `nil` if it can't
|
32
|
+
# find a git SCH associated with a path. If it is ever extended to
|
33
|
+
# handle non-git sources we'll need to add an additional check here.
|
34
|
+
return Source.from_url(tmp_path).url if Source.from_url(tmp_path)
|
35
|
+
return "https://#{tmp_path}" if tmp_path.end_with?(".git")
|
36
|
+
return unless (metadata_response = fetch_path_metadata(path))
|
37
|
+
|
38
|
+
# Look for a GitHub, Bitbucket or GitLab URL in the response
|
39
|
+
metadata_response.scan(Dependabot::Source::SOURCE_REGEX) do
|
40
|
+
source_url = Regexp.last_match.to_s
|
41
|
+
return Source.from_url(source_url).url
|
42
|
+
end
|
43
|
+
|
44
|
+
# If none are found, parse the response and return the go-import path
|
45
|
+
doc = Nokogiri::XML(metadata_response)
|
46
|
+
doc.remove_namespaces!
|
47
|
+
import_details =
|
48
|
+
doc.xpath("//meta").
|
49
|
+
find { |n| n.attributes["name"]&.value == "go-import" }&.
|
50
|
+
attributes&.fetch("content")&.value&.split(/\s+/)
|
51
|
+
return unless import_details && import_details[1] == "git"
|
52
|
+
|
53
|
+
import_details[2]
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.fetch_path_metadata(path)
|
57
|
+
# TODO: This is not robust! Instead, we should shell out to Go and
|
58
|
+
# use https://github.com/Masterminds/vcs.
|
59
|
+
response = Excon.get(
|
60
|
+
"https://#{path}?go-get=1",
|
61
|
+
idempotent: true,
|
62
|
+
**SharedHelpers.excon_defaults
|
63
|
+
)
|
64
|
+
|
65
|
+
return unless response.status == 200
|
66
|
+
|
67
|
+
response.body
|
68
|
+
end
|
69
|
+
private_class_method :fetch_path_metadata
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
################################################################################
|
4
|
+
# For more details on Go version constraints, see: #
|
5
|
+
# - https://github.com/Masterminds/semver #
|
6
|
+
# - https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md #
|
7
|
+
################################################################################
|
8
|
+
|
9
|
+
require "dependabot/go_modules/version"
|
10
|
+
|
11
|
+
module Dependabot
|
12
|
+
module GoModules
|
13
|
+
class Requirement < Gem::Requirement
|
14
|
+
WILDCARD_REGEX = /(?:\.|^)[xX*]/.freeze
|
15
|
+
OR_SEPARATOR = /(?<=[a-zA-Z0-9*])\s*\|{2}/.freeze
|
16
|
+
|
17
|
+
# Override the version pattern to allow a 'v' prefix
|
18
|
+
quoted = OPS.keys.map { |k| Regexp.quote(k) }.join("|")
|
19
|
+
version_pattern = "v?#{Version::VERSION_PATTERN}"
|
20
|
+
|
21
|
+
PATTERN_RAW = "\\s*(#{quoted})?\\s*(#{version_pattern})\\s*"
|
22
|
+
PATTERN = /\A#{PATTERN_RAW}\z/.freeze
|
23
|
+
|
24
|
+
# Use GoModules::Version rather than Gem::Version to ensure that
|
25
|
+
# pre-release versions aren't transformed.
|
26
|
+
def self.parse(obj)
|
27
|
+
return ["=", Version.new(obj.to_s)] if obj.is_a?(Gem::Version)
|
28
|
+
|
29
|
+
unless (matches = PATTERN.match(obj.to_s))
|
30
|
+
msg = "Illformed requirement [#{obj.inspect}]"
|
31
|
+
raise BadRequirementError, msg
|
32
|
+
end
|
33
|
+
|
34
|
+
return DefaultRequirement if matches[1] == ">=" && matches[2] == "0"
|
35
|
+
|
36
|
+
[matches[1] || "=", Version.new(matches[2])]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns an array of requirements. At least one requirement from the
|
40
|
+
# returned array must be satisfied for a version to be valid.
|
41
|
+
def self.requirements_array(requirement_string)
|
42
|
+
return [new(nil)] if requirement_string.nil?
|
43
|
+
|
44
|
+
requirement_string.strip.split(OR_SEPARATOR).map do |req_string|
|
45
|
+
new(req_string)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def initialize(*requirements)
|
50
|
+
requirements = requirements.flatten.flat_map do |req_string|
|
51
|
+
req_string.split(",").map do |r|
|
52
|
+
convert_go_constraint_to_ruby_constraint(r.strip)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
super(requirements)
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def convert_go_constraint_to_ruby_constraint(req_string)
|
62
|
+
req_string = req_string
|
63
|
+
req_string = convert_wildcard_characters(req_string)
|
64
|
+
|
65
|
+
if req_string.match?(WILDCARD_REGEX)
|
66
|
+
ruby_range(req_string.gsub(WILDCARD_REGEX, "").gsub(/^[^\d]/, ""))
|
67
|
+
elsif req_string.match?(/^~[^>]/) then convert_tilde_req(req_string)
|
68
|
+
elsif req_string.include?(" - ") then convert_hyphen_req(req_string)
|
69
|
+
elsif req_string.match?(/^[\dv^]/) then convert_caret_req(req_string)
|
70
|
+
elsif req_string.match?(/[<=>]/) then req_string
|
71
|
+
else ruby_range(req_string)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def convert_wildcard_characters(req_string)
|
76
|
+
if req_string.match?(/^[\dv^>~]/)
|
77
|
+
replace_wildcard_in_lower_bound(req_string)
|
78
|
+
elsif req_string.start_with?("<")
|
79
|
+
parts = req_string.split(".")
|
80
|
+
parts.map.with_index do |part, index|
|
81
|
+
next "0" if part.match?(WILDCARD_REGEX)
|
82
|
+
next part.to_i + 1 if parts[index + 1]&.match?(WILDCARD_REGEX)
|
83
|
+
|
84
|
+
part
|
85
|
+
end.join(".")
|
86
|
+
else
|
87
|
+
req_string
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def replace_wildcard_in_lower_bound(req_string)
|
92
|
+
after_wildcard = false
|
93
|
+
|
94
|
+
if req_string.start_with?("~")
|
95
|
+
req_string = req_string.gsub(/(?:(?:\.|^)[xX*])(\.[xX*])+/, "")
|
96
|
+
end
|
97
|
+
|
98
|
+
req_string.split(".").
|
99
|
+
map do |part|
|
100
|
+
part.split("-").map.with_index do |p, i|
|
101
|
+
# Before we hit a wildcard we just return the existing part
|
102
|
+
next p unless p.match?(WILDCARD_REGEX) || after_wildcard
|
103
|
+
|
104
|
+
# On or after a wildcard we replace the version part with zero
|
105
|
+
after_wildcard = true
|
106
|
+
i.zero? ? "0" : "a"
|
107
|
+
end.join("-")
|
108
|
+
end.join(".")
|
109
|
+
end
|
110
|
+
|
111
|
+
def convert_tilde_req(req_string)
|
112
|
+
version = req_string.gsub(/^~/, "")
|
113
|
+
parts = version.split(".")
|
114
|
+
parts << "0" if parts.count < 3
|
115
|
+
"~> #{parts.join('.')}"
|
116
|
+
end
|
117
|
+
|
118
|
+
def convert_hyphen_req(req_string)
|
119
|
+
lower_bound, upper_bound = req_string.split(/\s+-\s+/)
|
120
|
+
[">= #{lower_bound}", "<= #{upper_bound}"]
|
121
|
+
end
|
122
|
+
|
123
|
+
def ruby_range(req_string)
|
124
|
+
parts = req_string.split(".")
|
125
|
+
|
126
|
+
# If we have three or more parts then this is an exact match
|
127
|
+
return req_string if parts.count >= 3
|
128
|
+
|
129
|
+
# If we have no parts then the version is completely unlocked
|
130
|
+
return ">= 0" if parts.count.zero?
|
131
|
+
|
132
|
+
# If we have fewer than three parts we do a partial match
|
133
|
+
parts << "0"
|
134
|
+
"~> #{parts.join('.')}"
|
135
|
+
end
|
136
|
+
|
137
|
+
# Note: Dep's caret notation implementation doesn't distinguish between
|
138
|
+
# pre and post-1.0.0 requirements (unlike in JS)
|
139
|
+
def convert_caret_req(req_string)
|
140
|
+
version = req_string.gsub(/^\^?v?/, "")
|
141
|
+
parts = version.split(".")
|
142
|
+
upper_bound = [parts.first.to_i + 1, 0, 0, "a"].map(&:to_s).join(".")
|
143
|
+
|
144
|
+
[">= #{version}", "< #{upper_bound}"]
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|