namelessjon-jeweler 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.markdown ADDED
@@ -0,0 +1,183 @@
1
+ # Jeweler: Let us craft you the perfect gem
2
+
3
+ Rubygems are the awesome way of distributing your code to others. GitHub is the awesome way of managing the source code of your project. GitHub can even generate a Rubygem if you include a gemspec.
4
+
5
+ Trouble is when developing your Rubygems on GitHub, you generally do one of the following:
6
+
7
+ * Manage the gemspec by hand
8
+ * ... why bother doing something by hand when you can automate it?
9
+ * Write your own Rake stuff to create the Gem::Specification and output it to a gemspec file, and deal with keeping the Rakefile and gemspec in sync
10
+ * ... why keep reinventing the wheel?
11
+ * Use hoe or echoe for generating the gemspec
12
+ * ... why use utilities made for the days before GitHub existed?
13
+ * ... why have extra stuff you aren't going to use?
14
+
15
+ Jeweler was created with a few goals in mind:
16
+
17
+ * Only use a Gem::Specification as configuration
18
+ * Be one command away from version bumping and releasing
19
+ * Store version information in one place
20
+ * Only concern itself with git, gems, and versioning
21
+ * Not be a requirement for using your Rakefile (you just wouldn't be able to use its tasks)
22
+ * Use Jeweler internally. Oh the meta!
23
+
24
+ ## Installation
25
+
26
+ Run the following if you haven't already:
27
+
28
+ gem sources -a http://gems.github.com
29
+
30
+ Install the gem:
31
+
32
+ sudo gem install technicalpickles-jeweler
33
+
34
+ ## Configuration for an existing project
35
+
36
+ Armed with the gem, we can begin diving into an example. [the-perfect-gem](http://github.com/technicalpickles/the-perfect-gem/tree) was setup as a Jeweler showcase, and a simple example:
37
+
38
+ begin
39
+ require 'jeweler'
40
+ Jeweler::Tasks.new do |s|
41
+ s.name = "the-perfect-gem"
42
+ s.summary = "TODO"
43
+ s.email = "josh@technicalpickles.com"
44
+ s.homepage = "http://github.com/technicalpickles/the-perfect-gem"
45
+ s.description = "TODO"
46
+ s.authors = ["Josh Nichols"]
47
+ end
48
+ rescue LoadError
49
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
50
+ end
51
+
52
+ Here's a rundown of what's happening:
53
+
54
+ * Wrap everything in a begin block, and rescue from LoadError
55
+ * This lets us degrade gracefully if jeweler isn't installed
56
+ * Make a new `Jeweler::Tasks`
57
+ * It gets yielded a new `Gem::Specification`
58
+ * This is where all the configuration happens
59
+ * Things you definitely need to specify:
60
+ * `name`
61
+ * Things you probably want to specify:
62
+ * `summary`
63
+ * `email`
64
+ * `homepage`
65
+ * `description`
66
+ * `authors`
67
+ * Things you can specify, but have defaults
68
+ * `files`, defaults to `FileList["[A-Z]*.*", "{bin,generators,lib,test,spec}/**/*"]`
69
+ * Things you shouldn't specify:
70
+ * `version`, because Jeweler takes care of this for you
71
+ * Other things of interest
72
+ * `executables`, if you have any scripts
73
+ * `add_dependency`, if you have any dependencies
74
+ * Keep in mind that this is a `Gem::Specification`, so you can do whatever you would need to do to get your gem in shape
75
+
76
+ ## Bootstrap a new project
77
+
78
+ Before proceeding, take a minute to setup your git environment, specifically your name and email address:
79
+
80
+ $ git config --global user.email johndoe@example.com
81
+ $ git config --global user.name 'John Doe'
82
+
83
+ Jeweler provides a generator of sorts, `jeweler`. It takes two arguments: your GitHub username and a repository name.
84
+
85
+ $ jeweler technicalpickles the-perfect-gem
86
+
87
+ Basically, this does:
88
+
89
+ * Creates the the-perfect-gem directory
90
+ * Seeds it with some basic files:
91
+ * `.gitignore`, with the usual suspects predefined
92
+ * `Rakefile`, setup with tasks for jeweler, test, rdoc, and rcov
93
+ * `README`, with your project name
94
+ * `LICENSE`, MIT, with your name prefilled
95
+ * `test/test_helper`, setup with shoulda, mocha, and a re-opened `Test::Unit::TestCase`
96
+ * `test/the_perfect_gem.rb`, placeholder failing test
97
+ * `lib/the_perfect_gem.rb`, placeholder library file
98
+ * Makes it a git repo
99
+ * Sets up `git@github.com:technicalpickles/jeweler.git` as the `origin` git remote
100
+ * Makes an initial commit, but does not push
101
+
102
+ At this point, you probably should create a repository by wandering to [http://github.com/repositories/new](http://github.com/repositories/new). Be sure to use the same project name you told Jeweler.
103
+
104
+ With the repository firmly created, just push it:
105
+
106
+ $ git push origin master
107
+
108
+ You also probably should [enable RubyGem creation for you repository](http://github.com/blog/51-github-s-rubygem-server): Go to your project's edit page and check the 'RubyGem' box.
109
+
110
+ ## Overview of Jeweler workflow
111
+
112
+ Here's the general idea:
113
+
114
+ * Hack, commit, hack, commit, etc, etc
115
+ * Version bump
116
+ * Release
117
+ * Have a delicious scotch
118
+
119
+ The hacking and the scotch are up to you, but Jeweler provides rake tasks for the rest.
120
+
121
+ ### Versioning
122
+
123
+ Versioning information is stored in `VERSION.yml`. It's a plain ol' YAML file which contains three things:
124
+
125
+ * major
126
+ * minor
127
+ * patch
128
+
129
+ Consider, for a second, `1.5.3`.
130
+
131
+ * major = 1
132
+ * minor = 5
133
+ * patch = 3
134
+
135
+ #### Your first time
136
+
137
+ When you first start using Jeweler, there won't be a `VERSION.yml`, so it'll assume 0.0.0.
138
+
139
+ If you need some arbitrary version, you can do one of the following:
140
+
141
+ * `rake version:write MAJOR=6 MINOR=0 PATCH=3`
142
+ * Write `VERSION.yml` by hand (lame)
143
+
144
+
145
+ #### After that
146
+
147
+ You have a few rake tasks for doing the version bump:
148
+
149
+ $ rake version:bump:patch # 1.5.1 -> 1.5.2
150
+ $ rake version:bump:minor # 1.5.1 -> 1.6.0
151
+ $ rake version:bump:major # 1.5.1 -> 2.0.0
152
+
153
+ If you need to do an arbitrary bump, use the same task you used to create `VERSION.yml`:
154
+
155
+ $ rake version:write MAJOR=6 MINOR=0 PATCH=3
156
+
157
+ The process of version bumping does a commit to your repo, so make sure your repo is in a clean state (ie nothing uncommitted).
158
+
159
+ ### Release it
160
+
161
+ It's pretty straight forward:
162
+
163
+ $ rake release
164
+
165
+ This takes care of:
166
+
167
+ * Generating a `.gemspec` for you project, with the version you just bumped to
168
+ * Commit and push the updated `.gemspec`
169
+ * Create a tag
170
+ * Push the tag
171
+
172
+ ### Play the waiting game
173
+
174
+ How do you know when your gem is built? [Has My Gem Built Yet](http://hasmygembuiltyet.org/) was specifically designed to answer that question.
175
+
176
+ If it happens to be down, you can also check out the GitHub Gem repo's [list](http://gems.github.com/list). Just search for youname-yourrepo.s
177
+
178
+ ### Putting it all together
179
+
180
+ <hack, hack, hack, commit>
181
+ $ rake version:bump:patch release
182
+
183
+ Now browse to http://gems.github.com/yourname/yourproject, and wait for it to be built.
data/Rakefile ADDED
@@ -0,0 +1,46 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+ require 'rake/rdoctask'
4
+ require 'rcov/rcovtask'
5
+
6
+ $:.unshift('lib')
7
+
8
+ begin
9
+ require 'jeweler'
10
+ Jeweler::Tasks.new do |gemspec|
11
+ gemspec.name = "jeweler"
12
+ gemspec.executables = "jeweler"
13
+ gemspec.summary = "Simple and opinionated helper for creating Rubygem projects on GitHub"
14
+ gemspec.email = "josh@technicalpickles.com"
15
+ gemspec.homepage = "http://github.com/technicalpickles/jeweler"
16
+ gemspec.description = "Simple and opinionated helper for creating Rubygem projects on GitHub"
17
+ gemspec.authors = ["Josh Nichols"]
18
+ gemspec.files = FileList["[A-Z]*", "{bin,generators,lib,test}/**/*", 'lib/jeweler/templates/.gitignore']
19
+ gemspec.add_dependency 'schacon-git'
20
+ end
21
+ rescue LoadError
22
+ puts "Jeweler, or one of its dependencies, is not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
23
+ end
24
+
25
+
26
+ Rake::TestTask.new do |t|
27
+ t.libs << 'lib'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ Rake::RDocTask.new do |rdoc|
33
+ rdoc.rdoc_dir = 'rdoc'
34
+ rdoc.title = 'jeweler'
35
+ rdoc.options << '--line-numbers' << '--inline-source'
36
+ rdoc.rdoc_files.include('README.markdown')
37
+ rdoc.rdoc_files.include('lib/**/*.rb')
38
+ end
39
+
40
+ Rcov::RcovTask.new do |t|
41
+ t.libs << "test"
42
+ t.test_files = FileList['test/**/*_test.rb']
43
+ t.verbose = true
44
+ end
45
+
46
+ task :default => :rcov
data/TODO ADDED
@@ -0,0 +1,7 @@
1
+ * Generators
2
+ * Rails generator for making a plugin that's Jeweler enabled
3
+ * Support rspec?
4
+ * Releasing
5
+ * Create tag based on version
6
+ * Push tags
7
+ * Open hasmygembuiltyet.org
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ patch: 0
3
+ major: 0
4
+ minor: 5
data/bin/jeweler ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby
2
+ require 'rubygems'
3
+ require 'optparse'
4
+
5
+ $:.unshift File.join(File.dirname(__FILE__), '..', 'lib')
6
+ require 'jeweler'
7
+
8
+ class JewelerOpts < Hash
9
+ attr_reader :opts
10
+ def initialize(args)
11
+ super()
12
+ self[:spec] = false
13
+ @opts = OptionParser.new do |o|
14
+ o.banner = "Usage: #{File.basename($0)} [options] reponame\ne.g. #{File.basename($0)} the-perfect-gem"
15
+ o.on('-s','--spec', 'use bacon to generate specs') do
16
+ self[:spec] = true
17
+ end
18
+ o.on_tail('-h', '--help', 'display this help and exit') do
19
+ puts o
20
+ exit
21
+ end
22
+ end
23
+ @opts.parse!(args)
24
+ end
25
+ end
26
+
27
+ o = JewelerOpts.new(ARGV)
28
+
29
+ unless ARGV.size == 1
30
+ puts o.opts
31
+ exit 1
32
+ end
33
+
34
+ github_repo_name = ARGV.first
35
+ generator = Jeweler::Generator.new github_repo_name
36
+ generator.spec = o[:spec]
37
+ generator.run
data/lib/jeweler.rb ADDED
@@ -0,0 +1,34 @@
1
+ require 'date'
2
+
3
+ require 'jeweler/bumping'
4
+ require 'jeweler/versioning'
5
+ require 'jeweler/gemspec'
6
+ require 'jeweler/errors'
7
+ require 'jeweler/generator'
8
+ require 'jeweler/release'
9
+
10
+ require 'jeweler/tasks'
11
+
12
+ # A Jeweler helps you craft the perfect Rubygem. Give him a gemspec, and he takes care of the rest.
13
+ class Jeweler
14
+ include Jeweler::Bumping
15
+ include Jeweler::Versioning
16
+ include Jeweler::Gemspec
17
+ include Jeweler::Release
18
+
19
+ attr_reader :gemspec
20
+ attr_accessor :base_dir
21
+
22
+ def initialize(gemspec, base_dir = '.')
23
+ raise(GemspecError, "Can't create a Jeweler with a nil gemspec") if gemspec.nil?
24
+ @gemspec = gemspec
25
+ @base_dir = base_dir
26
+
27
+ @gemspec.files ||= FileList["[A-Z]*.*", "{bin,generators,lib,test,spec}/**/*"]
28
+
29
+ if File.exists?(File.join(base_dir, '.git'))
30
+ @repo = Git.open(base_dir)
31
+ end
32
+ end
33
+ end
34
+
@@ -0,0 +1,61 @@
1
+ class Jeweler
2
+ module Bumping
3
+ # Bumps the patch version.
4
+ #
5
+ # 1.5.1 -> 1.5.2
6
+ def bump_patch_version()
7
+ patch = self.patch_version.to_i + 1
8
+
9
+ write_version(major_version, minor_version, patch)
10
+ end
11
+
12
+ # Bumps the minor version.
13
+ #
14
+ # 1.5.1 -> 1.6.0
15
+ def bump_minor_version()
16
+ minor = minor_version.to_i + 1
17
+
18
+ write_version(major_version, minor)
19
+ end
20
+
21
+ # Bumps the major version.
22
+ #
23
+ # 1.5.1 -> 2.0.0
24
+ def bump_major_version()
25
+ major = major_version.to_i + 1
26
+
27
+ write_version(major)
28
+ end
29
+
30
+ # Bumps the version, to the specific major/minor/patch version, writing out the appropriate version.rb, and then reloads it.
31
+ def write_version(major = 0, minor = 0, patch = 0)
32
+ major ||= 0
33
+ minor ||= 0
34
+ patch ||= 0
35
+
36
+ File.open(version_yaml_path, 'w+') do |f|
37
+ version_hash = {
38
+ 'major' => major.to_i,
39
+ 'minor' => minor.to_i,
40
+ 'patch' => patch.to_i
41
+ }
42
+ YAML.dump(version_hash, f)
43
+ end
44
+
45
+ refresh_version
46
+
47
+ @gemspec.version = version
48
+
49
+ puts "Wrote to #{version_yaml_path}: #{version}"
50
+
51
+ commit_version
52
+ end
53
+
54
+ def commit_version
55
+ if @repo
56
+ @repo.add('VERSION.yml')
57
+ @repo.commit("Version bump to #{version}", 'VERSION.yml')
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,8 @@
1
+ class Jeweler
2
+ # Gemspec related error
3
+ class GemspecError < StandardError
4
+ end
5
+
6
+ class VersionYmlError < StandardError
7
+ end
8
+ end
@@ -0,0 +1,48 @@
1
+ class Jeweler
2
+ module Gemspec
3
+ # Writes out the gemspec
4
+ def write_gemspec
5
+ self.refresh_version
6
+ @gemspec.version = self.version
7
+ @gemspec.date = Time.now
8
+ File.open(gemspec_path, 'w') do |f|
9
+ f.write @gemspec.to_ruby
10
+ end
11
+ puts "Generated: #{gemspec_path}"
12
+ end
13
+
14
+ # Validates the gemspec in an environment similar to how GitHub would build
15
+ # it. See http://gist.github.com/16215
16
+ def validate_gemspec
17
+ begin
18
+ parse_gemspec
19
+ puts "#{gemspec_path} is valid."
20
+ rescue Exception => e
21
+ puts "#{gemspec_path} is invalid. See the backtrace for more details."
22
+ raise
23
+ end
24
+ end
25
+
26
+
27
+ def valid_gemspec?
28
+ begin
29
+ parse_gemspec
30
+ true
31
+ rescue Exception => e
32
+ false
33
+ end
34
+ end
35
+
36
+ def parse_gemspec(data = nil)
37
+ data ||= File.read(gemspec_path)
38
+ Thread.new { eval("$SAFE = 3\n#{data}", binding, gemspec_path) }.join
39
+ end
40
+
41
+ protected
42
+ def gemspec_path
43
+ denormalized_path = File.join(@base_dir, "#{@gemspec.name}.gemspec")
44
+ absolute_path = File.expand_path(denormalized_path)
45
+ absolute_path.gsub(Dir.getwd + File::SEPARATOR, '')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,124 @@
1
+ require 'git'
2
+ require 'erb'
3
+
4
+ class Jeweler
5
+ class NoGitUserName < StandardError
6
+ end
7
+ class NoGitUserEmail < StandardError
8
+ end
9
+ class FileInTheWay < StandardError
10
+ end
11
+ class NoGitHubRepoNameGiven < StandardError
12
+ end
13
+ class NoGitHubUser < StandardError
14
+ end
15
+
16
+ class Generator
17
+ attr_accessor :target_dir, :user_name, :user_email,
18
+ :github_repo_name, :github_remote, :github_url, :github_username,
19
+ :lib_dir, :constant_name, :file_name_prefix, :config, :spec
20
+
21
+ def initialize(github_repo_name, dir = nil)
22
+ check_user_git_config()
23
+
24
+ if github_repo_name.nil?
25
+ raise NoGitHubRepoNameGiven
26
+ end
27
+ self.github_repo_name = github_repo_name
28
+
29
+ self.github_remote = "git@github.com:#{github_username}/#{github_repo_name}.git"
30
+ self.github_url = "http://github.com/#{github_username}/#{github_repo_name}"
31
+
32
+
33
+ self.target_dir = dir || self.github_repo_name
34
+ self.lib_dir = File.join(target_dir, 'lib')
35
+ self.constant_name = self.github_repo_name.split(/[-_]/).collect{|each| each.capitalize }.join
36
+ self.file_name_prefix = self.github_repo_name.gsub('-', '_')
37
+ end
38
+
39
+ def run
40
+ create_files
41
+ gitify
42
+ end
43
+
44
+ def testspec
45
+ if self.spec
46
+ 'spec'
47
+ else
48
+ 'test'
49
+ end
50
+ end
51
+
52
+ def test_dir
53
+ File.join(target_dir, testspec)
54
+ end
55
+
56
+
57
+ private
58
+ def create_files
59
+ begin
60
+ FileUtils.mkdir target_dir
61
+ rescue Errno::EEXIST => e
62
+ puts "The directory #{target_dir} already exists, aborting. Maybe move it out of the way before continuing?"
63
+ exit 1
64
+ end
65
+
66
+ FileUtils.mkdir lib_dir
67
+ FileUtils.mkdir test_dir
68
+
69
+ output_template_in_target('.gitignore')
70
+ output_template_in_target('Rakefile')
71
+ output_template_in_target('LICENSE')
72
+ output_template_in_target('README')
73
+ output_template_in_target("#{testspec}/#{testspec}_helper.rb")
74
+ output_template_in_target("#{testspec}/flunking_#{testspec}.rb", "#{testspec}/#{file_name_prefix}_#{testspec}.rb")
75
+
76
+ FileUtils.touch File.join(lib_dir, "#{file_name_prefix}.rb")
77
+ end
78
+
79
+ def check_user_git_config
80
+ self.config = read_git_config
81
+ unless config.has_key? 'user.name'
82
+ raise NoGitUserName, %Q{No user.name set in ~/.gitconfig. Set it with: git config --global user.name 'Your Name Here'}
83
+ end
84
+ unless config.has_key? 'user.email'
85
+ raise NoGitUserEmail, %Q{No user.name set in ~/.gitconfig. Set it with: git config --global user.name 'Your Name Here'}
86
+ end
87
+ unless config.has_key? 'github.user'
88
+ raise NoGitHubUser, %Q{No github.user set in ~/.gitconfig. Set it with: git config --global github.user 'Your username here'}
89
+ end
90
+
91
+ self.user_name = config['user.name']
92
+ self.user_email = config['user.email']
93
+ self.github_username = config['github.user']
94
+ end
95
+
96
+ def output_template_in_target(source, destination = source)
97
+ template = ERB.new(File.read(File.join(File.dirname(__FILE__), 'templates', source)))
98
+ File.open(File.join(target_dir, destination), 'w') {|file| file.write(template.result(binding))}
99
+ end
100
+
101
+ def gitify
102
+ saved_pwd = Dir.pwd
103
+ Dir.chdir(target_dir)
104
+ begin
105
+ repo = Git.init()
106
+ repo.add('.')
107
+ repo.commit "Initial commit to #{github_repo_name}."
108
+ repo.add_remote('origin', github_remote)
109
+ rescue Git::GitExecuteError => e
110
+ puts "Encountered an error during gitification. Maybe the repo already exists, or has already been pushed to?"
111
+ puts
112
+ raise
113
+ end
114
+ Dir.chdir(saved_pwd)
115
+ end
116
+
117
+ def read_git_config
118
+ # we could just use Git::Base's .config, but that relies on a repo being around already
119
+ # ... which we don't have yet, since this is part of a sanity check
120
+ lib = Git::Lib.new(nil, nil)
121
+ config = lib.parse_config '~/.gitconfig'
122
+ end
123
+ end
124
+ end