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,329 +1,297 @@
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
- module CUICommands
24
-
25
- class BuildCommand < Command
26
-
27
- def self.help
28
- <<-EOF
29
- USAGE: #{File.basename($0)} build [DIRECTORY]
30
-
31
- If DIRECTORY is given, validates it's structure against the SMC packaging
32
- guidelines and then packages it into a .smcpak file. If DIRECTORY is not
33
- given, enters an interactive process that queries you for the files you
34
- want to include in your SMC package. In both cases you’ll end up with
35
- a SMC package in your current directory.
36
-
37
- Files you enter during the interrogative process are looked for in your
38
- user-level SMC directory, i.e. ~/.smc.
39
- EOF
40
- end
41
-
42
- def self.summary
43
- "build\t\tBuild SMC packages."
44
- end
45
-
46
- def parse(args)
47
- CUI.debug("Parsing #{args.count} args for build.")
48
- raise(InvalidCommandline, "Too many arguments.") if args.count > 1
49
- @directory = args.shift #nil if not given
50
- end
51
-
52
- def execute(config)
53
- CUI.debug("Executing build.")
54
- begin
55
- if @directory
56
- Package.create(@directory)
57
- else #No directory given
58
- puts(<<-MSG)
59
- Welcome to the smc-get build process!
60
- Answer the following questions properly and you'll end up with a ready-to-
61
- install package you can either install locally or contribute to the repository
62
- (which would make it installable via 'smc-get install' directly). When a question
63
- asks you to input multiple files, you can end the query with an empty line.
64
- If you like, you can specify multiple files at once by separating them
65
- with a comma. Wildcards in filenames are allowed.
66
-
67
- Files you don't specify via an absolute path (i.e. a path beginning with
68
- either / on *nix or <letter>:\\ on Windows) are searched for in your
69
- home directory's .smc directory and your SMC installation.
70
- MSG
71
- #All the information will be collected in this hash
72
- spec = Hash.new{|hsh, key| hsh[key] = []}
73
-
74
- #Start the questionaire
75
- [:levels, :graphics, :music, :sounds, :worlds].each do |sym|
76
- spec[sym].concat(input_files(sym))
77
- end
78
-
79
- puts
80
- puts("Who participated in creating this package?")
81
- loop do
82
- print "> "
83
- str = $stdin.gets.chomp
84
-
85
- if str.empty?
86
- if spec[:authors].empty?
87
- $stderr.puts("You have to input at least one author.")
88
- else
89
- break
90
- end
91
- else #Something was entered
92
- spec[:authors].concat(str.split(",").map(&:strip))
93
- end
94
- end
95
-
96
- puts
97
- puts "Enter this package's dependecy packages:"
98
- loop do
99
- print "> "
100
- str = $stdin.gets.chomp
101
- if str.empty?
102
- break
103
- else
104
- spec[:dependencies].concat(str.split(",").map(&:strip))
105
- end
106
- end
107
-
108
- loop do
109
- puts
110
- print("Enter the difficulty: ")
111
- break unless (spec[:difficulty] = $stdin.gets.chomp).empty?
112
- end
113
-
114
- puts
115
- puts("Enter the package's description. A single line containing containg")
116
- puts("END")
117
- puts("terminates the query.")
118
- spec[:description] = ""
119
- loop do
120
- print "> "
121
- str = $stdin.gets #No chomp here, the user may set spaces at the line end intentionally
122
- if str == "END\n"
123
- if spec[:description].strip.empty?
124
- $stderr.puts("You *have* to input a description!")
125
- $stderr.puts("And it must consist of something else than only whitespace!")
126
- else
127
- break
128
- end
129
- else
130
- spec[:description] << str
131
- end
132
- end
133
-
134
- [:install_message, :remove_message].each{|sym| spec[sym] = input_desc(sym)}
135
-
136
- loop do
137
- puts
138
- print("Enter the package's full title (it can contain whitespace):")
139
- spec[:title] = $stdin.gets.chomp
140
- if spec[:title].strip.empty?
141
- $stderr.puts "You *have* to specify a title that doesn't consist solely of whitespace!"
142
- else
143
- break
144
- end
145
- end
146
-
147
- pkgname = nil
148
- loop do
149
- puts
150
- print "Enter the package's name (this mustn't contain whitespace):"
151
- pkgname = $stdin.gets.chomp
152
- if pkgname.strip.empty?
153
- $stderr.puts "You *have* to specify a name!"
154
- elsif pkgname =~ /\s/
155
- $stderr.puts "The package name mustn't contain whitespace!"
156
- else
157
- break
158
- end
159
- end
160
-
161
- #Set the last_update spec field to now
162
- spec[:last_update] = Time.now.utc
163
-
164
- puts
165
- #This is all. Start building the package.
166
- Dir.mktmpdir("smc-get-build-package") do |tmpdir|
167
- puts "Creating package..."
168
- tmpdir = Pathname.new(tmpdir)
169
- pkgdir = tmpdir + pkgname
170
- pkgdir.mkdir
171
- #Copy all the level, music, etc. files
172
- [:levels, :music, :sounds, :worlds].each do |sym|
173
- subdir = pkgdir + sym.to_s
174
- subdir.mkdir
175
- spec[sym].each{|file| FileUtils.cp(file, subdir)}
176
- end
177
- #The graphics for whatever reason have an own name...
178
- subdir = pkgdir + "pixmaps"
179
- subdir.mkdir
180
- spec[:graphics].each{|file| FileUtils.cp(file, subdir)}
181
-
182
- #Turn the file paths in the spec to relative ones and the
183
- #keys to strings. Compute the checksums.
184
- puts "Creating spec and computing SHA1 checksums..."
185
- real_spec = {"checksums" => {}}
186
- spec.each_pair do |key, value|
187
- real_spec[key.to_s] = case key
188
- when :levels, :graphics, :music, :sounds
189
- real_spec["checksums"][key.to_s] = {}
190
- value.map do |abs_path|
191
- basename = File.basename(abs_path)
192
- real_spec["checksums"][key.to_s][basename] = Digest::SHA1.hexdigest(File.read(abs_path))
193
- basename #for map
194
- end
195
- when :worlds
196
- real_spec["checksums"]["worlds"] = {}
197
- value.map do |abs_path|
198
- basename = File.basename(abs_path)
199
- real_spec["checksums"]["worlds"][basename] = {
200
- "description.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "description.xml"))),
201
- "layer.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "layer.xml"))),
202
- "world.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "world.xml")))
203
- }
204
- basename #for map
205
- end
206
- else
207
- value
208
- end
209
- end
210
-
211
- #Create the spec
212
- File.open(pkgdir + "#{pkgname}.yml", "w"){|file| YAML.dump(real_spec, file)}
213
-
214
- puts "Compressing..."
215
- pkg = Package.create(pkgdir)
216
- puts "Copying..."
217
- FileUtils.cp(pkg.path, "./")
218
- #compressed_file_name returns only the name of the package file,
219
- #no path. Expanding it therefore results in the current
220
- #working directory prepended to it.
221
- puts "Done. Your package is at #{File.expand_path(pkg.spec.compressed_file_name)}."
222
- end
223
-
224
- end
225
- rescue Errors::BrokenPackageError => e
226
- $stderr.puts("Failed to build SMC package:")
227
- $stderr.puts(e.message)
228
- if CUI.debug_mode?
229
- $stderr.puts("Class: #{e.class}")
230
- $stderr.puts("Message: #{e.message}")
231
- $stderr.puts("Backtrace:")
232
- $stderr.puts(e.backtrace.join("\n\t"))
233
- end
234
- return 1 #Exit code
235
- end
236
- end
237
-
238
- private
239
-
240
- #Queries the user for a set of file names in an uniform mannor.
241
- #Pass in the pluralized symbol of the resource you want to query
242
- #for, e.g. :levels or :graphics. Return value is an array of all
243
- #found files which may be empty if no files were found.
244
- def input_files(plural_name)
245
- result = []
246
- puts
247
- puts "Enter the names of the #{plural_name} you want to include:"
248
- loop do
249
- print "> "
250
- str = $stdin.gets.chomp
251
-
252
- #Test if the user entered an empty line, i.e. wants to end the
253
- #query for this question
254
- if str.empty?
255
- if result.empty?
256
- print("No #{plural_name} specified. Is this correct?(y/n) ")
257
- break if $stdin.gets.chomp.downcase == "y"
258
- else
259
- break
260
- end
261
- else #User entered something
262
- str.split(",").each do |file|
263
- file.strip! #Due to possible whitespace behind the comma
264
- ary = get_file_paths(plural_name.to_s, file)
265
- $stderr.puts("Warning: File(s) not found: #{file}. Ignoring.") if ary.empty?
266
- result.concat(ary)
267
- end
268
- end
269
- end
270
- result
271
- end
272
-
273
- #Queries the user for a longer, but optional text that is returned. If
274
- #the user enters no text beside the END marker, returns nil. Pass in
275
- #what to tell the user is to be entered.
276
- def input_desc(sym)
277
- puts
278
- puts("Enter the package's #{sym}. A single line containing containg")
279
- puts("END")
280
- puts("terminates the query. Enter END immediately if you don't want")
281
- puts "a #{sym}."
282
- result = ""
283
- loop do
284
- print "> "
285
- str = $stdin.gets #No chomp here, the user may set spaces at the line end intentionally
286
- if str == "END\n"
287
- break
288
- else
289
- result << str
290
- end
291
- end
292
- result.empty? ? nil : result
293
- end
294
-
295
- def get_file_paths(plural_name, path)
296
- #Even on Windows we work with forward slash (Windows supports this,
297
- #although it’s not well known)
298
- path = path.gsub("\\", "/")
299
- ary = []
300
- #First check if it’s an absolute path
301
- if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ and path =~ /^[a-z]:/i
302
- ary.replace(Dir.glob(path)) #Works even without an actual escape char like *
303
- elsif path.start_with?("/")
304
- ary.replace(Dir.glob(path))
305
- else #OK, relative path
306
- plural_name = "pixmaps" if plural_name == "graphics" #As always...
307
-
308
- #The user level directory only contains levels, but for the
309
- #sake of simplicity I treat it as if sounds etc existed there
310
- #as well. It doesn’t hurt if not, because that just causes
311
- #an empty array.
312
- user_level_dir = CUI::USER_SMC_DIR + plural_name
313
- smc_install_dir = @cui.local_repository.path + plural_name
314
- global_files = Dir.glob(smc_install_dir.join(path).to_s)
315
- user_files = Dir.glob(user_level_dir.join(path).to_s)
316
- #In case a file with the same name exists in both paths,
317
- #the user-level file overrides the SMC installation ones’s.
318
- ary.replace(user_files)
319
- global_files.each{|path| ary << path unless ary.any?{|p2| File.basename(path) == File.basename(p2)}}
320
- end
321
- ary
322
- end
323
-
324
- end
325
-
326
- end
327
-
328
- end
329
- # vim:set ts=8 sts=2 sw=2 et: #
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
+ module CUICommands
24
+
25
+ class BuildCommand < Command
26
+
27
+ def self.help
28
+ <<-EOF
29
+ USAGE: #{File.basename($0)} build [DIRECTORY]
30
+
31
+ If DIRECTORY is given, validates it's structure against the SMC packaging
32
+ guidelines and then packages it into a .smcpak file. If DIRECTORY is not
33
+ given, enters an interactive process that queries you for the files you
34
+ want to include in your SMC package. In both cases you’ll end up with
35
+ a SMC package in your current directory.
36
+
37
+ Files you enter during the interrogative process are looked for in your
38
+ user-level SMC directory, i.e. ~/.smc.
39
+ EOF
40
+ end
41
+
42
+ def self.summary
43
+ "build\t\tBuild SMC packages."
44
+ end
45
+
46
+ def parse(args)
47
+ CUI.debug("Parsing #{args.count} args for build.")
48
+ raise(InvalidCommandline, "Too many arguments.") if args.count > 1
49
+ @directory = args.shift #nil if not given
50
+ end
51
+
52
+ def execute(config)
53
+ CUI.debug("Executing build.")
54
+ begin
55
+ if @directory
56
+ Package.create(@directory)
57
+ else #No directory given
58
+ puts(<<-MSG)
59
+ Welcome to the smc-get build process!
60
+ Answer the following questions properly and you'll end up with a ready-to-
61
+ install package you can either install locally or contribute to the repository
62
+ (which would make it installable via 'smc-get install' directly). When a question
63
+ asks you to input multiple files, you can end the query with an empty line.
64
+ If you like, you can specify multiple files at once by separating them
65
+ with a comma. Wildcards in filenames are allowed.
66
+
67
+ Files you don't specify via an absolute path (i.e. a path beginning with
68
+ either / on *nix or <letter>:\\ on Windows) are searched for in your
69
+ home directory's .smc directory and your SMC installation.
70
+ MSG
71
+ #All the information will be collected in this hash
72
+ spec = {}
73
+
74
+ #Start the questionaire
75
+ [:levels, :graphics, :music, :sounds, :worlds].each do |sym|
76
+ spec[sym] = input_files(sym)
77
+ end
78
+
79
+ spec[:authors] = ask_list "Who participated in creating this package?"
80
+ spec[:dependencies] = ask_list "Enter this package's dependecy packages.", true
81
+ spec[:difficulty] = ask_string "Enter the difficulty: "
82
+ spec[:description] = ask_text "Enter the package's description."
83
+ spec[:install_message] = ask_text "Enter the installation message.", true
84
+ spec[:remove_message] = ask_text "Enter the remove message.", true
85
+ spec[:title] = ask_string "Enter the package's full title (it can contain whitespace):"
86
+ pkgname = ask_string "Enter the package's name (this mustn't contain whitespace):"
87
+
88
+ #Set the last_update spec field to now
89
+ spec[:last_update] = Time.now.utc #autoconvert to UTC
90
+
91
+ #This is all. Start building the package.
92
+ Dir.mktmpdir("smc-get-build-package") do |tmpdir|
93
+ puts "Creating package..."
94
+ tmpdir = Pathname.new(tmpdir)
95
+ pkgdir = tmpdir + pkgname
96
+ pkgdir.mkdir
97
+ #Copy all the level, music, etc. files
98
+ [:levels, :music, :sounds].each do |sym|
99
+ subdir = pkgdir + sym.to_s
100
+ subdir.mkdir
101
+ spec[sym].each{|file| FileUtils.cp(file, subdir)}
102
+ end
103
+ #Copy worlds
104
+ subdir = pkgdir + "worlds"
105
+ subdir.mkdir
106
+ spec[:worlds].each{|dir| FileUtils.cp_r(dir, subdir)}
107
+ #The graphics for whatever reason have an own name...
108
+ subdir = pkgdir + "pixmaps"
109
+ subdir.mkdir
110
+ spec[:graphics].each{|file| FileUtils.cp(file, subdir)}
111
+
112
+ #Turn the file paths in the spec to relative ones and the
113
+ #keys to strings. Compute the checksums.
114
+ puts "Creating spec and computing SHA1 checksums..."
115
+ real_spec = {"checksums" => {}}
116
+ spec.each_pair do |key, value|
117
+ real_spec[key.to_s] = case key
118
+ when :levels, :graphics, :music, :sounds
119
+ real_spec["checksums"][key.to_s] = {}
120
+ value.map do |abs_path|
121
+ basename = File.basename(abs_path)
122
+ real_spec["checksums"][key.to_s][basename] = Digest::SHA1.hexdigest(File.read(abs_path))
123
+ basename #for map
124
+ end
125
+ when :worlds
126
+ real_spec["checksums"]["worlds"] = {}
127
+ value.map do |abs_path|
128
+ basename = File.basename(abs_path)
129
+ real_spec["checksums"]["worlds"][basename] = {
130
+ "description.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "description.xml"))),
131
+ "layer.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "layer.xml"))),
132
+ "world.xml" => Digest::SHA1.hexdigest(File.read(File.join(abs_path, "world.xml")))
133
+ }
134
+ basename #for map
135
+ end
136
+ else
137
+ value
138
+ end
139
+ end
140
+
141
+ #Create the spec
142
+ File.open(pkgdir + "#{pkgname}.yml", "w"){|file| YAML.dump(real_spec, file)}
143
+
144
+ puts "Compressing..."
145
+ pkg = Package.create(pkgdir)
146
+ puts "Copying..."
147
+ FileUtils.cp(pkg.path, "./")
148
+ #compressed_file_name returns only the name of the package file,
149
+ #no path. Expanding it therefore results in the current
150
+ #working directory prepended to it.
151
+ puts "Done. Your package is at #{File.expand_path(pkg.spec.compressed_file_name)}."
152
+ end
153
+
154
+ end
155
+ rescue Errors::BrokenPackageError => e
156
+ $stderr.puts("Failed to build SMC package:")
157
+ $stderr.puts(e.message)
158
+ if CUI.debug_mode?
159
+ $stderr.puts("Class: #{e.class}")
160
+ $stderr.puts("Message: #{e.message}")
161
+ $stderr.puts("Backtrace:")
162
+ $stderr.puts(e.backtrace.join("\n\t"))
163
+ end
164
+ return 1 #Exit code
165
+ end
166
+ end
167
+
168
+ private
169
+
170
+ #Queries the user for a set of file names in an uniform mannor.
171
+ #Pass in the pluralized symbol of the resource you want to query
172
+ #for, e.g. :levels or :graphics. Return value is an array of all
173
+ #found files which may be empty if no files were found.
174
+ def input_files(plural_name)
175
+ result = []
176
+ puts
177
+ puts "Enter the names of the #{plural_name} you want to include:"
178
+ loop do
179
+ print "> "
180
+ str = $stdin.gets.chomp
181
+
182
+ #Test if the user entered an empty line, i.e. wants to end the
183
+ #query for this question
184
+ if str.empty?
185
+ if result.empty?
186
+ print("No #{plural_name} specified. Is this correct?(y/n) ")
187
+ break if $stdin.gets.chomp.downcase == "y"
188
+ else
189
+ break
190
+ end
191
+ else #User entered something
192
+ str.split(",").each do |file|
193
+ file.strip! #Due to possible whitespace behind the comma
194
+ ary = get_file_paths(plural_name.to_s, file)
195
+ $stderr.puts("Warning: File(s) not found: #{file}. Ignoring.") if ary.empty?
196
+ result.concat(ary)
197
+ end
198
+ end
199
+ end
200
+ result
201
+ end
202
+
203
+ def get_file_paths(plural_name, path)
204
+ #Even on Windows we work with forward slash (Windows supports this,
205
+ #although it’s not well known)
206
+ path = path.gsub("\\", "/")
207
+ ary = []
208
+ #First check if it’s an absolute path
209
+ if RUBY_PLATFORM =~ /mswin|mingw|cygwin/ and path =~ /^[a-z]:/i
210
+ ary.replace(Dir.glob(path)) #Works even without an actual escape char like *
211
+ elsif path.start_with?("/")
212
+ ary.replace(Dir.glob(path))
213
+ else #OK, relative path
214
+ plural_name = "pixmaps" if plural_name == "graphics" #As always...
215
+
216
+ #The user level directory only contains levels, but for the
217
+ #sake of simplicity I treat it as if sounds etc existed there
218
+ #as well. It doesn’t hurt if not, because that just causes
219
+ #an empty array.
220
+ user_level_dir = CUI::USER_SMC_DIR + plural_name
221
+ smc_install_dir = @cui.local_repository.path + plural_name
222
+ global_files = Dir.glob(smc_install_dir.join(path).to_s)
223
+ user_files = Dir.glob(user_level_dir.join(path).to_s)
224
+ #In case a file with the same name exists in both paths,
225
+ #the user-level file overrides the SMC installation ones’s.
226
+ ary.replace(user_files)
227
+ global_files.each{|path| ary << path unless ary.any?{|p2| File.basename(path) == File.basename(p2)}}
228
+ end
229
+ ary
230
+ end
231
+
232
+ #Asks for a newline and/or comma-separated list of things which are
233
+ #returned as an array.
234
+ def ask_list(message, blank_allowed = false)
235
+ puts(message)
236
+ list = []
237
+ loop do
238
+ print "> "
239
+ str = $stdin.gets.chomp
240
+
241
+ if str.empty?
242
+ if list.empty? and !blank_allowed
243
+ $stderr.puts("You have to input at least one entry.")
244
+ else
245
+ break
246
+ end
247
+ else #Something was entered
248
+ list.concat(str.split(",").map(&:strip))
249
+ end
250
+ end
251
+ puts #Blank line
252
+ list
253
+ end
254
+
255
+ #Ask for a short string. If +no_whitespace+ is true, the user is
256
+ #prompted again and again until he finally enters something without
257
+ #whitespace. The string is returned.
258
+ def ask_string(message, no_whitespace = false)
259
+ loop do
260
+ print(message)
261
+ str = $stdin.gets.chomp
262
+ if no_whitespace and str =~ /\s/
263
+ $stderr.puts("No whitespace allowed.")
264
+ else
265
+ puts #Blank line
266
+ return str
267
+ end
268
+ end
269
+ end
270
+
271
+ #Ask for a longer text that will be returned. If +blank_allowed+ is true,
272
+ #an empty text causes a return value of +nil+.
273
+ def ask_text(message, blank_allowed = false)
274
+ puts(message)
275
+ puts "End input with the word END on a single line."
276
+ text = ""
277
+ loop do
278
+ str = $stdin.gets
279
+ if str == "END\n"
280
+ if text.empty? and !blank_allowed
281
+ $stderr.puts("Nothing entered. Please input some text.")
282
+ else
283
+ puts #Blank line
284
+ return text.empty? ? nil : text.strip
285
+ end #if empty?
286
+ else
287
+ text << str
288
+ end #if END
289
+ end #loop
290
+ end #ask_text
291
+
292
+ end
293
+
294
+ end
295
+
296
+ end
297
+ # vim:set ts=8 sts=2 sw=2 et: #