smc-get 0.2.0.beta1 → 0.3.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,277 +1,290 @@
1
- #Encoding: UTF-8
2
- ################################################################################
3
- # This file is part of smc-get.
4
- # Copyright (C) 2010-2011 Entertaining Software, Inc.
5
- # Copyright (C) 2011 Marvin Gülker
6
- #
7
- # This program is free software: you can redistribute it and/or modify
8
- # it under the terms of the GNU General Public License as published by
9
- # the Free Software Foundation, either version 3 of the License, or
10
- # (at your option) any later version.
11
- #
12
- # This program is distributed in the hope that it will be useful,
13
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- # GNU General Public License for more details.
16
- #
17
- # You should have received a copy of the GNU General Public License
18
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
- ################################################################################
20
-
21
- module SmcGet
22
-
23
- #A LocalRepository contains all the packages that are installed locally, i.e.
24
- #that have been downloaded and added to your local SMC installation.
25
- #It provides the exact same methods as RemoteRepository, but works completely
26
- #local and therefore doesn’t need an internet connection as RemoteRepository does.
27
- #
28
- #The only notable difference is that instances of this class have some
29
- #attributes different from those defined for RemoteRepository, but usually
30
- #you shouldn’t have to worry about that.
31
- #
32
- #Concluding, you’ll find the documentation of most of the methods of this class
33
- #in the documentation of the RemoteRepository class, because duplicating docs
34
- #doesn’t make much sense.
35
- class LocalRepository < Repository
36
-
37
- #Directory where the package specs are kept.
38
- SPECS_DIR = Pathname.new("packages")
39
- #Directory where downloaded packages are cached.
40
- CACHE_DIR = Pathname.new("cache")
41
- #Directory where the packages’ level files are kept.
42
- CONTRIB_LEVELS_DIR = Pathname.new("levels") #Levels in subdirectories are currently not recognized by SMC
43
- #Directory where the packages’ music files are kept.
44
- CONTRIB_MUSIC_DIR = Pathname.new("music") + "contrib-music"
45
- #Directory where the packages’ graphic files are kept.
46
- CONTRIB_GRAPHICS_DIR = Pathname.new("pixmaps") + "contrib-graphics"
47
- #Directory where the packages’ sound files are kept.
48
- CONTRIB_SOUNDS_DIR = Pathname.new("sounds") + "contrib-sounds"
49
- #Directory where the packages’ world files are kept
50
- CONTRIB_WORLDS_DIR = Pathname.new("world") #Worlds in subdirectores are currently not recognized by SMC
51
-
52
- #Root path of the local repository. Should be the same as your SMC’s
53
- #installation path.
54
- attr_reader :path
55
- #This repository’s specs dir.
56
- attr_reader :specs_dir
57
- #This repository’s cache dir.
58
- attr_reader :cache_dir
59
- #This repository’s package levels dir.
60
- attr_reader :contrib_level_dir
61
- #This repository’s package music dir.
62
- attr_reader :contrib_music_dir
63
- #This repository’s package graphics dir.
64
- attr_reader :contrib_graphics_dir
65
- #This repository’s package sounds dir.
66
- attr_reader :contrib_sounds_dir
67
- #This repository’s package worlds dir.
68
- attr_reader :contrib_worlds_dir
69
- #An array of PackageSpecification objects containing the specs of
70
- #all packages installed in this repository.
71
- attr_reader :package_specs
72
-
73
- #"Creates" a new local repository whose root is located at the given +path+.
74
- #When instanciating this class, you should point it to the root of your
75
- #SMC installation’s *share* directory, e.g. <b>/usr/share/smc</b>.
76
- #==Parameter
77
- #[path] The path to your SMC installation.
78
- #==Return value
79
- #The newly created LocalRepository.
80
- #==Example
81
- # lr = SmcGet::LocalRepository.new("/usr/share/smc")
82
- #==Remarks
83
- #smc-get requires some additional directories in your SMC installation,
84
- #namely (where +smc+ is your SMC’s *share* directory):
85
- # * smc/packages
86
- # * smc/music/contrib-music
87
- # * smc/sounds/contrib-sounds
88
- # * smc/pixmaps/contrib-graphics
89
- #These will be created when you call this method, so make sure
90
- #you have the appropriate permissions for these directories or
91
- #you’ll get an Errno::EACCES exception when calling ::new.
92
- def initialize(path)
93
- @path = Pathname.new(path)
94
- @specs_dir = @path + SPECS_DIR
95
- @cache_dir = @path + CACHE_DIR
96
- @levels_dir = @path + CONTRIB_LEVELS_DIR
97
- @music_dir = @path + CONTRIB_MUSIC_DIR
98
- @graphics_dir = @path + CONTRIB_GRAPHICS_DIR
99
- @sounds_dir = @path + CONTRIB_SOUNDS_DIR
100
- @worlds_dir = @path + CONTRIB_WORLDS_DIR
101
-
102
- #Create the directories if they’re not there yet
103
- [@specs_dir, @cache_dir, @levels_dir, @music_dir, @graphics_dir, @sounds_dir, @worlds_dir].each do |dir|
104
- dir.mkpath unless dir.directory?
105
- end
106
-
107
- @package_specs = []
108
- @specs_dir.children.each do |spec_path|
109
- next unless spec_path.to_s.end_with?(".yml")
110
- @package_specs << PackageSpecification.from_file(spec_path)
111
- end
112
- end
113
-
114
- def fetch_spec(spec_file, directory = ".")
115
- directory = Pathname.new(directory)
116
-
117
- spec_file_path = @specs_dir + spec_file
118
- raise(Errors::NoSuchResourceError.new(:spec, spec_file), "Package specification '#{spec_file}' not found in the local repository '#{to_s}'!") unless spec_file_path.file?
119
-
120
- directory.mktree unless directory.directory?
121
-
122
- #No need to really "fetch" the spec--this is a *local* repository.
123
- FileUtils.cp(spec_file_path, directory)
124
- directory + spec_file
125
- end
126
-
127
- def fetch_package(pkg_file, directory = ".")
128
- directory = Pathname.new(directory)
129
-
130
- pkg_file_path = @cache_dir + pkg_file
131
- raise(Errors::NoSuchPackageError.new(pkg_file.sub(/\.smcpak/, "")), "Package file '#{pkg_file}' not found in this repository's cache!") unless pkg_file_path.file?
132
-
133
- directory.mktree unless directory.directory?
134
-
135
- #No need to really "fetch" the package--this is a *local* repository
136
- FileUtils.cp(pkg_file_path, directory)
137
- directory + pkg_file
138
- end
139
-
140
- #Installs a package into this local repository in a way that SMC will find
141
- #it’s contents.
142
- #==Parameter
143
- #[package] An instance of class Package. The package to install,
144
- #==Example
145
- # lr.install(a_package)
146
- #==Remarks
147
- #Ensure you have write permissions to the repository, otherwise
148
- #you’ll get an Errno::EACCES exception from this method.
149
- def install(package)
150
- path = package.decompress(SmcGet.temp_dir) + package.spec.name
151
-
152
- package.spec.save(@specs_dir)
153
-
154
- FileUtils.cp_r(path.join(Package::LEVELS_DIR).children, @levels_dir)
155
- FileUtils.cp_r(path.join(Package::MUSIC_DIR).children, @music_dir)
156
- FileUtils.cp_r(path.join(Package::GRAPHICS_DIR).children, @graphics_dir)
157
- FileUtils.cp_r(path.join(Package::SOUNDS_DIR).children, @sounds_dir)
158
- FileUtils.cp_r(path.join(Package::WORLDS_DIR).children, @worlds_dir)
159
-
160
- FileUtils.cp(package.path, @cache_dir)
161
-
162
- @package_specs << package.spec #This package is now installed and therefore the spec must be in that array
163
- end
164
-
165
- #call-seq:
166
- # uninstall(pkg_name)
167
- # uninstall(pkg_name){|path| ...}
168
- #
169
- #Uninstalls a package by removing all files it owns. This method checks the
170
- #checksums specified in the respective package specifications and if it
171
- #detects a user-modified file, the given block is invoked. If the block
172
- #evaluates to a truth value, the modified file is copied to a file
173
- #in the same directory as the original with ".MODIFIED" just before the
174
- #file extension. The blockless form always discards all modified files.
175
- #==Parameters
176
- #[pkg_name] The name of the package (without the .smcpak extension) to remove.
177
- #[full_path] *Blockargument*. The full Pathname of a file that has been modified.
178
- #==Example
179
- # # Delete a package and save all modified files
180
- # rr.uninstall("cool-world"){|file| true}
181
- # # Delete a package and discard all modified files
182
- # rr.uninstall("cool-world")
183
- def uninstall(pkg_name)
184
- spec = @package_specs.find{|spec| spec.name == pkg_name}
185
-
186
- [:levels, :music, :sounds, :graphics].each do |sym|
187
- contrib_dir = @path + self.class.const_get(:"CONTRIB_#{sym.upcase}_DIR")
188
-
189
- #Delete all the files
190
- files = spec[sym]
191
- files.each do |filename|
192
- full_path = contrib_dir + filename
193
- #Check if the file was modified
194
- if block_given? and Digest::SHA1.hexdigest(File.read(full_path)) != spec[:checksums][sym.to_s][filename] #to_s as the keys are strings there, see PackageSpecification.from_file
195
- if yield(full_path) #Getting a truth value from the block means copying
196
- FileUtils.cp(full_path, full_path.parent + filename.sub(/\.(.*?)$/, '.MODIFIED.\1'))
197
- end
198
- end
199
- File.delete(full_path)
200
- end
201
-
202
- #Delete now empty directories
203
- loop do
204
- empty_dirs = []
205
- contrib_dir.find do |path|
206
- next if path == contrib_dir #We surely don’t want to delete the toplevel dir.
207
- empty_dirs << path if path.directory? and path.children.empty?
208
- end
209
- #If no empty directories are present anymore, break out of the loop.
210
- break if empty_dirs.empty?
211
- #Otherwise delete the empty directories and redo the process, because
212
- #the parent directories could be empty now.
213
- empty_dirs.each{|path| File.delete(path)}
214
- end
215
- end
216
-
217
- #Delete worlds as well. Worlds can’t reside in subdirectories, therefore it’s unnecessary
218
- #to check for the empty-directory thing.
219
- spec[:worlds].each do |dirname|
220
- full_path = @worlds_dir + dirname
221
- #Check if any of the world’s files has been modified
222
- ["description.xml", "layer.xml", "world.xml"].each do |wfile|
223
- full_wfile_path = full_path + wfile
224
- if block_given? and Digest::SHA1.hexdigest(full_wfile_path) != spec[:checksums]["worlds"][dirname][wfile] #"worlds" is a string for technical reasons, see PackageSpecification.from_file
225
- if yield(full_wfile_path) #Getting a truth value from the block means copying
226
- FileUtils.cp_r(full_path, full_path.parent + "#{dirname}.MODIFIED")
227
- break #Break from the inner iteration, we just need to copy once
228
- end
229
- end
230
- end
231
- FileUtils.rm_r(full_path)
232
- end
233
-
234
- File.delete(@specs_dir + spec.spec_file_name) #Remove the spec itself
235
- @package_specs.delete(spec) #Otherwise we have a stale package in the array
236
- end
237
-
238
- #Returns the path this repository refers to.
239
- def to_s
240
- @path.to_s
241
- end
242
-
243
- def contain?(pkg)
244
- if pkg.kind_of? Package
245
- @package_specs.include?(pkg.spec)
246
- else
247
- @package_specs.any?{|spec| spec.name == pkg}
248
- end
249
- end
250
- alias contains? contain?
251
-
252
- def search(regexp, *attributes)
253
- attributes << :name if attributes.empty? #Default value
254
-
255
- @package_specs.each do |spec|
256
- attributes.each do |att|
257
- case att
258
- when :name then yield(spec.name) if spec.name =~ regexp
259
- when :title then yield(spec.name) if spec.title =~ regexp
260
- when :authors then yield(spec.name) if spec.authors.any?{|a| a =~ regexp}
261
- when :difficulty then yield(spec.name) if spec.difficulty =~ regexp
262
- when :description then yield(spec.name) if spec.description =~ regexp
263
- when :levels then yield(spec.name) if spec.levels.any?{|l| l =~ regexp}
264
- when :music then yield(spec.name) if spec.music.any?{|m| m =~ regexp}
265
- when :sounds then yield(spec.name) if spec.sound.any?{|s| s =~ regexp}
266
- when :graphics then yield(spec.name) if spec.graphics.any?{|g| g =~ regexp}
267
- when :worlds then yield(spec.name) if spec.worlds.any?{|w| w =~ regexp}
268
- else
269
- $stderr.puts("Warning: Unknown attribute #{att}, ignoring it.")
270
- end #case
271
- end #attributes.each
272
- end # @package_specs.each
273
- end #search
274
-
275
- end #LocalRepository
276
-
277
- end
1
+ #Encoding: UTF-8
2
+ ################################################################################
3
+ # This file is part of smc-get.
4
+ # Copyright (C) 2010-2011 Entertaining Software, Inc.
5
+ # Copyright (C) 2011 Marvin Gülker
6
+ #
7
+ # This program is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU General Public License as published by
9
+ # the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # This program is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU General Public License
18
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
+ ################################################################################
20
+
21
+ module SmcGet
22
+
23
+ #A LocalRepository contains all the packages that are installed locally, i.e.
24
+ #that have been downloaded and added to your local SMC installation.
25
+ #It provides the exact same methods as RemoteRepository, but works completely
26
+ #local and therefore doesn’t need an internet connection as RemoteRepository does.
27
+ #
28
+ #The only notable difference is that instances of this class have some
29
+ #attributes different from those defined for RemoteRepository, but usually
30
+ #you shouldn’t have to worry about that.
31
+ #
32
+ #Concluding, you’ll find the documentation of most of the methods of this class
33
+ #in the documentation of the RemoteRepository class, because duplicating docs
34
+ #doesn’t make much sense.
35
+ class LocalRepository < Repository
36
+
37
+ #Directory where the package specs are kept.
38
+ SPECS_DIR = Pathname.new("packages")
39
+ #Directory where downloaded packages are cached.
40
+ CACHE_DIR = Pathname.new("cache")
41
+ #Directory where the packages’ level files are kept.
42
+ CONTRIB_LEVELS_DIR = Pathname.new("levels") #Levels in subdirectories are currently not recognized by SMC
43
+ #Directory where the packages’ music files are kept.
44
+ CONTRIB_MUSIC_DIR = Pathname.new("music") + "contrib-music"
45
+ #Directory where the packages’ graphic files are kept.
46
+ CONTRIB_GRAPHICS_DIR = Pathname.new("pixmaps") + "contrib-graphics"
47
+ #Directory where the packages’ sound files are kept.
48
+ CONTRIB_SOUNDS_DIR = Pathname.new("sounds") + "contrib-sounds"
49
+ #Directory where the packages’ world files are kept
50
+ CONTRIB_WORLDS_DIR = Pathname.new("world") #Worlds in subdirectores are currently not recognized by SMC
51
+
52
+ #Root path of the local repository. Should be the same as your SMC’s
53
+ #installation path.
54
+ attr_reader :path
55
+ #This repository’s specs dir.
56
+ attr_reader :specs_dir
57
+ #This repository’s cache dir.
58
+ attr_reader :cache_dir
59
+ #This repository’s package levels dir.
60
+ attr_reader :contrib_level_dir
61
+ #This repository’s package music dir.
62
+ attr_reader :contrib_music_dir
63
+ #This repository’s package graphics dir.
64
+ attr_reader :contrib_graphics_dir
65
+ #This repository’s package sounds dir.
66
+ attr_reader :contrib_sounds_dir
67
+ #This repository’s package worlds dir.
68
+ attr_reader :contrib_worlds_dir
69
+ #An array of PackageSpecification objects containing the specs of
70
+ #all packages installed in this repository.
71
+ attr_reader :package_specs
72
+
73
+ #"Creates" a new local repository whose root is located at the given +path+.
74
+ #When instanciating this class, you should point it to the root of your
75
+ #SMC installation’s *share* directory, e.g. <b>/usr/share/smc</b>.
76
+ #==Parameter
77
+ #[path] The path to your SMC installation.
78
+ #==Return value
79
+ #The newly created LocalRepository.
80
+ #==Example
81
+ # lr = SmcGet::LocalRepository.new("/usr/share/smc")
82
+ #==Remarks
83
+ #smc-get requires some additional directories in your SMC installation,
84
+ #namely (where +smc+ is your SMC’s *share* directory):
85
+ # * smc/packages
86
+ # * smc/music/contrib-music
87
+ # * smc/sounds/contrib-sounds
88
+ # * smc/pixmaps/contrib-graphics
89
+ #These will be created when you call this method, so make sure
90
+ #you have the appropriate permissions for these directories or
91
+ #you’ll get an Errno::EACCES exception when calling ::new.
92
+ def initialize(path)
93
+ @path = Pathname.new(path)
94
+ @specs_dir = @path + SPECS_DIR
95
+ @cache_dir = @path + CACHE_DIR
96
+ @levels_dir = @path + CONTRIB_LEVELS_DIR
97
+ @music_dir = @path + CONTRIB_MUSIC_DIR
98
+ @graphics_dir = @path + CONTRIB_GRAPHICS_DIR
99
+ @sounds_dir = @path + CONTRIB_SOUNDS_DIR
100
+ @worlds_dir = @path + CONTRIB_WORLDS_DIR
101
+
102
+ #Create the directories if they’re not there yet
103
+ [@specs_dir, @cache_dir, @levels_dir, @music_dir, @graphics_dir, @sounds_dir, @worlds_dir].each do |dir|
104
+ begin
105
+ dir.mkpath unless dir.directory?
106
+ rescue Errno::EACCES #This is fatal, don’t raise InvalidRepository
107
+ $stderr.puts("FATAL: Can't create the directory #{dir}, do you have write permissions there? ")
108
+ exit 2
109
+ end
110
+ end
111
+
112
+ @package_specs = []
113
+ @specs_dir.children.each do |spec_path|
114
+ next unless spec_path.to_s.end_with?(".yml")
115
+ @package_specs << PackageSpecification.from_file(spec_path)
116
+ end
117
+ rescue Errors::InvalidSpecification => e
118
+ raise(Errors::InvalidRepository.new(@path), "Repository contains an invalid specification: #{e.message}")
119
+ rescue => e
120
+ raise(Errors::InvalidRepository.new(@path), e.message)
121
+ end
122
+
123
+ def fetch_spec(spec_file, directory = ".")
124
+ directory = Pathname.new(directory)
125
+
126
+ spec_file_path = @specs_dir + spec_file
127
+ raise(Errors::NoSuchResourceError.new(:spec, spec_file), "Package specification '#{spec_file}' not found in the local repository '#{to_s}'!") unless spec_file_path.file?
128
+
129
+ directory.mktree unless directory.directory?
130
+
131
+ #No need to really "fetch" the spec--this is a *local* repository.
132
+ FileUtils.cp(spec_file_path, directory)
133
+ directory + spec_file
134
+ end
135
+
136
+ def fetch_package(pkg_file, directory = ".")
137
+ directory = Pathname.new(directory)
138
+
139
+ pkg_file_path = @cache_dir + pkg_file
140
+ raise(Errors::NoSuchPackageError.new(pkg_file.sub(/\.smcpak/, "")), "Package file '#{pkg_file}' not found in this repository's cache!") unless pkg_file_path.file?
141
+
142
+ directory.mktree unless directory.directory?
143
+
144
+ #No need to really "fetch" the package--this is a *local* repository
145
+ FileUtils.cp(pkg_file_path, directory)
146
+ directory + pkg_file
147
+ end
148
+
149
+ #Installs a package into this local repository in a way that SMC will find
150
+ #it’s contents.
151
+ #==Parameter
152
+ #[package] An instance of class Package. The package to install,
153
+ #==Example
154
+ # lr.install(a_package)
155
+ #==Remarks
156
+ #Ensure you have write permissions to the repository, otherwise
157
+ #you’ll get an Errno::EACCES exception from this method.
158
+ def install(package)
159
+ path = package.decompress(SmcGet.temp_dir) + package.spec.name
160
+
161
+ unless package.spec.valid?
162
+ raise(Errors::InvalidSpecification, package.spec.validate.first)
163
+ end
164
+
165
+ package.spec.save(@specs_dir)
166
+
167
+ FileUtils.cp_r(path.join(Package::LEVELS_DIR).children, @levels_dir)
168
+ FileUtils.cp_r(path.join(Package::MUSIC_DIR).children, @music_dir)
169
+ FileUtils.cp_r(path.join(Package::GRAPHICS_DIR).children, @graphics_dir)
170
+ FileUtils.cp_r(path.join(Package::SOUNDS_DIR).children, @sounds_dir)
171
+ FileUtils.cp_r(path.join(Package::WORLDS_DIR).children, @worlds_dir)
172
+
173
+ FileUtils.cp(package.path, @cache_dir)
174
+
175
+ @package_specs << package.spec #This package is now installed and therefore the spec must be in that array
176
+ end
177
+
178
+ #call-seq:
179
+ # uninstall(pkg_name)
180
+ # uninstall(pkg_name){|path| ...}
181
+ #
182
+ #Uninstalls a package by removing all files it owns. This method checks the
183
+ #checksums specified in the respective package specifications and if it
184
+ #detects a user-modified file, the given block is invoked. If the block
185
+ #evaluates to a truth value, the modified file is copied to a file
186
+ #in the same directory as the original with ".MODIFIED" just before the
187
+ #file extension. The blockless form always discards all modified files.
188
+ #==Parameters
189
+ #[pkg_name] The name of the package (without the .smcpak extension) to remove.
190
+ #[full_path] *Blockargument*. The full Pathname of a file that has been modified.
191
+ #==Example
192
+ # # Delete a package and save all modified files
193
+ # rr.uninstall("cool-world"){|file| true}
194
+ # # Delete a package and discard all modified files
195
+ # rr.uninstall("cool-world")
196
+ def uninstall(pkg_name)
197
+ spec = @package_specs.find{|spec| spec.name == pkg_name}
198
+
199
+ [:levels, :music, :sounds, :graphics].each do |sym|
200
+ contrib_dir = @path + self.class.const_get(:"CONTRIB_#{sym.upcase}_DIR")
201
+
202
+ #Delete all the files
203
+ files = spec[sym]
204
+ files.each do |filename|
205
+ full_path = contrib_dir + filename
206
+ #Check if the file was modified
207
+ if block_given? and Digest::SHA1.hexdigest(File.read(full_path)) != spec[:checksums][sym.to_s][filename] #to_s as the keys are strings there, see PackageSpecification.from_file
208
+ if yield(full_path) #Getting a truth value from the block means copying
209
+ FileUtils.cp(full_path, full_path.parent + filename.sub(/\.(.*?)$/, '.MODIFIED.\1'))
210
+ end
211
+ end
212
+ File.delete(full_path)
213
+ end
214
+
215
+ #Delete now empty directories
216
+ loop do
217
+ empty_dirs = []
218
+ contrib_dir.find do |path|
219
+ next if path == contrib_dir #We surely don’t want to delete the toplevel dir.
220
+ empty_dirs << path if path.directory? and path.children.empty?
221
+ end
222
+ #If no empty directories are present anymore, break out of the loop.
223
+ break if empty_dirs.empty?
224
+ #Otherwise delete the empty directories and redo the process, because
225
+ #the parent directories could be empty now.
226
+ empty_dirs.each{|path| FileUtils.rmdir(path)}
227
+ end
228
+ end
229
+
230
+ #Delete worlds as well. Worlds can’t reside in subdirectories, therefore it’s unnecessary
231
+ #to check for the empty-directory thing.
232
+ spec[:worlds].each do |dirname|
233
+ full_path = @worlds_dir + dirname
234
+ #Check if any of the world’s files has been modified
235
+ ["description.xml", "layer.xml", "world.xml"].each do |wfile|
236
+ full_wfile_path = full_path + wfile
237
+ if block_given? and Digest::SHA1.hexdigest(File.read(full_wfile_path)) != spec[:checksums]["worlds"][dirname][wfile] #"worlds" is a string for technical reasons, see PackageSpecification.from_file
238
+ if yield(full_wfile_path) #Getting a truth value from the block means copying
239
+ FileUtils.cp_r(full_path, full_path.parent + "#{dirname}.MODIFIED")
240
+ break #Break from the inner iteration, we just need to copy once
241
+ end
242
+ end
243
+ end
244
+ FileUtils.rm_r(full_path)
245
+ end
246
+
247
+ File.delete(@specs_dir + spec.spec_file_name) #Remove the spec itself
248
+ @package_specs.delete(spec) #Otherwise we have a stale package in the array
249
+ end
250
+
251
+ #Returns the path this repository refers to.
252
+ def to_s
253
+ @path.to_s
254
+ end
255
+
256
+ def contain?(pkg)
257
+ if pkg.kind_of? Package
258
+ @package_specs.include?(pkg.spec)
259
+ else
260
+ @package_specs.any?{|spec| spec.name == pkg}
261
+ end
262
+ end
263
+ alias contains? contain?
264
+
265
+ def search(regexp, *attributes)
266
+ attributes << :name if attributes.empty? #Default value
267
+
268
+ @package_specs.each do |spec|
269
+ attributes.each do |att|
270
+ case att
271
+ when :name then yield(spec.name) if spec.name =~ regexp
272
+ when :title then yield(spec.name) if spec.title =~ regexp
273
+ when :authors then yield(spec.name) if spec.authors.any?{|a| a =~ regexp}
274
+ when :difficulty then yield(spec.name) if spec.difficulty =~ regexp
275
+ when :description then yield(spec.name) if spec.description =~ regexp
276
+ when :levels then yield(spec.name) if spec.levels.any?{|l| l =~ regexp}
277
+ when :music then yield(spec.name) if spec.music.any?{|m| m =~ regexp}
278
+ when :sounds then yield(spec.name) if spec.sound.any?{|s| s =~ regexp}
279
+ when :graphics then yield(spec.name) if spec.graphics.any?{|g| g =~ regexp}
280
+ when :worlds then yield(spec.name) if spec.worlds.any?{|w| w =~ regexp}
281
+ else
282
+ $stderr.puts("Warning: Unknown attribute #{att}, ignoring it.")
283
+ end #case
284
+ end #attributes.each
285
+ end # @package_specs.each
286
+ end #search
287
+
288
+ end #LocalRepository
289
+
290
+ end