stickler 0.1.1 → 2.0.0a

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 (72) hide show
  1. data/HISTORY.rdoc +5 -2
  2. data/Rakefile +31 -0
  3. data/examples/config.ru +9 -0
  4. data/examples/gemcutter_repo.ru +19 -0
  5. data/examples/index_repo.ru +15 -0
  6. data/examples/local_repo.ru +14 -0
  7. data/examples/mirror_repo.ru +16 -0
  8. data/examples/not_found.ru +8 -0
  9. data/lib/stickler/error.rb +3 -0
  10. data/lib/stickler/middleware/compression.rb +30 -0
  11. data/lib/stickler/middleware/gemcutter.rb +62 -0
  12. data/lib/stickler/middleware/helpers.rb +84 -0
  13. data/lib/stickler/middleware/index.rb +137 -0
  14. data/lib/stickler/middleware/local.rb +38 -0
  15. data/lib/stickler/middleware/mirror.rb +60 -0
  16. data/lib/stickler/middleware/not_found.rb +62 -0
  17. data/lib/stickler/middleware.rb +4 -0
  18. data/lib/stickler/repository/api.rb +167 -0
  19. data/lib/stickler/repository/index.rb +97 -0
  20. data/lib/stickler/repository/local.rb +251 -0
  21. data/lib/stickler/repository/mirror.rb +48 -0
  22. data/lib/stickler/repository/null.rb +58 -0
  23. data/lib/stickler/repository/remote.rb +235 -0
  24. data/lib/stickler/repository.rb +7 -499
  25. data/lib/stickler/spec_lite.rb +60 -14
  26. data/lib/stickler/version.rb +6 -6
  27. data/lib/stickler/web.rb +19 -0
  28. data/spec/data/gems/bar-1.0.0.gem +0 -0
  29. data/spec/data/gems/foo-1.0.0.gem +0 -0
  30. data/spec/data/specifications/bar-1.0.0.gemspec +31 -0
  31. data/spec/data/specifications/foo-1.0.0.gemspec +31 -0
  32. data/spec/middleware/common_gem_server_helpers.rb +67 -0
  33. data/spec/middleware/index_spec.rb +26 -0
  34. data/spec/middleware/legacy_gem_server_behavior.rb +33 -0
  35. data/spec/middleware/local_spec.rb +25 -0
  36. data/spec/middleware/modern_gem_server_behavior.rb +20 -0
  37. data/spec/middleware/not_found_spec.rb +25 -0
  38. data/spec/repository/api_behavior.rb +162 -0
  39. data/spec/repository/api_spec.rb +38 -0
  40. data/spec/repository/index_spec.rb +32 -0
  41. data/spec/repository/local_spec.rb +36 -0
  42. data/spec/repository/null_spec.rb +17 -0
  43. data/spec/repository/remote_spec.rb +49 -0
  44. data/spec/spec.opts +2 -0
  45. data/spec/spec_helper.rb +15 -3
  46. data/spec/spec_lite_spec.rb +62 -0
  47. data/stickler.gemspec +60 -0
  48. data/views/index.erb +19 -0
  49. data/views/layout.erb +39 -0
  50. metadata +93 -63
  51. data/COPYING +0 -339
  52. data/bin/stickler +0 -58
  53. data/data/stickler.yml +0 -14
  54. data/gemspec.rb +0 -62
  55. data/lib/stickler/cli.rb +0 -302
  56. data/lib/stickler/configuration.rb +0 -74
  57. data/lib/stickler/console.rb +0 -72
  58. data/lib/stickler/paths.rb +0 -62
  59. data/lib/stickler/source.rb +0 -75
  60. data/lib/stickler/source_group.rb +0 -365
  61. data/lib/stickler.rb +0 -19
  62. data/spec/configuration_spec.rb +0 -68
  63. data/spec/paths_spec.rb +0 -25
  64. data/spec/repository_spec.rb +0 -55
  65. data/spec/version_spec.rb +0 -17
  66. data/tasks/announce.rake +0 -39
  67. data/tasks/config.rb +0 -107
  68. data/tasks/distribution.rake +0 -45
  69. data/tasks/documentation.rake +0 -31
  70. data/tasks/rspec.rake +0 -29
  71. data/tasks/rubyforge.rake +0 -51
  72. data/tasks/utils.rb +0 -80
@@ -0,0 +1,167 @@
1
+ require 'stickler/repository'
2
+ require 'stickler/spec_lite'
3
+
4
+ module Stickler::Repository
5
+ #
6
+ # The API that all Stickler Repository classes MUST implement.
7
+ # This file is here to document the API
8
+ #
9
+ module Api
10
+ #
11
+ # :call-seq:
12
+ # repo.uri -> URI
13
+ #
14
+ # Return the URI of the repo
15
+ #
16
+ def uri
17
+ raise NotImplementedError, not_implemented_msg( :uri )
18
+ end
19
+
20
+ #
21
+ # :call-seq:
22
+ # repo.gems_uri -> URI
23
+ #
24
+ # Return the URI to the location holding all the +.gem+ files.
25
+ #
26
+ #
27
+ def gems_uri
28
+ raise NotImplementedError, not_implemented_msg( :gems_uri )
29
+ end
30
+
31
+ #
32
+ # :call-seq:
33
+ # repo.uri_for_gem( spec ) -> URI
34
+ #
35
+ # Given a SpecLite like object, return a URI that can be used
36
+ # to directly access the gem in the repository.
37
+ #
38
+ def uri_for_gem( spec )
39
+ raise NotImplementedError, not_implemented_msg( :uri_for_gem )
40
+ end
41
+
42
+ #
43
+ # :call-seq:
44
+ # repo.search_for( spec ) -> Array
45
+ #
46
+ # +match+ MUST be an object that responds to +name+, +version+ and
47
+ # +platform+.
48
+ #
49
+ # The Array that is returned will be +empty?+ if no gems are found that
50
+ # match +match+.
51
+ #
52
+ # When one or matches is found, the Array will contain contain
53
+ # Stickler::SpecLite instances.
54
+ #
55
+ def search_for( spec )
56
+ raise NotImplementedError, not_implemented_msg( :search_for )
57
+ end
58
+
59
+ #
60
+ # :call-seq:
61
+ # repo.push( path_to_gem_file ) -> Stickler::SpecLite
62
+ #
63
+ # Push, in the sense of the gem commandline command <tt>gem push</tt>.
64
+ # +path_to_gem_file+ must be a file system location to a .gem file
65
+ # that is then _pushed_ into the repository.
66
+ #
67
+ # The SpecLite returned can be used to retrieve the gem
68
+ # from the repo using the #get() method. A direct URI to the
69
+ # gem may be obtained using the #uri_for() method.
70
+ #
71
+ # If the gem pushed already exists, then a Stickler::Repository::Error is
72
+ # raised.
73
+ #
74
+ def push( path_to_gem_file )
75
+ raise NotImplementedError, not_implemented_msg( :push )
76
+ end
77
+
78
+ #
79
+ # :call-seq:
80
+ # repo.delete( spec ) -> true|false
81
+ #
82
+ # Remove the gem matching the spec completely from the respository.
83
+ # Return +true+ if the gem was removed, +false+ otherwise
84
+ #
85
+ def delete( spec )
86
+ raise NotImplementedError, not_implemented_msg( :delete)
87
+ end
88
+
89
+ #
90
+ # :call-seq:
91
+ # repo.yank( spec ) -> Stickler::SpecLite
92
+ #
93
+ # "yank" in the sense of
94
+ # http://update.gemcutter.org/2010/03/05/february-changelog.html.
95
+ # This means, remove the gem matching +spec+ from the index, so it will not
96
+ # be found when searching, but do not remove the gem physically from the
97
+ # server. It can still be downloaded directly.
98
+ #
99
+ # The SpecLite instance that is returned will have the information that may
100
+ # be used in the #get() or #uri_for_gem() methods to retrieve the actual
101
+ # gemfile.
102
+ #
103
+ # After a gem has been 'yanked' it MUST not longer be found via the
104
+ # #search_for() method, nor can it's specification be retrieved via the
105
+ # #uri_for_specification() method.
106
+ #
107
+ # If the gem described by spec does not exist, nil is returned.
108
+ #
109
+ def yank( spec )
110
+ raise NotImplementedError, not_implemented_msg( :yank )
111
+ end
112
+
113
+ #
114
+ # :call-seq:
115
+ # repo.get( spec ) -> bytes
116
+ #
117
+ # Retrieve the gem matching the spec from the repository. The bytes
118
+ # returned MUST be something that would be acceptable to be written
119
+ # directly to disk as a .gem file.
120
+ #
121
+ # If the gem described by spec does not exist, nil is returned.
122
+ #
123
+ def get( spec )
124
+ raise NotImplementedError, not_implemented_msg( :get )
125
+ end
126
+
127
+ #
128
+ # :call-seq:
129
+ # repo.open( spec ) -> reader
130
+ # repo.open( spec ) { |reader| block }
131
+ #
132
+ # Open the gem in a readonly manner, similar to that of File.open.
133
+ # the +reader+ object that is returned MUST respond to +read+,
134
+ # +close+ and +rewind+. These methods behave like their corresponding
135
+ # IO#read, IO#close and IO#rewind methods.
136
+ #
137
+ # If the gem described by spec does not exist, nil is returned.
138
+ # If the gem described by spec does not exist, the block is not called.
139
+ #
140
+ def open( spec, &block )
141
+ raise NotImplementedError, not_implemented_msg( :open )
142
+ end
143
+
144
+ # :stopdoc:
145
+ def self.api_methods
146
+ %w[
147
+ delete
148
+ gems_uri
149
+ get
150
+ open
151
+ push
152
+ search_for
153
+ uri
154
+ uri_for_gem
155
+ yank
156
+ ]
157
+ end
158
+ # :startdoc:
159
+
160
+ private
161
+ # :stopdoc:
162
+ def not_implemented_msg( method )
163
+ "Please implement #{self.class.name}##{method}"
164
+ end
165
+ # :startdoc:
166
+ end
167
+ end
@@ -0,0 +1,97 @@
1
+ require 'stickler/spec_lite'
2
+ require 'stickler/repository'
3
+
4
+ module Stickler::Repository
5
+ #
6
+ # A repository index is a container holding all the SpecLite elements
7
+ # in the repository. All the gem specs that this index holds are derived
8
+ # from actual files on the file system.
9
+ #
10
+ # It is modelled somewhat like a Gem::SourceIndex.
11
+ #
12
+ class Index
13
+ class Error < ::Stickler::Repository::Error; end
14
+
15
+ # The list of specs in the index
16
+ attr_reader :specs
17
+
18
+ # The directory the specs live
19
+ attr_reader :spec_dir
20
+
21
+ #
22
+
23
+ def initialize( spec_dir )
24
+ @specs = []
25
+ @spec_dir = spec_dir
26
+ load_specs
27
+ end
28
+
29
+ def specs
30
+ load_specs if reload_necessary?
31
+ return @specs
32
+ end
33
+
34
+ def latest_specs
35
+ latest = {}
36
+ specs.each do |s|
37
+ if old_spec = latest[s.name] then
38
+ if old_spec.version < s.version then
39
+ latest[s.name] = s
40
+ end
41
+ else
42
+ latest[s.name] = s
43
+ end
44
+ end
45
+ latest.values
46
+ end
47
+
48
+ def load_specs
49
+ load_specs_in_dir( self.spec_dir )
50
+ end
51
+
52
+ def reload_necessary?
53
+ return true
54
+ # return true unless @last_modified_time
55
+ # current_modified_time = File.stat( self.spec_dir ).mtime
56
+ # return (current_modified_time > @last_modified_time )
57
+ end
58
+
59
+ def last_modified_time
60
+ File.stat( self.spec_dir ).mtime
61
+ end
62
+
63
+ def spec_dir=( d )
64
+ raise Error, "#{d} is not a directory" unless File.directory?( d )
65
+ @spec_dir = d
66
+ @last_modified_time = File.stat( d ).mtime
67
+ end
68
+
69
+ def load_specs_in_dir( spec_dir )
70
+ return nil unless File.directory?( spec_dir )
71
+ @specs.clear
72
+ self.spec_dir = spec_dir
73
+
74
+ Dir.foreach( spec_dir ) do |entry|
75
+ next unless entry =~ /\.gemspec\Z/
76
+ add_spec_from_file( File.join( spec_dir, entry ) )
77
+ end
78
+ end
79
+
80
+ def add_spec_from_file( path )
81
+ return nil unless File.exist?( path )
82
+ code = File.read( path )
83
+ full_spec = eval( code, binding, path )
84
+ raise Error, "File #{path} is not Gem::Specification in ruby format" unless full_spec.is_a?( Gem::Specification )
85
+
86
+ full_spec.untaint
87
+ spec = Stickler::SpecLite.new( full_spec.name, full_spec.version, full_spec.platform )
88
+ @specs << spec
89
+ end
90
+
91
+ def search( find_spec )
92
+ specs.select do |spec|
93
+ spec =~ find_spec
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,251 @@
1
+ require 'stickler/spec_lite'
2
+ require 'stickler/repository'
3
+ require 'stickler/repository/api'
4
+ require 'stickler/repository/index'
5
+ require 'addressable/uri'
6
+ require 'tempfile'
7
+
8
+ module Stickler::Repository
9
+ #
10
+ # A local repository of gems. It implements the Repository::Api
11
+ # and stores all the gems and specifications local to a root directory.
12
+ #
13
+ # It currently has two subdirectories:
14
+ #
15
+ # gems/ -> holding the .gem files
16
+ # specifications/ -> holding the .gemspec files
17
+ #
18
+ #
19
+ class Local
20
+ class Error < ::Stickler::Repository::Error; end
21
+
22
+ # the root directory of the repository
23
+ attr_reader :root_dir
24
+
25
+ # the directory containing the .gem files
26
+ attr_reader :gems_dir
27
+
28
+ # the directory containing the .gemspec files
29
+ attr_reader :specifications_dir
30
+
31
+ # a temporary directory for odds and ends
32
+ attr_reader :temp_dir
33
+
34
+ # the index of the repository
35
+ attr_reader :index
36
+
37
+ def initialize( root_dir )
38
+ @root_dir = File.expand_path( root_dir ) + File::SEPARATOR
39
+ @gems_dir = File.join( @root_dir, 'gems/' )
40
+ @specifications_dir = File.join( @root_dir, 'specifications/' )
41
+ @temp_dir = File.join( @root_dir, "tmp/" )
42
+ @index = ::Stickler::Repository::Index.new( @specifications_dir )
43
+ setup_dirs
44
+ end
45
+
46
+ #
47
+ # See Api#uri
48
+ #
49
+ def uri
50
+ @uri ||= Addressable::URI.convert_path( root_dir )
51
+ end
52
+
53
+ #
54
+ # See Api#gems_uri
55
+ #
56
+ def gems_uri
57
+ @gems_uri ||= Addressable::URI.convert_path( gems_dir )
58
+ end
59
+
60
+ #
61
+ # See Api#uri_from_gem
62
+ #
63
+ def uri_for_gem( spec )
64
+ return nil unless gem_file_exist?( spec )
65
+ return self.gems_uri.join( spec.file_name )
66
+ end
67
+
68
+ #
69
+ # A list of all the specs in the repo
70
+ #
71
+ def specs
72
+ @index.specs
73
+ end
74
+
75
+ #
76
+ # A list of just the latests specs in the repo
77
+ #
78
+ def latest_specs
79
+ @index.latest_specs
80
+ end
81
+
82
+ #
83
+ # The last time this index was modified
84
+ #
85
+ def last_modified_time
86
+ @index.last_modified_time
87
+ end
88
+
89
+ #
90
+ # See Api#search_for
91
+ #
92
+ def search_for( spec )
93
+ return index.search( spec )
94
+ end
95
+
96
+ #
97
+ # See Api#delete
98
+ #
99
+ def delete( spec )
100
+ uninstall( spec )
101
+ end
102
+
103
+ #
104
+ # See Api#yank
105
+ #
106
+ def yank( spec )
107
+ uninstall_specification( spec ) if specification_file_exist?( spec )
108
+ return uri_for_gem( spec )
109
+ end
110
+
111
+
112
+ #
113
+ # :call-seq:
114
+ # repo.add( io ) -> Stickler::SpecLite
115
+ #
116
+ # A lower level version of #push. The item passed in is an IO like object
117
+ # that contains the binary stream that is a .gem file. IO must respond to
118
+ # #read and #rewind.
119
+ #
120
+ def add( io )
121
+ # spooling to a temp file because Gem::Format.from_io() closes the io
122
+ # stream it is sent. Why it does this, I do not know.
123
+ tempfile = Tempfile.new( "uploaded-gem.", temp_dir )
124
+ tempfile.write( io.read )
125
+ tempfile.rewind
126
+
127
+ format = Gem::Format.from_file_by_path( tempfile.path )
128
+ spec = Stickler::SpecLite.new( format.spec.name, format.spec.version, format.spec.platform )
129
+ specs = search_for( spec )
130
+
131
+ raise Error, "gem #{spec.full_name} already exists" unless specs.empty?
132
+
133
+ tempfile.rewind
134
+ return install( spec, tempfile )
135
+ ensure
136
+ tempfile.close!
137
+ end
138
+
139
+ #
140
+ # See Api#push
141
+ #
142
+ def push( path )
143
+ spec = specification_from_gem_file( path )
144
+ result = nil
145
+ File.open( path ) do |io|
146
+ result = add( io )
147
+ end
148
+ return result
149
+ end
150
+
151
+ #
152
+ # See Api#get
153
+ #
154
+ def get( spec )
155
+ return IO.read( full_path_to_gem( spec ) ) if gem_file_exist?( spec )
156
+ return nil
157
+ end
158
+
159
+ #
160
+ # See Api#open
161
+ #
162
+ def open( spec, &block )
163
+ return nil unless gem_file_exist?( spec )
164
+ path = full_path_to_gem( spec )
165
+ f = File.open( path, "rb" )
166
+ if block_given? then
167
+ begin
168
+ yield f
169
+ ensure
170
+ f.close
171
+ end
172
+ else
173
+ return f
174
+ end
175
+ return nil
176
+ end
177
+
178
+ def full_path_to_gem( spec )
179
+ File.join( gems_dir, spec.file_name )
180
+ end
181
+
182
+
183
+ private
184
+
185
+ def setup_dirs
186
+ [ root_dir, specifications_dir, gems_dir, temp_dir ].each do |dir|
187
+ FileUtils.mkdir_p( dir ) unless File.directory?( dir )
188
+ end
189
+ end
190
+
191
+ def full_path_to_specification( spec )
192
+ File.join( specifications_dir, spec.spec_file_name )
193
+ end
194
+
195
+ def install( spec, io )
196
+ install_gem( spec, io )
197
+ install_specification( spec )
198
+ end
199
+
200
+ def install_gem( spec, io )
201
+ File.open( full_path_to_gem( spec ) , "w+" ) do |of|
202
+ io.each do |str|
203
+ of.write( str )
204
+ end
205
+ end
206
+ end
207
+
208
+ def install_specification( spec )
209
+ gemspec = specification_from_gem_file( full_path_to_gem( spec ) )
210
+ File.open( full_path_to_specification( spec ) , "w+" ) do |f|
211
+ f.write( gemspec.to_ruby )
212
+ end
213
+ return speclite_from_specification( gemspec )
214
+ end
215
+
216
+ def uninstall( spec )
217
+ uninstall_gem( spec )
218
+ uninstall_specification( spec )
219
+ end
220
+
221
+ def uninstall_gem( spec )
222
+ remove_file( full_path_to_gem( spec ) )
223
+ end
224
+
225
+ def uninstall_specification( spec )
226
+ remove_file( full_path_to_specification( spec ) )
227
+ end
228
+
229
+ def remove_file( path )
230
+ return false unless File.exist?( path )
231
+ return true if File.unlink( path ) > 0
232
+ end
233
+
234
+ def gem_file_exist?( spec )
235
+ File.exist?( full_path_to_gem( spec ) )
236
+ end
237
+
238
+ def specification_file_exist?( spec )
239
+ File.exist?( full_path_to_specification( spec ) )
240
+ end
241
+
242
+ def specification_from_gem_file( path )
243
+ format = Gem::Format.from_file_by_path( path )
244
+ return format.spec
245
+ end
246
+
247
+ def speclite_from_specification( spec )
248
+ Stickler::SpecLite.new( spec.name, spec.version.to_s, spec.platform )
249
+ end
250
+ end
251
+ end
@@ -0,0 +1,48 @@
1
+ require 'stickler/repository'
2
+ require 'stickler/repository/remote'
3
+ require 'stickler/repository/local'
4
+ require 'forwardable'
5
+
6
+ module Stickler::Repository
7
+ #
8
+ # A Mirror mirrors gems in a Repository::Remote to a Repository::Local
9
+ # All of the Repository::Api methods are delegated to the Local instance
10
+ # and a new method #mirror() is added to pull gems from a Remote location
11
+ # and store in the Local instance
12
+ #
13
+ class Mirror
14
+ class Error < ::Stickler::Repository::Error ; end
15
+
16
+ extend Forwardable
17
+
18
+ def initialize( root_dir )
19
+ @root_dir = root_dir
20
+ @local_repo = ::Stickler::Repository::Local.new( @root_dir )
21
+ @remote_repos = {}
22
+ end
23
+ def_delegators :@local_repo, :uri, :gems_uri, :uri_for_gem, :search_for,
24
+ :push, :delete, :get, :open
25
+
26
+ #
27
+ # :call-seq:
28
+ # repo.mirror( spec, host = "rubygems.org" ) -> SpecLite
29
+ #
30
+ # Mirror the gem described by spec on the +host+. If no +host+
31
+ # is given, it is assumed to be http://rubygems.org/.
32
+ #
33
+ def mirror( host, spec )
34
+ specs = @local_repo.search_for( spec )
35
+ raise Error, "gem #{spec.full_name} already exists" unless specs.empty?
36
+
37
+ repo = remote_repo_for( host )
38
+ repo.open( spec ) do |io|
39
+ @local_repo.add( io )
40
+ end
41
+ return spec
42
+ end
43
+
44
+ def remote_repo_for( host )
45
+ @remote_repos[host] ||= ::Stickler::Repository::Remote.new( host )
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ require 'stickler/repository'
2
+
3
+ module Stickler::Repository
4
+ #
5
+ # A null repository. It is in most respecs like a Repository::Local that has
6
+ # nothing in it.
7
+ #
8
+ # The response to +root_dir+ is set by default to be the class name, or
9
+ # whatever is passed to the initializer.
10
+ #
11
+ class Null
12
+ # the root directory of the repository, this is set in the constructor
13
+ attr_reader :root_dir
14
+
15
+ def initialize( root_dir = self.class.name )
16
+ @root_dir = root_dir
17
+ end
18
+
19
+ def empty_string( junk = "" )
20
+ ""
21
+ end
22
+ alias :uri :empty_string
23
+ alias :gems_uri :empty_string
24
+
25
+ def nilish( junk = nil, &block )
26
+ nil
27
+ end
28
+ alias :push :nilish
29
+ alias :delete :nilish
30
+ alias :yank :nilish
31
+ alias :get :nilish
32
+ alias :open :nilish
33
+ alias :uri_for_gem :nilish
34
+ alias :full_path_to_gem :nilish
35
+
36
+ def last_modified_time
37
+ Time.now
38
+ end
39
+
40
+ def empty_array( junk = nil )
41
+ []
42
+ end
43
+ alias :specs :empty_array
44
+ alias :latest_specs :empty_array
45
+ alias :search_for :empty_array
46
+
47
+ def
48
+
49
+ def specs
50
+ Array.new
51
+ end
52
+
53
+ def latest_specs
54
+ Array.new
55
+ end
56
+
57
+ end
58
+ end