gjp 0.6.0 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +2 -2
- data/bin/gjp +8 -4
- data/lib/gjp.rb +1 -0
- data/lib/gjp/cli.rb +60 -43
- data/lib/gjp/get_pom.rb +78 -59
- data/lib/gjp/get_source.rb +72 -68
- data/lib/gjp/get_source_address.rb +37 -32
- data/lib/gjp/logger.rb +10 -17
- data/lib/gjp/pom.rb +28 -24
- data/lib/gjp/version.rb +1 -1
- data/lib/gjp/version_matcher.rb +77 -79
- data/spec/lib/get_pom_spec.rb +5 -5
- data/spec/lib/get_source_address_spec.rb +3 -3
- data/spec/lib/get_source_spec.rb +3 -3
- data/spec/lib/pom_spec.rb +28 -20
- data/spec/lib/version_matcher_spec.rb +22 -28
- data/spec/spec_helper.rb +5 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -15,8 +15,8 @@ Easiest install is via RubyGems:
|
|
15
15
|
## Usage
|
16
16
|
|
17
17
|
Currently available tools:
|
18
|
-
* `gjp get-pom
|
19
|
-
* `gjp get-source-address POM` will attempt to find the SCM Internet address of a pom.xml (from the file itself or through api.github.com);
|
18
|
+
* `gjp get-pom NAME` will attempt to find a pom. `NAME` can be a jar file on your disk, a project directory, or simply a `name-version` string. `gjp` will get the pom either from the package itself or through search.maven.org using heuristic searching;
|
19
|
+
* `gjp get-source-address POM` will attempt to find the SCM Internet address of a pom.xml at the `POM` filename or URI (from the file itself or through api.github.com);
|
20
20
|
* `gjp get-source POM ADDRESS` downloads the source of a pom.xml's project from its SCM at ADDRESS;
|
21
21
|
|
22
22
|
## Source
|
data/bin/gjp
CHANGED
@@ -15,11 +15,15 @@
|
|
15
15
|
# http://www.gnu.org/licenses/lgpl-2.1.html
|
16
16
|
#
|
17
17
|
|
18
|
+
if File.exist?(File.join(File.dirname(__FILE__), "..", ".git"))
|
19
|
+
$: << File.join(File.dirname(__FILE__), "..", "lib")
|
20
|
+
end
|
21
|
+
|
18
22
|
begin
|
19
|
-
require
|
23
|
+
require "gjp"
|
20
24
|
rescue LoadError
|
21
|
-
require
|
22
|
-
require
|
25
|
+
require "rubygems"
|
26
|
+
require "gjp"
|
23
27
|
end
|
24
28
|
|
25
|
-
MainCommand.run
|
29
|
+
Gjp::MainCommand.run
|
data/lib/gjp.rb
CHANGED
data/lib/gjp/cli.rb
CHANGED
@@ -1,51 +1,68 @@
|
|
1
1
|
# encoding: UTF-8
|
2
|
-
|
2
|
+
require "gjp/logger"
|
3
3
|
require "clamp"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
5
|
+
# Initialize global logger
|
6
|
+
Gjp.logger = ::Logger.new(STDERR)
|
7
|
+
Gjp.logger.datetime_format = "%Y-%m-%d %H:%M "
|
8
|
+
Gjp.logger.level = ::Logger::INFO
|
9
|
+
Gjp.logger.formatter = proc do |severity, datetime, progname, msg|
|
10
|
+
"#{severity.chars.first}: #{msg}\n"
|
11
|
+
end
|
12
|
+
|
13
|
+
module Gjp
|
14
|
+
class MainCommand < Clamp::Command
|
15
|
+
subcommand "get-pom", "Retrieves a pom corresponding to a filename" do
|
16
|
+
parameter "NAME", "a jar file path, a project directory path or a non-existing filename in the `project-version` form"
|
17
|
+
option ["-v", "--verbose"], :flag, "verbose output"
|
18
|
+
option ["--very-verbose"], :flag, "very verbose output"
|
19
|
+
option ["--very-very-verbose"], :flag, "very very verbose output"
|
20
|
+
|
21
|
+
def configure_log_level(v, vv, vvv)
|
22
|
+
if vvv
|
23
|
+
Gjp.logger.level = Logger::DEBUG
|
24
|
+
elsif vv
|
25
|
+
Gjp.logger.level = Logger::INFO
|
26
|
+
elsif v
|
27
|
+
Gjp.logger.level = Logger::WARN
|
28
|
+
else
|
29
|
+
Gjp.logger.level = Logger::ERROR
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def very_very_verbose=(flag)
|
34
|
+
configure_log_level(verbose?, very_verbose?, flag)
|
35
|
+
end
|
36
|
+
|
37
|
+
def very_verbose=(flag)
|
38
|
+
configure_log_level(verbose?, flag, very_very_verbose?)
|
39
|
+
end
|
40
|
+
|
41
|
+
def verbose=(flag)
|
42
|
+
configure_log_level(flag, very_verbose?, very_very_verbose?)
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute
|
46
|
+
puts Gjp::PomGetter.get_pom(name)
|
22
47
|
end
|
23
48
|
end
|
24
|
-
|
49
|
+
|
50
|
+
subcommand "get-source-address", "Retrieves a project's SCM Internet address" do
|
51
|
+
parameter "POM", "project's pom file path"
|
52
|
+
|
53
|
+
def execute
|
54
|
+
puts Gjp::SourceAddressGetter.get_source_address(pom)
|
55
|
+
end
|
56
|
+
end
|
25
57
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
subcommand "get-source", "Retrieves a project's source code directory" do
|
39
|
-
parameter "ADDRESS", "project's SCM Internet address"
|
40
|
-
parameter "POM", "project's pom file path"
|
41
|
-
parameter "[DIRECTORY]", "directory in which to save the source code", :default => "."
|
42
|
-
option ["-v", "--verbose"], :flag, "verbose output"
|
43
|
-
option ["--very-verbose"], :flag, "very verbose output"
|
44
|
-
option ["--very-very-verbose"], :flag, "very very verbose output"
|
45
|
-
|
46
|
-
def execute
|
47
|
-
init_logger
|
48
|
-
puts SourceGetter.get_source(address, pom, directory)
|
49
|
-
end
|
58
|
+
subcommand "get-source", "Retrieves a project's source code directory" do
|
59
|
+
parameter "ADDRESS", "project's SCM Internet address"
|
60
|
+
parameter "POM", "project's pom file path"
|
61
|
+
parameter "[DIRECTORY]", "directory in which to save the source code", :default => "."
|
62
|
+
|
63
|
+
def execute
|
64
|
+
puts Gjp::SourceGetter.get_source(address, pom, directory)
|
65
|
+
end
|
66
|
+
end
|
50
67
|
end
|
51
68
|
end
|
data/lib/gjp/get_pom.rb
CHANGED
@@ -8,81 +8,100 @@ require "pathname"
|
|
8
8
|
|
9
9
|
require "gjp/version_matcher"
|
10
10
|
|
11
|
-
|
12
|
-
|
11
|
+
module Gjp
|
12
|
+
# implements the get-pom subcommand
|
13
|
+
class PomGetter
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
def self.log
|
16
|
+
Gjp.logger
|
17
|
+
end
|
18
|
+
|
19
|
+
# returns the pom corresponding to a filename
|
20
|
+
def self.get_pom(filename)
|
21
|
+
(get_pom_from_dir(filename) or get_pom_from_jar(filename) or get_pom_from_sha1(filename) or get_pom_from_heuristic(filename))
|
22
|
+
end
|
18
23
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
24
|
+
# returns the pom in a project directory
|
25
|
+
def self.get_pom_from_dir(dir)
|
26
|
+
if File.directory?(dir)
|
27
|
+
pom_path = File.join(dir, "pom.xml")
|
28
|
+
if File.file?(pom_path)
|
29
|
+
log.info("pom.xml found in #{dir}/pom.xml")
|
25
30
|
return File.read(pom_path)
|
31
|
+
end
|
26
32
|
end
|
27
33
|
end
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
34
|
+
|
35
|
+
# returns a pom embedded in a jar file
|
36
|
+
def self.get_pom_from_jar(file)
|
37
|
+
begin
|
38
|
+
Zip::ZipFile.foreach(file) do |entry|
|
39
|
+
if entry.name =~ /\/pom.xml$/
|
40
|
+
log.info("pom.xml found in #{file}##{entry.name}")
|
41
|
+
return entry.get_input_stream.read
|
42
|
+
end
|
43
|
+
end
|
44
|
+
rescue Zip::ZipError
|
45
|
+
log.info "#{file} does not seem to be a valid jar archive, skipping"
|
46
|
+
rescue TypeError
|
47
|
+
log.info "#{file} seems to be a valid jar archive but is corrupt, skipping"
|
36
48
|
end
|
49
|
+
return nil
|
37
50
|
end
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
51
|
+
|
52
|
+
# returns a pom from search.maven.org with a jar sha1 search
|
53
|
+
def self.get_pom_from_sha1(file)
|
54
|
+
begin
|
55
|
+
if File.file?(file)
|
56
|
+
sha1 = Digest::SHA1.hexdigest File.read(file)
|
57
|
+
results = repository_search({:q => "1:\"#{sha1}\""}).select {|result| result["ec"].include?(".pom")}
|
58
|
+
result = results.first
|
59
|
+
if result != nil
|
60
|
+
log.info("pom.xml for #{file} found on search.maven.org for sha1 #{sha1} (#{result["g"]}:#{result["a"]}:#{result["v"]})")
|
61
|
+
return repository_download(result)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
return nil
|
65
|
+
rescue RestClient::ResourceNotFound
|
66
|
+
$stderr.puts "Got an error while looking for #{file}'s SHA1 in search.maven.org"
|
67
|
+
end
|
49
68
|
end
|
50
|
-
end
|
51
69
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
70
|
+
# returns a pom from search.maven.org with a heuristic name search
|
71
|
+
def self.get_pom_from_heuristic(filename)
|
72
|
+
begin
|
73
|
+
filename = Pathname.new(filename).basename.to_s.sub(/.jar$/, "")
|
74
|
+
my_artifact_id, my_version = VersionMatcher.split_version(filename)
|
57
75
|
|
58
|
-
|
59
|
-
|
76
|
+
result = repository_search({:q => my_artifact_id}).first
|
77
|
+
if result != nil
|
60
78
|
results = repository_search({:q => "g:\"#{result["g"]}\" AND a:\"#{result["a"]}\"", :core => "gav"})
|
61
79
|
their_versions = results.map {|doc| doc["v"]}
|
62
80
|
best_matched_version = if my_version != nil then VersionMatcher.best_match(my_version, their_versions) else their_versions.max end
|
63
81
|
best_matched_result = (results.select{|result| result["v"] == best_matched_version}).first
|
64
|
-
|
65
|
-
|
66
|
-
|
82
|
+
|
83
|
+
log.warn("pom.xml for #{filename} found on search.maven.org with heuristic search (#{best_matched_result["g"]}:#{best_matched_result["a"]}:#{best_matched_result["v"]})")
|
84
|
+
|
67
85
|
return repository_download(best_matched_result)
|
86
|
+
end
|
87
|
+
rescue RestClient::ResourceNotFound
|
88
|
+
$stderr.puts "Got an error while looking for #{filename} in search.maven.org"
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# returns a JSON result from search.maven.com
|
93
|
+
def self.repository_search(params)
|
94
|
+
response = RestClient.get "http://search.maven.org/solrsearch/select", {:params => params.merge({"rows" => "100", "wt" => "json"})}
|
95
|
+
json = JSON.parse(response.to_s)
|
96
|
+
return json["response"]["docs"]
|
97
|
+
end
|
98
|
+
|
99
|
+
# downloads a POM from a search.maven.com search result
|
100
|
+
def self.repository_download(result)
|
101
|
+
if result != nil
|
102
|
+
path = "#{result["g"].gsub(".", "/")}/#{result["a"]}/#{result["v"]}/#{result["a"]}-#{result["v"]}.pom"
|
103
|
+
return (RestClient.get "http://search.maven.org/remotecontent", {:params => {:filepath => path}}).to_s
|
68
104
|
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
|
72
|
-
# returns a JSON result from search.maven.com
|
73
|
-
def self.repository_search(params)
|
74
|
-
response = RestClient.get "http://search.maven.org/solrsearch/select", {:params => params.merge({"rows" => "100", "wt" => "json"})}
|
75
|
-
json = JSON.parse(response.to_s)
|
76
|
-
return json["response"]["docs"]
|
77
|
-
end
|
78
|
-
|
79
|
-
# downloads a POM from a search.maven.com search result
|
80
|
-
def self.repository_download(result)
|
81
|
-
if result != nil
|
82
|
-
path = "#{result["g"].gsub(".", "/")}/#{result["a"]}/#{result["v"]}/#{result["a"]}-#{result["v"]}.pom"
|
83
|
-
return (RestClient.get "http://search.maven.org/remotecontent", {:params => {:filepath => path}}).to_s
|
84
105
|
end
|
85
106
|
end
|
86
|
-
|
87
107
|
end
|
88
|
-
|
data/lib/gjp/get_source.rb
CHANGED
@@ -2,80 +2,84 @@
|
|
2
2
|
|
3
3
|
require "rest_client"
|
4
4
|
|
5
|
-
|
6
|
-
|
5
|
+
module Gjp
|
6
|
+
# implements the get-source subcommand
|
7
|
+
class SourceGetter
|
8
|
+
def self.log
|
9
|
+
Gjp.logger
|
10
|
+
end
|
7
11
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
# downloads a project's source into a specified directory
|
13
|
+
def self.get_source(address, pomfile, directory)
|
14
|
+
log.info("downloading: #{address} in #{directory}, pomfile: #{pomfile}")
|
15
|
+
|
16
|
+
dummy, prefix, scm_address = address.split(/^([^:]+):(.*)$/)
|
17
|
+
log.info("prefix: #{prefix}, scm_address: #{scm_address}")
|
18
|
+
|
19
|
+
get_source_from_scm(prefix, scm_address, pomfile, directory)
|
20
|
+
end
|
17
21
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
22
|
+
# checks code out from an scm
|
23
|
+
def self.get_source_from_scm(prefix, scm_address, pomfile, directory)
|
24
|
+
pom = Pom.new(pomfile)
|
25
|
+
dir = File.join(directory, "#{pom.group_id}:#{pom.artifact_id}:#{pom.version}")
|
26
|
+
begin
|
27
|
+
Dir::mkdir(dir)
|
28
|
+
rescue Errno::EEXIST
|
29
|
+
log.warn("Source directory exists, leaving...")
|
30
|
+
end
|
31
|
+
|
32
|
+
if prefix == "git"
|
33
|
+
get_source_from_git(scm_address, dir, pom.version)
|
34
|
+
elsif prefix == "svn"
|
35
|
+
get_source_from_svn(scm_address, dir, pom.version)
|
36
|
+
end
|
37
|
+
end
|
34
38
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
# checks code out of git
|
40
|
+
def self.get_source_from_git(scm_address, dir, version)
|
41
|
+
`git clone #{scm_address} #{dir}`
|
42
|
+
|
43
|
+
Dir.chdir(dir) do
|
44
|
+
tags = `git tag`.split("\n")
|
45
|
+
|
46
|
+
if tags.any?
|
47
|
+
best_tag = get_best_tag(tags, version)
|
48
|
+
log.info("checking out tag: #{best_tag}")
|
45
49
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
+
`git checkout #{best_tag}`
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
50
54
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
55
|
+
# checks code out of svn
|
56
|
+
def self.get_source_from_svn(scm_address, dir, version)
|
57
|
+
`svn checkout #{scm_address} #{dir}`
|
58
|
+
|
59
|
+
Dir.chdir(dir) do
|
60
|
+
tags = `svn ls "^/tags"`.split("\n")
|
61
|
+
|
62
|
+
if tags.any?
|
63
|
+
best_tag = get_best_tag(tags, version)
|
64
|
+
log.info("checking out tag: #{best_tag}")
|
61
65
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
+
`svn checkout #{scm_address}/tags/#{best_tag}`
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
66
70
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
71
|
+
# return the (heuristically) most similar tag to the specified version
|
72
|
+
def self.get_best_tag(tags, version)
|
73
|
+
versions_to_tags =Hash[
|
74
|
+
*tags.map do |tag|
|
75
|
+
[VersionMatcher.split_version(tag)[1], tag]
|
76
|
+
end.flatten
|
77
|
+
]
|
78
|
+
|
79
|
+
log.info("found the following versions and tags: #{versions_to_tags}")
|
76
80
|
|
77
|
-
|
78
|
-
|
79
|
-
|
81
|
+
best_version = VersionMatcher.best_match(version, versions_to_tags.keys)
|
82
|
+
versions_to_tags[best_version]
|
83
|
+
end
|
84
|
+
end
|
80
85
|
end
|
81
|
-
|
@@ -4,45 +4,50 @@ require "rest_client"
|
|
4
4
|
require "json"
|
5
5
|
require "open-uri"
|
6
6
|
|
7
|
-
|
8
|
-
|
7
|
+
module Gjp
|
8
|
+
# implements the get-source-address subcommand
|
9
|
+
class SourceAddressGetter
|
10
|
+
def self.log
|
11
|
+
Gjp.logger
|
12
|
+
end
|
9
13
|
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
14
|
+
# returns the pom corresponding to a file or directory, if it can be found
|
15
|
+
def self.get_source_address(file)
|
16
|
+
log.info("looking for source address for: #{file}")
|
17
|
+
(get_source_address_from_pom(file) or get_source_address_from_github(file))
|
18
|
+
end
|
15
19
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
+
# returns an scm address in a pom file
|
21
|
+
def self.get_source_address_from_pom(file)
|
22
|
+
pom = Pom.new(file)
|
23
|
+
result = pom.connection_address
|
20
24
|
|
21
|
-
|
22
|
-
|
23
|
-
|
25
|
+
if result != nil
|
26
|
+
log.info("address found in pom")
|
27
|
+
result
|
28
|
+
end
|
24
29
|
end
|
25
|
-
end
|
26
|
-
|
27
|
-
# returns an scm address looking for it on github
|
28
|
-
def self.get_source_address_from_github(file)
|
29
|
-
pom = Pom.new(file)
|
30
|
-
|
31
|
-
result = (github_search(pom.artifact_id) or github_search(pom.artifact_id.split("-").first) or github_search(pom.group_id))
|
32
30
|
|
33
|
-
|
34
|
-
|
35
|
-
|
31
|
+
# returns an scm address looking for it on github
|
32
|
+
def self.get_source_address_from_github(file)
|
33
|
+
pom = Pom.new(file)
|
34
|
+
|
35
|
+
result = (github_search(pom.artifact_id) or github_search(pom.artifact_id.split("-").first) or github_search(pom.group_id))
|
36
|
+
|
37
|
+
if result != nil
|
38
|
+
log.info("address found on Github: #{result}")
|
39
|
+
result
|
40
|
+
end
|
36
41
|
end
|
37
|
-
end
|
38
|
-
|
39
|
-
# returns a Giuthub repo address based on the keyword
|
40
|
-
def self.github_search(keyword)
|
41
|
-
if keyword != "" and keyword != nil
|
42
|
-
response = RestClient.get "https://api.github.com/legacy/repos/search/" + CGI::escape(keyword), :user_agent => "gjp/" + Gjp::VERSION, :language => "java", :sort => "forks"
|
43
|
-
json = JSON.parse(response.to_s)
|
44
42
|
|
45
|
-
|
43
|
+
# returns a Giuthub repo address based on the keyword
|
44
|
+
def self.github_search(keyword)
|
45
|
+
if keyword != "" and keyword != nil
|
46
|
+
response = RestClient.get "https://api.github.com/legacy/repos/search/" + CGI::escape(keyword), :user_agent => "gjp/" + Gjp::VERSION, :language => "java", :sort => "forks"
|
47
|
+
json = JSON.parse(response.to_s)
|
48
|
+
|
49
|
+
(json["repositories"].map {|repository| "git:" + repository["url"]}).first
|
50
|
+
end
|
46
51
|
end
|
47
52
|
end
|
48
53
|
end
|
data/lib/gjp/logger.rb
CHANGED
@@ -2,23 +2,16 @@
|
|
2
2
|
|
3
3
|
require "logger"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
if very_very_verbose?
|
9
|
-
Logger::DEBUG
|
10
|
-
elsif very_verbose?
|
11
|
-
Logger::INFO
|
12
|
-
elsif verbose?
|
13
|
-
Logger::WARN
|
14
|
-
else
|
15
|
-
Logger::ERROR
|
16
|
-
end
|
17
|
-
else
|
18
|
-
level
|
5
|
+
module Gjp
|
6
|
+
def self.logger=(logger)
|
7
|
+
@logger = logger
|
19
8
|
end
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
|
10
|
+
def self.logger
|
11
|
+
@logger ||= Logger.new('/dev/null')
|
12
|
+
end
|
13
|
+
|
14
|
+
def logger
|
15
|
+
Gjp.logger
|
23
16
|
end
|
24
17
|
end
|
data/lib/gjp/pom.rb
CHANGED
@@ -1,30 +1,34 @@
|
|
1
|
-
# encoding: UTF-8
|
1
|
+
# encoding: UTF-8
|
2
2
|
|
3
3
|
require "nokogiri"
|
4
|
+
require "open-uri"
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
connection_nodes.
|
6
|
+
module Gjp
|
7
|
+
# encapsulates a pom.xml file
|
8
|
+
class Pom
|
9
|
+
def initialize(filename)
|
10
|
+
@doc = Nokogiri::XML(open(filename).read)
|
11
|
+
@doc.remove_namespaces!
|
12
|
+
end
|
13
|
+
|
14
|
+
def connection_address
|
15
|
+
connection_nodes = @doc.xpath("//scm/connection/text()")
|
16
|
+
if connection_nodes.any?
|
17
|
+
connection_nodes.first.to_s.sub(/^scm:/, "")
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def group_id
|
22
|
+
@doc.xpath("project/groupId/text()").to_s
|
23
|
+
end
|
24
|
+
|
25
|
+
def artifact_id
|
26
|
+
@doc.xpath("project/artifactId/text()").to_s
|
27
|
+
end
|
28
|
+
|
29
|
+
def version
|
30
|
+
@doc.xpath("project/version/text()").to_s
|
16
31
|
end
|
17
|
-
end
|
18
|
-
|
19
|
-
def group_id
|
20
|
-
@doc.xpath("project/groupId/text()").to_s
|
21
|
-
end
|
22
|
-
|
23
|
-
def artifact_id
|
24
|
-
@doc.xpath("project/artifactId/text()").to_s
|
25
|
-
end
|
26
|
-
|
27
|
-
def version
|
28
|
-
@doc.xpath("project/version/text()").to_s
|
29
32
|
end
|
30
33
|
end
|
34
|
+
|
data/lib/gjp/version.rb
CHANGED
data/lib/gjp/version_matcher.rb
CHANGED
@@ -2,93 +2,92 @@
|
|
2
2
|
|
3
3
|
require "text"
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
def self.extract_version(full_name)
|
12
|
-
full_name.sub /^[^0-9]+/, ""
|
13
|
-
end
|
5
|
+
module Gjp
|
6
|
+
# heuristically matches version strings
|
7
|
+
class VersionMatcher
|
8
|
+
def self.log
|
9
|
+
Gjp.logger
|
10
|
+
end
|
14
11
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
12
|
+
# heuristically splits a full name into an artifact name and version string
|
13
|
+
# assumes that version strings begin with a numeric character and are separated
|
14
|
+
# by a ., -, _, ~ or space
|
15
|
+
# returns a [name, version] pair
|
16
|
+
def self.split_version(full_name)
|
17
|
+
matches = full_name.match(/(.*?)(?:[\.\-\_ ~,]?([0-9].*))?$/)
|
18
|
+
if matches != nil and matches.length > 1
|
19
|
+
[matches[1], matches[2]]
|
20
|
+
else
|
21
|
+
[full_string, nil]
|
22
|
+
end
|
23
|
+
end
|
27
24
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
my_chunks = my_version.split /[\.\-\_ ~,]/
|
39
|
-
their_chunks_hash = Hash[
|
40
|
-
their_versions.map do |their_version|
|
41
|
-
their_chunks_for_version = their_version.split /[\.\-\_ ~,]/
|
42
|
-
their_chunks_for_version += [nil]*[my_chunks.length - their_chunks_for_version.length, 0].max
|
43
|
-
[their_version, their_chunks_for_version]
|
44
|
-
end
|
45
|
-
]
|
25
|
+
# returns the "best match" between a version number and a set of available version numbers
|
26
|
+
# using a heuristic criterion. Idea:
|
27
|
+
# - split the version number in chunks divided by ., - etc.
|
28
|
+
# - every chunk with same index is "compared", differences make up a score
|
29
|
+
# - "comparison" is a subtraction if the chunk is an integer, a string distance measure otherwise
|
30
|
+
# - score weighs differently on chunk index (first chunks are most important)
|
31
|
+
# - lowest score wins
|
32
|
+
def self.best_match(my_version, their_versions)
|
33
|
+
log.debug("version comparison: #{my_version} vs #{their_versions.join(', ')}")
|
46
34
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
35
|
+
my_chunks = my_version.split /[\.\-\_ ~,]/
|
36
|
+
their_chunks_hash = Hash[
|
37
|
+
their_versions.map do |their_version|
|
38
|
+
their_chunks_for_version = their_version.split /[\.\-\_ ~,]/
|
39
|
+
their_chunks_for_version += [nil]*[my_chunks.length - their_chunks_for_version.length, 0].max
|
40
|
+
[their_version, their_chunks_for_version]
|
41
|
+
end
|
42
|
+
]
|
43
|
+
|
44
|
+
max_chunks_length = ([my_chunks.length] + their_chunks_hash.values.map {|chunk| chunk.length}).max
|
45
|
+
|
46
|
+
scoreboard = []
|
47
|
+
their_versions.each do |their_version|
|
48
|
+
their_chunks = their_chunks_hash[their_version]
|
49
|
+
score = 0
|
50
|
+
their_chunks.each_with_index do |their_chunk, i|
|
51
|
+
score_multiplier = 100**(max_chunks_length -i -1)
|
52
|
+
my_chunk = my_chunks[i]
|
53
|
+
score += chunk_distance(my_chunk, their_chunk) * score_multiplier
|
54
|
+
end
|
55
|
+
scoreboard << {:version => their_version, :score => score}
|
57
56
|
end
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
scoreboard = scoreboard.sort_by {|element| element[:score]}
|
57
|
+
|
58
|
+
scoreboard = scoreboard.sort_by {|element| element[:score]}
|
62
59
|
|
63
|
-
|
64
|
-
|
65
|
-
|
60
|
+
log.debug("scoreboard: ")
|
61
|
+
scoreboard.each_with_index do |element, i|
|
62
|
+
log.debug(" #{i+1}. #{element[:version]} (score: #{element[:score]})")
|
63
|
+
end
|
64
|
+
|
65
|
+
winner = scoreboard.first
|
66
|
+
|
67
|
+
if winner != nil
|
68
|
+
return winner[:version]
|
69
|
+
end
|
66
70
|
end
|
67
71
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
their_chunk = "0"
|
85
|
-
end
|
86
|
-
if my_chunk.is_i? and their_chunk.is_i?
|
87
|
-
return [(my_chunk.to_i - their_chunk.to_i).abs, 99].min
|
88
|
-
else
|
89
|
-
return [Text::Levenshtein.distance(my_chunk.upcase, their_chunk.upcase), 99].min
|
72
|
+
# returns a score representing the distance between two version chunks
|
73
|
+
# for integers, the score is the difference between their values
|
74
|
+
# for strings, the score is the Levenshtein distance
|
75
|
+
# in any case score is normalized between 0 (identical) and 99 (very different/uncomparable)
|
76
|
+
def self.chunk_distance(my_chunk, their_chunk)
|
77
|
+
if my_chunk == nil
|
78
|
+
my_chunk = "0"
|
79
|
+
end
|
80
|
+
if their_chunk == nil
|
81
|
+
their_chunk = "0"
|
82
|
+
end
|
83
|
+
if my_chunk.is_i? and their_chunk.is_i?
|
84
|
+
return [(my_chunk.to_i - their_chunk.to_i).abs, 99].min
|
85
|
+
else
|
86
|
+
return [Text::Levenshtein.distance(my_chunk.upcase, their_chunk.upcase), 99].min
|
87
|
+
end
|
90
88
|
end
|
91
89
|
end
|
90
|
+
|
92
91
|
end
|
93
92
|
|
94
93
|
class String
|
@@ -96,4 +95,3 @@ class String
|
|
96
95
|
!!(self =~ /^[0-9]+$/)
|
97
96
|
end
|
98
97
|
end
|
99
|
-
|
data/spec/lib/get_pom_spec.rb
CHANGED
@@ -2,33 +2,33 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe PomGetter do
|
5
|
+
describe Gjp::PomGetter do
|
6
6
|
describe ".get_pom" do
|
7
7
|
it "gets the pom from a directory" do
|
8
8
|
dir_path = File.join("spec", "data", "commons-logging")
|
9
9
|
pom_path = File.join(dir_path, "pom.xml")
|
10
|
-
PomGetter.get_pom(dir_path).should eq(File.read(pom_path))
|
10
|
+
Gjp::PomGetter.get_pom(dir_path).should eq(File.read(pom_path))
|
11
11
|
end
|
12
12
|
|
13
13
|
it "gets the pom from a jar" do
|
14
14
|
dir_path = File.join("spec", "data", "commons-logging")
|
15
15
|
pom_path = File.join(dir_path, "pom.xml")
|
16
16
|
jar_path = File.join(dir_path, "commons-logging-1.1.1.jar")
|
17
|
-
PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
17
|
+
Gjp::PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
18
18
|
end
|
19
19
|
|
20
20
|
it "gets the pom from sha1" do
|
21
21
|
dir_path = File.join("spec", "data", "antlr")
|
22
22
|
pom_path = File.join(dir_path, "pom.xml")
|
23
23
|
jar_path = File.join(dir_path, "antlr-2.7.2.jar")
|
24
|
-
PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
24
|
+
Gjp::PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
25
25
|
end
|
26
26
|
|
27
27
|
it "gets the pom from a heuristic" do
|
28
28
|
dir_path = File.join("spec", "data", "nailgun")
|
29
29
|
pom_path = File.join(dir_path, "pom.xml")
|
30
30
|
jar_path = File.join(dir_path, "nailgun-0.7.1.jar")
|
31
|
-
PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
31
|
+
Gjp::PomGetter.get_pom(jar_path).should eq(File.read(pom_path))
|
32
32
|
end
|
33
33
|
end
|
34
34
|
end
|
@@ -2,16 +2,16 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe SourceAddressGetter do
|
5
|
+
describe Gjp::SourceAddressGetter do
|
6
6
|
describe ".get_source_address" do
|
7
7
|
it "gets the source address from a pom file" do
|
8
8
|
pom_path = File.join("spec", "data", "commons-logging", "pom.xml")
|
9
|
-
SourceAddressGetter.get_source_address(pom_path).should eq "svn:http://svn.apache.org/repos/asf/commons/proper/logging/tags/commons-logging-1.1.1"
|
9
|
+
Gjp::SourceAddressGetter.get_source_address(pom_path).should eq "svn:http://svn.apache.org/repos/asf/commons/proper/logging/tags/commons-logging-1.1.1"
|
10
10
|
end
|
11
11
|
|
12
12
|
it "gets the source address from Github" do
|
13
13
|
pom_path = File.join("spec", "data", "antlr", "pom.xml")
|
14
|
-
SourceAddressGetter.get_source_address(pom_path).should eq "git:https://github.com/antlr/antlr4"
|
14
|
+
Gjp::SourceAddressGetter.get_source_address(pom_path).should eq "git:https://github.com/antlr/antlr4"
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/spec/lib/get_source_spec.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
require "spec_helper"
|
4
4
|
require "fileutils"
|
5
5
|
|
6
|
-
describe SourceGetter do
|
6
|
+
describe Gjp::SourceGetter do
|
7
7
|
describe ".get_source_from_git" do
|
8
8
|
it "gets the sources from a git repo" do
|
9
9
|
dir_path = File.join("spec", "data", "nailgun")
|
@@ -13,7 +13,7 @@ describe SourceGetter do
|
|
13
13
|
|
14
14
|
FileUtils.rm_rf(repo_path)
|
15
15
|
|
16
|
-
SourceGetter.get_source("git:git@github.com:martylamb/nailgun.git", pom_path, dir_path)
|
16
|
+
Gjp::SourceGetter.get_source("git:git@github.com:martylamb/nailgun.git", pom_path, dir_path)
|
17
17
|
|
18
18
|
File.open(file_path).readline.should eq "nailgun\n"
|
19
19
|
end
|
@@ -28,7 +28,7 @@ describe SourceGetter do
|
|
28
28
|
|
29
29
|
FileUtils.rm_rf(repo_path)
|
30
30
|
|
31
|
-
SourceGetter.get_source("svn:http://svn.apache.org/repos/asf/struts/struts2/tags/STRUTS_2_3_14/apps", pom_path, dir_path)
|
31
|
+
Gjp::SourceGetter.get_source("svn:http://svn.apache.org/repos/asf/struts/struts2/tags/STRUTS_2_3_14/apps", pom_path, dir_path)
|
32
32
|
|
33
33
|
File.open(file_path).readline.should eq "README.txt - showcase\n"
|
34
34
|
end
|
data/spec/lib/pom_spec.rb
CHANGED
@@ -2,30 +2,38 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe Pom do
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
describe Gjp::Pom do
|
6
|
+
[ File.join("spec", "data", "commons-logging", "pom.xml"),
|
7
|
+
'http://search.maven.org/remotecontent?filepath=commons-logging/commons-logging/1.1.1/commons-logging-1.1.1.pom'].each do |loc|
|
8
|
+
|
9
|
+
let(:pom) { Gjp::Pom.new(loc) }
|
10
|
+
|
11
|
+
describe "#connection_address" do
|
12
|
+
it "reads the SCM connection address" do
|
13
|
+
pom.connection_address.should eq "svn:http://svn.apache.org/repos/asf/commons/proper/logging/tags/commons-logging-1.1.1"
|
14
|
+
end
|
15
|
+
|
16
|
+
it "reads the SCM connection address from a remote repository" do
|
17
|
+
pom.connection_address.should eq "svn:http://svn.apache.org/repos/asf/commons/proper/logging/tags/commons-logging-1.1.1"
|
18
|
+
end
|
11
19
|
end
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
20
|
+
|
21
|
+
describe "#group_id" do
|
22
|
+
it "reads the group id" do
|
23
|
+
pom.group_id.should eq "commons-logging"
|
24
|
+
end
|
17
25
|
end
|
18
|
-
end
|
19
26
|
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
describe "#artifact_id" do
|
28
|
+
it "reads the artifact id" do
|
29
|
+
pom.artifact_id.should eq "commons-logging"
|
30
|
+
end
|
23
31
|
end
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
32
|
+
|
33
|
+
describe "#version" do
|
34
|
+
it "reads the version" do
|
35
|
+
pom.version.should eq "1.1.1"
|
36
|
+
end
|
29
37
|
end
|
30
38
|
end
|
31
39
|
end
|
@@ -2,63 +2,57 @@
|
|
2
2
|
|
3
3
|
require 'spec_helper'
|
4
4
|
|
5
|
-
describe VersionMatcher do
|
5
|
+
describe Gjp::VersionMatcher do
|
6
6
|
|
7
7
|
it "splits full names into names and version numbers" do
|
8
|
-
VersionMatcher.split_version("moio-3.2beta1").should eq(["moio", "3.2beta1"])
|
9
|
-
VersionMatcher.split_version("3.2beta1").should eq(["
|
10
|
-
VersionMatcher.split_version("v3.2beta1").should eq(["v", "3.2beta1"])
|
11
|
-
end
|
12
|
-
|
13
|
-
it "extracts version numbers" do
|
14
|
-
VersionMatcher.extract_version("moio-3.2beta1").should eq("3.2beta1")
|
15
|
-
VersionMatcher.extract_version("3.2beta1").should eq("3.2beta1")
|
16
|
-
VersionMatcher.extract_version("v3.2beta1").should eq("3.2beta1")
|
8
|
+
Gjp::VersionMatcher.split_version("moio-3.2beta1").should eq(["moio", "3.2beta1"])
|
9
|
+
Gjp::VersionMatcher.split_version("3.2beta1").should eq(["", "3.2beta1"])
|
10
|
+
Gjp::VersionMatcher.split_version("v3.2beta1").should eq(["v", "3.2beta1"])
|
17
11
|
end
|
18
12
|
|
19
13
|
it "computes chunk distances" do
|
20
|
-
VersionMatcher.chunk_distance(nil, "1").should eq(1)
|
21
|
-
VersionMatcher.chunk_distance("alpha", nil).should eq(5)
|
14
|
+
Gjp::VersionMatcher.chunk_distance(nil, "1").should eq(1)
|
15
|
+
Gjp::VersionMatcher.chunk_distance("alpha", nil).should eq(5)
|
22
16
|
|
23
|
-
VersionMatcher.chunk_distance("1", "1").should eq(0)
|
24
|
-
VersionMatcher.chunk_distance("1", "9").should eq(8)
|
25
|
-
VersionMatcher.chunk_distance("1", "999").should eq(99)
|
17
|
+
Gjp::VersionMatcher.chunk_distance("1", "1").should eq(0)
|
18
|
+
Gjp::VersionMatcher.chunk_distance("1", "9").should eq(8)
|
19
|
+
Gjp::VersionMatcher.chunk_distance("1", "999").should eq(99)
|
26
20
|
|
27
|
-
VersionMatcher.chunk_distance("snap", "SNAP").should eq(0)
|
28
|
-
VersionMatcher.chunk_distance("snap", "snippete").should eq(5)
|
29
|
-
VersionMatcher.chunk_distance("snap", "l"*999).should eq(99)
|
21
|
+
Gjp::VersionMatcher.chunk_distance("snap", "SNAP").should eq(0)
|
22
|
+
Gjp::VersionMatcher.chunk_distance("snap", "snippete").should eq(5)
|
23
|
+
Gjp::VersionMatcher.chunk_distance("snap", "l"*999).should eq(99)
|
30
24
|
|
31
|
-
VersionMatcher.chunk_distance("1", "SNAP").should eq(4)
|
25
|
+
Gjp::VersionMatcher.chunk_distance("1", "SNAP").should eq(4)
|
32
26
|
|
33
|
-
VersionMatcher.chunk_distance("0", "10").should eq(10)
|
34
|
-
VersionMatcher.chunk_distance("0", "9").should eq(9)
|
27
|
+
Gjp::VersionMatcher.chunk_distance("0", "10").should eq(10)
|
28
|
+
Gjp::VersionMatcher.chunk_distance("0", "9").should eq(9)
|
35
29
|
end
|
36
30
|
|
37
31
|
it "finds the best match" do
|
38
32
|
my_version = "1.0"
|
39
33
|
available_versions = ["1.0", "1", "2.0", "1.0.1", "4.5.6.7.8"]
|
40
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.0")
|
34
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.0")
|
41
35
|
|
42
36
|
available_versions = ["3.0", "2.0", "1.0.1"]
|
43
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.0.1")
|
37
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.0.1")
|
44
38
|
|
45
39
|
available_versions = ["1.snap", "2.0", "4.0.1"]
|
46
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
40
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
47
41
|
|
48
42
|
available_versions = ["1.10", "1.9", "2.0", "3.0.1"]
|
49
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.9")
|
43
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.9")
|
50
44
|
|
51
45
|
my_version = "1.snap"
|
52
46
|
available_versions = ["1.snap", "1"]
|
53
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
47
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
54
48
|
|
55
49
|
my_version = "1.very-very_very_longish"
|
56
50
|
available_versions = ["1.snap", "1"]
|
57
|
-
VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
51
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should eq("1.snap")
|
58
52
|
|
59
53
|
my_version = "1.snap"
|
60
54
|
available_versions = []
|
61
|
-
VersionMatcher.best_match(my_version, available_versions).should be_nil
|
55
|
+
Gjp::VersionMatcher.best_match(my_version, available_versions).should be_nil
|
62
56
|
end
|
63
57
|
end
|
64
58
|
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gjp
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-06-
|
12
|
+
date: 2013-06-12 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|