gemirro 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/ruby.yml +29 -0
  3. data/.rubocop.yml +6 -3
  4. data/Gemfile +9 -0
  5. data/Gemfile.lock +126 -0
  6. data/MANIFEST +6 -10
  7. data/README.md +1 -1
  8. data/bin/gemirro +7 -1
  9. data/gemirro.gemspec +9 -9
  10. data/lib/gemirro/cli/index.rb +12 -3
  11. data/lib/gemirro/cli/init.rb +6 -1
  12. data/lib/gemirro/cli/server.rb +2 -4
  13. data/lib/gemirro/cli/update.rb +6 -0
  14. data/lib/gemirro/configuration.rb +1 -1
  15. data/lib/gemirro/gems_fetcher.rb +5 -10
  16. data/lib/gemirro/http.rb +6 -6
  17. data/lib/gemirro/indexer.rb +397 -90
  18. data/lib/gemirro/mirror_file.rb +1 -0
  19. data/lib/gemirro/server.rb +73 -160
  20. data/lib/gemirro/source.rb +2 -2
  21. data/lib/gemirro/utils.rb +123 -68
  22. data/lib/gemirro/version.rb +1 -1
  23. data/lib/gemirro/versions_fetcher.rb +6 -2
  24. data/lib/gemirro.rb +1 -1
  25. data/spec/gemirro/http_spec.rb +83 -24
  26. data/spec/gemirro/indexer_spec.rb +3 -0
  27. data/spec/gemirro/server_spec.rb +76 -47
  28. data/template/config.rb +6 -0
  29. data/template/public/dist/css/gemirro.css +25 -1
  30. data/template/public/latest_specs.4.8 +0 -0
  31. data/template/public/prerelease_specs.4.8 +0 -0
  32. data/template/public/specs.4.8 +0 -0
  33. data/views/gem.erb +46 -37
  34. data/views/index.erb +41 -33
  35. data/views/layout.erb +5 -25
  36. data/views/not_found.erb +4 -4
  37. metadata +72 -89
  38. data/lib/gemirro/cache.rb +0 -115
  39. data/spec/gemirro/cache_spec.rb +0 -32
  40. data/template/public/dist/css/bootstrap.min.css +0 -7
  41. data/template/public/dist/fonts/glyphicons-halflings-regular.eot +0 -0
  42. data/template/public/dist/fonts/glyphicons-halflings-regular.svg +0 -288
  43. data/template/public/dist/fonts/glyphicons-halflings-regular.ttf +0 -0
  44. data/template/public/dist/fonts/glyphicons-halflings-regular.woff +0 -0
  45. data/template/public/dist/fonts/glyphicons-halflings-regular.woff2 +0 -0
  46. data/template/public/dist/js/bootstrap.min.js +0 -7
@@ -2,34 +2,93 @@ require 'spec_helper'
2
2
  require 'httpclient'
3
3
  require 'gemirro/http'
4
4
 
5
- # Http tests
6
5
  module Gemirro
7
- describe 'Http' do
8
- it 'should return http client' do
9
- expect(Http.client).to be_a(HTTPClient)
6
+ describe Http do
7
+ let(:config) { double('Configuration') }
8
+
9
+ before do
10
+ Http.instance_variable_set(:@client, nil)
11
+ allow(Utils).to receive(:configuration).and_return(config)
10
12
  end
11
13
 
12
- it 'should raise error when get request failed' do
13
- uri = 'http://github.com/PierreRambaud'
14
- Struct.new('HTTPError', :status, :reason)
15
- result = Struct::HTTPError.new(401, 'Unauthorized')
16
- allow(Http.client).to receive(:get)
17
- .once.with(uri, follow_redirect: true).and_return(result)
18
- expect { Http.get(uri) }
19
- .to raise_error HTTPClient::BadResponseError, 'Unauthorized'
14
+ describe '.client' do
15
+ it 'initializes a new HTTPClient' do
16
+ expect(Http.client).to be_a(HTTPClient)
17
+ end
18
+
19
+ context 'with proxy configuration' do
20
+ it 'sets proxy configuration' do
21
+ allow(config).to receive(:proxy).and_return('http://proxy.example.com:8080')
22
+
23
+ expect(Http.client.proxy.to_s).to eq('http://proxy.example.com:8080')
24
+ end
25
+ end
26
+
27
+ context 'with SSL configuration' do
28
+ context 'with invalid root CA path' do
29
+ before do
30
+ allow(config).to receive(:rootca).and_return('/nonexistent/ca.crt')
31
+ allow(File).to receive(:file?).with('/nonexistent/ca.crt').and_return(false)
32
+ end
33
+
34
+ it 'aborts with error message' do
35
+ expect { Http.client }.to raise_error(SystemExit)
36
+ end
37
+ end
38
+
39
+ context 'with verify_mode disabled' do
40
+ before do
41
+ allow(config).to receive(:verify_mode).and_return(false)
42
+ end
43
+
44
+ it 'sets SSL verify mode to VERIFY_NONE' do
45
+ expect(Http.client.ssl_config.verify_mode).to eq(OpenSSL::SSL::VERIFY_NONE)
46
+ end
47
+ end
48
+ end
49
+
50
+ context 'with basic auth forced' do
51
+ before do
52
+ allow(config).to receive(:basic_auth).and_return(true)
53
+ end
54
+
55
+ it 'forces basic authentication' do
56
+ expect(Http.client.www_auth.basic_auth.force_auth).to be true
57
+ end
58
+ end
20
59
  end
21
60
 
22
- it 'should execute get request' do
23
- uri = 'http://github.com/PierreRambaud'
24
- Struct.new('HTTPResponse', :status, :body)
25
- result = Struct::HTTPResponse.new(200, 'body content')
26
- allow(Http.client).to receive(:get)
27
- .once.with(uri, follow_redirect: true).and_return(result)
28
-
29
- response = Http.get(uri)
30
- expect(response).to be_a(Struct::HTTPResponse)
31
- expect(response.body).to eq('body content')
32
- expect(response.status).to eq(200)
61
+ describe '.get' do
62
+ let(:client) { instance_double(HTTPClient) }
63
+
64
+ before do
65
+ allow(Http).to receive(:client).and_return(client)
66
+ end
67
+
68
+ context 'with successful response' do
69
+ let(:response) { double('Response', status: 200, body: 'content') }
70
+
71
+ it 'returns response for successful request' do
72
+ allow(client).to receive(:get)
73
+ .with('http://example.com', follow_redirect: true)
74
+ .and_return(response)
75
+
76
+ expect(Http.get('http://example.com')).to eq(response)
77
+ end
78
+ end
79
+
80
+ context 'with error response' do
81
+ let(:response) { double('Response', status: 404, reason: 'Not Found') }
82
+
83
+ it 'raises BadResponseError for failed request' do
84
+ allow(client).to receive(:get)
85
+ .with('http://example.com', follow_redirect: true)
86
+ .and_return(response)
87
+
88
+ expect { Http.get('http://example.com') }
89
+ .to raise_error(HTTPClient::BadResponseError, 'Not Found')
90
+ end
91
+ end
33
92
  end
34
93
  end
35
- end
94
+ end
@@ -1,4 +1,5 @@
1
1
  require 'spec_helper'
2
+ require 'builder'
2
3
  require 'rubygems/indexer'
3
4
  require 'tempfile'
4
5
  require 'gemirro/source'
@@ -34,6 +35,8 @@ module Gemirro
34
35
  end
35
36
 
36
37
  it 'should install indices' do
38
+ allow(Gemirro.configuration).to receive(:destination).and_return('/tmp')
39
+
37
40
  dir = MirrorDirectory.new('/tmp')
38
41
  dir.add_directory('test')
39
42
  dir.add_directory('gem_generate_index/quick/Marshal.4.8')
@@ -1,7 +1,6 @@
1
1
  require 'rack/test'
2
2
  require 'json'
3
3
  require 'parallel'
4
- require 'gemirro/cache'
5
4
  require 'gemirro/utils'
6
5
  require 'gemirro/mirror_directory'
7
6
  require 'gemirro/mirror_file'
@@ -37,11 +36,17 @@ module Gemirro
37
36
  Utils.instance_eval('@gems_orig_collection = nil')
38
37
  Utils.instance_eval('@gems_source_collection = nil')
39
38
  FakeFS::FileSystem.clone(Gemirro::Configuration.views_directory)
39
+ allow_any_instance_of(Indexer).to receive(:compress_indices)
40
+ allow_any_instance_of(Indexer).to receive(:compress_indicies)
41
+ allow_any_instance_of(Indexer).to receive(:rand).and_return('0')
42
+
43
+ source = Source.new('Rubygems', 'https://rubygems.org')
44
+ allow(Gemirro.configuration).to receive(:source).and_return(source)
40
45
  end
41
46
 
42
47
  context 'HTML render' do
43
48
  it 'should display index page' do
44
- allow(Logger).to receive(:new).twice.and_return(@fake_logger)
49
+ allow(Logger).to receive(:new).exactly(3).times.and_return(@fake_logger)
45
50
  allow(@fake_logger).to receive(:tap)
46
51
  .and_return(nil)
47
52
  .and_yield(@fake_logger)
@@ -67,54 +72,63 @@ module Gemirro
67
72
  ::Gem::Version.create('0.1.0'),
68
73
  'ruby']])
69
74
 
70
- MirrorFile.new('/var/www/gemirro/specs.4.8.gz.orig').write(marshal_dump)
71
- Struct.new('SuccessGzipReader', :read)
72
- gzip_reader = Struct::SuccessGzipReader.new(marshal_dump)
73
- MirrorDirectory.new('/var/www/gemirro')
74
- .add_directory('quick/Marshal.4.8')
75
- # rubocop:disable Metrics/LineLength
76
- MirrorFile.new('/var/www/gemirro/quick/Marshal.4.8/' \
77
- 'volay-0.1.0.gemspec.rz')
78
- .write("x\x9C\x8D\x94]\x8F\xD2@\x14\x86\x89Y\xBB\xB4|\xEC\x12\xD7h" \
79
- "\xD4h\xD3K\x13J\x01\x97\xC84n\x9A\xA8\xBBi\xE2\xC5\x06\xBB" \
80
- "{\xC3\x85)\xE5\x00\x13f:u:E\xD1\xC4\xDF\xE6\xB5\xBF\xCAiK" \
81
- "\x11\xE3GK\xEF\x98\xF7\xBC\xCFy\xCF\xC9\xCCQ=A\x0F\xAE\x80" \
82
- "\"\xF4>\x82\x00/p\xE0\v\xCC\xC2;\xC1\xDD\xA3\xFA\xF4\xA1k4" \
83
- "\x06\xA6e\xF6_(Hy\xEBa\xD55\xB4\r#\xFEV\xB1k\xDE\r\xEAdu" \
84
- "\xB7\xC0cY1U\xE4\xA1\x95\x8A\xD3C7A\xAA\x87)\xB4\x9C\x1FO" \
85
- "\xBE\xD7\xE4OA\xEA\x17\x16\x82k\xD4o\xBC\xD7\x99\xC2x\xEC" \
86
- "\xAD@\xBFe$\xA1\xA0\xC7\xDBX\x00\xD5\x05/\xBC\xEFg\xDE\x13" \
87
- "\xF8\x98`\x0E\x14B1U\xE4w\xEC\x1A\xC7\x17\xAF2\x85\xADd\xC4" \
88
- "\xBE96\x87\xF9\x1F\xEA\xDF%\x8A\x95\xE3T\x9E\xCC2\xF3i\x9B" \
89
- "\xA1\xB3\xCC\xFE\rD\x10\xCE!\f\xB6\x1A\xD2\x9C\xD0\xA7\xB2" \
90
- "\xBF\x13\x8A?\x13<\xEB\x06\x04\xA7b\xD4q\xF8\xAF&\x0E!\xDF" \
91
- ".~\xEF\xE3\xDC\xCC@\xD2Hl\#@M\x9E\x84BN\x00\x9D:\x11\a\x0E" \
92
- "\x04\xFC\x18.\xD1#g\x93\xCF\xEB\xC3\x81m\\\xC1\x97\xD9" \
93
- "\x9Af7\\\xE3l\xD7_\xBC\x02BX\"\xD23\xBB\xF9o\x83A\xB1\x12" \
94
- "\xBBe\xB7\xED\x93K\xFB\xB4\x82\xB6\x80\xA9K\xB1\x1E\x96" \
95
- "\x10\xEA\x03sP\xCD\xBFP\x16\xEE\x8D\x85\xBF\x86E\\\x96" \
96
- "\xC02G\xF9\b\xEC\x16:\x9D\xC3\x06\b\x8B\xD2\xA9\x95\x84" \
97
- "\xD9\x97\xED\xC3p\x89+\x81\xA9}\xAB`\xD9\x9D\xFF\x03\xF6" \
98
- "\xD2\xC2\xBF\xCD\xFD`\xDD\x15\x10\x97\xED\xA4.[\xAB\xC6(" \
99
- "\x94\x05B\xE3\xB1\xBC\xA5e\xF6\xC3\xAA\x11\n\xE5>A\x8CiD " \
100
- "`\x9B\xF2\x04\xE3\xCA\t\xC6\x87\by-f,`Q\xD9\x1E,sp^q\x0F" \
101
- "\x85\xD4r\x8Dg\x11\x06\xCE\xC1\xE4>\x9D\xF9\xC9\xFC\xE5" \
102
- "\xC8YR\x1F\x133`4\xBB\xF9R~\xEF:\x93\xE8\x93\\\x92\xBF\r" \
103
- "\xA3\t\xF8\x84l\xF5<\xBF\xBE\xF9\xE3Q\xD2?q,\x04\x84:\x0E" \
104
- "\xF5\xF4\x1D1\xF3\xBA\xE7+!\"\xD4\xEB-\xB1X%\xB3\x14\xD3" \
105
- "\xCB\xEDw\xEE\xBD\xFDk\xE99OSz\xF3\xEA\xFA]w7\xF5\xAF\xB5" \
106
- "\x9F+\xFEG\x96")
107
- # rubocop:enable Metrics/LineLength
75
+ MirrorFile.new('/var/www/gemirro/specs.4.8.gz.local').write(Marshal.dump({}))
76
+
108
77
 
109
- allow(Zlib::GzipReader).to receive(:open)
110
- .once
111
- .with('/var/www/gemirro/specs.4.8.gz.orig')
112
- .and_return(gzip_reader)
78
+ allow(Zlib::GzipReader).to receive(:open).and_return(double(read: marshal_dump))
113
79
 
114
80
  get '/gem/volay'
81
+
82
+ expect(last_response.status).to eq(200)
83
+ expect(last_response).to be_ok
84
+ end
85
+
86
+ it 'responds to compact_index /names' do
87
+ MirrorFile.new('/var/www/gemirro/names.md5.sha256.list').write('---\n- volay\n')
88
+
89
+ get '/names'
90
+ expect(last_response.status).to eq(200)
91
+ expect(last_response).to be_ok
92
+ expect(last_response.body).to eq('---\n- volay\n')
93
+ expect(last_response.headers['etag']).to eq("md5")
94
+ expect(last_response.headers['repr-digest']).to eq('sha-256="sha256"')
95
+ end
96
+
97
+ it 'responds to compact_index /info/[gemname]' do
98
+ marshal_dump = Marshal.dump([['volay',
99
+ ::Gem::Version.create('0.1.0'),
100
+ 'ruby']])
101
+
102
+ MirrorFile.new('/var/www/gemirro/specs.4.8.gz.local').write(Marshal.dump({}))
103
+
104
+ allow(Zlib::GzipReader).to receive(:open).and_return(double(read: marshal_dump))
105
+
106
+
107
+ MirrorDirectory.new('/var/www/gemirro/info')
108
+ MirrorFile.new('/var/www/gemirro/info/volay.md5.sha256.list').write('---\n 0.1.0 |checksum:sha256\n')
109
+
110
+
111
+ get '/info/volay'
115
112
  expect(last_response.status).to eq(200)
116
113
  expect(last_response).to be_ok
114
+ expect(last_response.body).to eq('---\n 0.1.0 |checksum:sha256\n')
115
+ expect(last_response.headers['etag']).to eq("md5")
116
+ expect(last_response.headers['repr-digest']).to eq('sha-256="sha256"')
117
117
  end
118
+
119
+
120
+ it 'responds to compact_index /versions' do
121
+ MirrorFile.new('/var/www/gemirro/versions.md5.sha256.list').write('created_at: 2025-01-01T00:00:00Z\m---\nvolay 0.1.0\n')
122
+
123
+ get '/versions'
124
+ expect(last_response.status).to eq(200)
125
+ expect(last_response).to be_ok
126
+ expect(last_response.body).to eq('created_at: 2025-01-01T00:00:00Z\m---\nvolay 0.1.0\n')
127
+ expect(last_response.headers['etag']).to eq("md5")
128
+ expect(last_response.headers['repr-digest']).to eq('sha-256="sha256"')
129
+ end
130
+
131
+
118
132
  end
119
133
 
120
134
  context 'Download' do
@@ -150,8 +164,8 @@ module Gemirro
150
164
  allow(::Gem::SilentUI).to receive(:new).once.and_return(true)
151
165
 
152
166
  allow(Gemirro.configuration).to receive(:logger)
153
- .exactly(3).and_return(@fake_logger)
154
- allow(@fake_logger).to receive(:info).exactly(3)
167
+ .exactly(4).and_return(@fake_logger)
168
+ allow(@fake_logger).to receive(:info).exactly(4)
155
169
 
156
170
  get '/gems/gemirro-0.0.1.gem'
157
171
  expect(last_response).to_not be_ok
@@ -212,7 +226,7 @@ module Gemirro
212
226
  get '/api/v1/dependencies.json'
213
227
  expect(last_response.headers['Content-Type'])
214
228
  .to eq('application/json')
215
- expect(last_response.body).to eq('')
229
+ expect(last_response.body).to eq('[]')
216
230
  expect(last_response).to be_ok
217
231
  end
218
232
 
@@ -261,6 +275,21 @@ module Gemirro
261
275
  "\x9F+\xFEG\x96")
262
276
  # rubocop:enable Metrics/LineLength
263
277
 
278
+ MirrorFile.new('/var/www/gemirro/api/v1/dependencies/volay.md5.sha.list')
279
+ .write(Marshal.dump([
280
+ {
281
+ name: 'volay',
282
+ number: "0.1.0",
283
+ platform: 'ruby',
284
+ dependencies: [
285
+ {
286
+ name: 'json',
287
+ requirement: '~> 2.1'
288
+ }
289
+ ]
290
+ }
291
+ ]))
292
+
264
293
  gem = Gemirro::GemVersion.new('volay', '0.1.0', 'ruby')
265
294
  collection = Gemirro::GemVersionCollection.new([gem])
266
295
  allow(Utils).to receive(:gems_collection)
data/template/config.rb CHANGED
@@ -23,6 +23,12 @@ Gemirro.configuration.configure do
23
23
  server.access_log File.expand_path('../logs/access.log', __FILE__)
24
24
  server.error_log File.expand_path('../logs/error.log', __FILE__)
25
25
 
26
+ # Number of parallel processes while indexing. Too many will kill
27
+ # your indexing process prematurely.
28
+ #
29
+ # update_threads Etc.nprocessors - 1
30
+ # update_threads 4
31
+
26
32
  # If you don't want to generate indexes after each fetched gem.
27
33
  #
28
34
  # update_on_fetch false
@@ -1 +1,25 @@
1
- .pull-none {float:none;}
1
+ body { max-width: 620px; margin: 0 auto; padding: 0; font-family: sans-serif; }
2
+
3
+ dl { display: grid; grid-template-columns: 1fr min-content; }
4
+
5
+ dt { grid-column: 1; margin: 0; padding: 8px 0; border-bottom: 1px solid #e2e2e2; }
6
+ dd { grid-column: 2; margin: 0; padding: 8px 0; border-bottom: 1px solid #e2e2e2; }
7
+
8
+ dd.full { grid-column: 1/2; border-bottom: 0; }
9
+ dd.description, dd.authors, dd.dependencies { grid-column: 1/2; border-bottom: 0; }
10
+
11
+
12
+ a, a:visited, a:active, a:hover { color: #007bff; }
13
+ .btn { color: #fff; background: #007bff; padding: 5px; border-radius: 4px; text-decoration: none; }
14
+ .btn:visited, .btn:active, .btn:hover { color: #fff; }
15
+
16
+
17
+ ul { list-style-type: none; margin: 0; padding: 0 0 0 1em; }
18
+
19
+
20
+ @media (prefers-color-scheme: dark) {
21
+ html, body { background: #16161d; color: #fff; }
22
+ a, a:visited, a:active, a:hover { color: lightskyblue; }
23
+ .btn { color: #16161d; background: lightskyblue; padding: 5px; border-radius: 4px; text-decoration: none; }
24
+ .btn:visited, .btn:active, .btn:hover { color: #16161d; }
25
+ }
Binary file
Binary file
data/views/gem.erb CHANGED
@@ -1,58 +1,67 @@
1
- <div class="col-lg-12">
2
- <div class="col-lg-3 center-block pull-none text-center">
1
+
3
2
  <a class="btn btn-default" href="<%= url '/'%>">Back to Gem Index</a>
4
3
  </div>
5
4
  </div>
6
5
 
7
- <div class="col-lg-12">
6
+
7
+ <article>
8
8
  <% gem.by_name do |name, versions| %>
9
- <div class="col-lg-6 center-block pull-none">
10
- <div class="panel panel-info">
11
- <div class="panel-heading">
12
- <a href="<%= url("gem/#{name}") %>">
13
- <h2 class="panel-title"><%= escape(name) %> <span class="badge pull-right"><%= escape(versions.newest.number) %></span></h2>
14
- </a>
15
- </div>
16
- <div class="panel-body">
17
- <% newest_gem = versions.newest %>
18
- <% if spec = spec_for(name, newest_gem.number, newest_gem.platform) %>
19
- <p><%= escape(spec.description) %></p>
9
+ <dl>
10
+ <dt>
11
+ <h2><%= Rack::Utils.escape_html(name) %></h2>
12
+ </dt>
13
+ <dd>
14
+ <h2><%= Rack::Utils.escape_html(versions.newest.number) %></h2>
15
+ </dd>
16
+ <% newest_gem = versions.newest %>
17
+ <% spec = Gemirro::Utils.spec_for(name, newest_gem.number, newest_gem.platform) %>
18
+ <% if spec %>
19
+ <dd class="description">
20
+ <%= Rack::Utils.escape_html(spec.description) %>
21
+ </dd>
20
22
 
23
+ <% if spec.dependencies.size > 0 %>
24
+ <dd class="dependencies">
21
25
  <h3>Dependencies</h3>
22
- <ul class="list-group">
26
+ <ul>
23
27
  <% spec.dependencies.each do |dependency| %>
24
28
  <li class="list-group-item">
25
- <a href="<%= url("gem/#{dependency.name}") %>"><%= escape([dependency.name, dependency.requirement].join(' ')) %></a>
29
+ <a href="<%= url("gem/#{dependency.name}") %>"><%= Rack::Utils.escape_html([dependency.name, dependency.requirement].join(' ')) %></a>
26
30
  </li>
27
31
  <% end %>
28
32
  </ul>
33
+ </dd>
34
+ <% end %>
29
35
 
36
+ <% if spec.authors.size > 0 %>
37
+ <dd class="authors">
30
38
  <h3>Authors</h3>
31
- <ul class="list-group">
39
+ <ul>
32
40
  <% spec.authors.each do |author| %>
33
41
  <li class="list-group-item">
34
- <a href="<%= homepage(spec) %>"><%= escape(author) %></a>
42
+ <a href="<%= URI.parse(Addressable::URI.escape(spec.homepage)) %>"><%= Rack::Utils.escape_html(author) %></a>
35
43
  </li>
36
44
  <% end %>
37
45
  </ul>
38
- <% end %>
39
- </div>
46
+ </dd>
47
+ <% end %>
48
+ <% end %>
40
49
 
41
- <ul class="list-group">
42
- <% versions.each.reverse_each do |version| %>
43
- <li class="list-group-item clearfix">
44
- <p class="pull-left">
45
- <code>gem install <%= escape(version.name) %> -v "<%= escape(version.number) %>"</code>
46
- <% unless version.platform =~ /^ruby/i %>
47
- <small class="platform"><%= escape(version.platform) %></small>
48
- <% end %>
49
- </p>
50
- <div class="pull-right">
51
- <a class="btn btn-primary btn-sm" href="<%= url("/gems/#{version.gemfile_name}.gem") %>">Download</a>
52
- </div>
53
- </li>
54
- <% end %>
55
- </ul>
56
- </div>
50
+ <% versions.each.reverse_each do |version| %>
51
+ <dt>
52
+ <code>
53
+ gem install
54
+ <%= Rack::Utils.escape_html(version.name) %>
55
+ --version "<%= Rack::Utils.escape_html(version.number) %>"
56
+ <% unless version.platform =~ /^ruby/i %>
57
+ --platform <%= Rack::Utils.escape_html(version.platform) %>
58
+ <% end %>
59
+ </code>
60
+ </dt>
61
+ <dd>
62
+ <a class="btn" href="<%= url("/gems/#{version.gemfile_name}.gem") %>">Download</a>
63
+ </dd>
64
+ <% end %>
65
+ </dl>
57
66
  <% end %>
58
- </div>
67
+ </article>
data/views/index.erb CHANGED
@@ -1,38 +1,46 @@
1
1
 
2
- <div class="col-lg-12">
3
- <div class="col-lg-3 center-block pull-none">
4
- <pre class="bg-info"><code>gem sources -a <%= url("/") %></code></pre>
5
- </div>
6
- </div>
2
+ <code>gem sources -a <%= url("/") %></code>
7
3
 
8
- <div class="col-lg-12">
9
- <% if gems.any? %>
10
- <div class="col-lg-6 center-block pull-none">
11
- <% gems.by_name do |name, versions| %>
12
- <div class="panel panel-info">
13
- <div class="panel-heading">
14
- <a href="<%= url("gem/#{name}") %>">
15
- <h2 class="panel-title"><%= escape(name) %> <span class="badge pull-right"><%= escape(versions.newest.number) %></span></h2>
16
- </a>
17
- </div>
4
+ <% if gems.any? %>
5
+ <article>
6
+ <% gems.by_name do |name, versions| %>
7
+ <dl>
8
+ <dt>
9
+ <a href="<%= url("gem/#{name}") %>">
10
+ <h2><%= Rack::Utils.escape_html(name) %></h2>
11
+ </a>
12
+ </dt>
13
+ <dd>
14
+ <h2><%= Rack::Utils.escape_html(versions.newest.number) %></h2>
15
+ </dd>
16
+
17
+ <% spec = Gemirro::Utils.spec_for(name, versions.newest.number, versions.newest.platform) %>
18
+ <% if spec.is_a?(::Gem::Specification) %>
19
+ <dd class="description">
20
+ <%= Rack::Utils.escape_html(spec.description) %>
21
+ </dd>
22
+ <% end %>
18
23
 
19
- <div class="panel-body">
20
- <% spec = spec_for(name, versions.newest.number) %>
21
- <% if spec.is_a?(::Gem::Specification) %>
22
- <%= escape(spec.description) %>
23
- <% end %>
24
-
25
- <% versions.reverse_each.first(5).each do |version| %>
26
- <p>
27
- <code>gem install <%= escape(version.name) %> <%= "--prerelease" if version.number.to_s.match(/[a-z]/i) %> -v "<%= escape(version.number) %>"</code>
24
+ <% versions.reverse_each.first(5).each do |version| %>
25
+ <dt class="version">
26
+ <code>
27
+ gem install
28
+ <%= Rack::Utils.escape_html(version.name) %>
29
+ <%= "--prerelease" if version.number.to_s.match(/[a-z]/i) %>
30
+ --version "<%= Rack::Utils.escape_html(version.number) %>"
28
31
  <% unless version.platform =~ /^ruby/i %>
29
- <small class="platform"><%= escape(version.platform) %></small>
32
+ --platform <%= Rack::Utils.escape_html(version.platform) %></small>
30
33
  <% end %>
31
- </p>
32
- <% end %>
33
- </div>
34
- </div>
35
- <% end %>
36
- </div>
37
- <% end %>
38
- </div>
34
+ </code>
35
+ </dt>
36
+ <dd>
37
+ <a class="btn btn-primary btn-sm" href="<%= url("/gems/#{version.gemfile_name}.gem") %>">Download</a>
38
+ </dd>
39
+ <% end %>
40
+ <% if versions.size > 5 %>
41
+ <dd class="full"><a href="<%= url("gem/#{name}") %>"><%= "And %d More..." % [versions.size - 5] %></a></dd>
42
+ <% end %>
43
+ </dl>
44
+ <% end %>
45
+ </article>
46
+ <% end %>
data/views/layout.erb CHANGED
@@ -2,38 +2,18 @@
2
2
  <html>
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta http-equiv="X-UA-Compatible" content="IE=edge">
6
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
7
6
  <meta name="description" content="Ruby gems mirror">
8
7
  <meta name="author" content="Pierre Rambaud">
9
8
 
10
9
  <title>Gemirro - a simple ruby gems mirror</title>
11
10
 
12
- <%=
13
- stylesheet_link_tag(
14
- "/dist/css/bootstrap.min.css",
15
- "/dist/css/gemirro.css",
16
- :media => "screen"
17
- ) %>
11
+ <link href="/dist/css/gemirro.css" media="screen" rel="stylesheet" type="text/css" />
18
12
  </head>
19
13
  <body>
20
- <div class="container-fluid">
21
- <div class="row">
22
- <div class="col-lg-12 text-center">
23
- <h1>Gemirro</h1>
24
- </div>
25
- </div>
26
- <div class="row">
27
- <div class="col-lg-12">
28
- <%= yield %>
29
- </div>
30
- </div>
31
- </div>
32
- <%=
33
- javascript_script_tag(
34
- "/dist/js/jquery-3.2.1.slim.min.js",
35
- "/dist/js/bootstrap.bundle.min.js",
36
- :media => "screen"
37
- ) %>
14
+ <header>
15
+ <h1>Gemirro</h1>
16
+ </header>
17
+ <%= yield %>
38
18
  </body>
39
19
  </html>
data/views/not_found.erb CHANGED
@@ -1,4 +1,4 @@
1
- <div class="center-block text-center">
2
- <h1>Page not found.</h1>
3
- <a class="btn btn-default" href="<%= url('/') %>">Back to <%= url('/') %> →</a>
4
- </div>
1
+
2
+ <h1>Page not found.</h1>
3
+
4
+ <a class="btn" href="<%= url('/') %>">Back to <%= url('/') %> →</a>