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,240 @@
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 'find'
8
+
9
+ require 'digest'
10
+ require 'libgems/format'
11
+ require 'libgems/installer'
12
+
13
+ begin
14
+ gem 'test-unit'
15
+ rescue LibGems::LoadError
16
+ # Ignore - use the test-unit library that's part of the standard library
17
+ end
18
+
19
+ ##
20
+ # Validator performs various gem file and gem database validation
21
+
22
+ class LibGems::Validator
23
+
24
+ include LibGems::UserInteraction
25
+
26
+ ##
27
+ # Given a gem file's contents, validates against its own MD5 checksum
28
+ # gem_data:: [String] Contents of the gem file
29
+
30
+ def verify_gem(gem_data)
31
+ raise LibGems::VerificationError, 'empty gem file' if gem_data.size == 0
32
+
33
+ unless gem_data =~ /MD5SUM/ then
34
+ return # Don't worry about it...this sucks. Need to fix MD5 stuff for
35
+ # new format
36
+ # FIXME
37
+ end
38
+
39
+ sum_data = gem_data.gsub(/MD5SUM = "([a-z0-9]+)"/,
40
+ "MD5SUM = \"#{"F" * 32}\"")
41
+
42
+ unless Digest::MD5.hexdigest(sum_data) == $1.to_s then
43
+ raise LibGems::VerificationError, 'invalid checksum for gem file'
44
+ end
45
+ end
46
+
47
+ ##
48
+ # Given the path to a gem file, validates against its own MD5 checksum
49
+ #
50
+ # gem_path:: [String] Path to gem file
51
+
52
+ def verify_gem_file(gem_path)
53
+ open gem_path, LibGems.binary_mode do |file|
54
+ gem_data = file.read
55
+ verify_gem gem_data
56
+ end
57
+ rescue Errno::ENOENT, Errno::EINVAL
58
+ raise LibGems::VerificationError, "missing gem file #{gem_path}"
59
+ end
60
+
61
+ private
62
+
63
+ def find_files_for_gem(gem_directory)
64
+ installed_files = []
65
+ Find.find gem_directory do |file_name|
66
+ fn = file_name[gem_directory.size..file_name.size-1].sub(/^\//, "")
67
+ installed_files << fn unless
68
+ fn =~ /CVS/ || fn.empty? || File.directory?(file_name)
69
+ end
70
+ installed_files
71
+ end
72
+
73
+ public
74
+
75
+ ErrorData = Struct.new :path, :problem
76
+
77
+ ##
78
+ # Checks the gem directory for the following potential
79
+ # inconsistencies/problems:
80
+ #
81
+ # * Checksum gem itself
82
+ # * For each file in each gem, check consistency of installed versions
83
+ # * Check for files that aren't part of the gem but are in the gems directory
84
+ # * 1 cache - 1 spec - 1 directory.
85
+ #
86
+ # returns a hash of ErrorData objects, keyed on the problem gem's name.
87
+
88
+ def alien(gems=[])
89
+ errors = Hash.new { |h,k| h[k] = {} }
90
+
91
+ LibGems::SourceIndex.from_installed_gems.each do |gem_name, gem_spec|
92
+ next unless gems.include? gem_spec.name unless gems.empty?
93
+
94
+ install_dir = gem_spec.installation_path
95
+ gem_path = File.join install_dir, "cache", gem_spec.file_name
96
+ spec_path = File.join install_dir, "specifications", gem_spec.spec_name
97
+ gem_directory = gem_spec.full_gem_path
98
+
99
+ unless File.directory? gem_directory then
100
+ errors[gem_name][gem_spec.full_name] =
101
+ "Gem registered but doesn't exist at #{gem_directory}"
102
+ next
103
+ end
104
+
105
+ unless File.exist? spec_path then
106
+ errors[gem_name][spec_path] = "Spec file missing for installed gem"
107
+ end
108
+
109
+ begin
110
+ verify_gem_file(gem_path)
111
+
112
+ good, gone, unreadable = nil, nil, nil, nil
113
+
114
+ open gem_path, LibGems.binary_mode do |file|
115
+ format = LibGems::Format.from_file_by_path(gem_path)
116
+
117
+ good, gone = format.file_entries.partition { |entry, _|
118
+ File.exist? File.join(gem_directory, entry['path'])
119
+ }
120
+
121
+ gone.map! { |entry, _| entry['path'] }
122
+ gone.sort.each do |path|
123
+ errors[gem_name][path] = "Missing file"
124
+ end
125
+
126
+ good, unreadable = good.partition { |entry, _|
127
+ File.readable? File.join(gem_directory, entry['path'])
128
+ }
129
+
130
+ unreadable.map! { |entry, _| entry['path'] }
131
+ unreadable.sort.each do |path|
132
+ errors[gem_name][path] = "Unreadable file"
133
+ end
134
+
135
+ good.each do |entry, data|
136
+ begin
137
+ next unless data # HACK `gem check -a mkrf`
138
+
139
+ open File.join(gem_directory, entry['path']), LibGems.binary_mode do |f|
140
+ unless Digest::MD5.hexdigest(f.read).to_s ==
141
+ Digest::MD5.hexdigest(data).to_s then
142
+ errors[gem_name][entry['path']] = "Modified from original"
143
+ end
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ installed_files = find_files_for_gem(gem_directory)
150
+ good.map! { |entry, _| entry['path'] }
151
+ extras = installed_files - good - unreadable
152
+
153
+ extras.each do |extra|
154
+ errors[gem_name][extra] = "Extra file"
155
+ end
156
+ rescue LibGems::VerificationError => e
157
+ errors[gem_name][gem_path] = e.message
158
+ end
159
+ end
160
+
161
+ errors.each do |name, subhash|
162
+ errors[name] = subhash.map { |path, msg| ErrorData.new(path, msg) }
163
+ end
164
+
165
+ errors
166
+ end
167
+
168
+ if RUBY_VERSION < '1.9' then
169
+ class TestRunner
170
+ def initialize(suite, ui)
171
+ @suite = suite
172
+ @ui = ui
173
+ end
174
+
175
+ def self.run(suite, ui)
176
+ require 'test/unit/ui/testrunnermediator'
177
+ return new(suite, ui).start
178
+ end
179
+
180
+ def start
181
+ @mediator = Test::Unit::UI::TestRunnerMediator.new(@suite)
182
+ @mediator.add_listener(Test::Unit::TestResult::FAULT, &method(:add_fault))
183
+ return @mediator.run_suite
184
+ end
185
+
186
+ def add_fault(fault)
187
+ if LibGems.configuration.verbose then
188
+ @ui.say fault.long_display
189
+ end
190
+ end
191
+ end
192
+
193
+ autoload :TestRunner, 'test/unit/ui/testrunnerutilities'
194
+ end
195
+
196
+ ##
197
+ # Runs unit tests for a given gem specification
198
+
199
+ def unit_test(gem_spec)
200
+ start_dir = Dir.pwd
201
+ Dir.chdir(gem_spec.full_gem_path)
202
+ $: << gem_spec.full_gem_path
203
+ # XXX: why do we need this gem_spec when we've already got 'spec'?
204
+ test_files = gem_spec.test_files
205
+
206
+ if test_files.empty? then
207
+ say "There are no unit tests to run for #{gem_spec.full_name}"
208
+ return nil
209
+ end
210
+
211
+ gem gem_spec.name, "= #{gem_spec.version.version}"
212
+
213
+ test_files.each do |f| require f end
214
+
215
+ if RUBY_VERSION < '1.9' then
216
+ suite = Test::Unit::TestSuite.new("#{gem_spec.name}-#{gem_spec.version}")
217
+
218
+ ObjectSpace.each_object(Class) do |klass|
219
+ suite << klass.suite if (klass < Test::Unit::TestCase)
220
+ end
221
+
222
+ result = TestRunner.run suite, ui
223
+
224
+ alert_error result.to_s unless result.passed?
225
+ else
226
+ result = MiniTest::Unit.new
227
+ result.run
228
+ end
229
+
230
+ result
231
+ ensure
232
+ Dir.chdir(start_dir)
233
+ end
234
+
235
+ def remove_leading_dot_dir(path)
236
+ path.sub(/^\.\//, "")
237
+ end
238
+
239
+ end
240
+
@@ -0,0 +1,316 @@
1
+ ##
2
+ # The Version class processes string versions into comparable
3
+ # values. A version string should normally be a series of numbers
4
+ # separated by periods. Each part (digits separated by periods) is
5
+ # considered its own number, and these are used for sorting. So for
6
+ # instance, 3.10 sorts higher than 3.2 because ten is greater than
7
+ # two.
8
+ #
9
+ # If any part contains letters (currently only a-z are supported) then
10
+ # that version is considered prerelease. Versions with a prerelease
11
+ # part in the Nth part sort less than versions with N-1
12
+ # parts. Prerelease parts are sorted alphabetically using the normal
13
+ # Ruby string sorting rules. If a prerelease part contains both
14
+ # letters and numbers, it will be broken into multiple parts to
15
+ # provide expected sort behavior.(1.0.a10 becomes 1.0.a.10, and is
16
+ # greater than 1.0.a9).
17
+ #
18
+ # Prereleases sort between real releases (newest to oldest):
19
+ #
20
+ # 1. 1.0
21
+ # 2. 1.0.b1
22
+ # 3. 1.0.a.2
23
+ # 4. 0.9
24
+ #
25
+ # == How Software Changes
26
+ #
27
+ # Users expect to be able to specify a version constraint that gives them
28
+ # some reasonable expectation that new versions of a library will work with
29
+ # their software if the version constraint is true, and not work with their
30
+ # software if the version constraint is false. In other words, the perfect
31
+ # system will accept all compatible versions of the library and reject all
32
+ # incompatible versions.
33
+ #
34
+ # Libraries change in 3 ways (well, more than 3, but stay focused here!).
35
+ #
36
+ # 1. The change may be an implementation detail only and have no effect on
37
+ # the client software.
38
+ # 2. The change may add new features, but do so in a way that client software
39
+ # written to an earlier version is still compatible.
40
+ # 3. The change may change the public interface of the library in such a way
41
+ # that old software is no longer compatible.
42
+ #
43
+ # Some examples are appropriate at this point. Suppose I have a Stack class
44
+ # that supports a <tt>push</tt> and a <tt>pop</tt> method.
45
+ #
46
+ # === Examples of Category 1 changes:
47
+ #
48
+ # * Switch from an array based implementation to a linked-list based
49
+ # implementation.
50
+ # * Provide an automatic (and transparent) backing store for large stacks.
51
+ #
52
+ # === Examples of Category 2 changes might be:
53
+ #
54
+ # * Add a <tt>depth</tt> method to return the current depth of the stack.
55
+ # * Add a <tt>top</tt> method that returns the current top of stack (without
56
+ # changing the stack).
57
+ # * Change <tt>push</tt> so that it returns the item pushed (previously it
58
+ # had no usable return value).
59
+ #
60
+ # === Examples of Category 3 changes might be:
61
+ #
62
+ # * Changes <tt>pop</tt> so that it no longer returns a value (you must use
63
+ # <tt>top</tt> to get the top of the stack).
64
+ # * Rename the methods to <tt>push_item</tt> and <tt>pop_item</tt>.
65
+ #
66
+ # == SlimGems Rational Versioning
67
+ #
68
+ # * Versions shall be represented by three non-negative integers, separated
69
+ # by periods (e.g. 3.1.4). The first integers is the "major" version
70
+ # number, the second integer is the "minor" version number, and the third
71
+ # integer is the "build" number.
72
+ #
73
+ # * A category 1 change (implementation detail) will increment the build
74
+ # number.
75
+ #
76
+ # * A category 2 change (backwards compatible) will increment the minor
77
+ # version number and reset the build number.
78
+ #
79
+ # * A category 3 change (incompatible) will increment the major build number
80
+ # and reset the minor and build numbers.
81
+ #
82
+ # * Any "public" release of a gem should have a different version. Normally
83
+ # that means incrementing the build number. This means a developer can
84
+ # generate builds all day long for himself, but as soon as he/she makes a
85
+ # public release, the version must be updated.
86
+ #
87
+ # === Examples
88
+ #
89
+ # Let's work through a project lifecycle using our Stack example from above.
90
+ #
91
+ # Version 0.0.1:: The initial Stack class is release.
92
+ # Version 0.0.2:: Switched to a linked=list implementation because it is
93
+ # cooler.
94
+ # Version 0.1.0:: Added a <tt>depth</tt> method.
95
+ # Version 1.0.0:: Added <tt>top</tt> and made <tt>pop</tt> return nil
96
+ # (<tt>pop</tt> used to return the old top item).
97
+ # Version 1.1.0:: <tt>push</tt> now returns the value pushed (it used it
98
+ # return nil).
99
+ # Version 1.1.1:: Fixed a bug in the linked list implementation.
100
+ # Version 1.1.2:: Fixed a bug introduced in the last fix.
101
+ #
102
+ # Client A needs a stack with basic push/pop capability. He writes to the
103
+ # original interface (no <tt>top</tt>), so his version constraint looks
104
+ # like:
105
+ #
106
+ # gem 'stack', '~> 0.0'
107
+ #
108
+ # Essentially, any version is OK with Client A. An incompatible change to
109
+ # the library will cause him grief, but he is willing to take the chance (we
110
+ # call Client A optimistic).
111
+ #
112
+ # Client B is just like Client A except for two things: (1) He uses the
113
+ # <tt>depth</tt> method and (2) he is worried about future
114
+ # incompatibilities, so he writes his version constraint like this:
115
+ #
116
+ # gem 'stack', '~> 0.1'
117
+ #
118
+ # The <tt>depth</tt> method was introduced in version 0.1.0, so that version
119
+ # or anything later is fine, as long as the version stays below version 1.0
120
+ # where incompatibilities are introduced. We call Client B pessimistic
121
+ # because he is worried about incompatible future changes (it is OK to be
122
+ # pessimistic!).
123
+ #
124
+ # == Preventing Version Catastrophe:
125
+ #
126
+ # From: http://blog.zenspider.com/2008/10/rubygems-howto-preventing-cata.html
127
+ #
128
+ # Let's say you're depending on the fnord gem version 2.y.z. If you
129
+ # specify your dependency as ">= 2.0.0" then, you're good, right? What
130
+ # happens if fnord 3.0 comes out and it isn't backwards compatible
131
+ # with 2.y.z? Your stuff will break as a result of using ">=". The
132
+ # better route is to specify your dependency with a "spermy" version
133
+ # specifier. They're a tad confusing, so here is how the dependency
134
+ # specifiers work:
135
+ #
136
+ # Specification From ... To (exclusive)
137
+ # ">= 3.0" 3.0 ... &infin;
138
+ # "~> 3.0" 3.0 ... 4.0
139
+ # "~> 3.0.0" 3.0.0 ... 3.1
140
+ # "~> 3.5" 3.5 ... 4.0
141
+ # "~> 3.5.0" 3.5.0 ... 3.6
142
+
143
+ class LibGems::Version
144
+ include Comparable
145
+
146
+ VERSION_PATTERN = '[0-9]+(\.[0-9a-zA-Z]+)*' # :nodoc:
147
+ ANCHORED_VERSION_PATTERN = /\A\s*(#{VERSION_PATTERN})*\s*\z/ # :nodoc:
148
+
149
+ ##
150
+ # A string representation of this Version.
151
+
152
+ attr_reader :version
153
+ alias to_s version
154
+
155
+ ##
156
+ # True if the +version+ string matches SlimGems' requirements.
157
+
158
+ def self.correct? version
159
+ version.to_s =~ ANCHORED_VERSION_PATTERN
160
+ end
161
+
162
+ ##
163
+ # Factory method to create a Version object. Input may be a Version
164
+ # or a String. Intended to simplify client code.
165
+ #
166
+ # ver1 = Version.create('1.3.17') # -> (Version object)
167
+ # ver2 = Version.create(ver1) # -> (ver1)
168
+ # ver3 = Version.create(nil) # -> nil
169
+
170
+ def self.create input
171
+ if input.respond_to? :version then
172
+ input
173
+ elsif input.nil? then
174
+ nil
175
+ else
176
+ new input
177
+ end
178
+ end
179
+
180
+ ##
181
+ # Constructs a Version from the +version+ string. A version string is a
182
+ # series of digits or ASCII letters separated by dots.
183
+
184
+ def initialize version
185
+ raise ArgumentError, "Malformed version number string #{version}" unless
186
+ self.class.correct?(version)
187
+
188
+ @version = version.to_s
189
+ @version.strip!
190
+ end
191
+
192
+ ##
193
+ # Return a new version object where the next to the last revision
194
+ # number is one greater (e.g., 5.3.1 => 5.4).
195
+ #
196
+ # Pre-release (alpha) parts, e.g, 5.3.1.b.2 => 5.4, are ignored.
197
+
198
+ def bump
199
+ segments = self.segments.dup
200
+ segments.pop while segments.any? { |s| String === s }
201
+ segments.pop if segments.size > 1
202
+
203
+ segments[-1] = segments[-1].succ
204
+ self.class.new segments.join(".")
205
+ end
206
+
207
+ ##
208
+ # A Version is only eql? to another version if it's specified to the
209
+ # same precision. Version "1.0" is not the same as version "1".
210
+
211
+ def eql? other
212
+ self.class === other and @version == other.version
213
+ end
214
+
215
+ def hash # :nodoc:
216
+ @hash ||= segments.hash
217
+ end
218
+
219
+ def inspect # :nodoc:
220
+ "#<#{self.class} #{version.inspect}>"
221
+ end
222
+
223
+ ##
224
+ # Dump only the raw version string, not the complete object. It's a
225
+ # string for backwards (SlimGems 1.3.5 and earlier) compatibility.
226
+
227
+ def marshal_dump
228
+ [version]
229
+ end
230
+
231
+ ##
232
+ # Load custom marshal format. It's a string for backwards (SlimGems
233
+ # 1.3.5 and earlier) compatibility.
234
+
235
+ def marshal_load array
236
+ initialize array[0]
237
+ end
238
+
239
+ ##
240
+ # A version is considered a prerelease if it contains a letter.
241
+
242
+ def prerelease?
243
+ @prerelease ||= @version =~ /[a-zA-Z]/
244
+ end
245
+
246
+ def pretty_print q # :nodoc:
247
+ q.text "LibGems::Version.new(#{version.inspect})"
248
+ end
249
+
250
+ ##
251
+ # The release for this version (e.g. 1.2.0.a -> 1.2.0).
252
+ # Non-prerelease versions return themselves.
253
+
254
+ def release
255
+ return self unless prerelease?
256
+
257
+ segments = self.segments.dup
258
+ segments.pop while segments.any? { |s| String === s }
259
+ self.class.new segments.join('.')
260
+ end
261
+
262
+ def segments # :nodoc:
263
+
264
+ # segments is lazy so it can pick up version values that come from
265
+ # old marshaled versions, which don't go through marshal_load.
266
+
267
+ @segments ||= @version.scan(/[0-9]+|[a-z]+/i).map do |s|
268
+ /^\d+$/ =~ s ? s.to_i : s
269
+ end
270
+ end
271
+
272
+ ##
273
+ # A recommended version for use with a ~> Requirement.
274
+
275
+ def spermy_recommendation
276
+ segments = self.segments.dup
277
+
278
+ segments.pop while segments.any? { |s| String === s }
279
+ segments.pop while segments.size > 2
280
+ segments.push 0 while segments.size < 2
281
+
282
+ "~> #{segments.join(".")}"
283
+ end
284
+
285
+ ##
286
+ # Compares this version with +other+ returning -1, 0, or 1 if the
287
+ # other version is larger, the same, or smaller than this
288
+ # one. Attempts to compare to something that's not a
289
+ # <tt>LibGems::Version</tt> return +nil+.
290
+
291
+ def <=> other
292
+ return unless LibGems::Version === other
293
+ return 0 if @version == other.version
294
+
295
+ lhsegments = segments
296
+ rhsegments = other.segments
297
+
298
+ lhsize = lhsegments.size
299
+ rhsize = rhsegments.size
300
+ limit = (lhsize > rhsize ? lhsize : rhsize) - 1
301
+
302
+ i = 0
303
+
304
+ while i <= limit
305
+ lhs, rhs = lhsegments[i] || 0, rhsegments[i] || 0
306
+ i += 1
307
+
308
+ next if lhs == rhs
309
+ return -1 if String === lhs && Numeric === rhs
310
+ return 1 if Numeric === lhs && String === rhs
311
+ return lhs <=> rhs
312
+ end
313
+
314
+ return 0
315
+ end
316
+ end