gemstash 1.0.0.pre.1 → 1.0.0.pre.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +28 -0
- data/README.md +28 -6
- data/Rakefile +9 -19
- data/docs/private_gems.md +1 -1
- data/docs/reference.md +15 -0
- data/gemstash.gemspec +2 -0
- data/lib/gemstash.rb +1 -0
- data/lib/gemstash/cli.rb +6 -0
- data/lib/gemstash/cli/base.rb +3 -1
- data/lib/gemstash/cli/setup.rb +6 -6
- data/lib/gemstash/cli/start.rb +1 -0
- data/lib/gemstash/cli/stop.rb +1 -0
- data/lib/gemstash/env.rb +1 -1
- data/lib/gemstash/gem_source/private_source.rb +1 -2
- data/lib/gemstash/gem_source/upstream_source.rb +15 -15
- data/lib/gemstash/migrations/01_gem_dependencies.rb +7 -7
- data/lib/gemstash/migrations/02_authorizations.rb +2 -2
- data/lib/gemstash/storage.rb +142 -24
- data/lib/gemstash/version.rb +1 -1
- data/rake/changelog.citrus +157 -0
- data/rake/changelog.rb +201 -0
- data/rake/table_of_contents.rb +36 -0
- metadata +34 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 323e0282020fd74fa61840b2492370fd1e9b4241
|
4
|
+
data.tar.gz: 3e3d5e4569c9e46f81fcb383f3001d01421f3cf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4132d846fbbc750db4934f61acf625bb8fd5fe96c5b9b902f48e8a0630725b6af14dc1128fdcf12f875b242fc97a035c54ed782c163fc8f70b2deb0c27b02729
|
7
|
+
data.tar.gz: d67634e744f569a2fc3e677c8a7e0db65dfecab5d67804fb5aacf4e49c9c5071cc9b0e8d5581816b9d118fd070819531bac077f2c9ede20d40f66a1d3741a1ce
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
## 1.0.0.pre.2 (2015-12-14)
|
2
|
+
|
3
|
+
### Upgrade Notes
|
4
|
+
|
5
|
+
- If you pushed any private gems to your Gemstash instance, you will need to run: https://gist.github.com/smellsblue/53f5a6757dcc91ad10bc
|
6
|
+
|
7
|
+
### Bugfixes
|
8
|
+
|
9
|
+
- Add --pre option to gemstash installation documentation ([#54](https://github.com/bundler/gemstash/pull/54), [@farukaydin](https://github.com/farukaydin))
|
10
|
+
- Fix docs for `gemstash authorize` ([#59](https://github.com/bundler/gemstash/pull/59), [@farukaydin](https://github.com/farukaydin))
|
11
|
+
- Refactoring, changed resource metadata `:gemstash_storage_version` to use `:gemstash_resource_version` ([#60](https://github.com/bundler/gemstash/pull/60), [@smellsblue](https://github.com/smellsblue))
|
12
|
+
- Fix migrations for utf8 on MySQL >= 5.5 ([#64](https://github.com/bundler/gemstash/pull/64), [@chriseckhardt](https://github.com/chriseckhardt))
|
13
|
+
|
14
|
+
### Features
|
15
|
+
|
16
|
+
- Support MySQL as DB backend ([#52](https://github.com/bundler/gemstash/pull/52), [@pcarranza](https://github.com/pcarranza))
|
17
|
+
- Add start/stop output ([#58](https://github.com/bundler/gemstash/pull/58), [@farukaydin](https://github.com/farukaydin))
|
18
|
+
- Add `gemstash --version` ([#62](https://github.com/bundler/gemstash/pull/62), [@smellsblue](https://github.com/smellsblue))
|
19
|
+
- Create the CHANGELOG ([#63](https://github.com/bundler/gemstash/pull/63), [@smellsblue](https://github.com/smellsblue))
|
20
|
+
|
21
|
+
## 1.0.0.pre.1 (2015-11-30)
|
22
|
+
|
23
|
+
### Features
|
24
|
+
|
25
|
+
- Cache gems from multiple sources
|
26
|
+
- Push, yank, and unyank private gems
|
27
|
+
- Zero setup dependencies
|
28
|
+
- Optionally use Memcached for caching or PostgreSQL for the database
|
data/README.md
CHANGED
@@ -28,12 +28,14 @@ Quickstart Guide, you will be able to bundle stashed gems from public sources
|
|
28
28
|
against a Gemstash server running on your machine.
|
29
29
|
|
30
30
|
Install Gemstash to get started:
|
31
|
+
|
31
32
|
```
|
32
|
-
$ gem install gemstash
|
33
|
+
$ gem install gemstash --pre
|
33
34
|
```
|
34
35
|
|
35
36
|
After it is installed, starting Gemstash requires no additional steps. Simply
|
36
37
|
start the Gemstash server with the `gemstash` command:
|
38
|
+
|
37
39
|
```
|
38
40
|
$ gemstash start
|
39
41
|
```
|
@@ -43,17 +45,24 @@ will run the server in the background by default. The server runs on port 9292.
|
|
43
45
|
|
44
46
|
### Bundling
|
45
47
|
|
46
|
-
With the server running, you can bundle against it.
|
47
|
-
|
48
|
+
With the server running, you can bundle against it. Tell Bundler that you want
|
49
|
+
to use Gemstash to find gems from RubyGems.org:
|
50
|
+
|
51
|
+
```
|
52
|
+
$ bundle config mirror.https://rubygems.org http://localhost:9292
|
53
|
+
```
|
54
|
+
|
55
|
+
Now you can create a Gemfile and install gems through Gemstash:
|
56
|
+
|
48
57
|
```ruby
|
49
58
|
# ./Gemfile
|
50
|
-
source "
|
59
|
+
source "https://rubygems.org"
|
51
60
|
gem "rubywarrior"
|
52
61
|
```
|
53
62
|
|
54
|
-
The
|
55
|
-
server. The gems you include should be gems you don't yet have installed,
|
63
|
+
The gems you include should be gems you don't yet have installed,
|
56
64
|
otherwise Gemstash will have nothing to stash. Now bundle:
|
65
|
+
|
57
66
|
```
|
58
67
|
$ bundle install --path .bundle
|
59
68
|
```
|
@@ -63,6 +72,7 @@ cached them for you! To prove this, you can disable your Internet connection and
|
|
63
72
|
try again. The gem dependencies from https://www.rubygems.org are cached for 30
|
64
73
|
minutes, so if you bundle again before that, you can successfully bundle without
|
65
74
|
an Internet connection:
|
75
|
+
|
66
76
|
```
|
67
77
|
$ # Disable your Internet first!
|
68
78
|
$ rm -rf Gemfile.lock .bundle
|
@@ -73,10 +83,18 @@ $ bundle
|
|
73
83
|
|
74
84
|
Once you've finish using your Gemstash server, you can stop it just as easily as
|
75
85
|
you started it:
|
86
|
+
|
76
87
|
```
|
77
88
|
$ gemstash stop
|
78
89
|
```
|
79
90
|
|
91
|
+
You'll also want to tell Bundler that it can go back to getting gems from
|
92
|
+
RubyGems.org directly, instead of going through Gemstash:
|
93
|
+
|
94
|
+
```
|
95
|
+
$ bundle config --delete mirror.https://rubygems.org
|
96
|
+
```
|
97
|
+
|
80
98
|
### Under the Hood
|
81
99
|
|
82
100
|
You might wonder where the gems are stored. After running the commands above,
|
@@ -118,6 +136,10 @@ For an anatomy of various configuration and commands, follow the links:
|
|
118
136
|
* [Stop](docs/reference.md#stop)
|
119
137
|
* [Status](docs/reference.md#status)
|
120
138
|
* [Setup](docs/reference.md#setup)
|
139
|
+
* [Version](docs/reference.md#version)
|
140
|
+
|
141
|
+
To see what has changed in recent versions of Gemstash, see the
|
142
|
+
[CHANGELOG](CHANGELOG.md).
|
121
143
|
|
122
144
|
## Development
|
123
145
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
require "rubocop/rake_task"
|
4
|
+
require_relative "rake/changelog.rb"
|
5
|
+
require_relative "rake/table_of_contents.rb"
|
4
6
|
|
5
7
|
RuboCop::RakeTask.new
|
6
8
|
|
@@ -9,27 +11,15 @@ RSpec::Core::RakeTask.new(:spec) do |t|
|
|
9
11
|
t.rspec_opts = %w(--color)
|
10
12
|
end
|
11
13
|
|
12
|
-
task :
|
13
|
-
task :
|
14
|
+
task spec: :rubocop
|
15
|
+
task default: :spec
|
14
16
|
|
15
17
|
desc "Generate Table of Contents for certain docs"
|
16
18
|
task :toc do
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
unless File.exist?(toc)
|
21
|
-
require "open-uri"
|
22
|
-
toc_contents = open("https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc", &:read)
|
23
|
-
Dir.mkdir(toc_dir) unless Dir.exist?(toc_dir)
|
24
|
-
File.write(toc, toc_contents)
|
25
|
-
File.chmod(0776, toc)
|
26
|
-
end
|
19
|
+
TableOfContents.new.run
|
20
|
+
end
|
27
21
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
File.write(doc, old_contents)
|
32
|
-
toc_contents = `"#{toc}" "#{doc}"`
|
33
|
-
toc_contents.sub!(/Created by.*$/, "")
|
34
|
-
File.write(doc, "#{toc_contents}\n#{old_contents}")
|
22
|
+
desc "Update ChangeLog based on commits in master"
|
23
|
+
task :changelog do
|
24
|
+
Changelog.new.run
|
35
25
|
end
|
data/docs/private_gems.md
CHANGED
@@ -14,7 +14,7 @@ key against your server. Instead of the key value here, use whatever key is
|
|
14
14
|
generated from running the commands.
|
15
15
|
|
16
16
|
In order to push a gem to your Gemstash server, you need to first create an API
|
17
|
-
key. Utilize the `gemstash` command to create the API key:
|
17
|
+
key. Utilize the `gemstash authorize` command to create the API key:
|
18
18
|
```
|
19
19
|
$ gemstash authorize
|
20
20
|
Your new key is: e374e237fdf5fa5718d2a21bd63dc911
|
data/docs/reference.md
CHANGED
@@ -37,6 +37,8 @@ Table of Contents
|
|
37
37
|
* [--redo](#--redo)
|
38
38
|
* [--debug](#--debug)
|
39
39
|
* [--config-file](#--config-file-4)
|
40
|
+
* [Version](#version)
|
41
|
+
* [Usage](#usage-5)
|
40
42
|
|
41
43
|
|
42
44
|
|
@@ -182,6 +184,7 @@ Specify the API key to affect. This should be the actual key value, not a name.
|
|
182
184
|
This option is required when using `--remove` but is optional otherwise. If
|
183
185
|
adding an authorization, using this will either create or update the permissions
|
184
186
|
for the specified API key. If missing, a new API key will always be generated.
|
187
|
+
Note that a key can only have a maximum length of 255 chars.
|
185
188
|
|
186
189
|
#### --remove
|
187
190
|
|
@@ -303,6 +306,18 @@ Specify the config file to write to. Without this option, your configuration
|
|
303
306
|
will be written to `~/.gemstash/config.yml`. If you write to a custom location,
|
304
307
|
you will need to pass the `--config-file` option to all Gemstash commands.
|
305
308
|
|
309
|
+
## Version
|
310
|
+
|
311
|
+
Show what version of Gemstash you are using.
|
312
|
+
|
313
|
+
### Usage
|
314
|
+
|
315
|
+
```
|
316
|
+
gemstash version
|
317
|
+
gemstash --version
|
318
|
+
gemstash -v
|
319
|
+
```
|
320
|
+
|
306
321
|
---
|
307
322
|
|
308
323
|
Table of contents thanks to [gh-md-toc](https://github.com/ekalinin/github-markdown-toc).
|
data/gemstash.gemspec
CHANGED
@@ -40,6 +40,8 @@ you push your own private gems as well."
|
|
40
40
|
end
|
41
41
|
|
42
42
|
spec.add_development_dependency "bundler", "~> 1.10"
|
43
|
+
spec.add_development_dependency "citrus", "~> 3.0"
|
44
|
+
spec.add_development_dependency "octokit", "~> 4.2"
|
43
45
|
spec.add_development_dependency "rack-test", "~> 0.6"
|
44
46
|
spec.add_development_dependency "rake", "~> 10.0"
|
45
47
|
spec.add_development_dependency "rspec", "~> 3.3"
|
data/lib/gemstash.rb
CHANGED
@@ -17,6 +17,7 @@ module Gemstash
|
|
17
17
|
autoload :LruReduxClient, "gemstash/cache"
|
18
18
|
autoload :NotAuthorizedError, "gemstash/authorization"
|
19
19
|
autoload :RackEnvRewriter, "gemstash/rack_env_rewriter"
|
20
|
+
autoload :Resource, "gemstash/storage"
|
20
21
|
autoload :SpecsBuilder, "gemstash/specs_builder"
|
21
22
|
autoload :Storage, "gemstash/storage"
|
22
23
|
autoload :Upstream, "gemstash/upstream"
|
data/lib/gemstash/cli.rb
CHANGED
data/lib/gemstash/cli/base.rb
CHANGED
@@ -35,7 +35,9 @@ module Gemstash
|
|
35
35
|
def check_gemstash_version
|
36
36
|
version = Gem::Version.new(Gemstash::Storage.metadata[:gemstash_version])
|
37
37
|
return if Gem::Requirement.new("<= #{Gemstash::VERSION}").satisfied_by?(Gem::Version.new(version))
|
38
|
-
raise Gemstash::CLI::Error.new(@cli, "Gemstash version
|
38
|
+
raise Gemstash::CLI::Error.new(@cli, "Gemstash version #{Gemstash::VERSION} does not support version " \
|
39
|
+
"#{version}.\nIt appears you may have downgraded Gemstash, please " \
|
40
|
+
"install version #{version} or later.")
|
39
41
|
end
|
40
42
|
|
41
43
|
def pidfile_args
|
data/lib/gemstash/cli/setup.rb
CHANGED
@@ -77,27 +77,27 @@ module Gemstash
|
|
77
77
|
|
78
78
|
def ask_database
|
79
79
|
say_current_config(:db_adapter, "Current database adapter")
|
80
|
-
options = %w(sqlite3 postgres)
|
80
|
+
options = %w(sqlite3 postgres mysql)
|
81
81
|
database = nil
|
82
82
|
|
83
83
|
until database
|
84
|
-
database = @cli.ask "What database adapter? [SQLITE3, postgres]"
|
84
|
+
database = @cli.ask "What database adapter? [SQLITE3, postgres, mysql]"
|
85
85
|
database = database.downcase
|
86
86
|
database = "sqlite3" if database.empty?
|
87
87
|
database = nil unless options.include?(database)
|
88
88
|
end
|
89
89
|
|
90
90
|
@config[:db_adapter] = database
|
91
|
-
|
91
|
+
ask_database_details(database) unless database == "sqlite3"
|
92
92
|
end
|
93
93
|
|
94
|
-
def
|
94
|
+
def ask_database_details(database)
|
95
95
|
say_current_config(:db_url, "Current database url")
|
96
96
|
|
97
97
|
if RUBY_PLATFORM == "java"
|
98
|
-
default_value = "jdbc
|
98
|
+
default_value = "jdbc:#{database}:///gemstash"
|
99
99
|
else
|
100
|
-
default_value = "
|
100
|
+
default_value = "#{database}:///gemstash"
|
101
101
|
end
|
102
102
|
|
103
103
|
url = @cli.ask "Where is the database? [#{default_value}]"
|
data/lib/gemstash/cli/start.rb
CHANGED
data/lib/gemstash/cli/stop.rb
CHANGED
data/lib/gemstash/env.rb
CHANGED
@@ -113,7 +113,7 @@ module Gemstash
|
|
113
113
|
else
|
114
114
|
db = Sequel.connect("sqlite://#{URI.escape(db_path)}", max_connections: 1)
|
115
115
|
end
|
116
|
-
when "postgres"
|
116
|
+
when "postgres", "mysql"
|
117
117
|
db = Sequel.connect(config[:db_url])
|
118
118
|
else
|
119
119
|
raise "Unsupported DB adapter: '#{config[:db_adapter]}'"
|
@@ -71,7 +71,7 @@ module Gemstash
|
|
71
71
|
gem = fetch_gem(gem_full_name)
|
72
72
|
halt 404 unless gem.exist?(:spec)
|
73
73
|
content_type "application/octet-stream"
|
74
|
-
gem.
|
74
|
+
gem.content(:spec)
|
75
75
|
end
|
76
76
|
|
77
77
|
def serve_actual_gem(id)
|
@@ -130,7 +130,6 @@ module Gemstash
|
|
130
130
|
def fetch_gem(gem_full_name)
|
131
131
|
gem = storage.resource(gem_full_name)
|
132
132
|
halt 404 unless gem.exist?(:gem)
|
133
|
-
gem.load(:gem)
|
134
133
|
halt 403, "That gem has been yanked" unless gem.properties[:indexed]
|
135
134
|
gem
|
136
135
|
end
|
@@ -118,10 +118,10 @@ module Gemstash
|
|
118
118
|
|
119
119
|
private
|
120
120
|
|
121
|
-
def serve_cached(id,
|
122
|
-
gem = fetch_gem(id,
|
123
|
-
headers.update(gem.properties[:headers][
|
124
|
-
gem.content(
|
121
|
+
def serve_cached(id, resource_type)
|
122
|
+
gem = fetch_gem(id, resource_type)
|
123
|
+
headers.update(gem.properties[:headers][resource_type]) if gem.property?(:headers, resource_type)
|
124
|
+
gem.content(resource_type)
|
125
125
|
rescue Gemstash::WebError => e
|
126
126
|
halt e.code
|
127
127
|
end
|
@@ -142,25 +142,25 @@ module Gemstash
|
|
142
142
|
@gem_fetcher ||= Gemstash::GemFetcher.new(http_client_for(upstream))
|
143
143
|
end
|
144
144
|
|
145
|
-
def fetch_gem(id,
|
145
|
+
def fetch_gem(id, resource_type)
|
146
146
|
gem_name = Gemstash::Upstream::GemName.new(upstream, id)
|
147
147
|
gem_resource = storage.resource(gem_name.name)
|
148
|
-
if gem_resource.exist?(
|
149
|
-
fetch_local_gem(gem_name, gem_resource,
|
148
|
+
if gem_resource.exist?(resource_type)
|
149
|
+
fetch_local_gem(gem_name, gem_resource, resource_type)
|
150
150
|
else
|
151
|
-
fetch_remote_gem(gem_name, gem_resource,
|
151
|
+
fetch_remote_gem(gem_name, gem_resource, resource_type)
|
152
152
|
end
|
153
153
|
end
|
154
154
|
|
155
|
-
def fetch_local_gem(gem_name, gem_resource,
|
156
|
-
log.info "Gem #{gem_name.name} exists, returning cached #{
|
157
|
-
gem_resource
|
155
|
+
def fetch_local_gem(gem_name, gem_resource, resource_type)
|
156
|
+
log.info "Gem #{gem_name.name} exists, returning cached #{resource_type}"
|
157
|
+
gem_resource
|
158
158
|
end
|
159
159
|
|
160
|
-
def fetch_remote_gem(gem_name, gem_resource,
|
161
|
-
log.info "Gem #{gem_name.name} is not cached, fetching #{
|
162
|
-
gem_fetcher.fetch(gem_name.id,
|
163
|
-
gem_resource.save({
|
160
|
+
def fetch_remote_gem(gem_name, gem_resource, resource_type)
|
161
|
+
log.info "Gem #{gem_name.name} is not cached, fetching #{resource_type}"
|
162
|
+
gem_fetcher.fetch(gem_name.id, resource_type) do |content, properties|
|
163
|
+
gem_resource.save({ resource_type => content }, headers: { resource_type => properties })
|
164
164
|
end
|
165
165
|
end
|
166
166
|
end
|
@@ -2,7 +2,7 @@ Sequel.migration do
|
|
2
2
|
change do
|
3
3
|
create_table :rubygems do
|
4
4
|
primary_key :id
|
5
|
-
String :name, :size =>
|
5
|
+
String :name, :size => 191, :null => false
|
6
6
|
DateTime :created_at, :null => false
|
7
7
|
DateTime :updated_at, :null => false
|
8
8
|
index [:name], :unique => true
|
@@ -11,10 +11,10 @@ Sequel.migration do
|
|
11
11
|
create_table :versions do
|
12
12
|
primary_key :id
|
13
13
|
Integer :rubygem_id, :null => false
|
14
|
-
String :storage_id, :size =>
|
15
|
-
String :number, :size =>
|
16
|
-
String :platform, :size =>
|
17
|
-
String :full_name, :size =>
|
14
|
+
String :storage_id, :size => 191, :null => false
|
15
|
+
String :number, :size => 191, :null => false
|
16
|
+
String :platform, :size => 191, :null => false
|
17
|
+
String :full_name, :size => 191, :null => false
|
18
18
|
TrueClass :indexed, :default => true, :null => false
|
19
19
|
TrueClass :prerelease, :null => false
|
20
20
|
DateTime :created_at, :null => false
|
@@ -30,8 +30,8 @@ Sequel.migration do
|
|
30
30
|
create_table :dependencies do
|
31
31
|
primary_key :id
|
32
32
|
Integer :version_id, :null => false
|
33
|
-
String :rubygem_name, :size =>
|
34
|
-
String :requirements, :size =>
|
33
|
+
String :rubygem_name, :size => 191, :null => false
|
34
|
+
String :requirements, :size => 191, :null => false
|
35
35
|
DateTime :created_at, :null => false
|
36
36
|
DateTime :updated_at, :null => false
|
37
37
|
index [:version_id]
|
@@ -2,8 +2,8 @@ Sequel.migration do
|
|
2
2
|
change do
|
3
3
|
create_table :authorizations do
|
4
4
|
primary_key :id
|
5
|
-
String :auth_key, :size =>
|
6
|
-
String :permissions, :size =>
|
5
|
+
String :auth_key, :size => 191, :null => false
|
6
|
+
String :permissions, :size => 191, :null => false
|
7
7
|
DateTime :created_at, :null => false
|
8
8
|
DateTime :updated_at, :null => false
|
9
9
|
index [:auth_key], :unique => true
|
data/lib/gemstash/storage.rb
CHANGED
@@ -5,34 +5,57 @@ require "fileutils"
|
|
5
5
|
require "yaml"
|
6
6
|
|
7
7
|
module Gemstash
|
8
|
-
|
8
|
+
# The entry point into the storage engine for storing cached gems, specs, and
|
9
|
+
# private gems.
|
9
10
|
class Storage
|
10
11
|
extend Gemstash::Env::Helper
|
11
12
|
VERSION = 1
|
12
13
|
|
13
|
-
# If the storage engine detects
|
14
|
-
#
|
14
|
+
# If the storage engine detects the base cache directory was originally
|
15
|
+
# initialized with a newer version, this error is thrown.
|
15
16
|
class VersionTooNew < StandardError
|
17
|
+
def initialize(folder, version)
|
18
|
+
super("Gemstash storage version #{Gemstash::Storage::VERSION} does " \
|
19
|
+
"not support version #{version} found at #{folder}")
|
20
|
+
end
|
16
21
|
end
|
17
22
|
|
23
|
+
# This object should not be constructed directly, but instead via
|
24
|
+
# {for} and {#for}.
|
18
25
|
def initialize(folder, root: true)
|
19
|
-
check_engine if root
|
20
26
|
@folder = folder
|
27
|
+
check_storage_version if root
|
21
28
|
FileUtils.mkpath(@folder) unless Dir.exist?(@folder)
|
22
29
|
end
|
23
30
|
|
31
|
+
# Fetch the resource with the given +id+ within this storage.
|
32
|
+
#
|
33
|
+
# @param id [String] the id of the resource to fetch
|
34
|
+
# @return [Gemstash::Resource] a new resource instance from the +id+
|
24
35
|
def resource(id)
|
25
36
|
Resource.new(@folder, id)
|
26
37
|
end
|
27
38
|
|
39
|
+
# Fetch a nested entry from this instance in the storage engine.
|
40
|
+
#
|
41
|
+
# @param child [String] the name of the nested entry to load
|
42
|
+
# @return [Gemstash::Storage] a new storage instance for the +child+
|
28
43
|
def for(child)
|
29
44
|
Storage.new(File.join(@folder, child), root: false)
|
30
45
|
end
|
31
46
|
|
47
|
+
# Fetch a base entry in the storage engine.
|
48
|
+
#
|
49
|
+
# @param name [String] the name of the entry to load
|
50
|
+
# @return [Gemstash::Storage] a new storage instance for the +name+
|
32
51
|
def self.for(name)
|
33
52
|
new(gemstash_env.base_file(name))
|
34
53
|
end
|
35
54
|
|
55
|
+
# Read the global metadata for Gemstash and the storage engine. If the
|
56
|
+
# metadata hasn't been stored yet, it will be created.
|
57
|
+
#
|
58
|
+
# @return [Hash] the metadata about Gemstash and the storage engine
|
36
59
|
def self.metadata
|
37
60
|
file = gemstash_env.base_file("metadata.yml")
|
38
61
|
|
@@ -46,10 +69,10 @@ module Gemstash
|
|
46
69
|
|
47
70
|
private
|
48
71
|
|
49
|
-
def
|
72
|
+
def check_storage_version
|
50
73
|
version = Gemstash::Storage.metadata[:storage_version]
|
51
74
|
return if version <= Gemstash::Storage::VERSION
|
52
|
-
raise Gemstash::Storage::VersionTooNew,
|
75
|
+
raise Gemstash::Storage::VersionTooNew.new(@folder, version)
|
53
76
|
end
|
54
77
|
|
55
78
|
def path_valid?(path)
|
@@ -59,10 +82,25 @@ module Gemstash
|
|
59
82
|
end
|
60
83
|
end
|
61
84
|
|
62
|
-
|
85
|
+
# A resource within the storage engine. The resource may have 1 or more files
|
86
|
+
# associated with it along with a metadata Hash that is stored in a YAML file.
|
63
87
|
class Resource
|
64
88
|
include Gemstash::Logging
|
65
89
|
attr_reader :name, :folder
|
90
|
+
VERSION = 1
|
91
|
+
|
92
|
+
# If the storage engine detects a resource was originally saved from a newer
|
93
|
+
# version, this error is thrown.
|
94
|
+
class VersionTooNew < StandardError
|
95
|
+
def initialize(name, folder, version)
|
96
|
+
super("Gemstash resource version #{Gemstash::Resource::VERSION} does " \
|
97
|
+
"not support version #{version} for resource #{name.inspect} " \
|
98
|
+
"found at #{folder}")
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# This object should not be constructed directly, but instead via
|
103
|
+
# {Gemstash::Storage#resource}.
|
66
104
|
def initialize(folder, name)
|
67
105
|
@base_path = folder
|
68
106
|
@name = name
|
@@ -78,6 +116,13 @@ module Gemstash
|
|
78
116
|
@folder = File.join(@base_path, *trie_parents, child_folder)
|
79
117
|
end
|
80
118
|
|
119
|
+
# When +key+ is nil, this will test if this resource exists with any
|
120
|
+
# content. If a +key+ is provided, this will test that the resource exists
|
121
|
+
# with at least the given +key+ file. The +key+ corresponds to the +content+
|
122
|
+
# key provided to {#save}.
|
123
|
+
#
|
124
|
+
# @param key [Symbol, nil] the key of the content to check existence
|
125
|
+
# @return [Boolean] true if the indicated content exists
|
81
126
|
def exist?(key = nil)
|
82
127
|
if key
|
83
128
|
File.exist?(properties_filename) && File.exist?(content_filename(key))
|
@@ -86,6 +131,25 @@ module Gemstash
|
|
86
131
|
end
|
87
132
|
end
|
88
133
|
|
134
|
+
# Save one or more files for this resource given by the +content+ hash.
|
135
|
+
# Metadata properties about the file(s) may be provided in the optional
|
136
|
+
# +properties+ parameter. The keys in the content hash correspond to the
|
137
|
+
# file name for this resource, while the values will be the content stored
|
138
|
+
# for that key.
|
139
|
+
#
|
140
|
+
# Separate calls to save for the same resource will replace existing files,
|
141
|
+
# and add new ones. Properties on additional calls will be merged with
|
142
|
+
# existing properties.
|
143
|
+
#
|
144
|
+
# Examples:
|
145
|
+
#
|
146
|
+
# Gemstash::Storage.for("foo").resource("bar").save(baz: "qux")
|
147
|
+
# Gemstash::Storage.for("foo").resource("bar").save(baz: "one", qux: "two")
|
148
|
+
# Gemstash::Storage.for("foo").resource("bar").save({ baz: "qux" }, meta: true)
|
149
|
+
#
|
150
|
+
# @param content [Hash{Symbol => String}] files to save, *must not be nil*
|
151
|
+
# @param properties [Hash, nil] metadata properties related to this resource
|
152
|
+
# @return [Gemstash::Resource] self for chaining purposes
|
89
153
|
def save(content, properties = nil)
|
90
154
|
content.each do |key, value|
|
91
155
|
save_content(key, value)
|
@@ -95,28 +159,74 @@ module Gemstash
|
|
95
159
|
self
|
96
160
|
end
|
97
161
|
|
162
|
+
# Fetch the content for the given +key+. This will load and cache the
|
163
|
+
# properties and the content of the +key+. The +key+ corresponds to the
|
164
|
+
# +content+ key provided to {#save}.
|
165
|
+
#
|
166
|
+
# @param key [Symbol] the key of the content to load
|
167
|
+
# @return [String] the content stored in the +key+
|
98
168
|
def content(key)
|
169
|
+
@content ||= {}
|
170
|
+
load(key) unless @content.include?(key)
|
99
171
|
@content[key]
|
100
172
|
end
|
101
173
|
|
174
|
+
# Fetch the metadata properties for this resource. The properties will be
|
175
|
+
# cached for future calls.
|
176
|
+
#
|
177
|
+
# @return [Hash] the metadata properties for this resource
|
102
178
|
def properties
|
179
|
+
load_properties
|
103
180
|
@properties || {}
|
104
181
|
end
|
105
182
|
|
183
|
+
# Update the metadata properties of this resource. The +props+ will be
|
184
|
+
# merged with any existing properties.
|
185
|
+
#
|
186
|
+
# @param props [Hash] the properties to add
|
187
|
+
# @return [Gemstash::Resource] self for chaining purposes
|
106
188
|
def update_properties(props)
|
107
|
-
load_properties
|
189
|
+
load_properties(true)
|
108
190
|
save_properties(properties.merge(props || {}))
|
109
191
|
self
|
110
192
|
end
|
111
193
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
194
|
+
# Check if the metadata properties includes the +keys+. The +keys+ represent
|
195
|
+
# a nested path in the properties to check.
|
196
|
+
#
|
197
|
+
# Examples:
|
198
|
+
#
|
199
|
+
# resource = Gemstash::Storage.for("x").resource("y")
|
200
|
+
# resource.save({ file: "content" }, foo: "one", bar: { baz: "qux" })
|
201
|
+
# resource.has_property?(:foo) # true
|
202
|
+
# resource.has_property?(:bar, :baz) # true
|
203
|
+
# resource.has_property?(:missing) # false
|
204
|
+
# resource.has_property?(:foo, :bar) # false
|
205
|
+
#
|
206
|
+
# @param keys [Array<Object>] one or more keys pointing to a property
|
207
|
+
# @return [Boolean] whether the nested keys points to a valid property
|
208
|
+
def property?(*keys)
|
209
|
+
keys.inject(node: properties, result: true) do |memo, key|
|
210
|
+
if memo[:result]
|
211
|
+
memo[:result] = memo[:node].is_a?(Hash) && memo[:node].include?(key)
|
212
|
+
memo[:node] = memo[:node][key] if memo[:result]
|
213
|
+
end
|
214
|
+
|
215
|
+
memo
|
216
|
+
end[:result]
|
118
217
|
end
|
119
218
|
|
219
|
+
# Delete the content for the given +key+. If the +key+ is the last one for
|
220
|
+
# this resource, the metadata properties will be deleted as well. The +key+
|
221
|
+
# corresponds to the +content+ key provided to {#save}.
|
222
|
+
#
|
223
|
+
# The resource will be reset afterwards, clearing any cached content or
|
224
|
+
# properties.
|
225
|
+
#
|
226
|
+
# Does nothing if the key doesn't {#exist?}.
|
227
|
+
#
|
228
|
+
# @param key [Symbol] the key of the content to delete
|
229
|
+
# @return [Gemstash::Resource] self for chaining purposes
|
120
230
|
def delete(key)
|
121
231
|
return self unless exist?(key)
|
122
232
|
|
@@ -139,17 +249,25 @@ module Gemstash
|
|
139
249
|
|
140
250
|
private
|
141
251
|
|
142
|
-
def
|
252
|
+
def load(key)
|
253
|
+
raise "Resource #{@name} has no #{key.inspect} content to load" unless exist?(key)
|
254
|
+
load_properties # Ensures storage version is checked
|
255
|
+
@content ||= {}
|
256
|
+
@content[key] = read_file(content_filename(key))
|
257
|
+
end
|
258
|
+
|
259
|
+
def load_properties(force = false)
|
260
|
+
return if @properties && !force
|
143
261
|
return unless File.exist?(properties_filename)
|
144
|
-
@properties = YAML.load_file(properties_filename)
|
145
|
-
|
262
|
+
@properties = YAML.load_file(properties_filename) || {}
|
263
|
+
check_resource_version
|
146
264
|
end
|
147
265
|
|
148
|
-
def
|
149
|
-
version = @properties[:
|
150
|
-
return if version <= Gemstash::
|
266
|
+
def check_resource_version
|
267
|
+
version = @properties[:gemstash_resource_version]
|
268
|
+
return if version <= Gemstash::Resource::VERSION
|
151
269
|
reset
|
152
|
-
raise Gemstash::
|
270
|
+
raise Gemstash::Resource::VersionTooNew.new(name, folder, version)
|
153
271
|
end
|
154
272
|
|
155
273
|
def reset
|
@@ -159,8 +277,8 @@ module Gemstash
|
|
159
277
|
|
160
278
|
def content?
|
161
279
|
return false unless Dir.exist?(@folder)
|
162
|
-
entries = Dir.entries(@folder).reject {|file| file =~ /\A\.\.?\z/ }
|
163
|
-
!entries.empty?
|
280
|
+
entries = Dir.entries(@folder).reject {|file| file =~ /\A\.\.?\z/ || file == "properties.yaml" }
|
281
|
+
!entries.empty?
|
164
282
|
end
|
165
283
|
|
166
284
|
def sanitize(name)
|
@@ -175,7 +293,7 @@ module Gemstash
|
|
175
293
|
|
176
294
|
def save_properties(props)
|
177
295
|
props ||= {}
|
178
|
-
props = {
|
296
|
+
props = { gemstash_resource_version: Gemstash::Resource::VERSION }.merge(props)
|
179
297
|
store(properties_filename, props.to_yaml)
|
180
298
|
@properties = props
|
181
299
|
end
|
data/lib/gemstash/version.rb
CHANGED
@@ -0,0 +1,157 @@
|
|
1
|
+
# Resulting structure:
|
2
|
+
#
|
3
|
+
# versions[]
|
4
|
+
# number
|
5
|
+
# date
|
6
|
+
# description
|
7
|
+
# sections[]
|
8
|
+
# title
|
9
|
+
# description
|
10
|
+
# changes[]
|
11
|
+
# comment
|
12
|
+
# pull_requests[]
|
13
|
+
# number
|
14
|
+
# url
|
15
|
+
# authors[]
|
16
|
+
# username
|
17
|
+
# url
|
18
|
+
grammar Changelog::Grammar
|
19
|
+
rule versions
|
20
|
+
(version+) {
|
21
|
+
def versions
|
22
|
+
captures[:version]
|
23
|
+
end
|
24
|
+
}
|
25
|
+
end
|
26
|
+
|
27
|
+
rule version
|
28
|
+
(version_header version_section+) {
|
29
|
+
def number
|
30
|
+
capture(:version_header).capture(:version_number).value
|
31
|
+
end
|
32
|
+
|
33
|
+
def date
|
34
|
+
capture(:version_header).capture(:date).value
|
35
|
+
end
|
36
|
+
|
37
|
+
def description
|
38
|
+
result = capture(:version_header).capture(:description)
|
39
|
+
result.value if result
|
40
|
+
end
|
41
|
+
|
42
|
+
def sections
|
43
|
+
captures[:version_section]
|
44
|
+
end
|
45
|
+
|
46
|
+
def pull_requests
|
47
|
+
sections.map(&:changes).flatten.map(&:pull_requests).flatten
|
48
|
+
end
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
rule version_header
|
53
|
+
"## " version_number:(/\d+(\.\d+)*(\.pre\.\d+)?/) " (" date:(/\d{4}-\d{2}-\d{2}/) ")\n\n"
|
54
|
+
description?
|
55
|
+
end
|
56
|
+
|
57
|
+
rule version_section
|
58
|
+
("### " title:(/[^\n]*/) "\n\n" description? changes?) {
|
59
|
+
def title
|
60
|
+
capture(:title).value
|
61
|
+
end
|
62
|
+
|
63
|
+
def heading
|
64
|
+
"### #{title}\n\n#{description}"
|
65
|
+
end
|
66
|
+
|
67
|
+
def description
|
68
|
+
capture(:description).value if capture(:description)
|
69
|
+
end
|
70
|
+
|
71
|
+
def changes
|
72
|
+
if capture(:changes)
|
73
|
+
capture(:changes).captures[:change]
|
74
|
+
else
|
75
|
+
[]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
rule description
|
82
|
+
paragraph+
|
83
|
+
end
|
84
|
+
|
85
|
+
rule paragraph
|
86
|
+
(" " !"- " /\w[^\n]*/ "\n")+ "\n"
|
87
|
+
end
|
88
|
+
|
89
|
+
rule changes
|
90
|
+
change+ "\n"?
|
91
|
+
end
|
92
|
+
|
93
|
+
rule change
|
94
|
+
(" - " comment:(/[^()\n]*/) pull_requests_and_authors? "\n") {
|
95
|
+
def comment
|
96
|
+
capture(:comment).value
|
97
|
+
end
|
98
|
+
|
99
|
+
def pull_requests
|
100
|
+
pull_requests_and_authors(:pull_requests)
|
101
|
+
end
|
102
|
+
|
103
|
+
def authors
|
104
|
+
pull_requests_and_authors(:authors)
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
|
109
|
+
def pull_requests_and_authors(type)
|
110
|
+
return [] unless capture(:pull_requests_and_authors)
|
111
|
+
return [] unless capture(:pull_requests_and_authors).capture(type)
|
112
|
+
singular = type.to_s.sub(/s$/, "").to_sym
|
113
|
+
capture(:pull_requests_and_authors).capture(type).capture(singular) || []
|
114
|
+
end
|
115
|
+
}
|
116
|
+
end
|
117
|
+
|
118
|
+
rule pull_requests_and_authors
|
119
|
+
"(" pull_requests ", " authors ")"
|
120
|
+
end
|
121
|
+
|
122
|
+
rule pull_requests
|
123
|
+
pull_request (", " pull_request)*
|
124
|
+
end
|
125
|
+
|
126
|
+
rule pull_request
|
127
|
+
("[#" number:(/\d+/) "](" github_url ")") {
|
128
|
+
def number
|
129
|
+
capture(:number).value
|
130
|
+
end
|
131
|
+
|
132
|
+
def url
|
133
|
+
capture(:github_url).value
|
134
|
+
end
|
135
|
+
}
|
136
|
+
end
|
137
|
+
|
138
|
+
rule authors
|
139
|
+
author (", " author)*
|
140
|
+
end
|
141
|
+
|
142
|
+
rule author
|
143
|
+
("[@" username:(/\w+/) "](" github_url ")") {
|
144
|
+
def username
|
145
|
+
capture(:username).value
|
146
|
+
end
|
147
|
+
|
148
|
+
def url
|
149
|
+
capture(:github_url).value
|
150
|
+
end
|
151
|
+
}
|
152
|
+
end
|
153
|
+
|
154
|
+
rule github_url
|
155
|
+
"https://github.com" path:(/\/\w*/)*
|
156
|
+
end
|
157
|
+
end
|
data/rake/changelog.rb
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
require "set"
|
2
|
+
|
3
|
+
# Helper class for updating CHANGELOG.md
|
4
|
+
class Changelog
|
5
|
+
attr_reader :changelog_file, :parsed, :parsed_current_version, :parsed_last_version, :missing_pull_requests
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@changelog_file = File.expand_path("../../CHANGELOG.md", __FILE__)
|
9
|
+
end
|
10
|
+
|
11
|
+
def run
|
12
|
+
ensure_new_version_specified
|
13
|
+
parse_changelog
|
14
|
+
fetch_missing_pull_requests
|
15
|
+
update_changelog
|
16
|
+
end
|
17
|
+
|
18
|
+
def ensure_new_version_specified
|
19
|
+
tags = `git tag -l`
|
20
|
+
return unless tags.include? Changelog.current_version
|
21
|
+
Changelog.error("Please update lib/gemstash/version.rb with the new version first!")
|
22
|
+
end
|
23
|
+
|
24
|
+
def parse_changelog
|
25
|
+
require "citrus"
|
26
|
+
Citrus.load(File.expand_path("../changelog.citrus", __FILE__))
|
27
|
+
@parsed = Changelog::Grammar.parse(File.read(changelog_file))
|
28
|
+
@parsed_current_version = @parsed.versions.find {|version| version.number == Changelog.current_version }
|
29
|
+
|
30
|
+
if @parsed_current_version
|
31
|
+
index = @parsed.versions.index(@parsed_current_version)
|
32
|
+
@parsed_last_version = @parsed.versions[index + 1]
|
33
|
+
else
|
34
|
+
@parsed_last_version = @parsed.versions.first
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def last_version
|
39
|
+
@last_version ||= begin
|
40
|
+
version = parsed_last_version.number
|
41
|
+
|
42
|
+
unless version =~ /\A\d+(\.\d+)*(\.pre\.\d+)?\z/
|
43
|
+
error("Invalid last version: #{version}, instead use something like 1.1.0, or 1.1.0.pre.2")
|
44
|
+
end
|
45
|
+
|
46
|
+
version
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def octokit
|
51
|
+
@octokit ||= begin
|
52
|
+
require "octokit"
|
53
|
+
token_path = File.expand_path("../../.rake_github_token", __FILE__)
|
54
|
+
|
55
|
+
if File.exist?(token_path)
|
56
|
+
options = { access_token: File.read(token_path).strip }
|
57
|
+
else
|
58
|
+
puts "\e[31mWARNING:\e[0m You do not have a GitHub OAuth token configured"
|
59
|
+
puts "Please generate one at: https://github.com/settings/tokens"
|
60
|
+
puts "And store it at: #{token_path}"
|
61
|
+
puts "Otherwise you might hit rate limits while running this"
|
62
|
+
print "Continue without token? [yes/no] "
|
63
|
+
abort("Please create your token and retry") unless STDIN.gets.strip.downcase == "yes"
|
64
|
+
options = {}
|
65
|
+
end
|
66
|
+
|
67
|
+
client = Octokit::Client.new(options)
|
68
|
+
client.auto_paginate = true
|
69
|
+
client
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def fetch_missing_pull_requests
|
74
|
+
@missing_pull_requests = missing_pull_request_numbers.map {|pr| fetch_pull_request(pr) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def fetch_pull_request(number)
|
78
|
+
puts "Fetching pull request ##{number}"
|
79
|
+
octokit.pull_request("bundler/gemstash", number)
|
80
|
+
end
|
81
|
+
|
82
|
+
def missing_pull_request_numbers
|
83
|
+
@missing_pull_request_numbers ||= begin
|
84
|
+
commits = `git log --oneline HEAD ^v#{last_version} --grep "^Merge pull request"`.split("\n")
|
85
|
+
pull_requests = commits.map {|commit| commit[/Merge pull request #(\d+)/, 1].to_i }
|
86
|
+
documented = Set.new
|
87
|
+
|
88
|
+
if parsed_current_version
|
89
|
+
parsed_current_version.pull_requests.each do |pr|
|
90
|
+
documented << pr.number.to_i
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
pull_requests.sort.reject {|pr| documented.include?(pr) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def update_changelog
|
99
|
+
return if missing_pull_requests.empty?
|
100
|
+
|
101
|
+
File.open(changelog_file, "w") do |file|
|
102
|
+
begin
|
103
|
+
write_current_version(file)
|
104
|
+
ensure
|
105
|
+
parsed.versions.each do |version|
|
106
|
+
next if version == parsed_current_version
|
107
|
+
file.write version.value
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def write_current_version(file)
|
114
|
+
pull_requests_by_section = missing_pull_requests.group_by {|pr| section_for(pr) }
|
115
|
+
file.puts "## #{Changelog.current_version} (#{current_date})"
|
116
|
+
file.puts
|
117
|
+
|
118
|
+
if parsed_current_version
|
119
|
+
file.puts parsed_current_version.description if parsed_current_version.description
|
120
|
+
|
121
|
+
parsed_current_version.sections.each do |section|
|
122
|
+
if pull_requests_by_section[section.title].to_a.empty?
|
123
|
+
file.write section.value
|
124
|
+
else
|
125
|
+
file.write section.heading
|
126
|
+
section.changes.each {|change| file.write change.value }
|
127
|
+
write_pull_requests(file, pull_requests_by_section[section.title])
|
128
|
+
file.puts
|
129
|
+
pull_requests_by_section.delete(section.title)
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
pull_requests_by_section.keys.sort.each do |section_title|
|
135
|
+
file.puts "### #{section_title}"
|
136
|
+
file.puts
|
137
|
+
write_pull_requests(file, pull_requests_by_section[section_title])
|
138
|
+
file.puts
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def section_for(pull_request)
|
143
|
+
puts "Fetching issue for ##{pull_request.number}"
|
144
|
+
issue = pull_request.rels[:issue].get.data
|
145
|
+
labels = issue.labels.map(&:name)
|
146
|
+
|
147
|
+
if labels.include?("bug")
|
148
|
+
"Bugfixes"
|
149
|
+
elsif labels.include?("enhancement")
|
150
|
+
"Features"
|
151
|
+
else
|
152
|
+
"Changes"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def write_pull_requests(file, pull_requests)
|
157
|
+
pull_requests.each do |pr|
|
158
|
+
puts "Fetching commits for ##{pr.number}"
|
159
|
+
commits = pr.rels[:commits].get.data
|
160
|
+
authors = commits.map {|commit| author_link(commit) }.uniq
|
161
|
+
file.puts " - #{pr.title} ([##{pr.number}](#{pr.html_url}), #{authors.join(", ")})"
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def author_link(commit)
|
166
|
+
@author_links ||= {}
|
167
|
+
author = commit.author
|
168
|
+
|
169
|
+
if author
|
170
|
+
"[@#{author.login}](#{author.html_url})"
|
171
|
+
elsif @author_links[commit.commit.author.name]
|
172
|
+
@author_links[commit.commit.author.name]
|
173
|
+
else
|
174
|
+
puts "Cannot find GitHub link for author: #{commit.commit.author.name}"
|
175
|
+
print "What is their GitHub username? "
|
176
|
+
username = STDIN.gets.strip
|
177
|
+
@author_links[commit.commit.author.name] = "[@#{username}](https://github.com/#{username})"
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def current_date
|
182
|
+
@current_date ||= Time.now.strftime("%Y-%m-%d")
|
183
|
+
end
|
184
|
+
|
185
|
+
def self.error(msg)
|
186
|
+
STDERR.puts(msg)
|
187
|
+
exit(false)
|
188
|
+
end
|
189
|
+
|
190
|
+
def self.current_version
|
191
|
+
@current_version ||= begin
|
192
|
+
require_relative "../lib/gemstash/version.rb"
|
193
|
+
|
194
|
+
unless Gemstash::VERSION =~ /\A\d+(\.\d+)*(\.pre\.\d+)?\z/
|
195
|
+
error("Invalid version: #{Gemstash::VERSION}, instead use something like 1.1.0, or 1.1.0.pre.2")
|
196
|
+
end
|
197
|
+
|
198
|
+
Gemstash::VERSION
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
# Helper class for generating the table of contents in markdown files.
|
4
|
+
class TableOfContents
|
5
|
+
attr_reader :toc_dir, :toc, :docs_dir
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@toc_dir = Pathname.new(File.expand_path("../../tmp", __FILE__))
|
9
|
+
@toc = @toc_dir.join("gh-md-toc")
|
10
|
+
@docs_dir = Pathname.new(File.expand_path("../../docs", __FILE__))
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
cache_toc_script
|
15
|
+
update_toc("reference.md")
|
16
|
+
end
|
17
|
+
|
18
|
+
def update_toc(doc)
|
19
|
+
doc = docs_dir.join(doc)
|
20
|
+
old_contents = File.read(doc)
|
21
|
+
old_contents.sub!(/\A.*?^---$/m, "---")
|
22
|
+
File.write(doc, old_contents)
|
23
|
+
toc_contents = `"#{toc}" "#{doc}"`
|
24
|
+
toc_contents.sub!(/Created by.*$/, "")
|
25
|
+
File.write(doc, "#{toc_contents}\n#{old_contents}")
|
26
|
+
end
|
27
|
+
|
28
|
+
def cache_toc_script
|
29
|
+
return if toc.exist?
|
30
|
+
require "open-uri"
|
31
|
+
toc_contents = open("https://raw.githubusercontent.com/ekalinin/github-markdown-toc/master/gh-md-toc", &:read)
|
32
|
+
Dir.mkdir(toc_dir) unless toc_dir.exist?
|
33
|
+
File.write(toc, toc_contents)
|
34
|
+
File.chmod(0776, toc)
|
35
|
+
end
|
36
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gemstash
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0.pre.
|
4
|
+
version: 1.0.0.pre.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Andre Arko
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-12-15 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: dalli
|
@@ -150,6 +150,34 @@ dependencies:
|
|
150
150
|
- - "~>"
|
151
151
|
- !ruby/object:Gem::Version
|
152
152
|
version: '1.10'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: citrus
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - "~>"
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '3.0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - "~>"
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '3.0'
|
167
|
+
- !ruby/object:Gem::Dependency
|
168
|
+
name: octokit
|
169
|
+
requirement: !ruby/object:Gem::Requirement
|
170
|
+
requirements:
|
171
|
+
- - "~>"
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '4.2'
|
174
|
+
type: :development
|
175
|
+
prerelease: false
|
176
|
+
version_requirements: !ruby/object:Gem::Requirement
|
177
|
+
requirements:
|
178
|
+
- - "~>"
|
179
|
+
- !ruby/object:Gem::Version
|
180
|
+
version: '4.2'
|
153
181
|
- !ruby/object:Gem::Dependency
|
154
182
|
name: rack-test
|
155
183
|
requirement: !ruby/object:Gem::Requirement
|
@@ -222,6 +250,7 @@ files:
|
|
222
250
|
- ".rubocop-relax.yml"
|
223
251
|
- ".rubocop.yml"
|
224
252
|
- ".travis.yml"
|
253
|
+
- CHANGELOG.md
|
225
254
|
- CODE_OF_CONDUCT.md
|
226
255
|
- Gemfile
|
227
256
|
- LICENSE.txt
|
@@ -279,6 +308,9 @@ files:
|
|
279
308
|
- lib/gemstash/upstream.rb
|
280
309
|
- lib/gemstash/version.rb
|
281
310
|
- lib/gemstash/web.rb
|
311
|
+
- rake/changelog.citrus
|
312
|
+
- rake/changelog.rb
|
313
|
+
- rake/table_of_contents.rb
|
282
314
|
homepage: https://github.com/bundler/gemstash
|
283
315
|
licenses:
|
284
316
|
- MIT
|