wolftrans 0.0.1

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.
@@ -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: []