gemstash 1.0.0.pre.1 → 1.0.0.pre.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +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
|