smc-get 0.1.0 → 0.2.0.beta1
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.
- data/COPYING +674 -674
- data/HISTORY.rdoc +31 -0
- data/README.rdoc +163 -148
- data/VERSION.txt +1 -1
- data/bin/smc-checksum +44 -0
- data/bin/smc-get +31 -31
- data/config/smc-get.yml +32 -32
- data/lib/smc_get.rb +23 -21
- data/lib/smc_get/cui.rb +325 -289
- data/lib/smc_get/cui_commands/build.rb +329 -0
- data/lib/smc_get/cui_commands/command.rb +117 -81
- data/lib/smc_get/cui_commands/getinfo.rb +92 -91
- data/lib/smc_get/cui_commands/help.rb +65 -64
- data/lib/smc_get/cui_commands/install.rb +101 -91
- data/lib/smc_get/cui_commands/list.rb +101 -80
- data/lib/smc_get/cui_commands/search.rb +106 -109
- data/lib/smc_get/cui_commands/uninstall.rb +80 -78
- data/lib/smc_get/cui_commands/update.rb +119 -0
- data/lib/smc_get/cui_commands/version.rb +58 -57
- data/lib/smc_get/errors.rb +140 -113
- data/lib/smc_get/gui.rb +20 -19
- data/lib/smc_get/local_repository.rb +277 -0
- data/lib/smc_get/package.rb +260 -279
- data/lib/smc_get/package_archive.rb +80 -0
- data/lib/smc_get/package_specification.rb +253 -0
- data/lib/smc_get/remote_repository.rb +272 -0
- data/lib/smc_get/repository.rb +10 -0
- data/lib/smc_get/smc_get.rb +117 -139
- data/smcpak.rdoc +332 -0
- data/test/test_smc-get-cui.rb +123 -121
- data/test/test_smc-get-lib.rb +172 -170
- metadata +71 -36
data/lib/smc_get/gui.rb
CHANGED
@@ -1,19 +1,20 @@
|
|
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
|
-
################################################################################
|
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
|
+
# vim:set ts=8 sts=2 sw=2 et: #
|
@@ -0,0 +1,277 @@
|
|
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
|
data/lib/smc_get/package.rb
CHANGED
@@ -1,279 +1,260 @@
|
|
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
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#a
|
27
|
-
#
|
28
|
-
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
#
|
116
|
-
|
117
|
-
#
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
#
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
end
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
#
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
#
|
240
|
-
#
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
else
|
262
|
-
raise(Errors::NoSuchPackageError, "ERROR: Package not installed locally: #@name.")
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
#Returns the name of the package.
|
267
|
-
def to_s
|
268
|
-
@name
|
269
|
-
end
|
270
|
-
|
271
|
-
#Human-readable description of form
|
272
|
-
# #<SmcGet::Package package_name (installation_status)>
|
273
|
-
def inspect
|
274
|
-
"#<#{self.class} #{@name} (#{installed? ? 'installed' : 'not installed'})>"
|
275
|
-
end
|
276
|
-
|
277
|
-
end
|
278
|
-
|
279
|
-
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
|
+
module SmcGet
|
21
|
+
|
22
|
+
#Packages are the main objects you will have to deal with. An instance of
|
23
|
+
#class Package encapsulates all known information from a smc package file
|
24
|
+
#(these files are described in the smcpak.rdoc file), and the most important
|
25
|
+
#attribute of this class is +spec+, which is your direct interface to the
|
26
|
+
#package’s specification by providing you with a fitting instance of class
|
27
|
+
#PackageSpecification.
|
28
|
+
#
|
29
|
+
#Apart from inspecting existing packages, you can also use the Package class
|
30
|
+
#to create new pacakges by calling the ::create method (inspecting a given
|
31
|
+
#package file is possible with ::from_file). ::create expects a path to
|
32
|
+
#the directoy which you want to compress into a new SMC package, validates
|
33
|
+
#it against the packaging guidelines and will then either fail or do
|
34
|
+
#the actual compression, outputting an instance of class Package that
|
35
|
+
#(you guessed it) describes the newly created SMC package.
|
36
|
+
#
|
37
|
+
#==Example of building a SMC package
|
38
|
+
#The following is an example that shows you how to build a basic
|
39
|
+
#SMC package by use of the ::create method.
|
40
|
+
#
|
41
|
+
#First you have to decide how you want to name your package. If you
|
42
|
+
#have decided (remember: This is a decision for life!), create a
|
43
|
+
#directory named after the package, note that it isn’t allowed to
|
44
|
+
#contain whitespace.
|
45
|
+
#
|
46
|
+
#Inside the directory, say you named it +cool+, create the following
|
47
|
+
#structure:
|
48
|
+
#
|
49
|
+
# cool/
|
50
|
+
# - cool.yml
|
51
|
+
# - README.txt
|
52
|
+
# levels/
|
53
|
+
# music/
|
54
|
+
# pixmaps/
|
55
|
+
# sounds/
|
56
|
+
# worlds/
|
57
|
+
#
|
58
|
+
#If your package doesn’t contain a specific component, e.g. sounds, you
|
59
|
+
#may ommit the corresponding directory.
|
60
|
+
#
|
61
|
+
#Then add all the levels you want to include in the package to the +levels+
|
62
|
+
#subdirectory, e.g. if you have a level named "awesome_1" and one named
|
63
|
+
#"awesome_2", copy them from your personal SMC directory (usually
|
64
|
+
#<b>~/.smc/levels</b>) so that your structure looks like this:
|
65
|
+
#
|
66
|
+
# cool/
|
67
|
+
# - cool.yml
|
68
|
+
# - README.txt
|
69
|
+
# levels/
|
70
|
+
# - awesome_1.smclvl
|
71
|
+
# - awesome_2.smclvl
|
72
|
+
# music/
|
73
|
+
# pixmaps/
|
74
|
+
# sounds/
|
75
|
+
# worlds/
|
76
|
+
#
|
77
|
+
#Now the most important step. Open up *cool.yml* in your favourite text
|
78
|
+
#editor and write the package specification. You can read about the exact
|
79
|
+
#format with all available options in the smcpak.rdoc file,but for now
|
80
|
+
#just write the following:
|
81
|
+
#
|
82
|
+
# ---
|
83
|
+
# title: Cool levels
|
84
|
+
# last_update: 01-01-2011 04:07:00Z
|
85
|
+
# levels:
|
86
|
+
# - awesome_1.smclvl
|
87
|
+
# - awesome_2.smclvl
|
88
|
+
# authors:
|
89
|
+
# - Your Name Here
|
90
|
+
# difficulty: medium
|
91
|
+
# description: |
|
92
|
+
# Here goes your description
|
93
|
+
# which may span multiple lines.
|
94
|
+
#
|
95
|
+
#Of course put something appropriate into the +last_update+ field
|
96
|
+
#(the format is DD-MM-YYYY hh:mm:ssZ, time zone is UTC).
|
97
|
+
#
|
98
|
+
#After you wrote something into your README.txt (whatever it is),
|
99
|
+
#you *could* build the package the easy way with
|
100
|
+
# $ cd /path/to/dir/above/cool
|
101
|
+
# $ smc-get build cool
|
102
|
+
#on the commandline. But I promised you to show the use of
|
103
|
+
#Package.create, so instead do:
|
104
|
+
# $ cd /path/to/dir/above/cool
|
105
|
+
# $ ruby -Ipath/to/smcget/lib -rsmc_get -e 'SmcGet::Package.create("cool")'
|
106
|
+
#Either way, you should now end up with a file called *cool.smcpak* in the
|
107
|
+
#parent directory of <b>cool/</b>.
|
108
|
+
class Package
|
109
|
+
|
110
|
+
#A package name is considered valid if it matches this Regular
|
111
|
+
#expression.
|
112
|
+
VALID_PKG_NAME = /^[a-zA-Z_\-0-9]+$/
|
113
|
+
#Name of the directory the levels reside in the package.
|
114
|
+
LEVELS_DIR = "levels"
|
115
|
+
#Name of the directory the music resides in the package.
|
116
|
+
MUSIC_DIR = "music"
|
117
|
+
#Name of the directory the sounds reside in the package.
|
118
|
+
SOUNDS_DIR = "sounds"
|
119
|
+
#Name of the dierctory the graphics reside in the pacakge.
|
120
|
+
GRAPHICS_DIR = "pixmaps"
|
121
|
+
#Name of the directory the worlds reside in the package.
|
122
|
+
WORLDS_DIR = "worlds"
|
123
|
+
|
124
|
+
#The PackageSpecification of this package.
|
125
|
+
attr_reader :spec
|
126
|
+
#The Pathname of the .smcpak file.
|
127
|
+
attr_reader :path
|
128
|
+
|
129
|
+
class << self
|
130
|
+
|
131
|
+
#Creates a new Package from a local .smcpak file.
|
132
|
+
#==Parameter
|
133
|
+
#[file] The path to the SMC package.
|
134
|
+
#==Return value
|
135
|
+
#An instance of class Package.
|
136
|
+
#==Example
|
137
|
+
# pkg = SmcGet::Package.from_file("/home/freak/mycoolpackage.smcpak")
|
138
|
+
#==Remarks
|
139
|
+
#As this needs to decompress the package temporarily, this method
|
140
|
+
#may take some time to complete.
|
141
|
+
def from_file(file)
|
142
|
+
pkg_name = File.basename(file).sub(/\.smcpak$/, "")
|
143
|
+
#No spec file is provided, we therefore need to extract it from
|
144
|
+
#the archive.
|
145
|
+
path = PackageArchive.new(file).decompress(SmcGet.temp_dir) + pkg_name + "#{pkg_name}.yml"
|
146
|
+
new(path, file)
|
147
|
+
end
|
148
|
+
|
149
|
+
#Validates +directory+ against the packaging guidelines and compresses it
|
150
|
+
#into a .smcpak file.
|
151
|
+
#==Parameter
|
152
|
+
#[directory] The path to the directory you want to compress.
|
153
|
+
#==Return value
|
154
|
+
#An instance of this class describing the newly created package.
|
155
|
+
#==Example
|
156
|
+
# pkg = SmcGet::Package.create("/home/freak/mycoollevels")
|
157
|
+
#==Remarks
|
158
|
+
#The .smcpak file is placed in the parent
|
159
|
+
#directory of +directory+, you should therefore ensure you have write
|
160
|
+
#permissions for it.
|
161
|
+
def create(directory)
|
162
|
+
#0. Determine the names of the important files
|
163
|
+
directory = Pathname.new(directory)
|
164
|
+
pkg_name = directory.basename.to_s
|
165
|
+
spec_file = directory + "#{pkg_name}.yml"
|
166
|
+
readme = directory + "README.txt"
|
167
|
+
smcpak_file = directory.parent + "#{pkg_name}.smcpak"
|
168
|
+
|
169
|
+
#1. Validate the package name
|
170
|
+
raise(Errors::BrokenPackageError, "Invalid package name!") unless pkg_name =~ VALID_PKG_NAME
|
171
|
+
|
172
|
+
#2. Validate the package spec
|
173
|
+
spec = PackageSpecification.from_file(spec_file) #Raises if necessary
|
174
|
+
|
175
|
+
#3. Validate the rest of the structure
|
176
|
+
$stderr.puts("Warning: No README.txt found.") unless readme.file?
|
177
|
+
$stderr.puts("Warning: No levels found.") if spec.levels.empty?
|
178
|
+
|
179
|
+
#4. Compress the whole thing
|
180
|
+
#The process is as follows: A temporary directory is created, in which
|
181
|
+
#a subdirectory that is named after the package is created. The
|
182
|
+
#spec, the README and the levels, music, etc. are then copied into
|
183
|
+
#that subdirectory which in turn is then compressed. The resulting
|
184
|
+
#.smcpak file is copied back to the original directory’s parent dir.
|
185
|
+
#After that, the mktmpdir block ends and deletes the temporary
|
186
|
+
#directory.
|
187
|
+
path = Dir.mktmpdir("smc-get-create-#{pkg_name}") do |tmpdir|
|
188
|
+
goal_dir = Pathname.new(tmpdir) + pkg_name
|
189
|
+
goal_dir.mkdir
|
190
|
+
|
191
|
+
FileUtils.cp(spec_file, goal_dir)
|
192
|
+
FileUtils.cp(readme, goal_dir) if readme.file? #Optional
|
193
|
+
[:levels, :graphics, :music, :sounds, :worlds].each do |sym|
|
194
|
+
#4.1. Create the group’s subdir
|
195
|
+
dirname = const_get(:"#{sym.upcase}_DIR")
|
196
|
+
goal_group_dir = goal_dir + dirname
|
197
|
+
goal_group_dir.mkdir
|
198
|
+
#4.2. Copy all the group’s files over to it
|
199
|
+
spec[sym].each do |filename|
|
200
|
+
FileUtils.cp(directory + dirname + filename, goal_group_dir)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
#4.3. actual compression
|
204
|
+
PackageArchive.compress(goal_dir, smcpak_file).path
|
205
|
+
end
|
206
|
+
#5. Return a new instance of Package
|
207
|
+
from_file(path)
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
|
212
|
+
#Creates a new Package from the given specification file. You shouldn’t
|
213
|
+
#use this method directly, because you would duplicate the
|
214
|
+
#work the ::from_file method already does for you.
|
215
|
+
#==Parameters
|
216
|
+
#[spec_file] The path to the YAML specification file.
|
217
|
+
#[pkg_location] The path to the SMC package.
|
218
|
+
#==Return value
|
219
|
+
#The newly created Package instance.
|
220
|
+
#==Example
|
221
|
+
# pkg = SmcGet::Package.new("/home/freak/cool.yml", "/home/freak/cool.smcpak")
|
222
|
+
def initialize(spec_file, pkg_location)
|
223
|
+
@spec = PackageSpecification.from_file(spec_file)
|
224
|
+
@path = pkg_location
|
225
|
+
end
|
226
|
+
|
227
|
+
#Decompresses this package.
|
228
|
+
#==Parameter
|
229
|
+
#[directory] Where to extract the SMC package to. A subdirectory named after
|
230
|
+
# the package is automatically created in this directory.
|
231
|
+
#==Return value
|
232
|
+
#The Pathname to the created subdirectory.
|
233
|
+
#==Example
|
234
|
+
# pkg.decompress(".") #=> /home/freak/cool
|
235
|
+
def decompress(directory)
|
236
|
+
PackageArchive.new(@path).decompress(directory)
|
237
|
+
end
|
238
|
+
|
239
|
+
#Compares two packages. They’re considered equal if their package
|
240
|
+
#specifications are equal. See PackageSpecification#==.
|
241
|
+
def ==(other)
|
242
|
+
return false unless other.respond_to? :spec
|
243
|
+
@spec == other.spec
|
244
|
+
end
|
245
|
+
|
246
|
+
#Shorthand for:
|
247
|
+
# pkg.spec.name
|
248
|
+
def to_s
|
249
|
+
@spec.name
|
250
|
+
end
|
251
|
+
|
252
|
+
#Human-readabe description of form
|
253
|
+
# #<SmcGet::Package <package name>>
|
254
|
+
def inspect
|
255
|
+
"#<#{self.class} #{@spec.name}>"
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
259
|
+
|
260
|
+
end
|