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.
- data/COPYING +674 -674
- data/HISTORY.rdoc +53 -31
- data/README.rdoc +162 -162
- data/VERSION.txt +1 -1
- data/bin/smc-checksum +44 -44
- data/bin/smc-checksum~ +2 -0
- data/bin/smc-get +31 -31
- data/bin/smc-repo-conv +205 -0
- data/bin/smc-repo-conv~ +155 -0
- data/config/smc-get.yml +32 -32
- data/config/smc-get.yml~ +32 -0
- data/lib/smc_get.rb +22 -22
- data/lib/smc_get/cui.rb +325 -325
- data/lib/smc_get/cui_commands/build.rb +297 -329
- data/lib/smc_get/cui_commands/command.rb +181 -117
- data/lib/smc_get/cui_commands/getinfo.rb +91 -91
- data/lib/smc_get/cui_commands/help.rb +64 -64
- data/lib/smc_get/cui_commands/install.rb +101 -101
- data/lib/smc_get/cui_commands/list.rb +101 -101
- data/lib/smc_get/cui_commands/search.rb +106 -106
- data/lib/smc_get/cui_commands/server.rb +76 -0
- data/lib/smc_get/cui_commands/uninstall.rb +77 -80
- data/lib/smc_get/cui_commands/update.rb +107 -119
- data/lib/smc_get/cui_commands/version.rb +57 -57
- data/lib/smc_get/errors.rb +166 -140
- data/lib/smc_get/gui.rb +19 -19
- data/lib/smc_get/local_repository.rb +290 -277
- data/lib/smc_get/package.rb +240 -260
- data/lib/smc_get/package_archive.rb +186 -80
- data/lib/smc_get/package_specification.rb +257 -253
- data/lib/smc_get/remote_repository.rb +272 -272
- data/lib/smc_get/repository.rb +9 -9
- data/lib/smc_get/smc_get.rb +118 -117
- data/smcpak.rdoc +331 -331
- data/test/test_smc-get-cui.rb +122 -122
- data/test/test_smc-get-lib.rb +171 -171
- metadata +25 -22
@@ -1,80 +1,186 @@
|
|
1
|
-
#
|
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
|
-
#This class allows for easy compression and decompression of .smcpak
|
24
|
-
#files. Please note that during this process no validations are performed,
|
25
|
-
#i.e. you have to ensure that the directory you want to compress is in
|
26
|
-
#smc package layout and the .smcpak files you want to decompress are
|
27
|
-
#really smc packages.
|
28
|
-
class PackageArchive
|
29
|
-
|
30
|
-
#
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
#
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
directory
|
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
|
-
|
1
|
+
# -*- coding: 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
|
+
#This class allows for easy compression and decompression of .smcpak
|
24
|
+
#files. Please note that during this process no validations are performed,
|
25
|
+
#i.e. you have to ensure that the directory you want to compress is in
|
26
|
+
#smc package layout and the .smcpak files you want to decompress are
|
27
|
+
#really smc packages.
|
28
|
+
class PackageArchive
|
29
|
+
|
30
|
+
#Number of bytes to read from a file at a time when it’s being
|
31
|
+
#compressed.
|
32
|
+
CHUNK_SIZE = 4096
|
33
|
+
|
34
|
+
#The location of this archive. A Pathname object.
|
35
|
+
attr_reader :path
|
36
|
+
|
37
|
+
class << self
|
38
|
+
|
39
|
+
#Compresses all files in +directory+ into a TAR.XZ file with the
|
40
|
+
#extension ".smcpak".
|
41
|
+
#==Parameters
|
42
|
+
#[directory] The directory to compress. This will be the toplevel
|
43
|
+
# directory in the resulting TAR file.
|
44
|
+
#[goal_file] Full path to where to place the TAR file.
|
45
|
+
#==Return value
|
46
|
+
#Returns an object of this class.
|
47
|
+
#==Examples
|
48
|
+
# smcpak = PackageArchive.compress("/home/freak/packages/mypackage", "mypackage.smcpak")
|
49
|
+
# puts smcpak.path #=> mypackage.smcpak
|
50
|
+
#
|
51
|
+
# smcpak = PackageArchive.compress("mypackage", "/home/freak/compressed_packages/mypackage.smcpak")
|
52
|
+
# puts smcpak.path #=> /home/freak/compressed_packages/mypackage.smcpak
|
53
|
+
def compress(directory, goal_file)
|
54
|
+
directory = Pathname.new(directory).expand_path
|
55
|
+
tar_file = Pathname.new("#{goal_file}.tar").expand_path
|
56
|
+
xz_file = Pathname.new(goal_file).expand_path
|
57
|
+
|
58
|
+
tar_file.open("wb"){|file| compress_dir(directory, file)}
|
59
|
+
XZ.compress_file(tar_file, xz_file)
|
60
|
+
|
61
|
+
tar_file.delete #We don’t need it anymore
|
62
|
+
|
63
|
+
new(xz_file)
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
#This method creates a TAR archive inside the given +io+ by
|
69
|
+
#recursively packaging all files found in +path+ into it.
|
70
|
+
#
|
71
|
+
#Note that this method isn’t quite the same as <tt>Minitar.pack</tt>,
|
72
|
+
#because there is a big difference in how absolute paths are handled.
|
73
|
+
#Consider the following call:
|
74
|
+
#
|
75
|
+
# Minitar.pack("/home/freak/foo", file)
|
76
|
+
#
|
77
|
+
#Inside the TAR you’ll get this:
|
78
|
+
#
|
79
|
+
# /
|
80
|
+
# home/
|
81
|
+
# freak/
|
82
|
+
# foo/
|
83
|
+
# 1.txt
|
84
|
+
# 2.rb
|
85
|
+
#
|
86
|
+
#Surely not what you wanted, plus a leading / that can be misinterpreted
|
87
|
+
#by some archiving programs (e.g. XFCE’s XArchiver). Now compare that
|
88
|
+
#to what this method does:
|
89
|
+
#
|
90
|
+
# compress_dir("/home/freak/foo", file)
|
91
|
+
#
|
92
|
+
#Result in the TAR:
|
93
|
+
#
|
94
|
+
# foo/
|
95
|
+
# 1.txt
|
96
|
+
# 2.txt
|
97
|
+
#
|
98
|
+
#It strippes off the unwanted prefix and just places the actual contents of
|
99
|
+
#the last element in +path+ (and it’s subdirectories) inside the TAR
|
100
|
+
#archive. For a +path+ relative to the current working directory however
|
101
|
+
#this method should behave the same as the usual <tt>Minitar.pack</tt>.
|
102
|
+
#
|
103
|
+
#Extra bonus: This method is threadsafe, as it doesn’t use +chdir+ to
|
104
|
+
#change the directory representation in the TAR file. It uses Minitar’s
|
105
|
+
#lowlevel methods instead.
|
106
|
+
def compress_dir(path, io)
|
107
|
+
raise(ArgumentError, "#{path} is not a directory!") unless path.directory?
|
108
|
+
|
109
|
+
Archive::Tar::Minitar::Output.open(io) do |output|
|
110
|
+
tar = output.tar #Get the real Writer object
|
111
|
+
|
112
|
+
path.find do |entry|
|
113
|
+
#This is the path as it will show up inside the tar
|
114
|
+
relative_path = entry.relative_path_from(path.parent) #parent b/c first entry must be the toplevel dirname, not "."
|
115
|
+
|
116
|
+
#Copy permissions from the original file for the tar’ed file
|
117
|
+
stat = entry.stat
|
118
|
+
stats = {
|
119
|
+
:mode => stat.mode,
|
120
|
+
:mtime => stat.mtime,
|
121
|
+
:size => stat.size,
|
122
|
+
:uid => stat.uid, #Should be nil on Windows
|
123
|
+
:gid => stat.gid #Should be nil on Windows
|
124
|
+
}
|
125
|
+
|
126
|
+
if entry.directory?
|
127
|
+
tar.mkdir(relative_path.to_s, stats)
|
128
|
+
elsif entry.file?
|
129
|
+
tar.add_file_simple(relative_path.to_s, stats) do |stream|
|
130
|
+
File.open(entry, "rb") do |file|
|
131
|
+
chunk = nil
|
132
|
+
stream.write(chunk) while chunk = file.read(CHUNK_SIZE)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
else
|
136
|
+
raise(Errors::CompressionError.new("Unsupported file type for #{entry}!", entry))
|
137
|
+
end #if
|
138
|
+
end #find
|
139
|
+
end #Output.new
|
140
|
+
end #compress_dir
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
#Creates a new PackageArchive from an existing .smcpak file.
|
145
|
+
#==Parameter
|
146
|
+
#[archive] The path to the file.
|
147
|
+
#==Return value
|
148
|
+
#A new instance of this class.
|
149
|
+
#==Example
|
150
|
+
# archive = PackageArchive.new("/home/freak/compressed_packages/mypackage.smcpak")
|
151
|
+
def initialize(archive)
|
152
|
+
@path = Pathname.new(archive)
|
153
|
+
end
|
154
|
+
|
155
|
+
#Decompresses this archive, creating a subdirectory
|
156
|
+
#named after the archive without the extension.
|
157
|
+
#==Parameters
|
158
|
+
#[directory] Where to extract the archive to. Note a subdirectory
|
159
|
+
# will be created below this path.
|
160
|
+
#==Return value
|
161
|
+
#The path to the subdirectory, a Pathname object.
|
162
|
+
#==Example
|
163
|
+
# smcpak = PackageArchive.new("mypackage.smcpak")
|
164
|
+
# puts smcpak.decompress #=> #<Pathname:mypackage>
|
165
|
+
def decompress(directory)
|
166
|
+
directory = Pathname.new(directory)
|
167
|
+
tar_file = directory + @path.basename.to_s.sub(/\.smcpak$/, ".tar")
|
168
|
+
dest_dir = directory + tar_file.basename.to_s.sub(/\.tar$/, "")
|
169
|
+
|
170
|
+
XZ.decompress_file(@path.to_s, tar_file.to_s)
|
171
|
+
Archive::Tar::Minitar.unpack(tar_file.to_s, dest_dir.to_s)
|
172
|
+
|
173
|
+
tar_file.delete #We don’t need it anymore
|
174
|
+
|
175
|
+
dest_dir
|
176
|
+
end
|
177
|
+
|
178
|
+
#Human-readable description of form:
|
179
|
+
# #<SmcGet::PackageArchive <path>>
|
180
|
+
def inspect
|
181
|
+
"#<#{self.class} #{@path.expand_path}>"
|
182
|
+
end
|
183
|
+
|
184
|
+
end
|
185
|
+
|
186
|
+
end
|
@@ -1,253 +1,257 @@
|
|
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 PackageSpecification object is a mostly informational object
|
24
|
-
#directly related to Package objects. That is, each and every
|
25
|
-
#Package instance has a Package#spec method that will retrieve
|
26
|
-
#the PackageSpecification, an instance of this class, for you:
|
27
|
-
#
|
28
|
-
# puts pkg.spec.title #=> Cool package
|
29
|
-
#
|
30
|
-
#Instances of this class can be understood as the parsed content of
|
31
|
-
#a package’s specification file. It has getter and setter methods
|
32
|
-
#reflecting the keys that are allowed in the specification (see
|
33
|
-
#the smcpak.rdoc file), but unless you want to build smc packages,
|
34
|
-
#there’s no need for you to use the setter methods.
|
35
|
-
class PackageSpecification
|
36
|
-
|
37
|
-
#The keys listed here must be mentioned inside a package spec,
|
38
|
-
#otherwise the package is considered broken.
|
39
|
-
SPEC_MANDATORY_KEYS = [:title, :last_update, :authors, :difficulty, :description, :checksums].freeze
|
40
|
-
|
41
|
-
##
|
42
|
-
# :attr_accessor: title
|
43
|
-
#The package’s title.
|
44
|
-
|
45
|
-
##
|
46
|
-
# :attr_accessor: last_update
|
47
|
-
#The time (an instance of class Time) indicating when the package
|
48
|
-
#was last updated.
|
49
|
-
|
50
|
-
##
|
51
|
-
# :attr_accessor: authors
|
52
|
-
#The authors of this package. An array.
|
53
|
-
|
54
|
-
##
|
55
|
-
# :attr_accessor: difficulty
|
56
|
-
#The difficulty of this package as a string.
|
57
|
-
|
58
|
-
##
|
59
|
-
# :attr_accessor: description
|
60
|
-
#The description of this package.
|
61
|
-
|
62
|
-
##
|
63
|
-
# :attr_accessor: install_message
|
64
|
-
#A message to display during installation of this package or nil if
|
65
|
-
#no message shall be displayed.
|
66
|
-
|
67
|
-
##
|
68
|
-
# :attr_accessor: remove_message
|
69
|
-
#A message to display during removing of this package or nil if no
|
70
|
-
#message shall be displayed.
|
71
|
-
|
72
|
-
##
|
73
|
-
# :attr_accessor: dependecies
|
74
|
-
#An array of package names this package depends on, i.e. packages that
|
75
|
-
#need to be installed before this one can be installed. The array is
|
76
|
-
#empty if no dependecies exist.
|
77
|
-
|
78
|
-
##
|
79
|
-
# :attr_accessor: levels
|
80
|
-
#An array of level file names (strings).
|
81
|
-
|
82
|
-
##
|
83
|
-
# :attr_accessor: music
|
84
|
-
#An array of music file names (strings).
|
85
|
-
|
86
|
-
##
|
87
|
-
# :attr_accessor: sounds
|
88
|
-
#An array of sound file names (strings).
|
89
|
-
|
90
|
-
##
|
91
|
-
# :attr_accessor: graphics
|
92
|
-
#An array of graphic file names (strings).
|
93
|
-
|
94
|
-
##
|
95
|
-
# :attr_accessor: worlds
|
96
|
-
#An array of graphic file names (strings).
|
97
|
-
|
98
|
-
##
|
99
|
-
# :attr_accessor: checksums
|
100
|
-
#A hash that maps each filename in this package to it’s SHA1 checksum.
|
101
|
-
|
102
|
-
#The name of the package this specification is used in, without any
|
103
|
-
#file extension.
|
104
|
-
attr_reader :name
|
105
|
-
|
106
|
-
##
|
107
|
-
# :attr_reader: compressed_file_name
|
108
|
-
#The name of the compressed file this specification should belong to.
|
109
|
-
#The same as name, but the extension .smcpak was appended.
|
110
|
-
|
111
|
-
##
|
112
|
-
# :attr_reader: spec_file_name
|
113
|
-
#The name of the specification file. The same as name, but
|
114
|
-
#the extension .yml was appended.
|
115
|
-
|
116
|
-
#Creates a PackageSpecification by directly reading a complete
|
117
|
-
#spec from a YAML file.
|
118
|
-
#==Parameter
|
119
|
-
#[path] The path to the file to read.
|
120
|
-
#==Return value
|
121
|
-
#An instance of this class.
|
122
|
-
#==Raises
|
123
|
-
#[InvalidSpecification] +path+ was not found or was malformatted.
|
124
|
-
#==Example
|
125
|
-
# path = remote_repo.fetch_spec("cool_pkg.yml")
|
126
|
-
# spec = SmcGet::PackageSpecification.from_file(path)
|
127
|
-
# puts spec.title #=> "Cool package"
|
128
|
-
#==Remarks
|
129
|
-
#This method may be useful if you don’t need a full-blown
|
130
|
-
#Package object and just want to deal with it’s most important
|
131
|
-
#attributes.
|
132
|
-
def self.from_file(path)
|
133
|
-
info = nil
|
134
|
-
begin
|
135
|
-
info = YAML.load_file(path.to_s)
|
136
|
-
rescue Errno::ENOENT => e
|
137
|
-
raise(Errors::InvalidSpecification, "File '#{path}' doesn't exist!")
|
138
|
-
rescue => e
|
139
|
-
raise(Errors::InvalidSpecification, "Invalid YAML: #{e.message}")
|
140
|
-
end
|
141
|
-
|
142
|
-
spec = new(File.basename(path).sub(/\.yml$/, ""))
|
143
|
-
info.each_pair do |key, value|
|
144
|
-
spec.send(:"#{key}=", value)
|
145
|
-
end
|
146
|
-
#TODO: Convert the strings in :checksums to strings, except the
|
147
|
-
#filenames, those should be strings. Anyone???
|
148
|
-
|
149
|
-
raise(Errors::InvalidSpecification, spec.validate.first) unless spec.valid?
|
150
|
-
|
151
|
-
spec
|
152
|
-
end
|
153
|
-
|
154
|
-
#Returns the matching package name from the package specification’s name
|
155
|
-
#by replacing the .yml extension with .smcpak.
|
156
|
-
#==Return value
|
157
|
-
#A string ending in ".smcpak".
|
158
|
-
#==Example
|
159
|
-
# p SmcGet::PackageSpecification.spec2pkg("cool_pkg.yml") #=> "cool_pkg.smcpak")
|
160
|
-
def self.spec2pkg(spec_file_name) # :nodoc:
|
161
|
-
spec_file_name.to_s.sub(/\.yml$/, ".smcpak")
|
162
|
-
end
|
163
|
-
|
164
|
-
#Returns the matching specification file name from the package’s name
|
165
|
-
#by replacing the .smcpak extension with .yml.
|
166
|
-
#==Return value
|
167
|
-
#A string ending in ".yml"
|
168
|
-
#==Example
|
169
|
-
# p SmcGet::PackageSpecification.pkg2spec("cool_pkg.smcpak") #=> "cool_pkg.yml"
|
170
|
-
def self.pkg2spec(package_file_name) # :nodoc:
|
171
|
-
package_file_name.to_s.sub(/\.smcpak$/, ".yml")
|
172
|
-
end
|
173
|
-
|
174
|
-
def initialize(pkg_name)
|
175
|
-
@info = {:dependencies => [], :levels => [], :music => [], :sounds => [], :graphics => [], :worlds => []}
|
176
|
-
@name = pkg_name
|
177
|
-
end
|
178
|
-
|
179
|
-
#See attribute.
|
180
|
-
def compressed_file_name # :nodoc:
|
181
|
-
"#@name.smcpak"
|
182
|
-
end
|
183
|
-
|
184
|
-
#See attribute.
|
185
|
-
def spec_file_name # :nodoc:
|
186
|
-
"#@name.yml"
|
187
|
-
end
|
188
|
-
|
189
|
-
[:title, :last_update, :authors, :difficulty, :description, :install_message, :remove_message, :dependencies, :levels, :music, :sounds, :graphics, :worlds, :checksums].each do |sym|
|
190
|
-
define_method(sym){@info[sym]}
|
191
|
-
define_method(:"#{sym}="){|val| @info[sym] = val}
|
192
|
-
end
|
193
|
-
|
194
|
-
def [](sym)
|
195
|
-
if respond_to?(sym)
|
196
|
-
send(sym)
|
197
|
-
else
|
198
|
-
raise(IndexError, "No such specification key: #{sym}!")
|
199
|
-
end
|
200
|
-
end
|
201
|
-
|
202
|
-
def valid?
|
203
|
-
validate.empty?
|
204
|
-
end
|
205
|
-
|
206
|
-
def validate
|
207
|
-
errors = []
|
208
|
-
|
209
|
-
SPEC_MANDATORY_KEYS.each do |sym|
|
210
|
-
errors << "Mandatory key #{sym} is missing!" unless @info.has_key?(sym)
|
211
|
-
end
|
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
|
-
path.
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
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 PackageSpecification object is a mostly informational object
|
24
|
+
#directly related to Package objects. That is, each and every
|
25
|
+
#Package instance has a Package#spec method that will retrieve
|
26
|
+
#the PackageSpecification, an instance of this class, for you:
|
27
|
+
#
|
28
|
+
# puts pkg.spec.title #=> Cool package
|
29
|
+
#
|
30
|
+
#Instances of this class can be understood as the parsed content of
|
31
|
+
#a package’s specification file. It has getter and setter methods
|
32
|
+
#reflecting the keys that are allowed in the specification (see
|
33
|
+
#the smcpak.rdoc file), but unless you want to build smc packages,
|
34
|
+
#there’s no need for you to use the setter methods.
|
35
|
+
class PackageSpecification
|
36
|
+
|
37
|
+
#The keys listed here must be mentioned inside a package spec,
|
38
|
+
#otherwise the package is considered broken.
|
39
|
+
SPEC_MANDATORY_KEYS = [:title, :last_update, :authors, :difficulty, :description, :checksums].freeze
|
40
|
+
|
41
|
+
##
|
42
|
+
# :attr_accessor: title
|
43
|
+
#The package’s title.
|
44
|
+
|
45
|
+
##
|
46
|
+
# :attr_accessor: last_update
|
47
|
+
#The time (an instance of class Time) indicating when the package
|
48
|
+
#was last updated.
|
49
|
+
|
50
|
+
##
|
51
|
+
# :attr_accessor: authors
|
52
|
+
#The authors of this package. An array.
|
53
|
+
|
54
|
+
##
|
55
|
+
# :attr_accessor: difficulty
|
56
|
+
#The difficulty of this package as a string.
|
57
|
+
|
58
|
+
##
|
59
|
+
# :attr_accessor: description
|
60
|
+
#The description of this package.
|
61
|
+
|
62
|
+
##
|
63
|
+
# :attr_accessor: install_message
|
64
|
+
#A message to display during installation of this package or nil if
|
65
|
+
#no message shall be displayed.
|
66
|
+
|
67
|
+
##
|
68
|
+
# :attr_accessor: remove_message
|
69
|
+
#A message to display during removing of this package or nil if no
|
70
|
+
#message shall be displayed.
|
71
|
+
|
72
|
+
##
|
73
|
+
# :attr_accessor: dependecies
|
74
|
+
#An array of package names this package depends on, i.e. packages that
|
75
|
+
#need to be installed before this one can be installed. The array is
|
76
|
+
#empty if no dependecies exist.
|
77
|
+
|
78
|
+
##
|
79
|
+
# :attr_accessor: levels
|
80
|
+
#An array of level file names (strings).
|
81
|
+
|
82
|
+
##
|
83
|
+
# :attr_accessor: music
|
84
|
+
#An array of music file names (strings).
|
85
|
+
|
86
|
+
##
|
87
|
+
# :attr_accessor: sounds
|
88
|
+
#An array of sound file names (strings).
|
89
|
+
|
90
|
+
##
|
91
|
+
# :attr_accessor: graphics
|
92
|
+
#An array of graphic file names (strings).
|
93
|
+
|
94
|
+
##
|
95
|
+
# :attr_accessor: worlds
|
96
|
+
#An array of graphic file names (strings).
|
97
|
+
|
98
|
+
##
|
99
|
+
# :attr_accessor: checksums
|
100
|
+
#A hash that maps each filename in this package to it’s SHA1 checksum.
|
101
|
+
|
102
|
+
#The name of the package this specification is used in, without any
|
103
|
+
#file extension.
|
104
|
+
attr_reader :name
|
105
|
+
|
106
|
+
##
|
107
|
+
# :attr_reader: compressed_file_name
|
108
|
+
#The name of the compressed file this specification should belong to.
|
109
|
+
#The same as name, but the extension .smcpak was appended.
|
110
|
+
|
111
|
+
##
|
112
|
+
# :attr_reader: spec_file_name
|
113
|
+
#The name of the specification file. The same as name, but
|
114
|
+
#the extension .yml was appended.
|
115
|
+
|
116
|
+
#Creates a PackageSpecification by directly reading a complete
|
117
|
+
#spec from a YAML file.
|
118
|
+
#==Parameter
|
119
|
+
#[path] The path to the file to read.
|
120
|
+
#==Return value
|
121
|
+
#An instance of this class.
|
122
|
+
#==Raises
|
123
|
+
#[InvalidSpecification] +path+ was not found or was malformatted.
|
124
|
+
#==Example
|
125
|
+
# path = remote_repo.fetch_spec("cool_pkg.yml")
|
126
|
+
# spec = SmcGet::PackageSpecification.from_file(path)
|
127
|
+
# puts spec.title #=> "Cool package"
|
128
|
+
#==Remarks
|
129
|
+
#This method may be useful if you don’t need a full-blown
|
130
|
+
#Package object and just want to deal with it’s most important
|
131
|
+
#attributes.
|
132
|
+
def self.from_file(path)
|
133
|
+
info = nil
|
134
|
+
begin
|
135
|
+
info = YAML.load_file(path.to_s)
|
136
|
+
rescue Errno::ENOENT => e
|
137
|
+
raise(Errors::InvalidSpecification, "File '#{path}' doesn't exist!")
|
138
|
+
rescue => e
|
139
|
+
raise(Errors::InvalidSpecification, "Invalid YAML: #{e.message}")
|
140
|
+
end
|
141
|
+
|
142
|
+
spec = new(File.basename(path).sub(/\.yml$/, ""))
|
143
|
+
info.each_pair do |key, value|
|
144
|
+
spec.send(:"#{key}=", value)
|
145
|
+
end
|
146
|
+
#TODO: Convert the strings in :checksums to strings, except the
|
147
|
+
#filenames, those should be strings. Anyone???
|
148
|
+
|
149
|
+
raise(Errors::InvalidSpecification, spec.validate.first) unless spec.valid?
|
150
|
+
|
151
|
+
spec
|
152
|
+
end
|
153
|
+
|
154
|
+
#Returns the matching package name from the package specification’s name
|
155
|
+
#by replacing the .yml extension with .smcpak.
|
156
|
+
#==Return value
|
157
|
+
#A string ending in ".smcpak".
|
158
|
+
#==Example
|
159
|
+
# p SmcGet::PackageSpecification.spec2pkg("cool_pkg.yml") #=> "cool_pkg.smcpak")
|
160
|
+
def self.spec2pkg(spec_file_name) # :nodoc:
|
161
|
+
spec_file_name.to_s.sub(/\.yml$/, ".smcpak")
|
162
|
+
end
|
163
|
+
|
164
|
+
#Returns the matching specification file name from the package’s name
|
165
|
+
#by replacing the .smcpak extension with .yml.
|
166
|
+
#==Return value
|
167
|
+
#A string ending in ".yml"
|
168
|
+
#==Example
|
169
|
+
# p SmcGet::PackageSpecification.pkg2spec("cool_pkg.smcpak") #=> "cool_pkg.yml"
|
170
|
+
def self.pkg2spec(package_file_name) # :nodoc:
|
171
|
+
package_file_name.to_s.sub(/\.smcpak$/, ".yml")
|
172
|
+
end
|
173
|
+
|
174
|
+
def initialize(pkg_name)
|
175
|
+
@info = {:dependencies => [], :levels => [], :music => [], :sounds => [], :graphics => [], :worlds => []}
|
176
|
+
@name = pkg_name
|
177
|
+
end
|
178
|
+
|
179
|
+
#See attribute.
|
180
|
+
def compressed_file_name # :nodoc:
|
181
|
+
"#@name.smcpak"
|
182
|
+
end
|
183
|
+
|
184
|
+
#See attribute.
|
185
|
+
def spec_file_name # :nodoc:
|
186
|
+
"#@name.yml"
|
187
|
+
end
|
188
|
+
|
189
|
+
[:title, :last_update, :authors, :difficulty, :description, :install_message, :remove_message, :dependencies, :levels, :music, :sounds, :graphics, :worlds, :checksums].each do |sym|
|
190
|
+
define_method(sym){@info[sym]}
|
191
|
+
define_method(:"#{sym}="){|val| @info[sym] = val}
|
192
|
+
end
|
193
|
+
|
194
|
+
def [](sym)
|
195
|
+
if respond_to?(sym)
|
196
|
+
send(sym)
|
197
|
+
else
|
198
|
+
raise(IndexError, "No such specification key: #{sym}!")
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def valid?
|
203
|
+
validate.empty?
|
204
|
+
end
|
205
|
+
|
206
|
+
def validate
|
207
|
+
errors = []
|
208
|
+
|
209
|
+
SPEC_MANDATORY_KEYS.each do |sym|
|
210
|
+
errors << "Mandatory key #{sym} is missing!" unless @info.has_key?(sym)
|
211
|
+
end
|
212
|
+
|
213
|
+
[:levels, :graphics, :music, :sounds, :worlds].each do |sym|
|
214
|
+
errors << "Mandatory checksum key #{sym} is missing!" unless @info[:checksums].has_key?(sym.to_s)
|
215
|
+
end
|
216
|
+
|
217
|
+
errors
|
218
|
+
end
|
219
|
+
|
220
|
+
#Compares two specifications. They are considered equal if all their
|
221
|
+
#attributes (levels, difficulty, etc.) are equal.
|
222
|
+
def ==(other)
|
223
|
+
return false unless other.respond_to? :info
|
224
|
+
@info == other.info
|
225
|
+
end
|
226
|
+
|
227
|
+
#Saves the package specification in YAML format into a file.
|
228
|
+
#==Parameter
|
229
|
+
#[directory] The directory where to save the file to. The filename is automatically
|
230
|
+
# detected from the attributes set for the specification.
|
231
|
+
#==Raises
|
232
|
+
#[InvalidSpecification] The specification was not valid, i.e. contained incorrect
|
233
|
+
# or missing values.
|
234
|
+
#==Example
|
235
|
+
# p spec.name #=> "cool_pkg"
|
236
|
+
# spec.save(".")
|
237
|
+
# p File.file?("cool_pkg.yml") #=> true
|
238
|
+
def save(directory)
|
239
|
+
raise(Errors::InvalidSpecification, validate.first) unless valid?
|
240
|
+
|
241
|
+
path = Pathname.new(directory) + "#{@name}.yml"
|
242
|
+
#Turn the spec keys for serialization into strings
|
243
|
+
hsh = {}
|
244
|
+
@info.each_pair{|k, v| hsh[k.to_s] = v}
|
245
|
+
path.open("w"){|f| YAML.dump(hsh, f)}
|
246
|
+
end
|
247
|
+
|
248
|
+
protected
|
249
|
+
|
250
|
+
#Returns the complete internal information hash.
|
251
|
+
def info
|
252
|
+
@info
|
253
|
+
end
|
254
|
+
|
255
|
+
end
|
256
|
+
|
257
|
+
end
|