libgems 0.0.1

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 (177) hide show
  1. data/ChangeLog +5811 -0
  2. data/History.txt +887 -0
  3. data/LICENSE.txt +51 -0
  4. data/README.md +87 -0
  5. data/Rakefile +113 -0
  6. data/lib/gauntlet_libgems.rb +50 -0
  7. data/lib/libgems.rb +1246 -0
  8. data/lib/libgems/builder.rb +102 -0
  9. data/lib/libgems/command.rb +534 -0
  10. data/lib/libgems/command_manager.rb +182 -0
  11. data/lib/libgems/commands/build_command.rb +53 -0
  12. data/lib/libgems/commands/cert_command.rb +86 -0
  13. data/lib/libgems/commands/check_command.rb +80 -0
  14. data/lib/libgems/commands/cleanup_command.rb +106 -0
  15. data/lib/libgems/commands/contents_command.rb +98 -0
  16. data/lib/libgems/commands/dependency_command.rb +195 -0
  17. data/lib/libgems/commands/environment_command.rb +133 -0
  18. data/lib/libgems/commands/fetch_command.rb +67 -0
  19. data/lib/libgems/commands/generate_index_command.rb +133 -0
  20. data/lib/libgems/commands/help_command.rb +172 -0
  21. data/lib/libgems/commands/install_command.rb +178 -0
  22. data/lib/libgems/commands/list_command.rb +35 -0
  23. data/lib/libgems/commands/lock_command.rb +110 -0
  24. data/lib/libgems/commands/mirror_command.rb +111 -0
  25. data/lib/libgems/commands/outdated_command.rb +33 -0
  26. data/lib/libgems/commands/owner_command.rb +75 -0
  27. data/lib/libgems/commands/pristine_command.rb +93 -0
  28. data/lib/libgems/commands/push_command.rb +56 -0
  29. data/lib/libgems/commands/query_command.rb +280 -0
  30. data/lib/libgems/commands/rdoc_command.rb +91 -0
  31. data/lib/libgems/commands/search_command.rb +31 -0
  32. data/lib/libgems/commands/server_command.rb +86 -0
  33. data/lib/libgems/commands/sources_command.rb +157 -0
  34. data/lib/libgems/commands/specification_command.rb +125 -0
  35. data/lib/libgems/commands/stale_command.rb +27 -0
  36. data/lib/libgems/commands/uninstall_command.rb +83 -0
  37. data/lib/libgems/commands/unpack_command.rb +121 -0
  38. data/lib/libgems/commands/update_command.rb +160 -0
  39. data/lib/libgems/commands/which_command.rb +86 -0
  40. data/lib/libgems/config_file.rb +345 -0
  41. data/lib/libgems/custom_require.rb +44 -0
  42. data/lib/libgems/defaults.rb +101 -0
  43. data/lib/libgems/dependency.rb +227 -0
  44. data/lib/libgems/dependency_installer.rb +286 -0
  45. data/lib/libgems/dependency_list.rb +208 -0
  46. data/lib/libgems/doc_manager.rb +242 -0
  47. data/lib/libgems/errors.rb +35 -0
  48. data/lib/libgems/exceptions.rb +91 -0
  49. data/lib/libgems/ext.rb +18 -0
  50. data/lib/libgems/ext/builder.rb +56 -0
  51. data/lib/libgems/ext/configure_builder.rb +25 -0
  52. data/lib/libgems/ext/ext_conf_builder.rb +24 -0
  53. data/lib/libgems/ext/rake_builder.rb +39 -0
  54. data/lib/libgems/format.rb +81 -0
  55. data/lib/libgems/gem_openssl.rb +92 -0
  56. data/lib/libgems/gem_path_searcher.rb +100 -0
  57. data/lib/libgems/gem_runner.rb +79 -0
  58. data/lib/libgems/gemcutter_utilities.rb +49 -0
  59. data/lib/libgems/indexer.rb +720 -0
  60. data/lib/libgems/install_update_options.rb +125 -0
  61. data/lib/libgems/installer.rb +604 -0
  62. data/lib/libgems/local_remote_options.rb +135 -0
  63. data/lib/libgems/old_format.rb +153 -0
  64. data/lib/libgems/package.rb +97 -0
  65. data/lib/libgems/package/f_sync_dir.rb +23 -0
  66. data/lib/libgems/package/tar_header.rb +266 -0
  67. data/lib/libgems/package/tar_input.rb +222 -0
  68. data/lib/libgems/package/tar_output.rb +144 -0
  69. data/lib/libgems/package/tar_reader.rb +106 -0
  70. data/lib/libgems/package/tar_reader/entry.rb +141 -0
  71. data/lib/libgems/package/tar_writer.rb +241 -0
  72. data/lib/libgems/package_task.rb +126 -0
  73. data/lib/libgems/platform.rb +183 -0
  74. data/lib/libgems/remote_fetcher.rb +414 -0
  75. data/lib/libgems/require_paths_builder.rb +18 -0
  76. data/lib/libgems/requirement.rb +153 -0
  77. data/lib/libgems/security.rb +814 -0
  78. data/lib/libgems/server.rb +872 -0
  79. data/lib/libgems/source_index.rb +597 -0
  80. data/lib/libgems/source_info_cache.rb +395 -0
  81. data/lib/libgems/source_info_cache_entry.rb +56 -0
  82. data/lib/libgems/spec_fetcher.rb +337 -0
  83. data/lib/libgems/specification.rb +1487 -0
  84. data/lib/libgems/test_utilities.rb +147 -0
  85. data/lib/libgems/text.rb +65 -0
  86. data/lib/libgems/uninstaller.rb +278 -0
  87. data/lib/libgems/user_interaction.rb +527 -0
  88. data/lib/libgems/validator.rb +240 -0
  89. data/lib/libgems/version.rb +316 -0
  90. data/lib/libgems/version_option.rb +65 -0
  91. data/lib/rbconfig/datadir.rb +20 -0
  92. data/test/bogussources.rb +8 -0
  93. data/test/data/gem-private_key.pem +27 -0
  94. data/test/data/gem-public_cert.pem +20 -0
  95. data/test/fake_certlib/openssl.rb +7 -0
  96. data/test/foo/discover.rb +0 -0
  97. data/test/gem_installer_test_case.rb +97 -0
  98. data/test/gem_package_tar_test_case.rb +132 -0
  99. data/test/gemutilities.rb +605 -0
  100. data/test/insure_session.rb +43 -0
  101. data/test/mockgemui.rb +56 -0
  102. data/test/plugin/exception/libgems_plugin.rb +2 -0
  103. data/test/plugin/load/libgems_plugin.rb +1 -0
  104. data/test/plugin/standarderror/libgems_plugin.rb +2 -0
  105. data/test/private_key.pem +27 -0
  106. data/test/public_cert.pem +20 -0
  107. data/test/rubygems_plugin.rb +21 -0
  108. data/test/simple_gem.rb +66 -0
  109. data/test/test_config.rb +12 -0
  110. data/test/test_gem.rb +780 -0
  111. data/test/test_gem_builder.rb +27 -0
  112. data/test/test_gem_command.rb +178 -0
  113. data/test/test_gem_command_manager.rb +207 -0
  114. data/test/test_gem_commands_build_command.rb +74 -0
  115. data/test/test_gem_commands_cert_command.rb +124 -0
  116. data/test/test_gem_commands_check_command.rb +18 -0
  117. data/test/test_gem_commands_contents_command.rb +156 -0
  118. data/test/test_gem_commands_dependency_command.rb +216 -0
  119. data/test/test_gem_commands_environment_command.rb +144 -0
  120. data/test/test_gem_commands_fetch_command.rb +76 -0
  121. data/test/test_gem_commands_generate_index_command.rb +135 -0
  122. data/test/test_gem_commands_install_command.rb +315 -0
  123. data/test/test_gem_commands_list_command.rb +36 -0
  124. data/test/test_gem_commands_lock_command.rb +68 -0
  125. data/test/test_gem_commands_mirror_command.rb +60 -0
  126. data/test/test_gem_commands_outdated_command.rb +40 -0
  127. data/test/test_gem_commands_owner_command.rb +105 -0
  128. data/test/test_gem_commands_pristine_command.rb +108 -0
  129. data/test/test_gem_commands_push_command.rb +81 -0
  130. data/test/test_gem_commands_query_command.rb +426 -0
  131. data/test/test_gem_commands_server_command.rb +59 -0
  132. data/test/test_gem_commands_sources_command.rb +209 -0
  133. data/test/test_gem_commands_specification_command.rb +139 -0
  134. data/test/test_gem_commands_stale_command.rb +38 -0
  135. data/test/test_gem_commands_uninstall_command.rb +83 -0
  136. data/test/test_gem_commands_unpack_command.rb +199 -0
  137. data/test/test_gem_commands_update_command.rb +207 -0
  138. data/test/test_gem_commands_which_command.rb +66 -0
  139. data/test/test_gem_config_file.rb +287 -0
  140. data/test/test_gem_dependency.rb +149 -0
  141. data/test/test_gem_dependency_installer.rb +661 -0
  142. data/test/test_gem_dependency_list.rb +230 -0
  143. data/test/test_gem_doc_manager.rb +31 -0
  144. data/test/test_gem_ext_configure_builder.rb +84 -0
  145. data/test/test_gem_ext_ext_conf_builder.rb +173 -0
  146. data/test/test_gem_ext_rake_builder.rb +81 -0
  147. data/test/test_gem_format.rb +70 -0
  148. data/test/test_gem_gem_path_searcher.rb +78 -0
  149. data/test/test_gem_gem_runner.rb +45 -0
  150. data/test/test_gem_gemcutter_utilities.rb +103 -0
  151. data/test/test_gem_indexer.rb +673 -0
  152. data/test/test_gem_install_update_options.rb +68 -0
  153. data/test/test_gem_installer.rb +857 -0
  154. data/test/test_gem_local_remote_options.rb +97 -0
  155. data/test/test_gem_package_tar_header.rb +130 -0
  156. data/test/test_gem_package_tar_input.rb +112 -0
  157. data/test/test_gem_package_tar_output.rb +97 -0
  158. data/test/test_gem_package_tar_reader.rb +46 -0
  159. data/test/test_gem_package_tar_reader_entry.rb +109 -0
  160. data/test/test_gem_package_tar_writer.rb +144 -0
  161. data/test/test_gem_package_task.rb +59 -0
  162. data/test/test_gem_platform.rb +264 -0
  163. data/test/test_gem_remote_fetcher.rb +740 -0
  164. data/test/test_gem_requirement.rb +292 -0
  165. data/test/test_gem_server.rb +356 -0
  166. data/test/test_gem_silent_ui.rb +113 -0
  167. data/test/test_gem_source_index.rb +461 -0
  168. data/test/test_gem_spec_fetcher.rb +410 -0
  169. data/test/test_gem_specification.rb +1334 -0
  170. data/test/test_gem_stream_ui.rb +218 -0
  171. data/test/test_gem_text.rb +43 -0
  172. data/test/test_gem_uninstaller.rb +146 -0
  173. data/test/test_gem_validator.rb +63 -0
  174. data/test/test_gem_version.rb +181 -0
  175. data/test/test_gem_version_option.rb +89 -0
  176. data/test/test_kernel.rb +59 -0
  177. metadata +402 -0
@@ -0,0 +1,79 @@
1
+ #--
2
+ # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
3
+ # All rights reserved.
4
+ # See LICENSE.txt for permissions.
5
+ #++
6
+
7
+ require 'libgems/command_manager'
8
+ require 'libgems/config_file'
9
+ require 'libgems/doc_manager'
10
+
11
+ ##
12
+ # Run an instance of the gem program.
13
+ #
14
+ # LibGems::GemRunner is only intended for internal use by SlimGems itself. It
15
+ # does not form any public API and may change at any time for any reason.
16
+ #
17
+ # If you would like to duplicate functionality of `gem` commands, use the
18
+ # classes they call directly.
19
+
20
+ class LibGems::GemRunner
21
+
22
+ def initialize(options={})
23
+ @command_manager_class = options[:command_manager] || LibGems::CommandManager
24
+ @config_file_class = options[:config_file] || LibGems::ConfigFile
25
+ @doc_manager_class = options[:doc_manager] || LibGems::DocManager
26
+ end
27
+
28
+ ##
29
+ # Run the gem command with the following arguments.
30
+
31
+ def run(args)
32
+ start_time = Time.now
33
+
34
+ if args.include?('--')
35
+ # We need to preserve the original ARGV to use for passing gem options
36
+ # to source gems. If there is a -- in the line, strip all options after
37
+ # it...its for the source building process.
38
+ build_args = args[args.index("--") + 1...args.length]
39
+ args = args[0...args.index("--")]
40
+ end
41
+
42
+ LibGems::Command.build_args = build_args if build_args
43
+
44
+ do_configuration args
45
+ cmd = @command_manager_class.instance
46
+
47
+ cmd.command_names.each do |command_name|
48
+ config_args = LibGems.configuration[command_name]
49
+ config_args = case config_args
50
+ when String
51
+ config_args.split ' '
52
+ else
53
+ Array(config_args)
54
+ end
55
+ LibGems::Command.add_specific_extra_args command_name, config_args
56
+ end
57
+
58
+ cmd.run LibGems.configuration.args
59
+ end_time = Time.now
60
+
61
+ if LibGems.configuration.benchmark then
62
+ printf "\nExecution time: %0.2f seconds.\n", end_time - start_time
63
+ puts "Press Enter to finish"
64
+ STDIN.gets
65
+ end
66
+ end
67
+
68
+ private
69
+
70
+ def do_configuration(args)
71
+ LibGems.configuration = @config_file_class.new(args)
72
+ LibGems.use_paths(LibGems.configuration[:gemhome], LibGems.configuration[:gempath])
73
+ LibGems::Command.extra_args = LibGems.configuration[:gem]
74
+ @doc_manager_class.configured_args = LibGems.configuration[:rdoc]
75
+ end
76
+
77
+ end
78
+
79
+ LibGems.load_plugins
@@ -0,0 +1,49 @@
1
+ require 'libgems/remote_fetcher'
2
+
3
+ module LibGems::GemcutterUtilities
4
+
5
+ def sign_in
6
+ return if LibGems.configuration.rubygems_api_key
7
+
8
+ say "Enter your rubygems.org credentials."
9
+ say "Don't have an account yet? Create one at http://rubygems.org/sign_up"
10
+
11
+ email = ask " Email: "
12
+ password = ask_for_password "Password: "
13
+ say "\n"
14
+
15
+ response = rubygems_api_request :get, "api/v1/api_key" do |request|
16
+ request.basic_auth email, password
17
+ end
18
+
19
+ with_response response do |resp|
20
+ say "Signed in."
21
+ LibGems.configuration.rubygems_api_key = resp.body
22
+ end
23
+ end
24
+
25
+ def rubygems_api_request(method, path, host = LibGems.host, &block)
26
+ require 'net/http'
27
+ host = ENV['RUBYGEMS_HOST'] if ENV['RUBYGEMS_HOST']
28
+ uri = URI.parse "#{host}/#{path}"
29
+
30
+ request_method = Net::HTTP.const_get method.to_s.capitalize
31
+
32
+ LibGems::RemoteFetcher.fetcher.request(uri, request_method, &block)
33
+ end
34
+
35
+ def with_response(resp)
36
+ case resp
37
+ when Net::HTTPSuccess then
38
+ if block_given? then
39
+ yield resp
40
+ else
41
+ say resp.body
42
+ end
43
+ else
44
+ say resp.body
45
+ terminate_interaction 1
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,720 @@
1
+ require 'fileutils'
2
+ require 'tmpdir'
3
+ require 'zlib'
4
+
5
+ require 'libgems'
6
+ require 'libgems/format'
7
+
8
+ begin
9
+ gem 'builder'
10
+ require 'builder/xchar'
11
+ rescue LoadError
12
+ end
13
+
14
+ ##
15
+ # Top level class for building the gem repository index.
16
+
17
+ class LibGems::Indexer
18
+
19
+ include LibGems::UserInteraction
20
+
21
+ ##
22
+ # Build indexes for SlimGems older than 1.2.0 when true
23
+
24
+ attr_accessor :build_legacy
25
+
26
+ ##
27
+ # Build indexes for SlimGems 1.2.0 and newer when true
28
+
29
+ attr_accessor :build_modern
30
+
31
+ ##
32
+ # Index install location
33
+
34
+ attr_reader :dest_directory
35
+
36
+ ##
37
+ # Specs index install location
38
+
39
+ attr_reader :dest_specs_index
40
+
41
+ ##
42
+ # Latest specs index install location
43
+
44
+ attr_reader :dest_latest_specs_index
45
+
46
+ ##
47
+ # Prerelease specs index install location
48
+
49
+ attr_reader :dest_prerelease_specs_index
50
+
51
+ ##
52
+ # Index build directory
53
+
54
+ attr_reader :directory
55
+
56
+ ##
57
+ # Create an indexer that will index the gems in +directory+.
58
+
59
+ def initialize(directory, options = {})
60
+ unless defined?(Builder::XChar) then
61
+ raise "LibGems::Indexer requires that the XML Builder library be installed:" \
62
+ "\n\tgem install builder"
63
+ end
64
+
65
+ options = { :build_legacy => true, :build_modern => true }.merge options
66
+
67
+ @build_legacy = options[:build_legacy]
68
+ @build_modern = options[:build_modern]
69
+
70
+ @rss_title = options[:rss_title]
71
+ @rss_host = options[:rss_host]
72
+ @rss_gems_host = options[:rss_gems_host]
73
+
74
+ @dest_directory = directory
75
+ @directory = File.join Dir.tmpdir, "gem_generate_index_#{$$}"
76
+
77
+ marshal_name = "Marshal.#{LibGems.marshal_version}"
78
+
79
+ @master_index = File.join @directory, 'yaml'
80
+ @marshal_index = File.join @directory, marshal_name
81
+
82
+ @quick_dir = File.join @directory, 'quick'
83
+
84
+ @quick_marshal_dir = File.join @quick_dir, marshal_name
85
+
86
+ @quick_index = File.join @quick_dir, 'index'
87
+ @latest_index = File.join @quick_dir, 'latest_index'
88
+
89
+ @specs_index = File.join @directory, "specs.#{LibGems.marshal_version}"
90
+ @latest_specs_index = File.join @directory,
91
+ "latest_specs.#{LibGems.marshal_version}"
92
+ @prerelease_specs_index = File.join(@directory,
93
+ "prerelease_specs.#{LibGems.marshal_version}")
94
+
95
+ @dest_specs_index = File.join @dest_directory,
96
+ "specs.#{LibGems.marshal_version}"
97
+ @dest_latest_specs_index = File.join @dest_directory,
98
+ "latest_specs.#{LibGems.marshal_version}"
99
+ @dest_prerelease_specs_index = File.join @dest_directory,
100
+ "prerelease_specs.#{LibGems.marshal_version}"
101
+
102
+ @rss_index = File.join @directory, 'index.rss'
103
+
104
+ @files = []
105
+ end
106
+
107
+ ##
108
+ # Abbreviate the spec for downloading. Abbreviated specs are only used for
109
+ # searching, downloading and related activities and do not need deployment
110
+ # specific information (e.g. list of files). So we abbreviate the spec,
111
+ # making it much smaller for quicker downloads.
112
+
113
+ def abbreviate(spec)
114
+ spec.files = []
115
+ spec.test_files = []
116
+ spec.rdoc_options = []
117
+ spec.extra_rdoc_files = []
118
+ spec.cert_chain = []
119
+ spec
120
+ end
121
+
122
+ ##
123
+ # Build various indicies
124
+
125
+ def build_indicies(index)
126
+ # Marshal gemspecs are used by both modern and legacy SlimGems
127
+ build_marshal_gemspecs index
128
+ build_legacy_indicies index if @build_legacy
129
+ build_modern_indicies index if @build_modern
130
+ build_rss index
131
+
132
+ compress_indicies
133
+ end
134
+
135
+ ##
136
+ # Builds indicies for SlimGems older than 1.2.x
137
+
138
+ def build_legacy_indicies(index)
139
+ progress = ui.progress_reporter index.size,
140
+ "Generating YAML quick index gemspecs for #{index.size} gems",
141
+ "Complete"
142
+
143
+ LibGems.time 'Generated YAML quick index gemspecs' do
144
+ index.released_gems.each do |original_name, spec|
145
+ spec_file_name = "#{original_name}.gemspec.rz"
146
+ yaml_name = File.join @quick_dir, spec_file_name
147
+
148
+ yaml_zipped = LibGems.deflate spec.to_yaml
149
+ open yaml_name, 'wb' do |io| io.write yaml_zipped end
150
+
151
+ progress.updated original_name
152
+ end
153
+
154
+ progress.done
155
+ end
156
+
157
+ say "Generating quick index"
158
+
159
+ LibGems.time 'Generated quick index' do
160
+ open @quick_index, 'wb' do |io|
161
+ io.puts index.sort.map { |_, spec| spec.original_name }
162
+ end
163
+ end
164
+
165
+ say "Generating latest index"
166
+
167
+ LibGems.time 'Generated latest index' do
168
+ open @latest_index, 'wb' do |io|
169
+ io.puts index.latest_specs.sort.map { |spec| spec.original_name }
170
+ end
171
+ end
172
+
173
+ # Don't need prerelease legacy index
174
+
175
+ say "Generating Marshal master index"
176
+
177
+ LibGems.time 'Generated Marshal master index' do
178
+ open @marshal_index, 'wb' do |io|
179
+ io.write index.dump
180
+ end
181
+ end
182
+
183
+ progress = ui.progress_reporter index.size,
184
+ "Generating YAML master index for #{index.size} gems (this may take a while)",
185
+ "Complete"
186
+
187
+ LibGems.time 'Generated YAML master index' do
188
+ open @master_index, 'wb' do |io|
189
+ io.puts "--- !ruby/object:#{index.class}"
190
+ io.puts "gems:"
191
+
192
+ gems = index.sort_by { |name, gemspec| gemspec.sort_obj }
193
+ gems.each do |original_name, gemspec|
194
+ yaml = gemspec.to_yaml.gsub(/^/, ' ')
195
+ yaml = yaml.sub(/\A ---/, '') # there's a needed extra ' ' here
196
+ io.print " #{original_name}:"
197
+ io.puts yaml
198
+
199
+ progress.updated original_name
200
+ end
201
+ end
202
+
203
+ progress.done
204
+ end
205
+
206
+ @files << @quick_dir
207
+ @files << @master_index
208
+ @files << "#{@master_index}.Z"
209
+ @files << @marshal_index
210
+ @files << "#{@marshal_index}.Z"
211
+ end
212
+
213
+ ##
214
+ # Builds Marshal quick index gemspecs.
215
+
216
+ def build_marshal_gemspecs(index)
217
+ progress = ui.progress_reporter index.size,
218
+ "Generating Marshal quick index gemspecs for #{index.size} gems",
219
+ "Complete"
220
+
221
+ files = []
222
+
223
+ LibGems.time 'Generated Marshal quick index gemspecs' do
224
+ index.gems.each do |original_name, spec|
225
+ spec_file_name = "#{original_name}.gemspec.rz"
226
+ marshal_name = File.join @quick_marshal_dir, spec_file_name
227
+
228
+ marshal_zipped = LibGems.deflate Marshal.dump(spec)
229
+ open marshal_name, 'wb' do |io| io.write marshal_zipped end
230
+
231
+ files << marshal_name
232
+
233
+ progress.updated original_name
234
+ end
235
+
236
+ progress.done
237
+ end
238
+
239
+ @files << @quick_marshal_dir
240
+
241
+ files
242
+ end
243
+
244
+ ##
245
+ # Build a single index for SlimGems 1.2 and newer
246
+
247
+ def build_modern_index(index, file, name)
248
+ say "Generating #{name} index"
249
+
250
+ LibGems.time "Generated #{name} index" do
251
+ open(file, 'wb') do |io|
252
+ specs = index.map do |*spec|
253
+ # We have to splat here because latest_specs is an array,
254
+ # while the others are hashes. See the TODO in source_index.rb
255
+ spec = spec.flatten.last
256
+ platform = spec.original_platform
257
+
258
+ # win32-api-1.0.4-x86-mswin32-60
259
+ unless String === platform then
260
+ alert_warning "Skipping invalid platform in gem: #{spec.full_name}"
261
+ next
262
+ end
263
+
264
+ platform = LibGems::Platform::RUBY if platform.nil? or platform.empty?
265
+ [spec.name, spec.version, platform]
266
+ end
267
+
268
+ specs = compact_specs(specs)
269
+ Marshal.dump(specs, io)
270
+ end
271
+ end
272
+ end
273
+
274
+ ##
275
+ # Builds indicies for SlimGems 1.2 and newer. Handles full, latest, prerelease
276
+
277
+ def build_modern_indicies(index)
278
+ build_modern_index(index.released_specs.sort, @specs_index, 'specs')
279
+ build_modern_index(index.latest_specs.sort,
280
+ @latest_specs_index,
281
+ 'latest specs')
282
+ build_modern_index(index.prerelease_specs.sort,
283
+ @prerelease_specs_index,
284
+ 'prerelease specs')
285
+
286
+ @files += [@specs_index,
287
+ "#{@specs_index}.gz",
288
+ @latest_specs_index,
289
+ "#{@latest_specs_index}.gz",
290
+ @prerelease_specs_index,
291
+ "#{@prerelease_specs_index}.gz"]
292
+ end
293
+
294
+ ##
295
+ # Builds an RSS feed for past two days gem releases according to the gem's
296
+ # date.
297
+
298
+ def build_rss(index)
299
+ if @rss_host.nil? or @rss_gems_host.nil? then
300
+ if LibGems.configuration.really_verbose then
301
+ alert_warning "no --rss-host or --rss-gems-host, RSS generation disabled"
302
+ end
303
+ return
304
+ end
305
+
306
+ require 'cgi'
307
+ require 'libgems/text'
308
+
309
+ extend LibGems::Text
310
+
311
+ LibGems.time 'Generated rss' do
312
+ open @rss_index, 'wb' do |io|
313
+ rss_host = CGI.escapeHTML @rss_host
314
+ rss_title = CGI.escapeHTML(@rss_title || 'gems')
315
+
316
+ io.puts <<-HEADER
317
+ <?xml version="1.0"?>
318
+ <rss version="2.0">
319
+ <channel>
320
+ <title>#{rss_title}</title>
321
+ <link>http://#{rss_host}</link>
322
+ <description>Recently released gems from http://#{rss_host}</description>
323
+ <generator>#{LibGems::NAME} v#{LibGems::VERSION} (RubyGems v#{LibGems::GEM_VERSION})</generator>
324
+ <docs>http://cyber.law.harvard.edu/rss/rss.html</docs>
325
+ HEADER
326
+
327
+ today = LibGems::Specification::TODAY
328
+ yesterday = today - 86400
329
+
330
+ index = index.select do |_, spec|
331
+ spec_date = spec.date
332
+
333
+ case spec_date
334
+ when Date
335
+ Time.parse(spec_date.to_s) >= yesterday
336
+ when Time
337
+ spec_date >= yesterday
338
+ end
339
+ end
340
+
341
+ index = index.select do |_, spec|
342
+ spec_date = spec.date
343
+
344
+ case spec_date
345
+ when Date
346
+ Time.parse(spec_date.to_s) <= today
347
+ when Time
348
+ spec_date <= today
349
+ end
350
+ end
351
+
352
+ index.sort_by { |_, spec| [-spec.date.to_i, spec] }.each do |_, spec|
353
+ gem_path = CGI.escapeHTML "http://#{@rss_gems_host}/gems/#{spec.file_name}"
354
+ size = File.stat(spec.loaded_from).size rescue next
355
+
356
+ description = spec.description || spec.summary || ''
357
+ authors = Array spec.authors
358
+ emails = Array spec.email
359
+ authors = emails.zip(authors).map do |email, author|
360
+ email += " (#{author})" if author and not author.empty?
361
+ end.join ', '
362
+
363
+ description = description.split(/\n\n+/).map do |chunk|
364
+ format_text chunk, 78
365
+ end
366
+
367
+ description = description.join "\n\n"
368
+
369
+ item = ''
370
+
371
+ item << <<-ITEM
372
+ <item>
373
+ <title>#{CGI.escapeHTML spec.full_name}</title>
374
+ <description>
375
+ &lt;pre&gt;#{CGI.escapeHTML description.chomp}&lt;/pre&gt;
376
+ </description>
377
+ <author>#{CGI.escapeHTML authors}</author>
378
+ <guid>#{CGI.escapeHTML spec.full_name}</guid>
379
+ <enclosure url=\"#{gem_path}\"
380
+ length=\"#{size}\" type=\"application/octet-stream\" />
381
+ <pubDate>#{spec.date.rfc2822}</pubDate>
382
+ ITEM
383
+
384
+ item << <<-ITEM if spec.homepage
385
+ <link>#{CGI.escapeHTML spec.homepage}</link>
386
+ ITEM
387
+
388
+ item << <<-ITEM
389
+ </item>
390
+ ITEM
391
+
392
+ io.puts item
393
+ end
394
+
395
+ io.puts <<-FOOTER
396
+ </channel>
397
+ </rss>
398
+ FOOTER
399
+ end
400
+ end
401
+
402
+ @files << @rss_index
403
+ end
404
+
405
+ ##
406
+ # Collect specifications from .gem files from the gem directory.
407
+
408
+ def collect_specs(gems = gem_file_list)
409
+ index = LibGems::SourceIndex.new
410
+
411
+ progress = ui.progress_reporter gems.size,
412
+ "Loading #{gems.size} gems from #{@dest_directory}",
413
+ "Loaded all gems"
414
+
415
+ LibGems.time 'loaded' do
416
+ gems.each do |gemfile|
417
+ if File.size(gemfile.to_s) == 0 then
418
+ alert_warning "Skipping zero-length gem: #{gemfile}"
419
+ next
420
+ end
421
+
422
+ begin
423
+ spec = LibGems::Format.from_file_by_path(gemfile).spec
424
+ spec.loaded_from = gemfile
425
+
426
+ unless gemfile =~ /\/#{Regexp.escape spec.original_name}.*\.gem\z/i then
427
+ expected_name = spec.full_name
428
+ expected_name << " (#{spec.original_name})" if
429
+ spec.original_name != spec.full_name
430
+ alert_warning "Skipping misnamed gem: #{gemfile} should be named #{expected_name}"
431
+ next
432
+ end
433
+
434
+ abbreviate spec
435
+ sanitize spec
436
+
437
+ index.add_spec spec, spec.original_name
438
+
439
+ progress.updated spec.original_name
440
+
441
+ rescue SignalException => e
442
+ alert_error "Received signal, exiting"
443
+ raise
444
+ rescue Exception => e
445
+ alert_error "Unable to process #{gemfile}\n#{e.message} (#{e.class})\n\t#{e.backtrace.join "\n\t"}"
446
+ end
447
+ end
448
+
449
+ progress.done
450
+ end
451
+
452
+ index
453
+ end
454
+
455
+ ##
456
+ # Compresses indicies on disk
457
+ #--
458
+ # All future files should be compressed using gzip, not deflate
459
+
460
+ def compress_indicies
461
+ say "Compressing indicies"
462
+
463
+ LibGems.time 'Compressed indicies' do
464
+ if @build_legacy then
465
+ compress @quick_index, 'rz'
466
+ paranoid @quick_index, 'rz'
467
+
468
+ compress @latest_index, 'rz'
469
+ paranoid @latest_index, 'rz'
470
+
471
+ compress @marshal_index, 'Z'
472
+ paranoid @marshal_index, 'Z'
473
+
474
+ compress @master_index, 'Z'
475
+ paranoid @master_index, 'Z'
476
+ end
477
+
478
+ if @build_modern then
479
+ gzip @specs_index
480
+ gzip @latest_specs_index
481
+ gzip @prerelease_specs_index
482
+ end
483
+ end
484
+ end
485
+
486
+ ##
487
+ # Compacts Marshal output for the specs index data source by using identical
488
+ # objects as much as possible.
489
+
490
+ def compact_specs(specs)
491
+ names = {}
492
+ versions = {}
493
+ platforms = {}
494
+
495
+ specs.map do |(name, version, platform)|
496
+ names[name] = name unless names.include? name
497
+ versions[version] = version unless versions.include? version
498
+ platforms[platform] = platform unless platforms.include? platform
499
+
500
+ [names[name], versions[version], platforms[platform]]
501
+ end
502
+ end
503
+
504
+ ##
505
+ # Compress +filename+ with +extension+.
506
+
507
+ def compress(filename, extension)
508
+ data = LibGems.read_binary filename
509
+
510
+ zipped = LibGems.deflate data
511
+
512
+ open "#{filename}.#{extension}", 'wb' do |io|
513
+ io.write zipped
514
+ end
515
+ end
516
+
517
+ ##
518
+ # List of gem file names to index.
519
+
520
+ def gem_file_list
521
+ Dir.glob(File.join(@dest_directory, "gems", "*.gem"))
522
+ end
523
+
524
+ ##
525
+ # Builds and installs indicies.
526
+
527
+ def generate_index
528
+ make_temp_directories
529
+ index = collect_specs
530
+ build_indicies index
531
+ install_indicies
532
+ rescue SignalException
533
+ ensure
534
+ FileUtils.rm_rf @directory
535
+ end
536
+
537
+ ##
538
+ # Zlib::GzipWriter wrapper that gzips +filename+ on disk.
539
+
540
+ def gzip(filename)
541
+ Zlib::GzipWriter.open "#{filename}.gz" do |io|
542
+ io.write LibGems.read_binary(filename)
543
+ end
544
+ end
545
+
546
+ ##
547
+ # Install generated indicies into the destination directory.
548
+
549
+ def install_indicies
550
+ verbose = LibGems.configuration.really_verbose
551
+
552
+ say "Moving index into production dir #{@dest_directory}" if verbose
553
+
554
+ files = @files.dup
555
+ files.delete @quick_marshal_dir if files.include? @quick_dir
556
+
557
+ if files.include? @quick_marshal_dir and
558
+ not files.include? @quick_dir then
559
+ files.delete @quick_marshal_dir
560
+ quick_marshal_dir = @quick_marshal_dir.sub @directory, ''
561
+
562
+ dst_name = File.join @dest_directory, quick_marshal_dir
563
+
564
+ FileUtils.mkdir_p File.dirname(dst_name), :verbose => verbose
565
+ FileUtils.rm_rf dst_name, :verbose => verbose
566
+ FileUtils.mv @quick_marshal_dir, dst_name, :verbose => verbose,
567
+ :force => true
568
+ end
569
+
570
+ files = files.map do |path|
571
+ path.sub @directory, ''
572
+ end
573
+
574
+ files.each do |file|
575
+ src_name = File.join @directory, file
576
+ dst_name = File.join @dest_directory, file
577
+
578
+ FileUtils.rm_rf dst_name, :verbose => verbose
579
+ FileUtils.mv src_name, @dest_directory, :verbose => verbose,
580
+ :force => true
581
+ end
582
+ end
583
+
584
+ ##
585
+ # Make directories for index generation
586
+
587
+ def make_temp_directories
588
+ FileUtils.rm_rf @directory
589
+ FileUtils.mkdir_p @directory, :mode => 0700
590
+ FileUtils.mkdir_p @quick_marshal_dir
591
+ end
592
+
593
+ ##
594
+ # Ensure +path+ and path with +extension+ are identical.
595
+
596
+ def paranoid(path, extension)
597
+ data = LibGems.read_binary path
598
+ compressed_data = LibGems.read_binary "#{path}.#{extension}"
599
+
600
+ unless data == LibGems.inflate(compressed_data) then
601
+ raise "Compressed file #{compressed_path} does not match uncompressed file #{path}"
602
+ end
603
+ end
604
+
605
+ ##
606
+ # Sanitize the descriptive fields in the spec. Sometimes non-ASCII
607
+ # characters will garble the site index. Non-ASCII characters will
608
+ # be replaced by their XML entity equivalent.
609
+
610
+ def sanitize(spec)
611
+ spec.summary = sanitize_string(spec.summary)
612
+ spec.description = sanitize_string(spec.description)
613
+ spec.post_install_message = sanitize_string(spec.post_install_message)
614
+ spec.authors = spec.authors.collect { |a| sanitize_string(a) }
615
+
616
+ spec
617
+ end
618
+
619
+ ##
620
+ # Sanitize a single string.
621
+
622
+ def sanitize_string(string)
623
+ return string unless string
624
+
625
+ # HACK the #to_s is in here because RSpec has an Array of Arrays of
626
+ # Strings for authors. Need a way to disallow bad values on gempsec
627
+ # generation. (Probably won't happen.)
628
+ string = string.to_s
629
+
630
+ begin
631
+ Builder::XChar.encode string
632
+ rescue NameError, NoMethodError
633
+ string.to_xs
634
+ end
635
+ end
636
+
637
+ ##
638
+ # Perform an in-place update of the repository from newly added gems. Only
639
+ # works for modern indicies, and sets #build_legacy to false when run.
640
+
641
+ def update_index
642
+ @build_legacy = false
643
+
644
+ make_temp_directories
645
+
646
+ specs_mtime = File.stat(@dest_specs_index).mtime
647
+ newest_mtime = Time.at 0
648
+
649
+ updated_gems = gem_file_list.select do |gem|
650
+ gem_mtime = File.stat(gem).mtime
651
+ newest_mtime = gem_mtime if gem_mtime > newest_mtime
652
+ gem_mtime >= specs_mtime
653
+ end
654
+
655
+ if updated_gems.empty? then
656
+ say 'No new gems'
657
+ terminate_interaction 0
658
+ end
659
+
660
+ index = collect_specs updated_gems
661
+
662
+ files = build_marshal_gemspecs index
663
+
664
+ LibGems.time 'Updated indexes' do
665
+ update_specs_index index.released_gems, @dest_specs_index, @specs_index
666
+ update_specs_index index.released_gems, @dest_latest_specs_index, @latest_specs_index
667
+ update_specs_index(index.prerelease_gems, @dest_prerelease_specs_index,
668
+ @prerelease_specs_index)
669
+ end
670
+
671
+ compress_indicies
672
+
673
+ verbose = LibGems.configuration.really_verbose
674
+
675
+ say "Updating production dir #{@dest_directory}" if verbose
676
+
677
+ files << @specs_index
678
+ files << "#{@specs_index}.gz"
679
+ files << @latest_specs_index
680
+ files << "#{@latest_specs_index}.gz"
681
+ files << @prerelease_specs_index
682
+ files << "#{@prerelease_specs_index}.gz"
683
+
684
+ files = files.map do |path|
685
+ path.sub @directory, ''
686
+ end
687
+
688
+ files.each do |file|
689
+ src_name = File.join @directory, file
690
+ dst_name = File.join @dest_directory, File.dirname(file)
691
+
692
+ FileUtils.mv src_name, dst_name, :verbose => verbose,
693
+ :force => true
694
+
695
+ File.utime newest_mtime, newest_mtime, dst_name
696
+ end
697
+ end
698
+
699
+ ##
700
+ # Combines specs in +index+ and +source+ then writes out a new copy to
701
+ # +dest+. For a latest index, does not ensure the new file is minimal.
702
+
703
+ def update_specs_index(index, source, dest)
704
+ specs_index = Marshal.load LibGems.read_binary(source)
705
+
706
+ index.each do |_, spec|
707
+ platform = spec.original_platform
708
+ platform = LibGems::Platform::RUBY if platform.nil? or platform.empty?
709
+ specs_index << [spec.name, spec.version, platform]
710
+ end
711
+
712
+ specs_index = compact_specs specs_index.uniq.sort
713
+
714
+ open dest, 'wb' do |io|
715
+ Marshal.dump specs_index, io
716
+ end
717
+ end
718
+
719
+ end
720
+