twine 0.7.0 → 0.8.0

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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -0
  3. data/lib/twine.rb +21 -0
  4. data/lib/twine/cli.rb +68 -91
  5. data/lib/twine/formatters/abstract.rb +133 -82
  6. data/lib/twine/formatters/android.rb +49 -67
  7. data/lib/twine/formatters/apple.rb +33 -47
  8. data/lib/twine/formatters/django.rb +38 -48
  9. data/lib/twine/formatters/flash.rb +25 -40
  10. data/lib/twine/formatters/gettext.rb +37 -44
  11. data/lib/twine/formatters/jquery.rb +31 -33
  12. data/lib/twine/formatters/tizen.rb +38 -55
  13. data/lib/twine/output_processor.rb +57 -0
  14. data/lib/twine/placeholders.rb +54 -0
  15. data/lib/twine/runner.rb +78 -115
  16. data/lib/twine/stringsfile.rb +83 -60
  17. data/lib/twine/version.rb +1 -1
  18. data/test/command_test_case.rb +12 -0
  19. data/test/fixtures/consume_loc_drop.zip +0 -0
  20. data/test/fixtures/formatter_android.xml +15 -0
  21. data/test/fixtures/formatter_apple.strings +20 -0
  22. data/test/fixtures/formatter_django.po +28 -0
  23. data/test/fixtures/formatter_flash.properties +15 -0
  24. data/test/fixtures/formatter_gettext.po +26 -0
  25. data/test/fixtures/formatter_jquery.json +7 -0
  26. data/test/fixtures/formatter_tizen.xml +15 -0
  27. data/test/fixtures/gettext_multiline.po +10 -0
  28. data/test/fixtures/twine_accent_values.txt +13 -0
  29. data/test/test_abstract_formatter.rb +152 -0
  30. data/test/test_cli.rb +288 -0
  31. data/test/test_consume_loc_drop.rb +27 -0
  32. data/test/test_consume_string_file.rb +53 -0
  33. data/test/test_formatters.rb +236 -0
  34. data/test/test_generate_all_string_files.rb +44 -0
  35. data/test/test_generate_loc_drop.rb +44 -0
  36. data/test/test_generate_string_file.rb +51 -0
  37. data/test/test_output_processor.rb +85 -0
  38. data/test/test_placeholders.rb +86 -0
  39. data/test/test_strings_file.rb +58 -0
  40. data/test/test_strings_row.rb +47 -0
  41. data/test/test_validate_strings_file.rb +55 -0
  42. data/test/twine_file_dsl.rb +46 -0
  43. data/test/twine_test_case.rb +44 -0
  44. metadata +80 -37
  45. data/test/fixtures/en-1.json +0 -5
  46. data/test/fixtures/en-1.po +0 -16
  47. data/test/fixtures/en-1.strings +0 -10
  48. data/test/fixtures/en-2.po +0 -23
  49. data/test/fixtures/en-3.xml +0 -8
  50. data/test/fixtures/fr-1.xml +0 -10
  51. data/test/fixtures/strings-1.txt +0 -17
  52. data/test/fixtures/strings-2.txt +0 -5
  53. data/test/fixtures/strings-3.txt +0 -5
  54. data/test/fixtures/test-json-line-breaks/consumed.txt +0 -5
  55. data/test/fixtures/test-json-line-breaks/generated.json +0 -3
  56. data/test/fixtures/test-json-line-breaks/line-breaks.json +0 -3
  57. data/test/fixtures/test-json-line-breaks/line-breaks.txt +0 -4
  58. data/test/fixtures/test-output-1.txt +0 -12
  59. data/test/fixtures/test-output-10.txt +0 -9
  60. data/test/fixtures/test-output-11.txt +0 -9
  61. data/test/fixtures/test-output-12.txt +0 -12
  62. data/test/fixtures/test-output-2.txt +0 -12
  63. data/test/fixtures/test-output-3.txt +0 -18
  64. data/test/fixtures/test-output-4.txt +0 -21
  65. data/test/fixtures/test-output-5.txt +0 -4
  66. data/test/fixtures/test-output-6.txt +0 -10
  67. data/test/fixtures/test-output-7.txt +0 -16
  68. data/test/fixtures/test-output-8.txt +0 -9
  69. data/test/fixtures/test-output-9.txt +0 -21
  70. data/test/twine_test.rb +0 -134
@@ -60,50 +60,43 @@ module Twine
60
60
  end
61
61
  end
62
62
 
63
- def write_file(path, lang)
64
- default_lang = @strings.language_codes[0]
65
- encoding = @options[:output_encoding] || 'UTF-8'
66
- File.open(path, "w:#{encoding}") do |f|
67
- f.puts "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n\n"
68
- @strings.sections.each do |section|
69
- printed_section = false
70
- section.rows.each do |row|
71
- if row.matches_tags?(@options[:tags], @options[:untagged])
72
- if !printed_section
73
- f.puts ''
74
- if section.name && section.name.length > 0
75
- section_name = section.name.gsub('--', '—')
76
- f.puts "# SECTION: #{section_name}"
77
- end
78
- printed_section = true
79
- end
80
-
81
- basetrans = row.translated_string_for_lang(default_lang)
82
-
83
- if basetrans
84
- key = row.key
85
- key = key.gsub('"', '\\\\"')
86
-
87
- comment = row.comment
88
- if comment
89
- comment = comment.gsub('"', '\\\\"')
90
- end
91
-
92
- if comment && comment.length > 0
93
- f.print "#. \"#{comment}\"\n"
94
- end
95
-
96
- f.print "msgctxt \"#{key}\"\nmsgid \"#{basetrans}\"\n"
97
- value = row.translated_string_for_lang(lang)
98
- if value
99
- value = value.gsub('"', '\\\\"')
100
- end
101
- f.print "msgstr \"#{value}\"\n\n"
102
- end
103
- end
104
- end
105
- end
106
- end
63
+ def format_file(strings, lang)
64
+ @default_lang = strings.language_codes[0]
65
+ super
66
+ end
67
+
68
+ def format_header(lang)
69
+ "msgid \"\"\nmsgstr \"\"\n\"Language: #{lang}\\n\"\n\"X-Generator: Twine #{Twine::VERSION}\\n\"\n"
70
+ end
71
+
72
+ def format_section_header(section)
73
+ "# SECTION: #{section.name}"
74
+ end
75
+
76
+ def row_pattern
77
+ "%{comment}%{key}%{base_translation}%{value}"
78
+ end
79
+
80
+ def format_row(row, lang)
81
+ return nil unless row.translated_string_for_lang(@default_lang)
82
+
83
+ super
84
+ end
85
+
86
+ def format_comment(row, lang)
87
+ "#. \"#{escape_quotes(row.comment)}\"\n" if row.comment
88
+ end
89
+
90
+ def format_key(row, lang)
91
+ "msgctxt \"#{row.key.dup}\"\n"
92
+ end
93
+
94
+ def format_base_translation(row, lang)
95
+ "msgid \"#{row.translations[@default_lang]}\"\n"
96
+ end
97
+
98
+ def format_value(row, lang)
99
+ "msgstr \"#{row.translated_string_for_lang(lang)}\"\n"
107
100
  end
108
101
  end
109
102
  end
@@ -35,52 +35,50 @@ module Twine
35
35
  open(path) do |io|
36
36
  json = JSON.load(io)
37
37
  json.each do |key, value|
38
- value.gsub!("\n","\\n")
39
38
  set_translation_for_key(key, lang, value)
40
39
  end
41
40
  end
42
41
  end
43
42
 
44
- def write_file(path, lang)
45
- begin
46
- require "json"
47
- rescue LoadError
48
- raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
49
- end
43
+ def format_file(strings, lang)
44
+ "{\n#{super}\n}"
45
+ end
50
46
 
51
- printed_string = false
52
- default_lang = @strings.language_codes[0]
53
- encoding = @options[:output_encoding] || 'UTF-8'
54
- File.open(path, "w:#{encoding}") do |f|
55
- f.print "{"
47
+ def format_sections(strings, lang)
48
+ sections = strings.sections.map { |section| format_section(section, lang) }
49
+ sections.join(",\n\n")
50
+ end
51
+
52
+ def format_section_header(section)
53
+ end
56
54
 
57
- @strings.sections.each_with_index do |section, si|
58
- printed_section = false
59
- section.rows.each_with_index do |row, ri|
60
- if row.matches_tags?(@options[:tags], @options[:untagged])
61
- if printed_string
62
- f.print ",\n"
63
- end
55
+ def format_section(section, lang)
56
+ rows = section.rows.dup
64
57
 
65
- if !printed_section
66
- f.print "\n"
67
- printed_section = true
68
- end
58
+ rows.map! { |row| format_row(row, lang) }
59
+ rows.compact! # remove nil entries
60
+ rows.join(",\n")
61
+ end
69
62
 
70
- key = row.key
71
- key = key.gsub('"', '\\\\"')
63
+ def key_value_pattern
64
+ "\"%{key}\":\"%{value}\""
65
+ end
72
66
 
73
- value = row.translated_string_for_lang(lang, default_lang)
74
- value = value.gsub('"', '\\\\"')
67
+ def format_key(key)
68
+ escape_quotes(key)
69
+ end
75
70
 
76
- f.print "\"#{key}\":\"#{value}\""
77
- printed_string = true
78
- end
79
- end
80
- end
81
- f.puts "\n}"
71
+ def format_value(value)
72
+ escape_quotes(value)
73
+ end
82
74
 
75
+ def write_file(path, lang)
76
+ begin
77
+ require "json"
78
+ rescue LoadError
79
+ raise Twine::Error.new "You must run 'gem install json' in order to read or write jquery-localize files."
83
80
  end
81
+ super
84
82
  end
85
83
  end
86
84
  end
@@ -5,6 +5,8 @@ require 'rexml/document'
5
5
  module Twine
6
6
  module Formatters
7
7
  class Tizen < Abstract
8
+ include Twine::Placeholders
9
+
8
10
  FORMAT_NAME = 'tizen'
9
11
  EXTENSION = '.xml'
10
12
  DEFAULT_FILE_NAME = 'strings.xml'
@@ -90,7 +92,7 @@ module Twine
90
92
  value = CGI.unescapeHTML(value)
91
93
  value.gsub!('\\\'', '\'')
92
94
  value.gsub!('\\"', '"')
93
- value = iosify_substitutions(value)
95
+ value = convert_placeholders_from_android_to_twine(value)
94
96
  value.gsub!(/(\\u0020)*|(\\u0020)*\z/) { |spaces| ' ' * (spaces.length / 6) }
95
97
  else
96
98
  value = ""
@@ -111,64 +113,45 @@ module Twine
111
113
  end
112
114
  end
113
115
 
114
- def write_file(path, lang)
115
- default_lang = nil
116
- if DEFAULT_LANG_CODES.has_key?(lang)
117
- default_lang = DEFAULT_LANG_CODES[lang]
118
- end
119
- File.open(path, 'w:UTF-8') do |f|
120
- f.puts "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
121
- f.write '<string_table Bversion="2.0.0.201311071819" Dversion="20120315">'
122
- @strings.sections.each do |section|
123
- printed_section = false
124
- section.rows.each do |row|
125
- if row.matches_tags?(@options[:tags], @options[:untagged])
126
- if !printed_section
127
- f.puts ''
128
- if section.name && section.name.length > 0
129
- section_name = section.name.gsub('--', '—')
130
- f.puts "\t<!-- SECTION: #{section_name} -->"
131
- end
132
- printed_section = true
133
- end
116
+ def format_header(lang)
117
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!-- Tizen Strings File -->\n<!-- Generated by Twine #{Twine::VERSION} -->\n<!-- Language: #{lang} -->"
118
+ end
134
119
 
135
- key = row.key
120
+ def format_sections(strings, lang)
121
+ result = '<string_table Bversion="2.0.0.201311071819" Dversion="20120315">'
122
+
123
+ result += super + "\n"
136
124
 
137
- value = row.translated_string_for_lang(lang, default_lang)
138
- if !value && @options[:include_untranslated]
139
- value = row.translated_string_for_lang(@strings.language_codes[0])
140
- end
125
+ result += '</string_table>'
126
+ end
141
127
 
142
- if value # if values is nil, there was no appropriate translation, so let Tizen handle the defaulting
143
- value = String.new(value) # use a copy to prevent modifying the original
144
-
145
- # Tizen enforces the following rules on the values
146
- # 1) apostrophes and quotes must be escaped with a backslash
147
- value.gsub!('\'', '\\\\\'')
148
- value.gsub!('"', '\\\\"')
149
- # 2) HTML escape the string
150
- value = CGI.escapeHTML(value)
151
- # 3) fix substitutions (e.g. %s/%@)
152
- value = androidify_substitutions(value)
153
- # 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them.
154
- value.gsub!(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
155
-
156
- comment = row.comment
157
- if comment
158
- comment = comment.gsub('--', '—')
159
- end
160
-
161
- if comment && comment.length > 0
162
- f.puts "\t<!-- #{comment} -->\n"
163
- end
164
- f.puts "\t<text id=\"IDS_#{key.upcase}\">#{value}</text>"
165
- end
166
- end
167
- end
168
- end
128
+ def format_section_header(section)
129
+ "\t<!-- SECTION: #{section.name} -->"
130
+ end
169
131
 
170
- f.puts '</string_table>'
171
- end
132
+ def format_comment(row, lang)
133
+ "\t<!-- #{row.comment.gsub('--', '—')} -->\n" if row.comment
134
+ end
135
+
136
+ def key_value_pattern
137
+ "\t<text id=\"IDS_%{key}\">%{value}</text>"
138
+ end
139
+
140
+ def format_key(key)
141
+ key.upcase
142
+ end
143
+
144
+ def format_value(value)
145
+ value = escape_quotes(value)
146
+ # Tizen enforces the following rules on the values
147
+ # 1) apostrophes and quotes must be escaped with a backslash
148
+ value.gsub!("'", "\\\\'")
149
+ # 2) HTML escape the string
150
+ value = CGI.escapeHTML(value)
151
+ # 3) fix substitutions (e.g. %s/%@)
152
+ value = convert_placeholders_from_twine_to_android(value)
153
+ # 4) replace beginning and end spaces with \0020. Otherwise Tizen strips them.
154
+ value.gsub(/\A *| *\z/) { |spaces| '\u0020' * spaces.length }
172
155
  end
173
156
  end
174
157
  end
@@ -0,0 +1,57 @@
1
+ module Twine
2
+ module Processors
3
+
4
+ class OutputProcessor
5
+ def initialize(strings, options)
6
+ @strings = strings
7
+ @options = options
8
+ end
9
+
10
+ def default_language
11
+ @options[:developer_language] || @strings.language_codes[0]
12
+ end
13
+
14
+ def fallback_languages(language)
15
+ fallback_mapping = {
16
+ 'zh-TW' => 'zh-Hant' # if we don't have a zh-TW translation, try zh-Hant before en
17
+ }
18
+
19
+ [fallback_mapping[language], default_language].flatten.compact
20
+ end
21
+
22
+ def process(language)
23
+ result = StringsFile.new
24
+
25
+ result.language_codes.concat @strings.language_codes
26
+ @strings.sections.each do |section|
27
+ new_section = StringsSection.new section.name
28
+
29
+ section.rows.each do |row|
30
+ next unless row.matches_tags?(@options[:tags], @options[:untagged])
31
+
32
+ value = row.translated_string_for_lang(language)
33
+
34
+ next if value && @options[:include] == 'untranslated'
35
+
36
+ if value.nil? && @options[:include] != 'translated'
37
+ value = row.translated_string_for_lang(fallback_languages(language))
38
+ end
39
+
40
+ next unless value
41
+
42
+ new_row = row.dup
43
+ new_row.translations[language] = value
44
+
45
+ new_section.rows << new_row
46
+ result.strings_map[new_row.key] = new_row
47
+ end
48
+
49
+ result.sections << new_section
50
+ end
51
+
52
+ return result
53
+ end
54
+ end
55
+
56
+ end
57
+ end
@@ -0,0 +1,54 @@
1
+ module Twine
2
+ module Placeholders
3
+ extend self
4
+
5
+ PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH = '([-+ 0#])?(\d+|\*)?(\.(\d+|\*))?(hh?|ll?|L|z|j|t)?'
6
+ PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH = '(\d+\$)?' + PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH
7
+
8
+ # http://developer.android.com/guide/topics/resources/string-resource.html#FormattingAndStyling
9
+ # http://stackoverflow.com/questions/4414389/android-xml-percent-symbol
10
+ # https://github.com/mobiata/twine/pull/106
11
+ def convert_placeholders_from_twine_to_android(input)
12
+ placeholder_types = '[diufFeEgGxXoscpaA]'
13
+
14
+ # %@ -> %s
15
+ value = input.gsub(/(%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH})@/, '\1s')
16
+
17
+ placeholder_syntax = PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH + placeholder_types
18
+ placeholder_regex = /%#{placeholder_syntax}/
19
+
20
+ number_of_placeholders = value.scan(placeholder_regex).size
21
+
22
+ return value if number_of_placeholders == 0
23
+
24
+ # got placeholders -> need to double single percent signs
25
+ # % -> %% (but %% -> %%, %d -> %d)
26
+ single_percent_regex = /([^%])(%)(?!(%|#{placeholder_syntax}))/
27
+ value.gsub! single_percent_regex, '\1%%'
28
+
29
+ return value if number_of_placeholders < 2
30
+
31
+ # number placeholders
32
+ non_numbered_placeholder_regex = /%(#{PLACEHOLDER_FLAGS_WIDTH_PRECISION_LENGTH}#{placeholder_types})/
33
+
34
+ number_of_non_numbered_placeholders = value.scan(non_numbered_placeholder_regex).size
35
+
36
+ return value if number_of_non_numbered_placeholders == 0
37
+
38
+ raise Twine::Error.new("The value \"#{input}\" contains numbered and non-numbered placeholders") if number_of_placeholders != number_of_non_numbered_placeholders
39
+
40
+ # %d -> %$1d
41
+ index = 0
42
+ value.gsub!(non_numbered_placeholder_regex) { "%#{index += 1}$#{$1}" }
43
+
44
+ value
45
+ end
46
+
47
+ def convert_placeholders_from_android_to_twine(input)
48
+ placeholder_regex = /(%#{PLACEHOLDER_PARAMETER_FLAGS_WIDTH_PRECISION_LENGTH})s/
49
+
50
+ # %s -> %@
51
+ input.gsub(placeholder_regex, '\1@')
52
+ end
53
+ end
54
+ end
@@ -1,30 +1,38 @@
1
1
  require 'tmpdir'
2
+ require 'fileutils'
2
3
 
3
4
  Twine::Plugin.new # Initialize plugins first in Runner.
4
5
 
5
6
  module Twine
6
- VALID_COMMANDS = ['generate-string-file', 'generate-all-string-files', 'consume-string-file', 'consume-all-string-files', 'generate-loc-drop', 'consume-loc-drop', 'validate-strings-file']
7
-
8
7
  class Runner
9
- def initialize(args)
10
- @options = {}
11
- @args = args
12
- end
13
-
14
8
  def self.run(args)
15
- new(args).run
16
- end
9
+ options = CLI.parse(args)
10
+
11
+ strings = StringsFile.new
12
+ strings.read options[:strings_file]
13
+ runner = new(options, strings)
17
14
 
18
- def run
19
- # Parse all CLI arguments.
20
- CLI::parse_args(@args, @options)
21
- read_strings_data
22
- execute_command
15
+ case options[:command]
16
+ when 'generate-string-file'
17
+ runner.generate_string_file
18
+ when 'generate-all-string-files'
19
+ runner.generate_all_string_files
20
+ when 'consume-string-file'
21
+ runner.consume_string_file
22
+ when 'consume-all-string-files'
23
+ runner.consume_all_string_files
24
+ when 'generate-loc-drop'
25
+ runner.generate_loc_drop
26
+ when 'consume-loc-drop'
27
+ runner.consume_loc_drop
28
+ when 'validate-strings-file'
29
+ runner.validate_strings_file
30
+ end
23
31
  end
24
32
 
25
- def read_strings_data
26
- @strings = StringsFile.new
27
- @strings.read @options[:strings_file]
33
+ def initialize(options = {}, strings = StringsFile.new)
34
+ @options = options
35
+ @strings = strings
28
36
  end
29
37
 
30
38
  def write_strings_data(path)
@@ -34,44 +42,24 @@ module Twine
34
42
  @strings.write(path)
35
43
  end
36
44
 
37
- def execute_command
38
- case @options[:command]
39
- when 'generate-string-file'
40
- generate_string_file
41
- when 'generate-all-string-files'
42
- generate_all_string_files
43
- when 'consume-string-file'
44
- consume_string_file
45
- when 'consume-all-string-files'
46
- consume_all_string_files
47
- when 'generate-loc-drop'
48
- generate_loc_drop
49
- when 'consume-loc-drop'
50
- consume_loc_drop
51
- when 'validate-strings-file'
52
- validate_strings_file
53
- end
54
- end
55
-
56
45
  def generate_string_file
57
46
  lang = nil
58
- if @options[:languages]
59
- lang = @options[:languages][0]
60
- end
47
+ lang = @options[:languages][0] if @options[:languages]
61
48
 
62
49
  read_write_string_file(@options[:output_path], false, lang)
63
50
  end
64
51
 
65
52
  def generate_all_string_files
66
53
  if !File.directory?(@options[:output_path])
67
- raise Twine::Error.new("Directory does not exist: #{@options[:output_path]}")
54
+ if @options[:create_folders]
55
+ FileUtils.mkdir_p(@options[:output_path])
56
+ else
57
+ raise Twine::Error.new("Directory does not exist: #{@options[:output_path]}")
58
+ end
68
59
  end
69
60
 
70
- format = @options[:format]
71
- if !format
72
- format = determine_format_given_directory(@options[:output_path])
73
- end
74
- if !format
61
+ format = @options[:format] || determine_format_given_directory(@options[:output_path])
62
+ unless format
75
63
  raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
76
64
  end
77
65
 
@@ -101,7 +89,7 @@ module Twine
101
89
  begin
102
90
  read_write_string_file(item, true, nil)
103
91
  rescue Twine::Error => e
104
- STDERR.puts "#{e.message}"
92
+ Twine::stderr.puts "#{e.message}"
105
93
  end
106
94
  end
107
95
  end
@@ -115,23 +103,15 @@ module Twine
115
103
  raise Twine::Error.new("File does not exist: #{path}")
116
104
  end
117
105
 
118
- format = @options[:format]
119
- if !format
120
- format = determine_format_given_path(path)
121
- end
122
- if !format
106
+ format = @options[:format] || determine_format_given_path(path)
107
+ unless format
123
108
  raise Twine::Error.new "Unable to determine format of #{path}"
124
109
  end
125
110
 
126
111
  formatter = formatter_for_format(format)
127
112
 
128
- if !lang
129
- lang = determine_language_given_path(path)
130
- end
131
- if !lang
132
- lang = formatter.determine_language_given_path(path)
133
- end
134
- if !lang
113
+ lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
114
+ unless lang
135
115
  raise Twine::Error.new "Unable to determine language for #{path}"
136
116
  end
137
117
 
@@ -147,18 +127,14 @@ module Twine
147
127
  end
148
128
 
149
129
  def generate_loc_drop
150
- begin
151
- require 'zip/zip'
152
- rescue LoadError
153
- raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
154
- end
130
+ require_rubyzip
155
131
 
156
132
  if File.file?(@options[:output_path])
157
133
  File.delete(@options[:output_path])
158
134
  end
159
135
 
160
136
  Dir.mktmpdir do |dir|
161
- Zip::ZipFile.open(@options[:output_path], Zip::ZipFile::CREATE) do |zipfile|
137
+ Zip::File.open(@options[:output_path], Zip::File::CREATE) do |zipfile|
162
138
  zipfile.mkdir('Locales')
163
139
 
164
140
  formatter = formatter_for_format(@options[:format])
@@ -176,18 +152,14 @@ module Twine
176
152
  end
177
153
 
178
154
  def consume_loc_drop
155
+ require_rubyzip
156
+
179
157
  if !File.file?(@options[:input_path])
180
158
  raise Twine::Error.new("File does not exist: #{@options[:input_path]}")
181
159
  end
182
160
 
183
- begin
184
- require 'zip/zip'
185
- rescue LoadError
186
- raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
187
- end
188
-
189
161
  Dir.mktmpdir do |dir|
190
- Zip::ZipFile.open(@options[:input_path]) do |zipfile|
162
+ Zip::File.open(@options[:input_path]) do |zipfile|
191
163
  zipfile.each do |entry|
192
164
  if !entry.name.end_with?'/' and !File.basename(entry.name).start_with?'.'
193
165
  real_path = File.join(dir, entry.name)
@@ -196,7 +168,7 @@ module Twine
196
168
  begin
197
169
  read_write_string_file(real_path, true, nil)
198
170
  rescue Twine::Error => e
199
- STDERR.puts "#{e.message}"
171
+ Twine::stderr.puts "#{e.message}"
200
172
  end
201
173
  end
202
174
  end
@@ -212,81 +184,72 @@ module Twine
212
184
  all_keys = Set.new
213
185
  duplicate_keys = Set.new
214
186
  keys_without_tags = Set.new
215
- errors = []
187
+ invalid_keys = Set.new
188
+ valid_key_regex = /^[A-Za-z0-9_]+$/
216
189
 
217
190
  @strings.sections.each do |section|
218
191
  section.rows.each do |row|
219
192
  total_strings += 1
220
193
 
221
- if all_keys.include? row.key
222
- duplicate_keys.add(row.key)
223
- else
224
- all_keys.add(row.key)
225
- end
194
+ duplicate_keys.add(row.key) if all_keys.include? row.key
195
+ all_keys.add(row.key)
226
196
 
227
- if row.tags == nil || row.tags.length == 0
228
- keys_without_tags.add(row.key)
229
- end
197
+ keys_without_tags.add(row.key) if row.tags == nil or row.tags.length == 0
198
+
199
+ invalid_keys << row.key unless row.key =~ valid_key_regex
230
200
  end
231
201
  end
232
202
 
233
- if duplicate_keys.length > 0
234
- error_body = duplicate_keys.to_a.join("\n ")
235
- errors << "Found duplicate string key(s):\n #{error_body}"
203
+ errors = []
204
+ join_keys = lambda { |set| set.map { |k| " " + k }.join("\n") }
205
+
206
+ unless duplicate_keys.empty?
207
+ errors << "Found duplicate string key(s):\n#{join_keys.call(duplicate_keys)}"
236
208
  end
237
209
 
238
210
  if keys_without_tags.length == total_strings
239
211
  errors << "None of your strings have tags."
240
212
  elsif keys_without_tags.length > 0
241
- error_body = keys_without_tags.to_a.join("\n ")
242
- errors << "Found strings(s) without tags:\n #{error_body}"
213
+ errors << "Found strings without tags:\n#{join_keys.call(keys_without_tags)}"
243
214
  end
244
215
 
245
- if errors.length > 0
246
- raise Twine::Error.new errors.join("\n\n")
216
+ unless invalid_keys.empty?
217
+ errors << "Found key(s) with invalid characters:\n#{join_keys.call(invalid_keys)}"
247
218
  end
248
219
 
249
- puts "#{@options[:strings_file]} is valid."
220
+ raise Twine::Error.new errors.join("\n\n") unless errors.empty?
221
+
222
+ Twine::stdout.puts "#{@options[:strings_file]} is valid."
250
223
  end
251
224
 
252
225
  def determine_language_given_path(path)
253
226
  code = File.basename(path, File.extname(path))
254
- if !@strings.language_codes.include? code
255
- code = nil
256
- end
257
-
258
- code
227
+ return code if @strings.language_codes.include? code
259
228
  end
260
229
 
261
230
  def determine_format_given_path(path)
262
- ext = File.extname(path)
263
- Formatters.formatters.each do |formatter|
264
- if formatter::EXTENSION == ext
265
- return formatter::FORMAT_NAME
266
- end
267
- end
268
-
269
- return
231
+ formatter = Formatters.formatters.find { |f| f::EXTENSION == File.extname(path) }
232
+ return formatter::FORMAT_NAME if formatter
270
233
  end
271
234
 
272
235
  def determine_format_given_directory(directory)
273
- Formatters.formatters.each do |formatter|
274
- if formatter.can_handle_directory?(directory)
275
- return formatter::FORMAT_NAME
276
- end
277
- end
278
-
279
- return
236
+ formatter = Formatters.formatters.find { |f| f.can_handle_directory?(directory) }
237
+ return formatter::FORMAT_NAME if formatter
280
238
  end
281
239
 
282
240
  def formatter_for_format(format)
283
- Formatters.formatters.each do |formatter|
284
- if formatter::FORMAT_NAME == format
285
- return formatter.new(@strings, @options)
286
- end
287
- end
241
+ formatter = Formatters.formatters.find { |f| f::FORMAT_NAME == format }
242
+ return formatter.new(@strings, @options) if formatter
243
+ end
244
+
245
+ private
288
246
 
289
- return
247
+ def require_rubyzip
248
+ begin
249
+ require 'zip'
250
+ rescue LoadError
251
+ raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
252
+ end
290
253
  end
291
254
  end
292
255
  end