stickler 0.1.1 → 2.0.0a

Sign up to get free protection for your applications and to get access to all the features.
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