wolftrans 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,319 @@
1
+ require 'wolftrans/context'
2
+ require 'wolfrpg'
3
+
4
+ require 'fileutils'
5
+ require 'find'
6
+
7
+ module WolfTrans
8
+ class Patch
9
+ ######################
10
+ # Loading Patch data #
11
+ def load_patch(patch_dir)
12
+ @patch_dir = WolfTrans.sanitize_path(patch_dir)
13
+ @patch_assets_dir = "#{@patch_dir}/Assets"
14
+ @patch_strings_dir = "#{@patch_dir}/Patch"
15
+
16
+ # Make sure these directories all exist
17
+ [@patch_assets_dir, @patch_strings_dir].each do |dir|
18
+ FileUtils.mkdir_p dir
19
+ end
20
+
21
+ # Find data dir
22
+ @patch_data_dir = WolfTrans.join_path_nocase(@patch_assets_dir, 'data')
23
+
24
+ # Load blacklist
25
+ @file_blacklist = []
26
+ if File.exists? "#{patch_dir}/blacklist.txt"
27
+ IO.read_txt("#{patch_dir}/blacklist.txt").each_line do |line|
28
+ line.strip!
29
+ next if line.empty?
30
+ if line.include? '\\'
31
+ raise "file specified in blacklist contains a backslash (use a forward slash instead)"
32
+ end
33
+ @file_blacklist << line.downcase!
34
+ end
35
+ end
36
+
37
+ # Load strings
38
+ Find.find(@patch_strings_dir) do |path|
39
+ next if FileTest.directory? path
40
+ next unless File.extname(path).casecmp '.txt'
41
+ process_patch_file(path, :load)
42
+ end
43
+
44
+ # Write back to patch files
45
+ processed_filenames = []
46
+
47
+ Find.find(@patch_strings_dir) do |path|
48
+ next if FileTest.directory? path
49
+ next unless File.extname(path).casecmp '.txt'
50
+ process_patch_file(path, :update)
51
+ processed_filenames << path[@patch_strings_dir.length+1..-1]
52
+ end
53
+
54
+ # Now "process" any files that should be generated
55
+ @strings.each do |string, contexts|
56
+ contexts.each do |context, trans|
57
+ unless processed_filenames.include? trans.patch_filename
58
+ process_patch_file("#{@patch_strings_dir}/#{trans.patch_filename}", :update)
59
+ processed_filenames << trans.patch_filename
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ # Load the translation strings indicated in the patch file,
66
+ # generate a new patch file with updated context information,
67
+ # and overwrite the patch
68
+ def process_patch_file(filename, mode)
69
+ patch_filename = filename[@patch_strings_dir.length+1..-1]
70
+
71
+ txt_version = nil
72
+
73
+ # Parser state information
74
+ state = :expecting
75
+ original_string = ''
76
+ contexts = []
77
+ translated_string = ''
78
+ new_contexts = nil
79
+
80
+ # Variables for the revised patch
81
+ context_comments = {}
82
+
83
+ # The revised patch
84
+ output = ''
85
+ output_write = false
86
+ pristine_translated_string = ''
87
+
88
+ if File.exists? filename
89
+ output_write = true if mode == :update
90
+ IO.read_txt(filename).each_line.with_index do |pristine_line, index|
91
+ # Remove comments and strip
92
+ pristine_line.gsub!(/\n$/, '')
93
+ line = pristine_line.gsub(/(?!\\)#.*$/, '').rstrip
94
+ comment = pristine_line.match(/(?<!\\)#.*$/).to_s.rstrip
95
+ line_num = index + 1
96
+
97
+ if line.start_with? '>'
98
+ instruction = line.gsub(/^>\s+/, '')
99
+
100
+ # Parse the patch version
101
+ parse_instruction(instruction, 'WOLF TRANS PATCH FILE VERSION') do |args|
102
+ unless txt_version == nil
103
+ raise "two version strings in file (line #{line_num})"
104
+ end
105
+ txt_version = Version.new(str: args.first)
106
+ if txt_version > TXT_VERSION
107
+ raise "patch version (#{new_version}) newer than can be read (#{TXT_VERSION})"
108
+ end
109
+ if mode == :update
110
+ output << "> WOLF TRANS PATCH FILE VERSION #{TXT_VERSION}"
111
+ output << comment unless comment.empty?
112
+ output << "\n"
113
+ end
114
+ end
115
+
116
+ # Make sure we have a version specified before reading other instructions
117
+ if txt_version == nil
118
+ raise "no version specified before first instruction"
119
+ end
120
+
121
+ # Now parse the instructions
122
+ parse_instruction(instruction, 'BEGIN STRING') do |args|
123
+ unless state == :expecting
124
+ raise "began another string without ending previous string (line #{line_num})"
125
+ end
126
+ state = :reading_original
127
+ original_string = ''
128
+ if mode == :update
129
+ output << pristine_line << "\n"
130
+ end
131
+ end
132
+
133
+ parse_instruction(instruction, 'END STRING') do |args|
134
+ if state == :expecting
135
+ raise "ended string without a begin (line #{line_num})"
136
+ elsif state == :reading_original
137
+ raise "ended string without a translation block (line #{line_num})"
138
+ end
139
+ state = :expecting
140
+ new_contexts = []
141
+ end
142
+
143
+ parse_instruction(instruction, 'CONTEXT') do |args|
144
+ if state == :expecting
145
+ raise "context outside of begin/end block (line #{line_num})"
146
+ end
147
+ if args.empty?
148
+ raise "no context string provided in context line (line #{line_num})"
149
+ end
150
+
151
+ # After a context, we're no longer reading the original text.
152
+ state = :reading_translation
153
+ begin
154
+ new_context = Context.from_string(args.shift)
155
+ rescue => e
156
+ raise e, "#{e} (line #{line_num})", e.backtrace
157
+ end
158
+ # Append context if translated_string is empty, since that means
159
+ # no translation was given.
160
+ if translated_string.empty?
161
+ contexts << new_context
162
+ else
163
+ new_contexts = [new_context]
164
+ end
165
+ if mode == :update
166
+ # Save the comment for later
167
+ context_comments[new_context] = comment
168
+ end
169
+ end
170
+
171
+ # If we have a new context list queued, flush the translation to all
172
+ # of the collected contexts
173
+ if new_contexts
174
+ original_string_new = unescape_string(original_string, false)
175
+ translated_string_new = unescape_string(translated_string, true)
176
+ contexts.each do |context|
177
+ if mode == :update
178
+ # Write an appropriate context line to the output
179
+ output << '> CONTEXT '
180
+ if @strings.include?(original_string_new) &&
181
+ @strings[original_string_new].include?(context)
182
+ output << @strings[original_string_new].select { |k,v| k.eql? context }.keys.first.to_s
183
+ output << ' < UNTRANSLATED' if translated_string_new.empty?
184
+ else
185
+ output << context.to_s << ' < UNUSED'
186
+ end
187
+ output << " " << context_comments[context] unless comment.empty?
188
+ output << "\n"
189
+ else
190
+ # Put translation in hash
191
+ @strings[original_string_new][context] = Translation.new(patch_filename, translated_string_new, false)
192
+ end
193
+ end
194
+ if mode == :update
195
+ # Write the translation
196
+ output << pristine_translated_string.rstrip << "\n"
197
+ # If the state is "expecting", that means we need to write the END STRING
198
+ # line to the output too.
199
+ if state == :expecting
200
+ output << pristine_line << "\n"
201
+ end
202
+ end
203
+
204
+ # Reset variables for next read
205
+ translated_string = ''
206
+ pristine_translated_string = ''
207
+ contexts = new_contexts
208
+
209
+ new_contexts = nil
210
+ end
211
+ else
212
+ # Parse text
213
+ if state == :expecting
214
+ unless line.empty?
215
+ raise "stray text outside of begin/end block (line #{line_num})"
216
+ end
217
+ elsif state == :reading_original
218
+ original_string << line << "\n"
219
+ elsif state == :reading_translation
220
+ translated_string << line << "\n"
221
+ if mode == :update
222
+ pristine_translated_string << pristine_line << "\n"
223
+ end
224
+ end
225
+ # Make no modifications to the patch line if we're not reading translations
226
+ unless state == :reading_translation
227
+ if mode == :update
228
+ output << pristine_line << "\n"
229
+ end
230
+ end
231
+ end
232
+ end
233
+
234
+ # Final error checking
235
+ if state != :expecting
236
+ raise "final begin/end block has no end"
237
+ end
238
+ else
239
+ # It's a new file, so just stick a header on it
240
+ if mode == :update
241
+ output << "> WOLF TRANS PATCH FILE VERSION #{TXT_VERSION}\n"
242
+ end
243
+ end
244
+
245
+ if mode == :update
246
+ # Write all the new strings to the file
247
+ @strings.each do |orig_string, contexts|
248
+ if contexts.values.any? { |trans| trans.autogenerate? && trans.patch_filename == patch_filename }
249
+ output_write = true
250
+ output << "\n> BEGIN STRING\n#{escape_string(orig_string)}\n"
251
+ contexts.each do |context, trans|
252
+ next unless trans.autogenerate?
253
+ trans.autogenerate = false
254
+ output << "> CONTEXT " << context.to_s << " < UNTRANSLATED\n"
255
+ end
256
+ output << "\n> END STRING\n"
257
+ end
258
+ end
259
+
260
+ # Write the output to the file
261
+ if output_write
262
+ FileUtils.mkdir_p(File.dirname(filename))
263
+ File.open(filename, 'wb') { |file| file.write(output) }
264
+ end
265
+ end
266
+ end
267
+
268
+ private
269
+ # Yields the arguments of the instruction if it matches tested_instruction
270
+ def parse_instruction(instruction, tested_instruction)
271
+ if instruction.start_with? tested_instruction
272
+ args = instruction.slice(tested_instruction.length..-1).strip.split(/\s+<\s+/)
273
+ if args.first == nil || args.first.empty?
274
+ yield []
275
+ else
276
+ yield args
277
+ end
278
+ end
279
+ end
280
+
281
+ # Unescapes a patch file string
282
+ def unescape_string(string, do_rtrim)
283
+ # Remove trailing whitespace or just newline
284
+ if do_rtrim
285
+ string = string.rstrip
286
+ else
287
+ string = string.gsub(/\n\z/m, '')
288
+ end
289
+ # Change escape sequences
290
+ string.match(/(?<!\\)\\[^sntr><#\\]/) do |match|
291
+ raise "unknown escape sequence '#{match}' #{string}"
292
+ end
293
+ string.gsub!("\\\\", "\x00") #HACK
294
+ string.gsub!("\\s", " ")
295
+ string.gsub!("\\n", "\n")
296
+ string.gsub!("\\t", "\t")
297
+ string.gsub!("\\r", "\r")
298
+ string.gsub!("\\>", ">")
299
+ string.gsub!("\\#", "#")
300
+ string.gsub!("\x00") { "\\" } #HACK
301
+ string
302
+ end
303
+
304
+ # Escapes a string for writing into a patch file
305
+ def escape_string(string)
306
+ string = string.gsub("\\", "\x00") #HACK
307
+ string.gsub!("\t", "\\t")
308
+ string.gsub!("\r", "\\r")
309
+ string.gsub!(">", "\\>")
310
+ string.gsub!("#", "\\#")
311
+ string.gsub!("\x00") { "\\\\" } #HACK
312
+ # Replace trailing spaces with \s
313
+ string.gsub!(/ *$/) { |m| "\\s" * m.length }
314
+ # Replace trailing newlines with \n
315
+ string.gsub!(/\n*\z/m) { |m| "\\n" * m.length }
316
+ string
317
+ end
318
+ end
319
+ end
@@ -0,0 +1,14 @@
1
+ # coding: utf-8
2
+ Gem::Specification.new do |s|
3
+ s.name = 'wolftrans'
4
+ s.version = '0.0.1'
5
+ s.date = '2015-09-14'
6
+ s.summary = 'A utility to translate Wolf RPG Editor games'
7
+ s.description = s.summary
8
+ s.authors = ['Mathew Velasquez']
9
+ s.email = 'mathewvq@gmail.com'
10
+ s.files = `git ls-files -z`.split("\x0")
11
+ s.executables << 'wolftrans'
12
+ s.homepage = 'http://mathew.link/'
13
+ s.license = 'MPL'
14
+ end
metadata ADDED
@@ -0,0 +1,60 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: wolftrans
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Mathew Velasquez
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-09-14 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: A utility to translate Wolf RPG Editor games
14
+ email: mathewvq@gmail.com
15
+ executables:
16
+ - wolftrans
17
+ extensions: []
18
+ extra_rdoc_files: []
19
+ files:
20
+ - LICENSE
21
+ - README.md
22
+ - bin/wolftrans
23
+ - lib/wolfrpg.rb
24
+ - lib/wolfrpg/command.rb
25
+ - lib/wolfrpg/common_events.rb
26
+ - lib/wolfrpg/database.rb
27
+ - lib/wolfrpg/game_dat.rb
28
+ - lib/wolfrpg/io.rb
29
+ - lib/wolfrpg/map.rb
30
+ - lib/wolfrpg/route.rb
31
+ - lib/wolftrans.rb
32
+ - lib/wolftrans/context.rb
33
+ - lib/wolftrans/patch_data.rb
34
+ - lib/wolftrans/patch_text.rb
35
+ - wolftrans.gemspec
36
+ homepage: http://mathew.link/
37
+ licenses:
38
+ - MPL
39
+ metadata: {}
40
+ post_install_message:
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubyforge_project:
56
+ rubygems_version: 2.4.5.1
57
+ signing_key:
58
+ specification_version: 4
59
+ summary: A utility to translate Wolf RPG Editor games
60
+ test_files: []