gembuild 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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