gjp 0.5.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.
@@ -0,0 +1,21 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ coverage
6
+ InstalledFiles
7
+ lib/bundler/man
8
+ pkg
9
+ rdoc
10
+ spec/reports
11
+ test/tmp
12
+ test/version_tmp
13
+ tmp
14
+
15
+ # SPEC generated files
16
+ spec/data/tomcat/org.apache.tomcat\:tomcat\:7.0.40
17
+
18
+ # YARD artifacts
19
+ .yardoc
20
+ _yardoc
21
+ doc/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # See gjp.gemspec
4
+ gemspec
@@ -0,0 +1,40 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ gjp (0.5.0)
5
+ clamp
6
+ json
7
+ nokogiri
8
+ rest-client
9
+ rubyzip
10
+ text
11
+
12
+ GEM
13
+ remote: http://rubygems.org/
14
+ specs:
15
+ clamp (0.6.1)
16
+ diff-lcs (1.2.4)
17
+ json (1.8.0)
18
+ mime-types (1.23)
19
+ nokogiri (1.5.9)
20
+ rake (10.0.4)
21
+ rest-client (1.6.7)
22
+ mime-types (>= 1.16)
23
+ rspec (2.13.0)
24
+ rspec-core (~> 2.13.0)
25
+ rspec-expectations (~> 2.13.0)
26
+ rspec-mocks (~> 2.13.0)
27
+ rspec-core (2.13.1)
28
+ rspec-expectations (2.13.0)
29
+ diff-lcs (>= 1.1.3, < 2.0)
30
+ rspec-mocks (2.13.1)
31
+ rubyzip (0.9.9)
32
+ text (1.2.1)
33
+
34
+ PLATFORMS
35
+ ruby
36
+
37
+ DEPENDENCIES
38
+ gjp!
39
+ rake
40
+ rspec
@@ -0,0 +1,30 @@
1
+ gjp – Green Java Packager's tools
2
+ ===
3
+
4
+ `gjp` (pronounced _/ˈdʒiː ˈaɪ ˈd͡ʒəʊ/_) is a set of tools to ease and partially automate Linux packaging of Java projects.
5
+
6
+ The project focus is on producing rpm packages for SUSE distributions, but it is general enough to be useful even for other distributions.
7
+
8
+
9
+ ## Install
10
+
11
+ Easiest install is via RubyGems:
12
+
13
+ $ gem install gjp
14
+
15
+ ## Usage
16
+
17
+ Currently available tools:
18
+ * `gjp get-pom PATH` will attempt to find an artifact's pom.xml, if it exists (from the package itself or through search.maven.org)
19
+ * `gjp get-source-address PATH` will attempt to find the SCM address of an artifact from its pom.xml (from the pom.xml itself or through api.github.com)
20
+ * `gjp get-source PATH ADDRESS` downloads the source of a pom.xml's project from its SCM at ADDRESS
21
+
22
+ ## Source
23
+
24
+ `gjp`'s Git repo is available on GitHub, which can be browsed at:
25
+
26
+ https://github.com/SilvioMoioli/gjp
27
+
28
+ and cloned with:
29
+
30
+ git clone git@github.com:SilvioMoioli/gjp.git
@@ -0,0 +1,7 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :test
7
+ task :test => :spec
data/bin/gjp ADDED
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+ #
4
+ # The (open)?SUSE ((Linux Enterprise) (Server|Desktop))?'s
5
+ # packager Swiss Army Knife.
6
+ #
7
+ # Copyright 2013 Silvio Moioli <smoioli@suse.de>
8
+ #
9
+ # Inspired mainly by pom2spec by:
10
+ # Pascal Bleser <pascal.bleser@opensuse.org>
11
+ # Duncan Mac-Vicar Prett <dmacvicar@suse.de>
12
+ #
13
+ # This file is licensed under the
14
+ # GNU Lesser General Public License version 2.1 or later:
15
+ # http://www.gnu.org/licenses/lgpl-2.1.html
16
+ #
17
+
18
+ begin
19
+ require 'gjp'
20
+ rescue LoadError
21
+ require 'rubygems'
22
+ require 'gjp'
23
+ end
24
+
25
+ MainCommand.run
@@ -0,0 +1,30 @@
1
+ # encoding: UTF-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "gjp/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "gjp"
7
+ s.version = Gjp::VERSION
8
+ s.authors = ["Silvio Moioli"]
9
+ s.email = ["smoioli@suse.de"]
10
+ s.homepage = "https://github.com/SilvioMoioli/gjp"
11
+ s.summary = "Green Java Packager's Tools"
12
+ s.description = "A suite of tools to ease Java packaging in SUSE systems"
13
+
14
+ s.rubyforge_project = "gjp"
15
+
16
+ s.files = `git ls-files`.split("\n")
17
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
18
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
19
+ s.require_paths = ["lib"]
20
+
21
+ s.add_development_dependency "rake"
22
+ s.add_development_dependency "rspec"
23
+
24
+ s.add_runtime_dependency "clamp"
25
+ s.add_runtime_dependency "rubyzip"
26
+ s.add_runtime_dependency "rest-client"
27
+ s.add_runtime_dependency "json"
28
+ s.add_runtime_dependency "text"
29
+ s.add_runtime_dependency "nokogiri"
30
+ end
@@ -0,0 +1,7 @@
1
+ require "gjp/version"
2
+ require "gjp/logger"
3
+ require "gjp/cli"
4
+ require "gjp/pom"
5
+ require "gjp/get_pom"
6
+ require "gjp/get_source_address"
7
+ require "gjp/get_source"
@@ -0,0 +1,51 @@
1
+ # encoding: UTF-8
2
+
3
+ require "clamp"
4
+
5
+ class MainCommand < Clamp::Command
6
+ subcommand "get-pom", "Retrieves a pom corresponding to a jar" do
7
+ parameter "JAR", "jar file path"
8
+ option ["-v", "--verbose"], :flag, "verbose output"
9
+ option ["--very-verbose"], :flag, "very verbose output"
10
+ option ["--very-very-verbose"], :flag, "very very verbose output"
11
+
12
+ def execute
13
+ begin
14
+ init_logger
15
+ puts PomGetter.get_pom(jar)
16
+ rescue Zip::ZipError
17
+ $stderr.puts "#{jar} does not seem to be a valid jar archive, skipping"
18
+ rescue TypeError
19
+ $stderr.puts "#{jar} seems to be a valid jar archive but is corrupt, skipping"
20
+ rescue RestClient::ResourceNotFound
21
+ $stderr.puts "Got an error while looking for #{jar} in search.maven.org"
22
+ end
23
+ end
24
+ end
25
+
26
+ subcommand "get-source-address", "Retrieves a project's SCM Internet address" do
27
+ parameter "POM", "project's pom file path"
28
+ option ["-v", "--verbose"], :flag, "verbose output"
29
+ option ["--very-verbose"], :flag, "very verbose output"
30
+ option ["--very-very-verbose"], :flag, "very very verbose output"
31
+
32
+ def execute
33
+ init_logger
34
+ puts SourceAddressGetter.get_source_address(pom)
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
50
+ end
51
+ end
@@ -0,0 +1,158 @@
1
+ # encoding: UTF-8
2
+
3
+ require "digest/sha1"
4
+ require "zip/zip"
5
+ require "rest_client"
6
+ require "json"
7
+ require "text"
8
+ require "pathname"
9
+
10
+ # implements the get-pom subcommand
11
+ class PomGetter
12
+
13
+ # returns the pom corresponding to a file or directory, if it can be found
14
+ def self.get_pom(file)
15
+ (get_pom_from_dir(file) or get_pom_from_jar(file) or get_pom_from_sha1(file) or get_pom_from_heuristic(file))
16
+ end
17
+
18
+ # returns the pom in a project directory
19
+ def self.get_pom_from_dir(dir)
20
+ if File.directory?(dir)
21
+ pom_path = File.join(dir, "pom.xml")
22
+ if File.file?(pom_path)
23
+ $log.info("pom.xml found in #{dir}/pom.xml")
24
+ return File.read(pom_path)
25
+ end
26
+ end
27
+ end
28
+
29
+ # returns a pom embedded in a jar file
30
+ def self.get_pom_from_jar(file)
31
+ Zip::ZipFile.foreach(file) do |entry|
32
+ if entry.name =~ /\/pom.xml$/
33
+ $log.info("pom.xml found in #{file}##{entry.name}")
34
+ return entry.get_input_stream.read
35
+ end
36
+ end
37
+ return nil
38
+ end
39
+
40
+ # returns a pom from search.maven.org with a jar sha1 search
41
+ def self.get_pom_from_sha1(file)
42
+ sha1 = Digest::SHA1.hexdigest File.read(file)
43
+ results = repository_search({:q => "1:\"#{sha1}\""}).select {|result| result["ec"].include?(".pom")}
44
+ result = results.first
45
+ if result != nil
46
+ $log.info("pom.xml for #{file} found on search.maven.org for sha1 #{sha1} (#{result["g"]}:#{result["a"]}:#{result["v"]})")
47
+ return repository_download(result)
48
+ end
49
+ end
50
+
51
+ # returns a pom from search.maven.org with a heuristic name search
52
+ def self.get_pom_from_heuristic(file)
53
+ filename = Pathname.new(file).basename.to_s
54
+ matches = filename.match(/([^\/]*?)(?:[\.\-\_ ~,]([0-9].*))?\.jar$/)
55
+ if matches != nil and matches.length > 1
56
+ my_artifact_id = matches[1]
57
+ my_version = matches[2]
58
+ result = repository_search({:q => my_artifact_id}).first
59
+ if result != nil
60
+ results = repository_search({:q => "g:\"#{result["g"]}\" AND a:\"#{result["a"]}\"", :core => "gav"})
61
+ their_versions = results.map {|doc| doc["v"]}
62
+ best_matched_version = if my_version != nil then best_match(my_version, their_versions) else their_versions.max end
63
+ best_matched_result = (results.select{|result| result["v"] == best_matched_version}).first
64
+
65
+ $log.warn("pom.xml for #{file} found on search.maven.org with heuristic search (#{best_matched_result["g"]}:#{best_matched_result["a"]}:#{best_matched_result["v"]})")
66
+
67
+ return repository_download(best_matched_result)
68
+ 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
+ end
85
+ end
86
+
87
+ # returns the "best match" between a version number and a set of available version numbers
88
+ # using a heuristic criterion. Idea:
89
+ # - split the version number in chunks divided by ., - etc.
90
+ # - every chunk with same index is "compared", differences make up a score
91
+ # - "comparison" is a subtraction if the chunk is an integer, a string distance measure otherwise
92
+ # - score weighs differently on chunk index (first chunks are most important)
93
+ # - lowest score wins
94
+ def self.best_match(my_version, their_versions)
95
+ $log.debug("version comparison: #{my_version} vs #{their_versions.join(', ')}")
96
+
97
+ my_chunks = my_version.split /[\.\-\_ ~,]/
98
+ their_chunks_hash = Hash[
99
+ their_versions.map do |their_version|
100
+ their_chunks_for_version = their_version.split /[\.\-\_ ~,]/
101
+ their_chunks_for_version += [nil]*[my_chunks.length - their_chunks_for_version.length, 0].max
102
+ [their_version, their_chunks_for_version]
103
+ end
104
+ ]
105
+
106
+ max_chunks_length = ([my_chunks.length] + their_chunks_hash.values.map {|chunk| chunk.length}).max
107
+
108
+ scoreboard = []
109
+ their_versions.each do |their_version|
110
+ their_chunks = their_chunks_hash[their_version]
111
+ score = 0
112
+ their_chunks.each_with_index do |their_chunk, i|
113
+ score_multiplier = 100**(max_chunks_length -i -1)
114
+ my_chunk = my_chunks[i]
115
+ score += chunk_distance(my_chunk, their_chunk) * score_multiplier
116
+ end
117
+ scoreboard << {:version => their_version, :score => score}
118
+ end
119
+
120
+ scoreboard = scoreboard.sort_by {|element| element[:score]}
121
+
122
+ $log.debug("scoreboard: ")
123
+ scoreboard.each_with_index do |element, i|
124
+ $log.debug(" #{i+1}. #{element[:version]} (score: #{element[:score]})")
125
+ end
126
+
127
+ winner = scoreboard.first
128
+
129
+ if winner != nil
130
+ return winner[:version]
131
+ end
132
+ end
133
+
134
+ # returns a score representing the distance between two version chunks
135
+ # for integers, the score is the difference between their values
136
+ # for strings, the score is the Levenshtein distance
137
+ # in any case score is normalized between 0 (identical) and 99 (very different/uncomparable)
138
+ def self.chunk_distance(my_chunk, their_chunk)
139
+ if my_chunk == nil
140
+ my_chunk = "0"
141
+ end
142
+ if their_chunk == nil
143
+ their_chunk = "0"
144
+ end
145
+ if my_chunk.is_i? and their_chunk.is_i?
146
+ return [(my_chunk.to_i - their_chunk.to_i).abs, 99].min
147
+ else
148
+ return [Text::Levenshtein.distance(my_chunk.upcase, their_chunk.upcase), 99].min
149
+ end
150
+ end
151
+ end
152
+
153
+ class String
154
+ def is_i?
155
+ !!(self =~ /^[0-9]+$/)
156
+ end
157
+ end
158
+
@@ -0,0 +1,29 @@
1
+ # encoding: UTF-8
2
+
3
+ require "rest_client"
4
+
5
+ # implements the get-source subcommand
6
+ class SourceGetter
7
+
8
+ # downloads a project's source into a specified directory
9
+ def self.get_source(address, pomfile, directory)
10
+ $log.info("downloading: #{address} in #{directory}, pomfile: #{pomfile}")
11
+
12
+ dummy, prefix, scm_address = address.split(/^([^:]+):(.*)$/)
13
+
14
+ $log.info("prefix: #{prefix}, scm_address: #{scm_address}")
15
+
16
+ if prefix == "git"
17
+ get_source_from_git(scm_address, pomfile, directory)
18
+ end
19
+ end
20
+
21
+ # checks out from git
22
+ def self.get_source_from_git(scm_address, pomfile, directory)
23
+ pom = Pom.new(pomfile)
24
+ dir = File.join(directory, "#{pom.group_id}:#{pom.artifact_id}:#{pom.version}")
25
+
26
+ Dir::mkdir(dir)
27
+ `git clone #{scm_address} #{dir}`
28
+ end
29
+ end
@@ -0,0 +1,48 @@
1
+ # encoding: UTF-8
2
+
3
+ require "rest_client"
4
+ require "json"
5
+ require "open-uri"
6
+
7
+ # implements the get-source-address subcommand
8
+ class SourceAddressGetter
9
+
10
+ # returns the pom corresponding to a file or directory, if it can be found
11
+ def self.get_source_address(file)
12
+ $log.info("looking for source address for: #{file}")
13
+ (get_source_address_from_pom(file) or get_source_address_from_github(file))
14
+ end
15
+
16
+ # returns an scm address in a pom file
17
+ def self.get_source_address_from_pom(file)
18
+ pom = Pom.new(file)
19
+ result = pom.connection_address
20
+
21
+ if result != nil
22
+ $log.info("address found in pom")
23
+ result
24
+ 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
+
33
+ if result != nil
34
+ $log.info("address found on Github: #{result}")
35
+ result
36
+ 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
+
45
+ (json["repositories"].map {|repository| "git:" + repository["url"]}).first
46
+ end
47
+ end
48
+ end