gembuild 1.0.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/.gitignore +26 -0
- data/.rspec +2 -0
- data/.travis.yml +15 -0
- data/CHANGELOG.md +15 -0
- data/Gemfile +3 -0
- data/LICENSE +675 -0
- data/README.md +80 -0
- data/Rakefile +14 -0
- data/bin/gembuild +10 -0
- data/gembuild.gemspec +45 -0
- data/lib/gembuild.rb +142 -0
- data/lib/gembuild/aur_scraper.rb +159 -0
- data/lib/gembuild/exceptions.rb +31 -0
- data/lib/gembuild/gem_scraper.rb +205 -0
- data/lib/gembuild/pkgbuild.erb +32 -0
- data/lib/gembuild/pkgbuild.rb +283 -0
- data/lib/gembuild/project.rb +167 -0
- data/lib/gembuild/version.rb +23 -0
- metadata +301 -0
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Gembuild
|
2
|
+
|
3
|
+
[](https://travis-ci.org/mfinelli/gembuild)
|
4
|
+
[](https://coveralls.io/github/mfinelli/gembuild?branch=master)
|
5
|
+
[](https://codeclimate.com/github/mfinelli/gembuild)
|
6
|
+
[](https://inch-ci.org/github/mfinelli/gembuild)
|
7
|
+
|
8
|
+
Create PKGBUILDs for ruby gems.
|
9
|
+
|
10
|
+
## Configuration
|
11
|
+
|
12
|
+
Upon first run the gem will prompt for your name and email address to use in
|
13
|
+
the maintainer field in PKGBUILDs and to use as the git author. If you have
|
14
|
+
already configured git it will confirm the values that it finds. It will also
|
15
|
+
ask where to store package repositories. All information is then saved in
|
16
|
+
`~/.gembuild` which is just a simple YAML file and can be easily changed
|
17
|
+
using your favorite text editor.
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
Simple usage to create/update a package for a gem:
|
22
|
+
|
23
|
+
```shell
|
24
|
+
$ bundle exec bin/gembuild mina
|
25
|
+
```
|
26
|
+
|
27
|
+
This will checkout the AUR package for `ruby-mina`, fetch the latest version
|
28
|
+
of the gem and update the PKGBUILD accordingly, and then regenerate the
|
29
|
+
metadata using `mksrcinfo` and commit all changes.
|
30
|
+
|
31
|
+
### Advanced Usage
|
32
|
+
|
33
|
+
There are four main parts: the AUR scraper, the rubygems scraper, the
|
34
|
+
PKGBUILD and the project.
|
35
|
+
|
36
|
+
#### AUR Scraper
|
37
|
+
|
38
|
+
The AUR scraper is used to get version information about a package currently
|
39
|
+
on the AUR.
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
s = Gembuild::AurScraper.new('ruby-mina')
|
43
|
+
s.scrape!
|
44
|
+
```
|
45
|
+
|
46
|
+
#### RubyGems Scraper
|
47
|
+
|
48
|
+
The RubyGems scraper gets the rest of the information needed for the PKGBUILD
|
49
|
+
by making several queries to rubygems.org. **N.B. that it skips any version
|
50
|
+
marked as a "prerelease".**
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
s = Gembuild::GemScraper.neW('mina')
|
54
|
+
s.scrape!
|
55
|
+
```
|
56
|
+
|
57
|
+
#### PKGBUILD
|
58
|
+
|
59
|
+
The `Pkgbuild` class is actually responsible for creating a PKGBUILD for a
|
60
|
+
gem.
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
Gembuild::Pkgbuild.create('mina')
|
64
|
+
```
|
65
|
+
|
66
|
+
#### Project
|
67
|
+
|
68
|
+
The `Project` class is what checks out the repository from the AUR,
|
69
|
+
configures git for it, makes sure there is a `.gitignore` and commits all
|
70
|
+
changes after creating or updating the PKGBUILD.
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
Gembuild::Project.new('mina').clone_and_commit!
|
74
|
+
```
|
75
|
+
|
76
|
+
## License
|
77
|
+
|
78
|
+
This project is licensed under the GPLv3 or any later version. For more
|
79
|
+
information please see the LICENSE file included with the project or
|
80
|
+
[https://www.gnu.org/licenses](https://www.gnu.org/licenses/gpl.html).
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'yard'
|
5
|
+
|
6
|
+
RSpec::Core::RakeTask.new(:spec)
|
7
|
+
RuboCop::RakeTask.new(:rubocop) do |task|
|
8
|
+
task.requires << 'rubocop-rspec'
|
9
|
+
end
|
10
|
+
YARD::Rake::YardocTask.new(:yard)
|
11
|
+
|
12
|
+
# Checking the spec files on travis causes an error and the build to fail.
|
13
|
+
# task default: [:rubocop, :spec]
|
14
|
+
task default: :spec
|
data/bin/gembuild
ADDED
data/gembuild.gemspec
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'gembuild/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'gembuild'
|
9
|
+
spec.version = Gembuild::VERSION
|
10
|
+
spec.authors = ['Mario Finelli']
|
11
|
+
spec.email = ['mario@finel.li']
|
12
|
+
|
13
|
+
spec.summary = 'Generate PKGBUILDs for ruby gems.'
|
14
|
+
spec.description = 'Generate PKGBUILDs for ruby gems.'
|
15
|
+
spec.homepage = 'https://github.com/mfinelli/gembuild'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
|
21
|
+
spec.bindir = 'bin'
|
22
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
23
|
+
spec.require_paths = ['lib']
|
24
|
+
|
25
|
+
spec.licenses = ['GPL-3.0+']
|
26
|
+
|
27
|
+
spec.add_dependency 'mechanize'
|
28
|
+
spec.add_dependency 'nokogiri'
|
29
|
+
|
30
|
+
spec.add_development_dependency 'bundler'
|
31
|
+
spec.add_development_dependency 'coveralls'
|
32
|
+
spec.add_development_dependency 'mutant-rspec'
|
33
|
+
spec.add_development_dependency 'pry'
|
34
|
+
spec.add_development_dependency 'rake'
|
35
|
+
spec.add_development_dependency 'redcarpet'
|
36
|
+
spec.add_development_dependency 'rspec'
|
37
|
+
spec.add_development_dependency 'rubocop'
|
38
|
+
spec.add_development_dependency 'rubocop-rspec'
|
39
|
+
spec.add_development_dependency 'rubycritic'
|
40
|
+
spec.add_development_dependency 'ruby-lint'
|
41
|
+
spec.add_development_dependency 'simplecov'
|
42
|
+
spec.add_development_dependency 'vcr'
|
43
|
+
spec.add_development_dependency 'webmock'
|
44
|
+
spec.add_development_dependency 'yard'
|
45
|
+
end
|
data/lib/gembuild.rb
ADDED
@@ -0,0 +1,142 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Gembuild: create Arch Linux PKGBUILDs for ruby gems.
|
4
|
+
# Copyright (C) 2015 Mario Finelli <mario@finel.li>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'English'
|
20
|
+
|
21
|
+
require 'gembuild/aur_scraper'
|
22
|
+
require 'gembuild/exceptions'
|
23
|
+
require 'gembuild/gem_scraper'
|
24
|
+
require 'gembuild/pkgbuild'
|
25
|
+
require 'gembuild/project'
|
26
|
+
require 'gembuild/version'
|
27
|
+
|
28
|
+
# Create Arch Linux PKGBUILDs for ruby gems.
|
29
|
+
module Gembuild
|
30
|
+
class << self
|
31
|
+
# The path to the gembuild configuration file.
|
32
|
+
#
|
33
|
+
# @return [String] real path to the configuration file
|
34
|
+
def conf_file
|
35
|
+
File.expand_path(File.join('~', '.gembuild'))
|
36
|
+
end
|
37
|
+
|
38
|
+
# Read from the configuration file if it exists, otherwise prompt for the
|
39
|
+
# configuration and save it to file.
|
40
|
+
#
|
41
|
+
# @return [Hash] the configuration options: maintainer name and email
|
42
|
+
# address and where to checkout packages
|
43
|
+
def configure
|
44
|
+
unless File.file?(conf_file)
|
45
|
+
name = fetch_git_global_name
|
46
|
+
email = fetch_git_global_email
|
47
|
+
pkgdir = fetch_pkgdir
|
48
|
+
|
49
|
+
File.write(
|
50
|
+
conf_file,
|
51
|
+
{ name: name, email: email, pkgdir: pkgdir }.to_yaml
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
YAML.load_file(conf_file)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Attempt to read the global git name, prompting the user for the name if
|
59
|
+
# unsuccessful.
|
60
|
+
#
|
61
|
+
# @return [String] the name to use as package maintainer
|
62
|
+
def fetch_git_global_name
|
63
|
+
name = `git config --global user.name`.strip
|
64
|
+
|
65
|
+
if $CHILD_STATUS.success?
|
66
|
+
if prompt_for_confirmation(name)
|
67
|
+
return name
|
68
|
+
else
|
69
|
+
prompt_for_git_name
|
70
|
+
end
|
71
|
+
else
|
72
|
+
prompt_for_git_name('Could not detect name from git configuration.')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Attempt to read the global git email, prompting the user for the email
|
77
|
+
# if unsuccessful.
|
78
|
+
#
|
79
|
+
# @return [String] the email to use as package maintainer
|
80
|
+
def fetch_git_global_email
|
81
|
+
email = `git config --global user.email`.strip
|
82
|
+
|
83
|
+
if $CHILD_STATUS.success?
|
84
|
+
if prompt_for_confirmation(email)
|
85
|
+
return email
|
86
|
+
else
|
87
|
+
prompt_for_git_email
|
88
|
+
end
|
89
|
+
else
|
90
|
+
prompt_for_git_email('Could not detect email from git configuration.')
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Prompt the user for the name to use.
|
95
|
+
#
|
96
|
+
# This method is only called if reading the global git configuration was
|
97
|
+
# unsuccessful or the user specified that it was incorrect.
|
98
|
+
#
|
99
|
+
# @param msg [String, nil] An optional message to display before
|
100
|
+
# prompting.
|
101
|
+
# @return [String] the name to use as package maintainer
|
102
|
+
def prompt_for_git_name(msg = nil)
|
103
|
+
puts msg unless msg.nil? || msg.empty?
|
104
|
+
puts 'Please enter desired name: '
|
105
|
+
gets.chomp
|
106
|
+
end
|
107
|
+
|
108
|
+
# Prompt the user for the email to use.
|
109
|
+
#
|
110
|
+
# This method is only called if reading the global git configuration was
|
111
|
+
# unsuccessful or the user specified that it was incorrect.
|
112
|
+
#
|
113
|
+
# @param msg [String, nil] An optional message to display before
|
114
|
+
# prompting.
|
115
|
+
# @return [String] the email address to use as package maintainer
|
116
|
+
def prompt_for_git_email(msg = nil)
|
117
|
+
puts msg unless msg.nil? || msg.empty?
|
118
|
+
puts 'Please enter desired email: '
|
119
|
+
gets.chomp
|
120
|
+
end
|
121
|
+
|
122
|
+
# Ask the user to confirm the detected value.
|
123
|
+
#
|
124
|
+
# @param detected [String] The value that was detected.
|
125
|
+
# @return [Boolean] whether or not the value is correct
|
126
|
+
def prompt_for_confirmation(detected)
|
127
|
+
puts "Detected \"#{detected}\", is this correct? (y/n)"
|
128
|
+
response = gets.chomp.downcase[0, 1]
|
129
|
+
|
130
|
+
response == 'y'
|
131
|
+
end
|
132
|
+
|
133
|
+
# Prompt the user for the location where they would like to store
|
134
|
+
# checked-out packages.
|
135
|
+
#
|
136
|
+
# @return [String] the filepath where to store package repositories
|
137
|
+
def fetch_pkgdir
|
138
|
+
puts 'Where should projects be checked out?'
|
139
|
+
File.expand_path(gets.chomp)
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
@@ -0,0 +1,159 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
# Gembuild: create Arch Linux PKGBUILDs for ruby gems.
|
4
|
+
# Copyright (C) 2015 Mario Finelli <mario@finel.li>
|
5
|
+
#
|
6
|
+
# This program is free software: you can redistribute it and/or modify
|
7
|
+
# it under the terms of the GNU General Public License as published by
|
8
|
+
# the Free Software Foundation, either version 3 of the License, or
|
9
|
+
# (at your option) any later version.
|
10
|
+
#
|
11
|
+
# This program is distributed in the hope that it will be useful,
|
12
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
13
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
14
|
+
# GNU General Public License for more details.
|
15
|
+
#
|
16
|
+
# You should have received a copy of the GNU General Public License
|
17
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
18
|
+
|
19
|
+
require 'mechanize'
|
20
|
+
|
21
|
+
module Gembuild
|
22
|
+
# This class is used to query the AUR for information about a package.
|
23
|
+
#
|
24
|
+
# @!attribute [r] agent
|
25
|
+
# @return [Mechanize] the Mechanize agent
|
26
|
+
# @!attribute [r] pkgname
|
27
|
+
# @return [String] the package about which to query the AUR
|
28
|
+
# @!attribute [r] url
|
29
|
+
# @return [String] the AUR url for the package
|
30
|
+
class AurScraper
|
31
|
+
attr_reader :agent, :pkgname, :url
|
32
|
+
|
33
|
+
# Creates a new AurScraper instance.
|
34
|
+
#
|
35
|
+
# @raise [Gembuild::UndefinedPkgnameError] if the pkgname is nil or empty
|
36
|
+
#
|
37
|
+
# @example Create new AurScraper object
|
38
|
+
# Gembuild::AurScraper.new('ruby-mina')
|
39
|
+
# # => #<Gembuild::AurScraper:0x000000040659b0
|
40
|
+
# # @agent=
|
41
|
+
# # #<Mechanize
|
42
|
+
# # #<Mechanize::CookieJar:0x000000040658c0
|
43
|
+
# # @store=
|
44
|
+
# # #<HTTP::CookieJar::HashStore:0x000000040813b8
|
45
|
+
# # @gc_index=0,
|
46
|
+
# # @gc_threshold=150,
|
47
|
+
# # @jar={},
|
48
|
+
# # @logger=nil,
|
49
|
+
# # @mon_count=0,
|
50
|
+
# # @mon_mutex=#<Mutex:0x00000004081368>,
|
51
|
+
# # @mon_owner=nil>>
|
52
|
+
# # nil>,
|
53
|
+
# # @pkgname="ruby-mina",
|
54
|
+
# # @url="https://aur.archlinux.org/rpc.php?type=info&arg=ruby-mina">
|
55
|
+
#
|
56
|
+
# @param pkgname [String] The name of the package about which to query.
|
57
|
+
# @return [Gembuild::AurScraper] a new AurScraper instance
|
58
|
+
def initialize(pkgname)
|
59
|
+
fail Gembuild::UndefinedPkgnameError if pkgname.nil? || pkgname.empty?
|
60
|
+
|
61
|
+
@agent = Mechanize.new
|
62
|
+
@pkgname = pkgname
|
63
|
+
|
64
|
+
@url = "https://aur.archlinux.org/rpc.php?type=info&arg=#{pkgname}"
|
65
|
+
end
|
66
|
+
|
67
|
+
# Query the AUR for information about a package and then parse the JSON
|
68
|
+
# results.
|
69
|
+
#
|
70
|
+
# @example Query the AUR about a package
|
71
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
72
|
+
# r = s.query_aur
|
73
|
+
# #=> {:version=>1,
|
74
|
+
# # :type=>"info",
|
75
|
+
# # :resultcount=>1,
|
76
|
+
# # :results=>
|
77
|
+
# # {:ID=>238062,
|
78
|
+
# # :Name=>"ruby-mina",
|
79
|
+
# # :PackageBaseID=>101492,
|
80
|
+
# # :PackageBase=>"ruby-mina",
|
81
|
+
# # :Version=>"0.3.7-1",
|
82
|
+
# # :Description=>"Really fast deployer and server automation tool.",
|
83
|
+
# # :URL=>"http://github.com/nadarei/mina",
|
84
|
+
# # :NumVotes=>0,
|
85
|
+
# # :OutOfDate=>nil,
|
86
|
+
# # :Maintainer=>"supermario",
|
87
|
+
# # :FirstSubmitted=>1444354070,
|
88
|
+
# # :LastModified=>1444354135,
|
89
|
+
# # :License=>"MIT",
|
90
|
+
# # :URLPath=>"/cgit/aur.git/snapshot/ruby-mina.tar.gz",
|
91
|
+
# # :CategoryID=>1}}
|
92
|
+
#
|
93
|
+
# @return [Hash] the information about the package
|
94
|
+
def query_aur
|
95
|
+
JSON.parse(agent.get(url).body, symbolize_names: true)
|
96
|
+
end
|
97
|
+
|
98
|
+
# Determine whether the package already exists on the AUR by the number of
|
99
|
+
# results returned.
|
100
|
+
#
|
101
|
+
# @example Check if package exists on the AUR
|
102
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
103
|
+
# r = s.query_aur
|
104
|
+
# s.package_exists?(r) #=> true
|
105
|
+
#
|
106
|
+
# @param response [Hash] The JSON parsed response from the AUR.
|
107
|
+
# @return [Boolean] whether or not the package exists already on the AUR
|
108
|
+
def package_exists?(response)
|
109
|
+
response[:results].count.zero? ? false : true
|
110
|
+
end
|
111
|
+
|
112
|
+
# Parse the version from the AUR response.
|
113
|
+
#
|
114
|
+
# A version string is expected to either look like 0.1.2-3 or like
|
115
|
+
# 1:2.3.4-5. So the strategy is to first split on the dash to get the
|
116
|
+
# package release number. Then with the remaining string attempt a split
|
117
|
+
# on the colon. If there is only one part then it means that there is no
|
118
|
+
# epoch (or rather that the epoch is zero). If there are two parts then we
|
119
|
+
# use the first as the epoch value. Finally, whatever is left is the
|
120
|
+
# actual version of the gem.
|
121
|
+
#
|
122
|
+
# @example Get package version from the AUR
|
123
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
124
|
+
# r = s.query_aur
|
125
|
+
# s.get_version_hash(r)
|
126
|
+
# #=> {:epoch=>0, :pkgver=>Gem::Version.new("0.3.7"), :pkgrel=>1}
|
127
|
+
#
|
128
|
+
# @param response [Hash] The JSON parsed response from the AUR.
|
129
|
+
# @return [Hash] a hash of the different version parts
|
130
|
+
def get_version_hash(response)
|
131
|
+
version = response[:results][:Version].split('-')
|
132
|
+
|
133
|
+
pkgrel = version.pop.to_i
|
134
|
+
version = version.join
|
135
|
+
|
136
|
+
version = version.split(':')
|
137
|
+
epoch = version.count == 1 ? 0 : version.shift.to_i
|
138
|
+
version = Gem::Version.new(version.join)
|
139
|
+
|
140
|
+
{ epoch: epoch, pkgver: version, pkgrel: pkgrel }
|
141
|
+
end
|
142
|
+
|
143
|
+
# Query the AUR and returned the parsed results.
|
144
|
+
#
|
145
|
+
# @example Query the AUR for information about a package
|
146
|
+
# s = Gembuild::AurScraper.new('ruby-mina')
|
147
|
+
# s.scrape!
|
148
|
+
# #=> {:epoch=>0, :pkgver=>Gem::Version.new("0.3.7"), :pkgrel=>1}
|
149
|
+
#
|
150
|
+
# @return [nil, Hash] the version hash or nil if the package doesn't exist
|
151
|
+
def scrape!
|
152
|
+
response = query_aur
|
153
|
+
|
154
|
+
return nil unless package_exists?(response)
|
155
|
+
|
156
|
+
get_version_hash(response)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|