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.
@@ -0,0 +1,80 @@
1
+ # Gembuild
2
+
3
+ [![Build Status](https://travis-ci.org/mfinelli/gembuild.svg?branch=master)](https://travis-ci.org/mfinelli/gembuild)
4
+ [![Coverage Status](https://coveralls.io/repos/mfinelli/gembuild/badge.svg?branch=master&service=github)](https://coveralls.io/github/mfinelli/gembuild?branch=master)
5
+ [![Code Climate](https://codeclimate.com/github/mfinelli/gembuild/badges/gpa.svg)](https://codeclimate.com/github/mfinelli/gembuild)
6
+ [![Inline Documentation](https://inch-ci.org/github/mfinelli/gembuild.svg)](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).
@@ -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
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'rubygems' unless Object.const_defined?(:Gem)
5
+ require 'bundler'
6
+ Bundler.require
7
+
8
+ require 'gembuild'
9
+
10
+ Gembuild::Project.new(ARGV[0]).clone_and_commit!
@@ -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
@@ -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