rvpacker 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
File without changes
@@ -1,129 +1,129 @@
1
- =begin
2
- This file contains significant portions of Psych 2.0.0 to modify behavior and to fix
3
- bugs. The license follows:
4
-
5
- Copyright 2009 Aaron Patterson, et al.
6
-
7
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
8
- software and associated documentation files (the 'Software'), to deal in the Software
9
- without restriction, including without limitation the rights to use, copy, modify, merge,
10
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11
- persons to whom the Software is furnished to do so, subject to the following conditions:
12
-
13
- The above copyright notice and this permission notice shall be included in all copies or
14
- substantial portions of the Software.
15
-
16
- THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
18
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
19
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
- DEALINGS IN THE SOFTWARE.
22
- =end
23
-
24
- gem 'psych', '2.0.0'
25
- require 'psych'
26
-
27
- if Psych::VERSION == '2.0.0'
28
- # Psych bugs:
29
- #
30
- # 1) Psych has a bug where it stores an anchor to the YAML for an object, but indexes
31
- # the reference by object_id. This doesn't keep the object alive, so if it gets garbage
32
- # collected, Ruby might generate an object with the same object_id and try to generate a
33
- # reference to the stored anchor. This monkey-patches the Registrar to keep the object
34
- # alive so incorrect references aren't generated. The bug is also present in Psych 1.3.4
35
- # but there isn't a convenient way to patch that.
36
- #
37
- # 2) Psych also doesn't create references and anchors for classes that implement
38
- # encode_with. This modifies dump_coder to handle that situation.
39
- #
40
- # Added two options:
41
- # :sort - sort hashes and instance variables for objects
42
- # :flow_classes - array of class types that will automatically emit with flow style
43
- # rather than block style
44
- module Psych
45
- module Visitors
46
- class YAMLTree < Psych::Visitors::Visitor
47
- class Registrar
48
- old_initialize = self.instance_method(:initialize)
49
- define_method(:initialize) do
50
- old_initialize.bind(self).call
51
- @obj_to_obj = {}
52
- end
53
-
54
- old_register = self.instance_method(:register)
55
- define_method(:register) do |target, node|
56
- old_register.bind(self).call(target, node)
57
- @obj_to_obj[target.object_id] = target
58
- end
59
- end
60
-
61
- remove_method(:visit_Hash)
62
- def visit_Hash o
63
- tag = o.class == ::Hash ? nil : "!ruby/hash:#{o.class}"
64
- implicit = !tag
65
-
66
- register(o, @emitter.start_mapping(nil, tag, implicit, Nodes::Mapping::BLOCK))
67
-
68
- keys = o.keys
69
- keys = keys.sort if @options[:sort]
70
- keys.each do |k|
71
- accept k
72
- accept o[k]
73
- end
74
-
75
- @emitter.end_mapping
76
- end
77
-
78
- remove_method(:visit_Object)
79
- def visit_Object o
80
- tag = Psych.dump_tags[o.class]
81
- unless tag
82
- klass = o.class == Object ? nil : o.class.name
83
- tag = ['!ruby/object', klass].compact.join(':')
84
- end
85
-
86
- if @options[:flow_classes] && @options[:flow_classes].include?(o.class)
87
- style = Nodes::Mapping::FLOW
88
- else
89
- style = Nodes::Mapping::BLOCK
90
- end
91
-
92
- map = @emitter.start_mapping(nil, tag, false, style)
93
- register(o, map)
94
-
95
- dump_ivars o
96
- @emitter.end_mapping
97
- end
98
-
99
- remove_method(:dump_coder)
100
- def dump_coder o
101
- @coders << o
102
- tag = Psych.dump_tags[o.class]
103
- unless tag
104
- klass = o.class == Object ? nil : o.class.name
105
- tag = ['!ruby/object', klass].compact.join(':')
106
- end
107
-
108
- c = Psych::Coder.new(tag)
109
- o.encode_with(c)
110
- register o, emit_coder(c)
111
- end
112
-
113
- remove_method(:dump_ivars)
114
- def dump_ivars target
115
- ivars = find_ivars target
116
- ivars = ivars.sort() if @options[:sort]
117
-
118
- ivars.each do |iv|
119
- @emitter.scalar("#{iv.to_s.sub(/^@/, '')}", nil, nil, true, false, Nodes::Scalar::ANY)
120
- accept target.instance_variable_get(iv)
121
- end
122
- end
123
-
124
- end
125
- end
126
- end
127
- else
128
- warn "Warning: Psych 2.0.0 not detected" if $VERBOSE
129
- end
1
+ =begin
2
+ This file contains significant portions of Psych 2.0.0 to modify behavior and to fix
3
+ bugs. The license follows:
4
+
5
+ Copyright 2009 Aaron Patterson, et al.
6
+
7
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
8
+ software and associated documentation files (the 'Software'), to deal in the Software
9
+ without restriction, including without limitation the rights to use, copy, modify, merge,
10
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit
11
+ persons to whom the Software is furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all copies or
14
+ substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
17
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
18
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
19
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
20
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
21
+ DEALINGS IN THE SOFTWARE.
22
+ =end
23
+
24
+ gem 'psych', '2.0.0'
25
+ require 'psych'
26
+
27
+ if Psych::VERSION == '2.0.0'
28
+ # Psych bugs:
29
+ #
30
+ # 1) Psych has a bug where it stores an anchor to the YAML for an object, but indexes
31
+ # the reference by object_id. This doesn't keep the object alive, so if it gets garbage
32
+ # collected, Ruby might generate an object with the same object_id and try to generate a
33
+ # reference to the stored anchor. This monkey-patches the Registrar to keep the object
34
+ # alive so incorrect references aren't generated. The bug is also present in Psych 1.3.4
35
+ # but there isn't a convenient way to patch that.
36
+ #
37
+ # 2) Psych also doesn't create references and anchors for classes that implement
38
+ # encode_with. This modifies dump_coder to handle that situation.
39
+ #
40
+ # Added two options:
41
+ # :sort - sort hashes and instance variables for objects
42
+ # :flow_classes - array of class types that will automatically emit with flow style
43
+ # rather than block style
44
+ module Psych
45
+ module Visitors
46
+ class YAMLTree < Psych::Visitors::Visitor
47
+ class Registrar
48
+ old_initialize = self.instance_method(:initialize)
49
+ define_method(:initialize) do
50
+ old_initialize.bind(self).call
51
+ @obj_to_obj = {}
52
+ end
53
+
54
+ old_register = self.instance_method(:register)
55
+ define_method(:register) do |target, node|
56
+ old_register.bind(self).call(target, node)
57
+ @obj_to_obj[target.object_id] = target
58
+ end
59
+ end
60
+
61
+ remove_method(:visit_Hash)
62
+ def visit_Hash o
63
+ tag = o.class == ::Hash ? nil : "!ruby/hash:#{o.class}"
64
+ implicit = !tag
65
+
66
+ register(o, @emitter.start_mapping(nil, tag, implicit, Nodes::Mapping::BLOCK))
67
+
68
+ keys = o.keys
69
+ keys = keys.sort if @options[:sort]
70
+ keys.each do |k|
71
+ accept k
72
+ accept o[k]
73
+ end
74
+
75
+ @emitter.end_mapping
76
+ end
77
+
78
+ remove_method(:visit_Object)
79
+ def visit_Object o
80
+ tag = Psych.dump_tags[o.class]
81
+ unless tag
82
+ klass = o.class == Object ? nil : o.class.name
83
+ tag = ['!ruby/object', klass].compact.join(':')
84
+ end
85
+
86
+ if @options[:flow_classes] && @options[:flow_classes].include?(o.class)
87
+ style = Nodes::Mapping::FLOW
88
+ else
89
+ style = Nodes::Mapping::BLOCK
90
+ end
91
+
92
+ map = @emitter.start_mapping(nil, tag, false, style)
93
+ register(o, map)
94
+
95
+ dump_ivars o
96
+ @emitter.end_mapping
97
+ end
98
+
99
+ remove_method(:dump_coder)
100
+ def dump_coder o
101
+ @coders << o
102
+ tag = Psych.dump_tags[o.class]
103
+ unless tag
104
+ klass = o.class == Object ? nil : o.class.name
105
+ tag = ['!ruby/object', klass].compact.join(':')
106
+ end
107
+
108
+ c = Psych::Coder.new(tag)
109
+ o.encode_with(c)
110
+ register o, emit_coder(c)
111
+ end
112
+
113
+ remove_method(:dump_ivars)
114
+ def dump_ivars target
115
+ ivars = find_ivars target
116
+ ivars = ivars.sort() if @options[:sort]
117
+
118
+ ivars.each do |iv|
119
+ @emitter.scalar("#{iv.to_s.sub(/^@/, '')}", nil, nil, true, false, Nodes::Scalar::ANY)
120
+ accept target.instance_variable_get(iv)
121
+ end
122
+ end
123
+
124
+ end
125
+ end
126
+ end
127
+ else
128
+ warn "Warning: Psych 2.0.0 not detected" if $VERBOSE
129
+ end
@@ -1,371 +1,384 @@
1
- =begin
2
- Copyright (c) 2013 Howard Jeng
3
-
4
- Permission is hereby granted, free of charge, to any person obtaining a copy of this
5
- software and associated documentation files (the "Software"), to deal in the Software
6
- without restriction, including without limitation the rights to use, copy, modify, merge,
7
- publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8
- to whom the Software is furnished to do so, subject to the following conditions:
9
-
10
- The above copyright notice and this permission notice shall be included in all copies or
11
- substantial portions of the Software.
12
-
13
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
- INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15
- PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16
- FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
- OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
- DEALINGS IN THE SOFTWARE.
19
- =end
20
-
21
- require 'RGSS/psych_mods'
22
- require 'fileutils'
23
- require 'zlib'
24
- require 'pp'
25
- require 'formatador'
26
-
27
- module RGSS
28
- def self.change_extension(file, new_ext)
29
- return File.basename(file, '.*') + new_ext
30
- end
31
-
32
- def self.sanitize_filename(filename)
33
- return filename.gsub(/[^0-9A-Za-z]+/, '_')
34
- end
35
-
36
- def self.files_with_extension(directory, extension)
37
- return Dir.entries(directory).select{|file| File.extname(file) == extension}
38
- end
39
-
40
- def self.inflate(str)
41
- text = Zlib::Inflate.inflate(str)
42
- return text.force_encoding("UTF-8")
43
- end
44
-
45
- def self.deflate(str)
46
- return Zlib::Deflate.deflate(str, Zlib::BEST_COMPRESSION)
47
- end
48
-
49
-
50
- def self.dump_data_file(file, data, time, options)
51
- File.open(file, "wb") do |f|
52
- Marshal.dump(data, f)
53
- end
54
- File.utime(time, time, file)
55
- end
56
-
57
- def self.dump_yaml_file(file, data, time, options)
58
- File.open(file, "wb") do |f|
59
- Psych::dump(data, f, options)
60
- end
61
- File.utime(time, time, file)
62
- end
63
-
64
- def self.dump_save(file, data, time, options)
65
- File.open(file, "wb") do |f|
66
- data.each do |chunk|
67
- Marshal.dump(chunk, f)
68
- end
69
- end
70
- File.utime(time, time, file)
71
- end
72
-
73
- def self.dump_raw_file(file, data, time, options)
74
- File.open(file, "wb") do |f|
75
- f.write(data)
76
- end
77
- File.utime(time, time, file)
78
- end
79
-
80
- def self.dump(dumper, file, data, time, options)
81
- self.method(dumper).call(file, data, time, options)
82
- rescue
83
- warn "Exception dumping #{file}"
84
- raise
85
- end
86
-
87
-
88
- def self.load_data_file(file)
89
- File.open(file, "rb") do |f|
90
- return Marshal.load(f)
91
- end
92
- end
93
-
94
- def self.load_yaml_file(file)
95
- formatador = Formatador.new
96
- obj = nil
97
- File.open(file, "rb") do |f|
98
- obj = Psych::load(f)
99
- end
100
- max = 0
101
- return obj unless obj.kind_of?(Array)
102
- seen = {}
103
- idx =
104
- obj.each do |elem|
105
- next if elem.nil?
106
- if elem.instance_variable_defined?("@id")
107
- id = elem.instance_variable_get("@id")
108
- else
109
- id = nil
110
- elem.instance_variable_set("@id", nil)
111
- end
112
- next if id.nil?
113
-
114
- if seen.has_key?(id)
115
- formatador.display_line("[red]#{file}: Duplicate ID #{id}[/]")
116
- formatador.indent {
117
- formatador.indent {
118
- elem.pretty_inspect.split(/\n/).each do |line|
119
- formatador.display_line("[red]#{line}[/]")
120
- end
121
- }
122
- formatador.display_line
123
- formatador.display_line("[red]Last seen at:\n[/]")
124
- formatador.indent {
125
- elem.pretty_inspect.split(/\n/).each do |line|
126
- formatador.display_line("[red]#{line}[/]")
127
- end
128
- }
129
- }
130
- exit
131
- end
132
- seen[id] = elem
133
- max = ((id + 1) unless id < max)
134
- end
135
- obj.each do |elem|
136
- next if elem.nil?
137
- id = elem.instance_variable_get("@id")
138
- if id.nil?
139
- elem.instance_variable_set("@id", max)
140
- max += 1
141
- end
142
- end
143
- return obj
144
- end
145
-
146
- def self.load_raw_file(file)
147
- File.open(file, "rb") do |f|
148
- return f.read
149
- end
150
- end
151
-
152
- def self.load_save(file)
153
- File.open(file, "rb") do |f|
154
- data = []
155
- while not f.eof?
156
- o = Marshal.load(f)
157
- data.push(o)
158
- end
159
- return data
160
- end
161
- end
162
-
163
- def self.load(loader, file)
164
- return self.method(loader).call(file)
165
- rescue
166
- warn "Exception loading #{file}"
167
- raise
168
- end
169
-
170
-
171
- def self.scripts_to_text(dirs, src, dest, options)
172
- formatador = Formatador.new
173
- src_file = File.join(dirs[:data], src)
174
- dest_file = File.join(dirs[:yaml], dest)
175
- raise "Missing #{src}" unless File.exists?(src_file)
176
-
177
- script_entries = load(:load_data_file, src_file)
178
- check_time = !options[:force] && File.exists?(dest_file)
179
- oldest_time = File.mtime(dest_file) if check_time
180
-
181
- file_map, script_index, script_code = Hash.new(-1), [], {}
182
-
183
- idx=0
184
- script_entries.each do |script|
185
- idx += 1
186
- magic_number, script_name, code = idx, script[1], inflate(script[2])
187
- script_name.force_encoding("UTF-8")
188
-
189
- if code.length > 0
190
- filename = script_name.empty? ? 'blank' : sanitize_filename(script_name)
191
- key = filename.upcase
192
- value = (file_map[key] += 1)
193
- actual_filename = filename + (value == 0 ? "" : ".#{value}") + RUBY_EXT
194
- script_index.push([magic_number, script_name, actual_filename])
195
- full_filename = File.join(dirs[:script], actual_filename)
196
- script_code[full_filename] = code
197
- check_time = false unless File.exists?(full_filename)
198
- oldest_time = [File.mtime(full_filename), oldest_time].min if check_time
199
- else
200
- script_index.push([magic_number, script_name, nil])
201
- end
202
- end
203
-
204
- src_time = File.mtime(src_file)
205
- if check_time && (src_time - 1) < oldest_time
206
- formatador.display_line("[yellow]Skipping scripts to text[/]") if $VERBOSE
207
- else
208
- formatador.display_line("[green]Converting scripts to text[/]") if $VERBOSE
209
- dump(:dump_yaml_file, dest_file, script_index, src_time, options)
210
- script_code.each {|file, code| dump(:dump_raw_file, file, code, src_time, options)}
211
- end
212
- end
213
-
214
- def self.scripts_to_binary(dirs, src, dest, options)
215
- formatador = Formatador.new
216
- src_file = File.join(dirs[:yaml], src)
217
- dest_file = File.join(dirs[:data], dest)
218
- raise "Missing #{src}" unless File.exists?(src_file)
219
- check_time = !options[:force] && File.exists?(dest_file)
220
- newest_time = File.mtime(src_file) if check_time
221
-
222
- index = load(:load_yaml_file, src_file)
223
- script_entries = []
224
- index.each do |entry|
225
- magic_number, script_name, filename = entry
226
- code = ''
227
- if filename
228
- full_filename = File.join(dirs[:script], filename)
229
- raise "Missing script file #{filename}" unless File.exists?(full_filename)
230
- newest_time = [File.mtime(full_filename), newest_time].max if check_time
231
- code = load(:load_raw_file, full_filename)
232
- end
233
- script_entries.push([magic_number, script_name, deflate(code)])
234
- end
235
- if check_time && (newest_time - 1) < File.mtime(dest_file)
236
- formatador.display_line("[yellow]Skipping scripts to binary[/]") if $VERBOSE
237
- else
238
- formatador.display_line("[green]Converting scripts to binary[/]") if $VERBOSE
239
- dump(:dump_data_file, dest_file, script_entries, newest_time, options)
240
- end
241
- end
242
-
243
- def self.process_file(file, src_file, dest_file, dest_ext, loader, dumper, options)
244
- formatador = Formatador.new
245
- src_time = File.mtime(src_file)
246
- if !options[:force] && File.exists?(dest_file) && (src_time - 1) < File.mtime(dest_file)
247
- formatador.display_line("[yellow]Skipping #{file}[/]") if $VERBOSE
248
- else
249
- formatador.display_line("[green]Converting #{file} to #{dest_ext}[/]") if $VERBOSE
250
- data = load(loader, src_file)
251
- dump(dumper, dest_file, data, src_time, options)
252
- end
253
- end
254
-
255
- def self.convert(src, dest, options)
256
- files = files_with_extension(src[:directory], src[:ext])
257
- files -= src[:exclude]
258
-
259
- files.each do |file|
260
- src_file = File.join(src[:directory], file)
261
- dest_file = File.join(dest[:directory], change_extension(file, dest[:ext]))
262
-
263
- process_file(file, src_file, dest_file, dest[:ext], src[:load_file],
264
- dest[:dump_file], options)
265
- end
266
- end
267
-
268
- def self.convert_saves(base, src, dest, options)
269
- save_files = files_with_extension(base, src[:ext])
270
- save_files.each do |file|
271
- src_file = File.join(base, file)
272
- dest_file = File.join(base, change_extension(file, dest[:ext]))
273
-
274
- process_file(file, src_file, dest_file, dest[:ext], src[:load_save],
275
- dest[:dump_save], options)
276
- end
277
- end
278
-
279
- # [version] one of :ace, :vx, :xp
280
- # [direction] one of :data_bin_to_text, :data_text_to_bin, :save_bin_to_text,
281
- # :save_text_to_bin, :scripts_bin_to_text, :scripts_text_to_bin,
282
- # :all_text_to_bin, :all_bin_to_text
283
- # [directory] directory that project file is in
284
- # [options] :force - ignores file dates when converting (default false)
285
- # :round_trip - create yaml data that matches original marshalled data skips
286
- # data cleanup operations (default false)
287
- # :line_width - line width form YAML files, -1 for no line width limit
288
- # (default 130)
289
- # :table_width - maximum number of entries per row for table data, -1 for no
290
- # table row limit (default 20)
291
- def self.serialize(version, direction, directory, options = {})
292
- raise "#{directory} not found" unless File.exist?(directory)
293
-
294
- setup_classes(version, options)
295
- options = options.clone()
296
- options[:sort] = true if [:vx, :xp].include?(version)
297
- options[:flow_classes] = FLOW_CLASSES
298
- options[:line_width] ||= 130
299
-
300
- table_width = options[:table_width]
301
- RGSS::reset_const(Table, :MAX_ROW_LENGTH, table_width ? table_width : 20)
302
-
303
- base = File.realpath(directory)
304
-
305
- dirs = {
306
- :base => base,
307
- :data => get_data_directory(base),
308
- :yaml => get_yaml_directory(base),
309
- :script => get_script_directory(base)
310
- }
311
-
312
- dirs.values.each do |d|
313
- FileUtils.mkdir(d) unless File.exists?(d)
314
- end
315
-
316
- exts = {
317
- :ace => ACE_DATA_EXT,
318
- :vx => VX_DATA_EXT,
319
- :xp => XP_DATA_EXT
320
- }
321
-
322
- yaml_scripts = SCRIPTS_BASE + YAML_EXT
323
- yaml = {
324
- :directory => dirs[:yaml],
325
- :exclude => [yaml_scripts],
326
- :ext => YAML_EXT,
327
- :load_file => :load_yaml_file,
328
- :dump_file => :dump_yaml_file,
329
- :load_save => :load_yaml_file,
330
- :dump_save => :dump_yaml_file
331
- }
332
-
333
- scripts = SCRIPTS_BASE + exts[version]
334
- data = {
335
- :directory => dirs[:data],
336
- :exclude => [scripts],
337
- :ext => exts[version],
338
- :load_file => :load_data_file,
339
- :dump_file => :dump_data_file,
340
- :load_save => :load_save,
341
- :dump_save => :dump_save
342
- }
343
-
344
- case direction
345
- when :data_bin_to_text
346
- convert(data, yaml, options)
347
- scripts_to_text(dirs, scripts, yaml_scripts, options)
348
- when :data_text_to_bin
349
- convert(yaml, data, options)
350
- scripts_to_binary(dirs, yaml_scripts, scripts, options)
351
- when :save_bin_to_text
352
- convert_saves(base, data, yaml, options)
353
- when :save_text_to_bin
354
- convert_saves(base, yaml, data, options)
355
- when :scripts_bin_to_text
356
- scripts_to_text(dirs, scripts, yaml_scripts, options)
357
- when :scripts_text_to_bin
358
- scripts_to_binary(dirs, yaml_scripts, scripts, options)
359
- when :all_bin_to_text
360
- convert(data, yaml, options)
361
- scripts_to_text(dirs, scripts, yaml_scripts, options)
362
- convert_saves(base, data, yaml, options)
363
- when :all_text_to_bin
364
- convert(yaml, data, options)
365
- scripts_to_binary(dirs, yaml_scripts, scripts, options)
366
- convert_saves(base, yaml, data, options)
367
- else
368
- raise "Unrecognized direction :#{direction}"
369
- end
370
- end
371
- end
1
+ =begin
2
+ Copyright (c) 2013 Howard Jeng
3
+
4
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this
5
+ software and associated documentation files (the "Software"), to deal in the Software
6
+ without restriction, including without limitation the rights to use, copy, modify, merge,
7
+ publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
8
+ to whom the Software is furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all copies or
11
+ substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
14
+ INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
15
+ PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
16
+ FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
17
+ OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
18
+ DEALINGS IN THE SOFTWARE.
19
+ =end
20
+
21
+ require 'RGSS/psych_mods'
22
+ require 'fileutils'
23
+ require 'zlib'
24
+ require 'pp'
25
+ require 'formatador'
26
+
27
+ module RGSS
28
+ def self.change_extension(file, new_ext)
29
+ return File.basename(file, '.*') + new_ext
30
+ end
31
+
32
+ def self.sanitize_filename(filename)
33
+ return filename.gsub(/[^0-9A-Za-z]+/, '_')
34
+ end
35
+
36
+ def self.files_with_extension(directory, extension)
37
+ return Dir.entries(directory).select{|file| File.extname(file) == extension}
38
+ end
39
+
40
+ def self.inflate(str)
41
+ text = Zlib::Inflate.inflate(str)
42
+ return text.force_encoding("UTF-8")
43
+ end
44
+
45
+ def self.deflate(str)
46
+ return Zlib::Deflate.deflate(str, Zlib::BEST_COMPRESSION)
47
+ end
48
+
49
+
50
+ def self.dump_data_file(file, data, time, options)
51
+ File.open(file, "wb") do |f|
52
+ Marshal.dump(data, f)
53
+ end
54
+ File.utime(time, time, file)
55
+ end
56
+
57
+ def self.dump_yaml_file(file, data, time, options)
58
+ File.open(file, "wb") do |f|
59
+ Psych::dump(data, f, options)
60
+ end
61
+ File.utime(time, time, file)
62
+ end
63
+
64
+ def self.dump_save(file, data, time, options)
65
+ File.open(file, "wb") do |f|
66
+ data.each do |chunk|
67
+ Marshal.dump(chunk, f)
68
+ end
69
+ end
70
+ File.utime(time, time, file)
71
+ end
72
+
73
+ def self.dump_raw_file(file, data, time, options)
74
+ File.open(file, "wb") do |f|
75
+ f.write(data)
76
+ end
77
+ File.utime(time, time, file)
78
+ end
79
+
80
+ def self.dump(dumper, file, data, time, options)
81
+ self.method(dumper).call(file, data, time, options)
82
+ rescue
83
+ warn "Exception dumping #{file}"
84
+ raise
85
+ end
86
+
87
+
88
+ def self.load_data_file(file)
89
+ File.open(file, "rb") do |f|
90
+ return Marshal.load(f)
91
+ end
92
+ end
93
+
94
+ def self.load_yaml_file(file)
95
+ formatador = Formatador.new
96
+ obj = nil
97
+ File.open(file, "rb") do |f|
98
+ obj = Psych::load(f)
99
+ end
100
+ max = 0
101
+ return obj unless obj.kind_of?(Array)
102
+ seen = {}
103
+ idx =
104
+ obj.each do |elem|
105
+ next if elem.nil?
106
+ if elem.instance_variable_defined?("@id")
107
+ id = elem.instance_variable_get("@id")
108
+ else
109
+ id = nil
110
+ elem.instance_variable_set("@id", nil)
111
+ end
112
+ next if id.nil?
113
+
114
+ if seen.has_key?(id)
115
+ formatador.display_line("[red]#{file}: Duplicate ID #{id}[/]")
116
+ formatador.indent {
117
+ formatador.indent {
118
+ elem.pretty_inspect.split(/\n/).each do |line|
119
+ formatador.display_line("[red]#{line}[/]")
120
+ end
121
+ }
122
+ formatador.display_line
123
+ formatador.display_line("[red]Last seen at:\n[/]")
124
+ formatador.indent {
125
+ elem.pretty_inspect.split(/\n/).each do |line|
126
+ formatador.display_line("[red]#{line}[/]")
127
+ end
128
+ }
129
+ }
130
+ exit
131
+ end
132
+ seen[id] = elem
133
+ max = ((id + 1) unless id < max)
134
+ end
135
+ obj.each do |elem|
136
+ next if elem.nil?
137
+ id = elem.instance_variable_get("@id")
138
+ if id.nil?
139
+ elem.instance_variable_set("@id", max)
140
+ max += 1
141
+ end
142
+ end
143
+ return obj
144
+ end
145
+
146
+ def self.load_raw_file(file)
147
+ File.open(file, "rb") do |f|
148
+ return f.read
149
+ end
150
+ end
151
+
152
+ def self.load_save(file)
153
+ File.open(file, "rb") do |f|
154
+ data = []
155
+ while not f.eof?
156
+ o = Marshal.load(f)
157
+ data.push(o)
158
+ end
159
+ return data
160
+ end
161
+ end
162
+
163
+ def self.load(loader, file)
164
+ return self.method(loader).call(file)
165
+ rescue
166
+ warn "Exception loading #{file}"
167
+ raise
168
+ end
169
+
170
+
171
+ def self.scripts_to_text(dirs, src, dest, options)
172
+ formatador = Formatador.new
173
+ src_file = File.join(dirs[:data], src)
174
+ dest_file = File.join(dirs[:yaml], dest)
175
+ raise "Missing #{src}" unless File.exists?(src_file)
176
+
177
+ script_entries = load(:load_data_file, src_file)
178
+ check_time = !options[:force] && File.exists?(dest_file)
179
+ oldest_time = File.mtime(dest_file) if check_time
180
+
181
+ file_map, script_index, script_code = Hash.new(-1), [], {}
182
+
183
+ idx=0
184
+ script_entries.each do |script|
185
+ idx += 1
186
+ magic_number, script_name, code = idx, script[1], inflate(script[2])
187
+ script_name.force_encoding("UTF-8")
188
+
189
+ if code.length > 0
190
+ filename = script_name.empty? ? 'blank' : sanitize_filename(script_name)
191
+ key = filename.upcase
192
+ value = (file_map[key] += 1)
193
+ actual_filename = filename + (value == 0 ? "" : ".#{value}") + RUBY_EXT
194
+ script_index.push([magic_number, script_name, actual_filename])
195
+ full_filename = File.join(dirs[:script], actual_filename)
196
+ script_code[full_filename] = code
197
+ check_time = false unless File.exists?(full_filename)
198
+ oldest_time = [File.mtime(full_filename), oldest_time].min if check_time
199
+ else
200
+ script_index.push([magic_number, script_name, nil])
201
+ end
202
+ end
203
+
204
+ src_time = File.mtime(src_file)
205
+ if check_time && (src_time - 1) < oldest_time
206
+ formatador.display_line("[yellow]Skipping scripts to text[/]") if $VERBOSE
207
+ else
208
+ formatador.display_line("[green]Converting scripts to text[/]") if $VERBOSE
209
+ dump(:dump_yaml_file, dest_file, script_index, src_time, options)
210
+ script_code.each {|file, code| dump(:dump_raw_file, file, code, src_time, options)}
211
+ end
212
+ end
213
+
214
+ def self.scripts_to_binary(dirs, src, dest, options)
215
+ formatador = Formatador.new
216
+ src_file = File.join(dirs[:yaml], src)
217
+ dest_file = File.join(dirs[:data], dest)
218
+ raise "Missing #{src}" unless File.exists?(src_file)
219
+ check_time = !options[:force] && File.exists?(dest_file)
220
+ newest_time = File.mtime(src_file) if check_time
221
+
222
+ index = load(:load_yaml_file, src_file)
223
+ script_entries = []
224
+ index.each do |entry|
225
+ magic_number, script_name, filename = entry
226
+ code = ''
227
+ if filename
228
+ full_filename = File.join(dirs[:script], filename)
229
+ raise "Missing script file #{filename}" unless File.exists?(full_filename)
230
+ newest_time = [File.mtime(full_filename), newest_time].max if check_time
231
+ code = load(:load_raw_file, full_filename)
232
+ end
233
+ script_entries.push([magic_number, script_name, deflate(code)])
234
+ end
235
+ if check_time && (newest_time - 1) < File.mtime(dest_file)
236
+ formatador.display_line("[yellow]Skipping scripts to binary[/]") if $VERBOSE
237
+ else
238
+ formatador.display_line("[green]Converting scripts to binary[/]") if $VERBOSE
239
+ dump(:dump_data_file, dest_file, script_entries, newest_time, options)
240
+ end
241
+ end
242
+
243
+ def self.process_file(file, src_file, dest_file, dest_ext, loader, dumper, options)
244
+ formatador = Formatador.new
245
+ fbase = File.basename(file, File.extname(file))
246
+ return if (! options[:database].nil? ) and (options[:database].downcase != fbase.downcase)
247
+ src_time = File.mtime(src_file)
248
+ if !options[:force] && File.exists?(dest_file) && (src_time - 1) < File.mtime(dest_file)
249
+ formatador.display_line("[yellow]Skipping #{file}[/]") if $VERBOSE
250
+ else
251
+ formatador.display_line("[green]Converting #{file} to #{dest_ext}[/]") if $VERBOSE
252
+ data = load(loader, src_file)
253
+ dump(dumper, dest_file, data, src_time, options)
254
+ end
255
+ end
256
+
257
+ def self.convert(src, dest, options)
258
+ files = files_with_extension(src[:directory], src[:ext])
259
+ files -= src[:exclude]
260
+
261
+ files.each do |file|
262
+ src_file = File.join(src[:directory], file)
263
+ dest_file = File.join(dest[:directory], change_extension(file, dest[:ext]))
264
+
265
+ process_file(file, src_file, dest_file, dest[:ext], src[:load_file],
266
+ dest[:dump_file], options)
267
+ end
268
+ end
269
+
270
+ def self.convert_saves(base, src, dest, options)
271
+ save_files = files_with_extension(base, src[:ext])
272
+ save_files.each do |file|
273
+ src_file = File.join(base, file)
274
+ dest_file = File.join(base, change_extension(file, dest[:ext]))
275
+
276
+ process_file(file, src_file, dest_file, dest[:ext], src[:load_save],
277
+ dest[:dump_save], options)
278
+ end
279
+ end
280
+
281
+ # [version] one of :ace, :vx, :xp
282
+ # [direction] one of :data_bin_to_text, :data_text_to_bin, :save_bin_to_text,
283
+ # :save_text_to_bin, :scripts_bin_to_text, :scripts_text_to_bin,
284
+ # :all_text_to_bin, :all_bin_to_text
285
+ # [directory] directory that project file is in
286
+ # [options] :force - ignores file dates when converting (default false)
287
+ # :round_trip - create yaml data that matches original marshalled data skips
288
+ # data cleanup operations (default false)
289
+ # :line_width - line width form YAML files, -1 for no line width limit
290
+ # (default 130)
291
+ # :table_width - maximum number of entries per row for table data, -1 for no
292
+ # table row limit (default 20)
293
+ def self.serialize(version, direction, directory, options = {})
294
+ raise "#{directory} not found" unless File.exist?(directory)
295
+
296
+ setup_classes(version, options)
297
+ options = options.clone()
298
+ options[:sort] = true if [:vx, :xp].include?(version)
299
+ options[:flow_classes] = FLOW_CLASSES
300
+ options[:line_width] ||= 130
301
+
302
+ table_width = options[:table_width]
303
+ RGSS::reset_const(Table, :MAX_ROW_LENGTH, table_width ? table_width : 20)
304
+
305
+ base = File.realpath(directory)
306
+
307
+ dirs = {
308
+ :base => base,
309
+ :data => get_data_directory(base),
310
+ :yaml => get_yaml_directory(base),
311
+ :script => get_script_directory(base)
312
+ }
313
+
314
+ dirs.values.each do |d|
315
+ FileUtils.mkdir(d) unless File.exists?(d)
316
+ end
317
+
318
+ exts = {
319
+ :ace => ACE_DATA_EXT,
320
+ :vx => VX_DATA_EXT,
321
+ :xp => XP_DATA_EXT
322
+ }
323
+
324
+ yaml_scripts = SCRIPTS_BASE + YAML_EXT
325
+ yaml = {
326
+ :directory => dirs[:yaml],
327
+ :exclude => [yaml_scripts],
328
+ :ext => YAML_EXT,
329
+ :load_file => :load_yaml_file,
330
+ :dump_file => :dump_yaml_file,
331
+ :load_save => :load_yaml_file,
332
+ :dump_save => :dump_yaml_file
333
+ }
334
+
335
+ scripts = SCRIPTS_BASE + exts[version]
336
+ data = {
337
+ :directory => dirs[:data],
338
+ :exclude => [scripts],
339
+ :ext => exts[version],
340
+ :load_file => :load_data_file,
341
+ :dump_file => :dump_data_file,
342
+ :load_save => :load_save,
343
+ :dump_save => :dump_save
344
+ }
345
+
346
+ if options[:database].nil? or options[:database].downcase == 'scripts'
347
+ convert_scripts = true
348
+ else
349
+ convert_scripts = false
350
+ end
351
+ if options[:database].nil? or options[:database].downcase == 'saves'
352
+ convert_saves = true
353
+ else
354
+ convert_saves = false
355
+ end
356
+
357
+ case direction
358
+ when :data_bin_to_text
359
+ convert(data, yaml, options)
360
+ scripts_to_text(dirs, scripts, yaml_scripts, options) if convert_scripts
361
+ when :data_text_to_bin
362
+ convert(yaml, data, options)
363
+ scripts_to_binary(dirs, yaml_scripts, scripts, options) if convert_scripts
364
+ when :save_bin_to_text
365
+ convert_saves(base, data, yaml, options) if convert_saves
366
+ when :save_text_to_bin
367
+ convert_saves(base, yaml, data, options) if convert_saves
368
+ when :scripts_bin_to_text
369
+ scripts_to_text(dirs, scripts, yaml_scripts, options) if convert_scripts
370
+ when :scripts_text_to_bin
371
+ scripts_to_binary(dirs, yaml_scripts, scripts, options) if convert_scripts
372
+ when :all_bin_to_text
373
+ convert(data, yaml, options)
374
+ scripts_to_text(dirs, scripts, yaml_scripts, options) if convert_scripts
375
+ convert_saves(base, data, yaml, options) if convert_saves
376
+ when :all_text_to_bin
377
+ convert(yaml, data, options)
378
+ scripts_to_binary(dirs, yaml_scripts, scripts, options) if convert_scripts
379
+ convert_saves(base, yaml, data, options) if convert_saves
380
+ else
381
+ raise "Unrecognized direction :#{direction}"
382
+ end
383
+ end
384
+ end