smc-get 0.2.0.beta1 → 0.3.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 +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
|