twine 0.8.0 → 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
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
  - - ">="