twine 0.8.0 → 0.8.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 46fb29dd5d1ec899c427901e72e40c081fa8c6c4
4
- data.tar.gz: 3faf58c634c4d1903fda10a1ece3cace658dc761
3
+ metadata.gz: ebc7f372d107cb4c3b9a12da92b792c510209d59
4
+ data.tar.gz: 72ccda8290612adfc0f7ac75f86756bc1d945333
5
5
  SHA512:
6
- metadata.gz: 6ec4b6098a24520cba05e4bb2bd0ea16602c9b52382abfeb45e9bf0308a4b1c03287251160cdec49cb26f44ccd69ff96d17f5dd0c9d56b7612068435c750dfb2
7
- data.tar.gz: b1a85e5a3fb56e20a790e7beb8649784f5ffab42f4e8b6db7132680a634275caad8ab698a09369d9b46821c873bf8a51c416f941337d1b101ad079f6bd6d2960
6
+ metadata.gz: 44bf1d87f0b3e88e70cc8962dd708a35866a74710e95706ae5e4061c02995b00afb6175acb1d5219605ec391b60ceeddf01edb83bd253d73e14681847cd8aac6
7
+ data.tar.gz: ea504d997033b778bb9f458fb33ef1f75bfb4d57c6da1771685f092c3fa1b2362f8bdf82d2a85cbcaade440c42617f21e130c7baab7767f5522f27843f78ca5f
@@ -23,11 +23,11 @@ module Twine
23
23
 
24
24
  require 'twine/plugin'
25
25
  require 'twine/cli'
26
+ require 'twine/stringsfile'
26
27
  require 'twine/encoding'
27
28
  require 'twine/output_processor'
28
29
  require 'twine/placeholders'
29
30
  require 'twine/formatters'
30
31
  require 'twine/runner'
31
- require 'twine/stringsfile'
32
32
  require 'twine/version'
33
33
  end
@@ -46,7 +46,7 @@ module Twine
46
46
  opts.on('-u', '--untagged', 'If you have specified tags using the --tags flag, then only those tags will be selected. If you also want to select all strings that are untagged, then you can specify this option to do so.') do |u|
47
47
  options[:untagged] = true
48
48
  end
49
- formats = Formatters.formatters.map { |f| f::FORMAT_NAME }
49
+ formats = Formatters.formatters.map(&:format_name)
50
50
  opts.on('-f', '--format FORMAT', "The file format to read or write (#{formats.join(', ')}). Additional formatters can be placed in the formats/ directory.") do |format|
51
51
  unless formats.include?(format.downcase)
52
52
  raise Twine::Error.new "Invalid format: #{format}"
@@ -89,6 +89,12 @@ module Twine
89
89
  end
90
90
  options[:output_encoding] = e
91
91
  end
92
+ opts.on('--validate', 'Validate the strings file before formatting it') do
93
+ options[:validate] = true
94
+ end
95
+ opts.on('-p', '--pedantic', 'When validating a strings file, perform additional checks that go beyond pure validity (like presence of tags)') do
96
+ options[:pedantic] = true
97
+ end
92
98
  opts.on('-h', '--help', 'Show this message.') do |h|
93
99
  puts opts.help
94
100
  exit
@@ -1,15 +1,6 @@
1
- require 'twine/formatters/abstract'
2
- require 'twine/formatters/android'
3
- require 'twine/formatters/apple'
4
- require 'twine/formatters/flash'
5
- require 'twine/formatters/gettext'
6
- require 'twine/formatters/jquery'
7
- require 'twine/formatters/django'
8
- require 'twine/formatters/tizen'
9
-
10
1
  module Twine
11
2
  module Formatters
12
- @formatters = [Formatters::Apple, Formatters::Android, Formatters::Gettext, Formatters::JQuery, Formatters::Flash, Formatters::Django, Formatters::Tizen]
3
+ @formatters = []
13
4
 
14
5
  class << self
15
6
  attr_reader :formatters
@@ -22,9 +13,12 @@ module Twine
22
13
  # returns array of active formatters
23
14
  #
24
15
  def register_formatter formatter_class
25
- raise "#{formatter_class} already registered" if @formatters.include? formatter_class
26
- @formatters << formatter_class
16
+ @formatters << formatter_class.new
27
17
  end
28
18
  end
29
19
  end
30
- end
20
+ end
21
+
22
+ Dir[File.join(File.dirname(__FILE__), 'formatters', '*.rb')].each do |file|
23
+ require file
24
+ end
@@ -3,17 +3,28 @@ require 'fileutils'
3
3
  module Twine
4
4
  module Formatters
5
5
  class Abstract
6
- attr_reader :strings
7
- attr_reader :options
6
+ attr_accessor :strings
7
+ attr_accessor :options
8
8
 
9
- def self.can_handle_directory?(path)
10
- return false
9
+ def initialize
10
+ @strings = StringsFile.new
11
+ @options = {}
11
12
  end
12
13
 
13
- def initialize(strings, options)
14
- @strings = strings
15
- @options = options
16
- @output_processor = Processors::OutputProcessor.new @strings, @options
14
+ def format_name
15
+ raise NotImplementedError.new("You must implement format_name in your formatter class.")
16
+ end
17
+
18
+ def extension
19
+ raise NotImplementedError.new("You must implement extension in your formatter class.")
20
+ end
21
+
22
+ def can_handle_directory?(path)
23
+ raise NotImplementedError.new("You must implement can_handle_directory? in your formatter class.")
24
+ end
25
+
26
+ def default_file_name
27
+ raise NotImplementedError.new("You must implement default_file_name in your formatter class.")
17
28
  end
18
29
 
19
30
  def set_translation_for_key(key, lang, value)
@@ -64,10 +75,6 @@ module Twine
64
75
  end
65
76
  end
66
77
 
67
- def default_file_name
68
- raise NotImplementedError.new("You must implement default_file_name in your formatter class.")
69
- end
70
-
71
78
  def determine_language_given_path(path)
72
79
  raise NotImplementedError.new("You must implement determine_language_given_path in your formatter class.")
73
80
  end
@@ -92,21 +99,25 @@ module Twine
92
99
 
93
100
  def format_sections(strings, lang)
94
101
  sections = strings.sections.map { |section| format_section(section, lang) }
95
- sections.join("\n")
102
+ sections.compact.join("\n")
96
103
  end
97
104
 
98
105
  def format_section_header(section)
99
106
  end
100
107
 
108
+ def should_include_row(row, lang)
109
+ row.translated_string_for_lang(lang)
110
+ end
111
+
101
112
  def format_section(section, lang)
102
- rows = section.rows.dup
113
+ rows = section.rows.select { |row| should_include_row(row, lang) }
114
+ return if rows.empty?
103
115
 
104
116
  result = ""
105
- unless rows.empty?
106
- if section.name && section.name.length > 0
107
- section_header = format_section_header(section)
108
- result += "\n#{section_header}" if section_header
109
- end
117
+
118
+ if section.name && section.name.length > 0
119
+ section_header = format_section_header(section)
120
+ result += "\n#{section_header}" if section_header
110
121
  end
111
122
 
112
123
  rows.map! { |row| format_row(row, lang) }
@@ -115,16 +126,8 @@ module Twine
115
126
  result += rows.join
116
127
  end
117
128
 
118
- def row_pattern
119
- "%{comment}%{key_value}"
120
- end
121
-
122
129
  def format_row(row, lang)
123
- return nil unless row.translated_string_for_lang(lang)
124
-
125
- result = row_pattern.scan(/%\{([a-z_]+)\}/).flatten
126
- result.map! { |element| send("format_#{element}".to_sym, row, lang) }
127
- result.flatten.join
130
+ [format_comment(row, lang), format_key_value(row, lang)].compact.join
128
131
  end
129
132
 
130
133
  def format_comment(row, lang)
@@ -152,10 +155,10 @@ module Twine
152
155
  end
153
156
 
154
157
  def write_file(path, lang)
155
- encoding = @options[:output_encoding] || 'UTF-8'
156
-
157
- processed_strings = @output_processor.process(lang)
158
+ output_processor = Processors::OutputProcessor.new(@strings, @options)
159
+ processed_strings = output_processor.process(lang)
158
160
 
161
+ encoding = @options[:output_encoding] || 'UTF-8'
159
162
  File.open(path, "w:#{encoding}") do |f|
160
163
  f.puts format_file(processed_strings, lang)
161
164
  end
@@ -169,7 +172,8 @@ module Twine
169
172
 
170
173
  FileUtils.mkdir_p(output_path)
171
174
 
172
- write_file(File.join(output_path, file_name), lang)
175
+ file_path = File.join(output_path, file_name)
176
+ write_file(file_path, lang)
173
177
  end
174
178
  else
175
179
  language_written = false
@@ -182,7 +186,8 @@ module Twine
182
186
  lang = determine_language_given_path(item)
183
187
  next unless lang
184
188
 
185
- write_file(File.join(item, file_name), lang)
189
+ file_path = File.join(item, file_name)
190
+ write_file(file_path, lang)
186
191
  language_written = true
187
192
  end
188
193
 
@@ -7,9 +7,6 @@ module Twine
7
7
  class Android < Abstract
8
8
  include Twine::Placeholders
9
9
 
10
- FORMAT_NAME = 'android'
11
- EXTENSION = '.xml'
12
- DEFAULT_FILE_NAME = 'strings.xml'
13
10
  LANG_CODES = Hash[
14
11
  'zh' => 'zh-Hans',
15
12
  'zh-rCN' => 'zh-Hans',
@@ -20,12 +17,20 @@ module Twine
20
17
  # TODO: spanish
21
18
  ]
22
19
 
23
- def self.can_handle_directory?(path)
20
+ def format_name
21
+ 'android'
22
+ end
23
+
24
+ def extension
25
+ '.xml'
26
+ end
27
+
28
+ def can_handle_directory?(path)
24
29
  Dir.entries(path).any? { |item| /^values.*$/.match(item) }
25
30
  end
26
31
 
27
32
  def default_file_name
28
- return DEFAULT_FILE_NAME
33
+ return 'strings.xml'
29
34
  end
30
35
 
31
36
  def determine_language_given_path(path)
@@ -135,3 +140,5 @@ module Twine
135
140
  end
136
141
  end
137
142
  end
143
+
144
+ Twine::Formatters.formatters << Twine::Formatters::Android.new
@@ -1,16 +1,20 @@
1
1
  module Twine
2
2
  module Formatters
3
3
  class Apple < Abstract
4
- FORMAT_NAME = 'apple'
5
- EXTENSION = '.strings'
6
- DEFAULT_FILE_NAME = 'Localizable.strings'
4
+ def format_name
5
+ 'apple'
6
+ end
7
+
8
+ def extension
9
+ '.strings'
10
+ end
7
11
 
8
- def self.can_handle_directory?(path)
12
+ def can_handle_directory?(path)
9
13
  Dir.entries(path).any? { |item| /^.+\.lproj$/.match(item) }
10
14
  end
11
15
 
12
16
  def default_file_name
13
- return DEFAULT_FILE_NAME
17
+ return 'Localizable.strings'
14
18
  end
15
19
 
16
20
  def determine_language_given_path(path)
@@ -111,3 +115,5 @@ module Twine
111
115
  end
112
116
  end
113
117
  end
118
+
119
+ Twine::Formatters.formatters << Twine::Formatters::Apple.new
@@ -1,16 +1,20 @@
1
1
  module Twine
2
2
  module Formatters
3
3
  class Django < Abstract
4
- FORMAT_NAME = 'django'
5
- EXTENSION = '.po'
6
- DEFAULT_FILE_NAME = 'strings.po'
4
+ def format_name
5
+ 'django'
6
+ end
7
+
8
+ def extension
9
+ '.po'
10
+ end
7
11
 
8
- def self.can_handle_directory?(path)
9
- Dir.entries(path).any? { |item| /^.+\.po$/.match(item) }
12
+ def can_handle_directory?(path)
13
+ Dir.entries(path).any? { |item| /^.+\.po$/.match(item) }
10
14
  end
11
15
 
12
16
  def default_file_name
13
- return DEFAULT_FILE_NAME
17
+ return 'strings.po'
14
18
  end
15
19
 
16
20
  def determine_language_given_path(path)
@@ -92,7 +96,9 @@ module Twine
92
96
 
93
97
  def format_file(strings, lang)
94
98
  @default_lang = strings.language_codes[0]
95
- super
99
+ result = super
100
+ @default_lang = nil
101
+ result
96
102
  end
97
103
 
98
104
  def format_header(lang)
@@ -103,11 +109,11 @@ module Twine
103
109
  "#--------- #{section.name} ---------#\n"
104
110
  end
105
111
 
106
- def row_pattern
107
- "%{comment}%{base_translation}%{key_value}"
112
+ def format_row(row, lang)
113
+ [format_comment(row, lang), format_base_translation(row), format_key_value(row, lang)].compact.join
108
114
  end
109
115
 
110
- def format_base_translation(row, lang)
116
+ def format_base_translation(row)
111
117
  base_translation = row.translations[@default_lang]
112
118
  "# base translation: \"#{base_translation}\"\n" if base_translation
113
119
  end
@@ -131,3 +137,5 @@ module Twine
131
137
  end
132
138
  end
133
139
  end
140
+
141
+ Twine::Formatters.formatters << Twine::Formatters::Django.new
@@ -1,16 +1,20 @@
1
1
  module Twine
2
2
  module Formatters
3
3
  class Flash < Abstract
4
- FORMAT_NAME = 'flash'
5
- EXTENSION = '.properties'
6
- DEFAULT_FILE_NAME = 'resources.properties'
4
+ def format_name
5
+ 'flash'
6
+ end
7
+
8
+ def extension
9
+ '.properties'
10
+ end
7
11
 
8
- def self.can_handle_directory?(path)
12
+ def can_handle_directory?(path)
9
13
  return false
10
14
  end
11
15
 
12
16
  def default_file_name
13
- return DEFAULT_FILE_NAME
17
+ return 'resources.properties'
14
18
  end
15
19
 
16
20
  def determine_language_given_path(path)
@@ -93,3 +97,5 @@ module Twine
93
97
  end
94
98
  end
95
99
  end
100
+
101
+ Twine::Formatters.formatters << Twine::Formatters::Flash.new
@@ -3,16 +3,20 @@
3
3
  module Twine
4
4
  module Formatters
5
5
  class Gettext < Abstract
6
- FORMAT_NAME = 'gettext'
7
- EXTENSION = '.po'
8
- DEFAULT_FILE_NAME = 'strings.po'
6
+ def format_name
7
+ 'gettext'
8
+ end
9
+
10
+ def extension
11
+ '.po'
12
+ end
9
13
 
10
- def self.can_handle_directory?(path)
14
+ def can_handle_directory?(path)
11
15
  Dir.entries(path).any? { |item| /^.+\.po$/.match(item) }
12
16
  end
13
17
 
14
18
  def default_file_name
15
- return DEFAULT_FILE_NAME
19
+ return 'strings.po'
16
20
  end
17
21
 
18
22
  def determine_language_given_path(path)
@@ -62,7 +66,9 @@ module Twine
62
66
 
63
67
  def format_file(strings, lang)
64
68
  @default_lang = strings.language_codes[0]
65
- super
69
+ result = super
70
+ @default_lang = nil
71
+ result
66
72
  end
67
73
 
68
74
  def format_header(lang)
@@ -73,31 +79,32 @@ module Twine
73
79
  "# SECTION: #{section.name}"
74
80
  end
75
81
 
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
82
+ def should_include_row(row, lang)
83
+ super and row.translated_string_for_lang(@default_lang)
84
84
  end
85
85
 
86
86
  def format_comment(row, lang)
87
87
  "#. \"#{escape_quotes(row.comment)}\"\n" if row.comment
88
88
  end
89
89
 
90
- def format_key(row, lang)
91
- "msgctxt \"#{row.key.dup}\"\n"
90
+ def format_key_value(row, lang)
91
+ value = row.translated_string_for_lang(lang)
92
+ [format_key(row.key.dup), format_base_translation(row), format_value(value.dup)].compact.join
93
+ end
94
+
95
+ def format_key(key)
96
+ "msgctxt \"#{key}\"\n"
92
97
  end
93
98
 
94
- def format_base_translation(row, lang)
99
+ def format_base_translation(row)
95
100
  "msgid \"#{row.translations[@default_lang]}\"\n"
96
101
  end
97
102
 
98
- def format_value(row, lang)
99
- "msgstr \"#{row.translated_string_for_lang(lang)}\"\n"
103
+ def format_value(value)
104
+ "msgstr \"#{value}\"\n"
100
105
  end
101
106
  end
102
107
  end
103
108
  end
109
+
110
+ Twine::Formatters.formatters << Twine::Formatters::Gettext.new
@@ -1,16 +1,20 @@
1
1
  module Twine
2
2
  module Formatters
3
3
  class JQuery < Abstract
4
- FORMAT_NAME = 'jquery'
5
- EXTENSION = '.json'
6
- DEFAULT_FILE_NAME = 'localize.json'
4
+ def format_name
5
+ 'jquery'
6
+ end
7
+
8
+ def extension
9
+ '.json'
10
+ end
7
11
 
8
- def self.can_handle_directory?(path)
12
+ def can_handle_directory?(path)
9
13
  Dir.entries(path).any? { |item| /^.+\.json$/.match(item) }
10
14
  end
11
15
 
12
16
  def default_file_name
13
- return DEFAULT_FILE_NAME
17
+ return 'localize.json'
14
18
  end
15
19
 
16
20
  def determine_language_given_path(path)
@@ -71,15 +75,8 @@ module Twine
71
75
  def format_value(value)
72
76
  escape_quotes(value)
73
77
  end
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."
80
- end
81
- super
82
- end
83
78
  end
84
79
  end
85
80
  end
81
+
82
+ Twine::Formatters.formatters << Twine::Formatters::JQuery.new
@@ -7,9 +7,6 @@ module Twine
7
7
  class Tizen < Abstract
8
8
  include Twine::Placeholders
9
9
 
10
- FORMAT_NAME = 'tizen'
11
- EXTENSION = '.xml'
12
- DEFAULT_FILE_NAME = 'strings.xml'
13
10
  LANG_CODES = Hash[
14
11
  'eng-GB' => 'en',
15
12
  'rus-RU' => 'ru',
@@ -22,39 +19,21 @@ module Twine
22
19
  'por-PT' => 'pt',
23
20
  'ukr-UA' => 'uk'
24
21
  ]
25
- DEFAULT_LANG_CODES = Hash[
26
- ]
27
22
 
28
- def self.can_handle_directory?(path)
29
- Dir.entries(path).any? { |item| /^values.*$/.match(item) }
23
+ def format_name
24
+ 'tizen'
30
25
  end
31
26
 
32
- def default_file_name
33
- return DEFAULT_FILE_NAME
27
+ def extension
28
+ '.xml'
34
29
  end
35
30
 
36
- def write_all_files(path)
37
- if !File.directory?(path)
38
- raise Twine::Error.new("Directory does not exist: #{path}")
39
- end
31
+ def can_handle_directory?(path)
32
+ Dir.entries(path).any? { |item| /^values.*$/.match(item) }
33
+ end
40
34
 
41
- langs_written = []
42
- Dir.foreach(path) do |item|
43
- if item == "." or item == ".."
44
- next
45
- end
46
- item = File.join(path, item)
47
- if !File.directory?(item)
48
- lang = determine_language_given_path(item)
49
- if lang
50
- write_file(item, lang)
51
- langs_written << lang
52
- end
53
- end
54
- end
55
- if langs_written.empty?
56
- raise Twine::Error.new("Failed to genertate any files: No languages found at #{path}")
57
- end
35
+ def default_file_name
36
+ return 'strings.xml'
58
37
  end
59
38
 
60
39
  def determine_language_given_path(path)
@@ -156,3 +135,5 @@ module Twine
156
135
  end
157
136
  end
158
137
  end
138
+
139
+ Twine::Formatters.formatters << Twine::Formatters::Tizen.new
@@ -43,13 +43,17 @@ module Twine
43
43
  end
44
44
 
45
45
  def generate_string_file
46
+ validate_strings_file if @options[:validate]
47
+
46
48
  lang = nil
47
49
  lang = @options[:languages][0] if @options[:languages]
48
50
 
49
- read_write_string_file(@options[:output_path], false, lang)
51
+ write_string_file(@options[:output_path], lang)
50
52
  end
51
53
 
52
54
  def generate_all_string_files
55
+ validate_strings_file if @options[:validate]
56
+
53
57
  if !File.directory?(@options[:output_path])
54
58
  if @options[:create_folders]
55
59
  FileUtils.mkdir_p(@options[:output_path])
@@ -58,13 +62,13 @@ module Twine
58
62
  end
59
63
  end
60
64
 
61
- format = @options[:format] || determine_format_given_directory(@options[:output_path])
62
- unless format
65
+ formatter_for_directory = find_formatter { |f| f.can_handle_directory?(@options[:output_path]) }
66
+ formatter = formatter_for_format(@options[:format]) || formatter_for_directory
67
+
68
+ unless formatter
63
69
  raise Twine::Error.new "Could not determine format given the contents of #{@options[:output_path]}"
64
70
  end
65
71
 
66
- formatter = formatter_for_format(format)
67
-
68
72
  formatter.write_all_files(@options[:output_path])
69
73
  end
70
74
 
@@ -74,7 +78,7 @@ module Twine
74
78
  lang = @options[:languages][0]
75
79
  end
76
80
 
77
- read_write_string_file(@options[:input_path], true, lang)
81
+ read_string_file(@options[:input_path], lang)
78
82
  output_path = @options[:output_path] || @options[:strings_file]
79
83
  write_strings_data(output_path)
80
84
  end
@@ -87,7 +91,7 @@ module Twine
87
91
  Dir.glob(File.join(@options[:input_path], "**/*")) do |item|
88
92
  if File.file?(item)
89
93
  begin
90
- read_write_string_file(item, true, nil)
94
+ read_string_file(item)
91
95
  rescue Twine::Error => e
92
96
  Twine::stderr.puts "#{e.message}"
93
97
  end
@@ -98,35 +102,9 @@ module Twine
98
102
  write_strings_data(output_path)
99
103
  end
100
104
 
101
- def read_write_string_file(path, is_read, lang)
102
- if is_read && !File.file?(path)
103
- raise Twine::Error.new("File does not exist: #{path}")
104
- end
105
-
106
- format = @options[:format] || determine_format_given_path(path)
107
- unless format
108
- raise Twine::Error.new "Unable to determine format of #{path}"
109
- end
110
-
111
- formatter = formatter_for_format(format)
112
-
113
- lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
114
- unless lang
115
- raise Twine::Error.new "Unable to determine language for #{path}"
116
- end
117
-
118
- if !@strings.language_codes.include? lang
119
- @strings.language_codes << lang
120
- end
121
-
122
- if is_read
123
- formatter.read_file(path, lang)
124
- else
125
- formatter.write_file(path, lang)
126
- end
127
- end
128
-
129
105
  def generate_loc_drop
106
+ validate_strings_file if @options[:validate]
107
+
130
108
  require_rubyzip
131
109
 
132
110
  if File.file?(@options[:output_path])
@@ -140,7 +118,7 @@ module Twine
140
118
  formatter = formatter_for_format(@options[:format])
141
119
  @strings.language_codes.each do |lang|
142
120
  if @options[:languages] == nil || @options[:languages].length == 0 || @options[:languages].include?(lang)
143
- file_name = lang + formatter.class::EXTENSION
121
+ file_name = lang + formatter.extension
144
122
  real_path = File.join(dir, file_name)
145
123
  zip_path = File.join('Locales', file_name)
146
124
  formatter.write_file(real_path, lang)
@@ -166,7 +144,7 @@ module Twine
166
144
  FileUtils.mkdir_p(File.dirname(real_path))
167
145
  zipfile.extract(entry.name, real_path)
168
146
  begin
169
- read_write_string_file(real_path, true, nil)
147
+ read_string_file(real_path)
170
148
  rescue Twine::Error => e
171
149
  Twine::stderr.puts "#{e.message}"
172
150
  end
@@ -207,10 +185,12 @@ module Twine
207
185
  errors << "Found duplicate string key(s):\n#{join_keys.call(duplicate_keys)}"
208
186
  end
209
187
 
210
- if keys_without_tags.length == total_strings
211
- errors << "None of your strings have tags."
212
- elsif keys_without_tags.length > 0
213
- errors << "Found strings without tags:\n#{join_keys.call(keys_without_tags)}"
188
+ if @options[:pedantic]
189
+ if keys_without_tags.length == total_strings
190
+ errors << "None of your strings have tags."
191
+ elsif keys_without_tags.length > 0
192
+ errors << "Found strings without tags:\n#{join_keys.call(keys_without_tags)}"
193
+ end
214
194
  end
215
195
 
216
196
  unless invalid_keys.empty?
@@ -222,34 +202,64 @@ module Twine
222
202
  Twine::stdout.puts "#{@options[:strings_file]} is valid."
223
203
  end
224
204
 
205
+ private
206
+
207
+ def require_rubyzip
208
+ begin
209
+ require 'zip'
210
+ rescue LoadError
211
+ raise Twine::Error.new "You must run 'gem install rubyzip' in order to create or consume localization drops."
212
+ end
213
+ end
214
+
225
215
  def determine_language_given_path(path)
226
216
  code = File.basename(path, File.extname(path))
227
217
  return code if @strings.language_codes.include? code
228
218
  end
229
219
 
230
- def determine_format_given_path(path)
231
- formatter = Formatters.formatters.find { |f| f::EXTENSION == File.extname(path) }
232
- return formatter::FORMAT_NAME if formatter
220
+ def formatter_for_format(format)
221
+ find_formatter { |f| f.format_name == format }
233
222
  end
234
223
 
235
- def determine_format_given_directory(directory)
236
- formatter = Formatters.formatters.find { |f| f.can_handle_directory?(directory) }
237
- return formatter::FORMAT_NAME if formatter
224
+ def find_formatter(&block)
225
+ formatter = Formatters.formatters.find &block
226
+ return nil unless formatter
227
+ formatter.strings = @strings
228
+ formatter.options = @options
229
+ formatter
238
230
  end
239
231
 
240
- def formatter_for_format(format)
241
- formatter = Formatters.formatters.find { |f| f::FORMAT_NAME == format }
242
- return formatter.new(@strings, @options) if formatter
232
+ def read_string_file(path, lang = nil)
233
+ unless File.file?(path)
234
+ raise Twine::Error.new("File does not exist: #{path}")
235
+ end
236
+
237
+ formatter, lang = prepare_read_write(path, lang)
238
+
239
+ formatter.read_file(path, lang)
243
240
  end
244
241
 
245
- private
242
+ def write_string_file(path, lang)
243
+ formatter, lang = prepare_read_write(path, lang)
244
+ formatter.write_file(path, lang)
245
+ end
246
246
 
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."
247
+ def prepare_read_write(path, lang)
248
+ formatter_for_path = find_formatter { |f| f.extension == File.extname(path) }
249
+ formatter = formatter_for_format(@options[:format]) || formatter_for_path
250
+
251
+ unless formatter
252
+ raise Twine::Error.new "Unable to determine format of #{path}"
253
+ end
254
+
255
+ lang = lang || determine_language_given_path(path) || formatter.determine_language_given_path(path)
256
+ unless lang
257
+ raise Twine::Error.new "Unable to determine language for #{path}"
252
258
  end
259
+
260
+ @strings.language_codes << lang unless @strings.language_codes.include? lang
261
+
262
+ return formatter, lang
253
263
  end
254
264
  end
255
265
  end
@@ -1,3 +1,3 @@
1
1
  module Twine
2
- VERSION = '0.8.0'
2
+ VERSION = '0.8.1'
3
3
  end
@@ -5,8 +5,10 @@ class CommandTestCase < TwineTestCase
5
5
  strings = Twine::StringsFile.new
6
6
  strings.language_codes.concat KNOWN_LANGUAGES
7
7
 
8
- formatter = formatter_class.new(strings, {})
9
- formatter_class.stubs(:new).returns(formatter)
8
+ formatter = formatter_class.new
9
+ formatter.strings = strings
10
+ Twine::Formatters.formatters.clear
11
+ Twine::Formatters.formatters << formatter
10
12
  formatter
11
13
  end
12
14
  end
@@ -1,6 +1,6 @@
1
1
  ##
2
2
  # Django Strings File
3
- # Generated by Twine 0.7.0
3
+ # Generated by Twine <%= Twine::VERSION %>
4
4
  # Language: en
5
5
 
6
6
 
@@ -1,5 +1,5 @@
1
1
  ## Flash Strings File
2
- ## Generated by Twine 0.7.0
2
+ ## Generated by Twine <%= Twine::VERSION %>
3
3
  ## Language: en
4
4
 
5
5
  ## Section 1 ##
@@ -12,7 +12,8 @@ class TestAbstractFormatter < TwineTestCase
12
12
  end
13
13
  end
14
14
 
15
- @formatter = Twine::Formatters::Abstract.new(@strings, {})
15
+ @formatter = Twine::Formatters::Abstract.new
16
+ @formatter.strings = @strings
16
17
  end
17
18
 
18
19
  def test_set_translation_updates_existing_value
@@ -46,7 +47,9 @@ class TestAbstractFormatter < TwineTestCase
46
47
  end
47
48
 
48
49
  def test_set_translation_consume_all_adds_new_key
49
- formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true })
50
+ formatter = Twine::Formatters::Abstract.new
51
+ formatter.strings = @strings
52
+ formatter.options = { consume_all: true }
50
53
  formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
51
54
 
52
55
  assert_equal 'new-key-english', @strings.strings_map['new-key'].translations['en']
@@ -54,14 +57,18 @@ class TestAbstractFormatter < TwineTestCase
54
57
 
55
58
  def test_set_translation_consume_all_adds_tags
56
59
  random_tag = SecureRandom.uuid
57
- formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true, tags: [random_tag] })
60
+ formatter = Twine::Formatters::Abstract.new
61
+ formatter.strings = @strings
62
+ formatter.options = { consume_all: true, tags: [random_tag] }
58
63
  formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
59
64
 
60
65
  assert_equal [random_tag], @strings.strings_map['new-key'].tags
61
66
  end
62
67
 
63
68
  def test_set_translation_adds_new_keys_to_category_uncategoriezed
64
- formatter = Twine::Formatters::Abstract.new(@strings, { consume_all: true })
69
+ formatter = Twine::Formatters::Abstract.new
70
+ formatter.strings = @strings
71
+ formatter.options = { consume_all: true }
65
72
  formatter.set_translation_for_key 'new-key', 'en', 'new-key-english'
66
73
 
67
74
  assert_equal 'Uncategorized', @strings.sections[0].name
@@ -80,7 +87,8 @@ class TestAbstractFormatter < TwineTestCase
80
87
  end
81
88
  end
82
89
 
83
- @formatter = Twine::Formatters::Abstract.new(@strings, {})
90
+ @formatter = Twine::Formatters::Abstract.new
91
+ @formatter.strings = @strings
84
92
  end
85
93
 
86
94
  def test_set_translation_does_not_add_unchanged_translation
@@ -108,14 +116,17 @@ class TestAbstractFormatter < TwineTestCase
108
116
  end
109
117
 
110
118
  def test_set_comment_for_key_does_not_update_comment
111
- formatter = Twine::Formatters::Abstract.new(@strings, {})
119
+ formatter = Twine::Formatters::Abstract.new
120
+ formatter.strings = @strings
112
121
  formatter.set_comment_for_key('key', 'comment')
113
122
 
114
123
  assert_nil formatter.strings.strings_map['key'].comment
115
124
  end
116
125
 
117
126
  def test_set_comment_for_key_updates_comment_with_update_comments
118
- formatter = Twine::Formatters::Abstract.new(@strings, { consume_comments: true })
127
+ formatter = Twine::Formatters::Abstract.new
128
+ formatter.strings = @strings
129
+ formatter.options = { consume_comments: true }
119
130
  formatter.set_comment_for_key('key', 'comment')
120
131
 
121
132
  assert_equal 'comment', formatter.strings.strings_map['key'].comment
@@ -133,7 +144,9 @@ class TestAbstractFormatter < TwineTestCase
133
144
  end
134
145
  end
135
146
 
136
- @formatter = Twine::Formatters::Abstract.new(@strings, { consume_comments: true })
147
+ @formatter = Twine::Formatters::Abstract.new
148
+ @formatter.strings = @strings
149
+ @formatter.options = { consume_comments: true }
137
150
  end
138
151
 
139
152
  def test_set_comment_does_not_add_unchanged_comment
@@ -21,6 +21,11 @@ class CLITestCase < TwineTestCase
21
21
  assert_equal @strings_file_path, @options[:strings_file]
22
22
  end
23
23
 
24
+ def test_pedantic
25
+ parse "validate-strings-file #{@strings_file_path} --pedantic"
26
+ assert @options[:pedantic]
27
+ end
28
+
24
29
  def test_missing_parameter
25
30
  assert_raises Twine::Error do
26
31
  parse 'validate-strings-file'
@@ -49,6 +54,11 @@ class CLITestCase < TwineTestCase
49
54
  end
50
55
  end
51
56
 
57
+ def test_validate
58
+ parse "generate-string-file #{@strings_file_path} #{@output_path} --validate"
59
+ assert @options[:validate]
60
+ end
61
+
52
62
  def test_extra_parameter
53
63
  assert_raises Twine::Error do
54
64
  parse 'generate-string-file strings output extra'
@@ -77,6 +87,11 @@ class CLITestCase < TwineTestCase
77
87
  end
78
88
  end
79
89
 
90
+ def test_validate
91
+ parse "generate-all-string-files #{@strings_file_path} #{@output_dir} --validate"
92
+ assert @options[:validate]
93
+ end
94
+
80
95
  def test_extra_parameter
81
96
  assert_raises Twine::Error do
82
97
  parse "generate-all-string-files strings output extra"
@@ -149,6 +164,11 @@ class CLITestCase < TwineTestCase
149
164
  end
150
165
  end
151
166
 
167
+ def test_validate
168
+ parse "generate-loc-drop #{@strings_file_path} #{@output_path} --format apple --validate"
169
+ assert @options[:validate]
170
+ end
171
+
152
172
  def test_extra_parameter
153
173
  assert_raises Twine::Error do
154
174
  parse "generate-loc-drop strings output extra --format apple"
@@ -246,13 +266,13 @@ class CLITestCase < TwineTestCase
246
266
  end
247
267
 
248
268
  def test_format
249
- random_format = Twine::Formatters.formatters.sample::FORMAT_NAME
269
+ random_format = Twine::Formatters.formatters.sample.format_name
250
270
  parse_with "--format #{random_format}"
251
271
  assert_equal random_format, @options[:format]
252
272
  end
253
273
 
254
274
  def test_format_ignores_case
255
- random_format = Twine::Formatters.formatters.sample::FORMAT_NAME
275
+ random_format = Twine::Formatters.formatters.sample.format_name
256
276
  parse_with "--format #{random_format.upcase}"
257
277
  assert_equal random_format, @options[:format]
258
278
  end
@@ -17,7 +17,9 @@ class FormatterTest < TwineTestCase
17
17
  end
18
18
 
19
19
  @strings = Twine::StringsFile.new
20
- @formatter = formatter_class.new @strings, { consume_all: true, consume_comments: true }
20
+ @formatter = formatter_class.new
21
+ @formatter.strings = @strings
22
+ @formatter.options = { consume_all: true, consume_comments: true }
21
23
  end
22
24
 
23
25
  def assert_translations_read_correctly
@@ -66,7 +68,8 @@ class TestAndroidFormatter < FormatterTest
66
68
  end
67
69
 
68
70
  def test_write_file_output_format
69
- formatter = Twine::Formatters::Android.new @twine_file, {}
71
+ formatter = Twine::Formatters::Android.new
72
+ formatter.strings = @twine_file
70
73
  formatter.write_file @output_path, 'en'
71
74
  assert_equal content('formatter_android.xml'), output_content
72
75
  end
@@ -111,7 +114,8 @@ class TestAppleFormatter < FormatterTest
111
114
  end
112
115
 
113
116
  def test_write_file_output_format
114
- formatter = Twine::Formatters::Apple.new @twine_file, {}
117
+ formatter = Twine::Formatters::Apple.new
118
+ formatter.strings = @twine_file
115
119
  formatter.write_file @output_path, 'en'
116
120
  assert_equal content('formatter_apple.strings'), output_content
117
121
  end
@@ -142,7 +146,8 @@ class TestJQueryFormatter < FormatterTest
142
146
  end
143
147
 
144
148
  def test_write_file_output_format
145
- formatter = Twine::Formatters::JQuery.new @twine_file, {}
149
+ formatter = Twine::Formatters::JQuery.new
150
+ formatter.strings = @twine_file
146
151
  formatter.write_file @output_path, 'en'
147
152
  assert_equal content('formatter_jquery.json'), output_content
148
153
  end
@@ -171,7 +176,8 @@ class TestGettextFormatter < FormatterTest
171
176
  end
172
177
 
173
178
  def test_write_file_output_format
174
- formatter = Twine::Formatters::Gettext.new @twine_file, {}
179
+ formatter = Twine::Formatters::Gettext.new
180
+ formatter.strings = @twine_file
175
181
  formatter.write_file @output_path, 'en'
176
182
  assert_equal content('formatter_gettext.po'), output_content
177
183
  end
@@ -192,7 +198,8 @@ class TestTizenFormatter < FormatterTest
192
198
  end
193
199
 
194
200
  def test_write_file_output_format
195
- formatter = Twine::Formatters::Tizen.new @twine_file, {}
201
+ formatter = Twine::Formatters::Tizen.new
202
+ formatter.strings = @twine_file
196
203
  formatter.write_file @output_path, 'en'
197
204
  assert_equal content('formatter_tizen.xml'), output_content
198
205
  end
@@ -211,7 +218,8 @@ class TestDjangoFormatter < FormatterTest
211
218
  end
212
219
 
213
220
  def test_write_file_output_format
214
- formatter = Twine::Formatters::Django.new @twine_file, {}
221
+ formatter = Twine::Formatters::Django.new
222
+ formatter.strings = @twine_file
215
223
  formatter.write_file @output_path, 'en'
216
224
  assert_equal content('formatter_django.po'), output_content
217
225
  end
@@ -229,7 +237,8 @@ class TestFlashFormatter < FormatterTest
229
237
  end
230
238
 
231
239
  def test_write_file_output_format
232
- formatter = Twine::Formatters::Flash.new @twine_file, {}
240
+ formatter = Twine::Formatters::Flash.new
241
+ formatter.strings = @twine_file
233
242
  formatter.write_file @output_path, 'en'
234
243
  assert_equal content('formatter_flash.properties'), output_content
235
244
  end
@@ -8,13 +8,13 @@ class TestGenerateAllStringFiles < CommandTestCase
8
8
  options[:format] = 'apple'
9
9
  options[:create_folders] = create_folders
10
10
 
11
- @twine_file = build_twine_file 'en', 'es' do
11
+ twine_file = build_twine_file 'en', 'es' do
12
12
  add_section 'Section' do
13
13
  add_row key: 'value'
14
14
  end
15
15
  end
16
16
 
17
- Twine::Runner.new(options, @twine_file)
17
+ Twine::Runner.new(options, twine_file)
18
18
  end
19
19
 
20
20
  def test_fails_if_output_folder_does_not_exist
@@ -41,4 +41,36 @@ class TestGenerateAllStringFiles < CommandTestCase
41
41
  assert File.exists?(File.join(@output_dir, 'es.lproj')), "language folder 'es.lproj' should be created"
42
42
  end
43
43
  end
44
+
45
+ class TestDeliberate < CommandTestCase
46
+ def new_runner(validate)
47
+ Dir.mkdir File.join @output_dir, 'values-en'
48
+
49
+ options = {}
50
+ options[:output_path] = @output_dir
51
+ options[:format] = 'android'
52
+ options[:validate] = validate
53
+
54
+ twine_file = build_twine_file 'en' do
55
+ add_section 'Section' do
56
+ add_row key: 'value'
57
+ add_row key: 'value'
58
+ end
59
+ end
60
+
61
+ Twine::Runner.new(options, twine_file)
62
+ end
63
+
64
+ def test_does_not_validate_strings_file
65
+ prepare_mock_formatter Twine::Formatters::Android
66
+
67
+ new_runner(false).generate_all_string_files
68
+ end
69
+
70
+ def test_validates_strings_file_if_validate
71
+ assert_raises Twine::Error do
72
+ new_runner(true).generate_all_string_files
73
+ end
74
+ end
75
+ end
44
76
  end
@@ -41,4 +41,34 @@ class TestGenerateLocDrop < CommandTestCase
41
41
 
42
42
  @runner.generate_loc_drop
43
43
  end
44
+
45
+ class TestDeliberate < CommandTestCase
46
+ def new_runner(validate)
47
+ options = {}
48
+ options[:output_path] = @output_path
49
+ options[:format] = 'android'
50
+ options[:validate] = validate
51
+
52
+ twine_file = build_twine_file 'en' do
53
+ add_section 'Section' do
54
+ add_row key: 'value'
55
+ add_row key: 'value'
56
+ end
57
+ end
58
+
59
+ Twine::Runner.new(options, twine_file)
60
+ end
61
+
62
+ def test_does_not_validate_strings_file
63
+ prepare_mock_formatter Twine::Formatters::Android
64
+
65
+ new_runner(false).generate_loc_drop
66
+ end
67
+
68
+ def test_validates_strings_file_if_validate
69
+ assert_raises Twine::Error do
70
+ new_runner(true).generate_loc_drop
71
+ end
72
+ end
73
+ end
44
74
  end
@@ -6,10 +6,10 @@ class TestGenerateStringFile < CommandTestCase
6
6
  options[:output_path] = File.join(@output_dir, file) if file
7
7
  options[:languages] = language if language
8
8
 
9
- @strings = Twine::StringsFile.new
10
- @strings.language_codes.concat KNOWN_LANGUAGES
9
+ strings = Twine::StringsFile.new
10
+ strings.language_codes.concat KNOWN_LANGUAGES
11
11
 
12
- Twine::Runner.new(options, @strings)
12
+ Twine::Runner.new(options, strings)
13
13
  end
14
14
 
15
15
  def prepare_mock_write_file_formatter(formatter_class)
@@ -48,4 +48,35 @@ class TestGenerateStringFile < CommandTestCase
48
48
 
49
49
  new_runner(nil, "#{random_language}.xml").generate_string_file
50
50
  end
51
+
52
+ class TestDeliberate < CommandTestCase
53
+ def new_runner(validate)
54
+ options = {}
55
+ options[:output_path] = @output_path
56
+ options[:languages] = ['en']
57
+ options[:format] = 'android'
58
+ options[:validate] = validate
59
+
60
+ twine_file = build_twine_file 'en' do
61
+ add_section 'Section' do
62
+ add_row key: 'value'
63
+ add_row key: 'value'
64
+ end
65
+ end
66
+
67
+ Twine::Runner.new(options, twine_file)
68
+ end
69
+
70
+ def test_does_not_validate_strings_file
71
+ prepare_mock_formatter Twine::Formatters::Android
72
+
73
+ new_runner(false).generate_string_file
74
+ end
75
+
76
+ def test_validates_strings_file_if_validate
77
+ assert_raises Twine::Error do
78
+ new_runner(true).generate_string_file
79
+ end
80
+ end
81
+ end
51
82
  end
@@ -37,19 +37,25 @@ class TestValidateStringsFile < CommandTestCase
37
37
  end
38
38
  end
39
39
 
40
- def test_reports_missing_tags
41
- random_row.tags.clear
40
+ def test_reports_invalid_characters_in_keys
41
+ random_row.key[0] = "!?;:,^`´'\"\\|/(){}[]~-+*=#$%".chars.to_a.sample
42
42
 
43
43
  assert_raises Twine::Error do
44
44
  Twine::Runner.new(@options, @twine_file).validate_strings_file
45
45
  end
46
46
  end
47
47
 
48
- def test_reports_invalid_characters_in_keys
49
- random_row.key[0] = "!?;:,^`´'\"\\|/(){}[]~-+*=#$%".chars.to_a.sample
48
+ def test_does_not_reports_missing_tags_by_default
49
+ random_row.tags.clear
50
+
51
+ Twine::Runner.new(@options, @twine_file).validate_strings_file
52
+ end
53
+
54
+ def test_reports_missing_tags
55
+ random_row.tags.clear
50
56
 
51
57
  assert_raises Twine::Error do
52
- Twine::Runner.new(@options, @twine_file).validate_strings_file
58
+ Twine::Runner.new(@options.merge(pedantic: true), @twine_file).validate_strings_file
53
59
  end
54
60
  end
55
61
  end
@@ -15,12 +15,17 @@ class TwineTestCase < Minitest::Test
15
15
  super
16
16
  Twine::stdout = StringIO.new
17
17
  Twine::stderr = StringIO.new
18
+
19
+ @formatters = Twine::Formatters.formatters.dup
20
+
18
21
  @output_dir = Dir.mktmpdir
19
22
  @output_path = File.join @output_dir, SecureRandom.uuid
20
23
  end
21
24
 
22
25
  def teardown
23
26
  FileUtils.remove_entry_secure @output_dir if File.exists? @output_dir
27
+ Twine::Formatters.formatters.clear
28
+ Twine::Formatters.formatters.concat @formatters
24
29
  super
25
30
  end
26
31
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: twine
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0
4
+ version: 0.8.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sebastian Celis
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-12 00:00:00.000000000 Z
11
+ date: 2016-02-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubyzip
@@ -150,7 +150,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
150
150
  requirements:
151
151
  - - ">="
152
152
  - !ruby/object:Gem::Version
153
- version: 1.8.7
153
+ version: '2.0'
154
154
  required_rubygems_version: !ruby/object:Gem::Requirement
155
155
  requirements:
156
156
  - - ">="