aigu 0.3.1 → 0.4

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: 6b973b5aa391a17f4dc4d001e5a8ab7e0a286b32
4
- data.tar.gz: 6d38f76e465218a22923333650a7e30650a27c14
3
+ metadata.gz: 1891efbb20cc81f439f20f30fb4d74586e09c2c9
4
+ data.tar.gz: 2715fb01c3d8f203c791ffc55357469b077ad793
5
5
  SHA512:
6
- metadata.gz: 22e775bce2767935d0a5f260dd1f8e7a7ca4d6eea1dce18a6c2bff6ca4dfde13fe2b4711cd38bb7f828ec4041b62a4f0b361ca4ecf3616cf51d486a580697d63
7
- data.tar.gz: 07c4cb7b0708431a4a38f3c399451121ee8f8c8287a16ca980a6ff80ea5f2c0f70f6a307d5d7295fda0d9b2b5fbfaa79cf03dce3355d09495ccab615b711378a
6
+ metadata.gz: 83421bf2129630b61d54543cfa484963415151301c345fd624febb67d65de937a45634173c54fb3017c5a3c3a3f95f3ab92ce1f814b147c99a560336f675f390
7
+ data.tar.gz: 3a60ed45b7ee4764a8b809cb297e8a9b9a7bfa395ae683cd335009308b189a8bb56039e39e099abee88c5a637a3e1f9ac635b10bf575b5c40bdfc32b33b562ab
@@ -0,0 +1,52 @@
1
+ AllCops:
2
+ Include:
3
+ - Rakefile
4
+ Exclude:
5
+ - bin/phare
6
+
7
+ Documentation:
8
+ Enabled: false
9
+
10
+ Encoding:
11
+ Enabled: false
12
+
13
+ LineLength:
14
+ Max: 200
15
+
16
+ AccessModifierIndentation:
17
+ EnforcedStyle: outdent
18
+
19
+ IfUnlessModifier:
20
+ Enabled: false
21
+
22
+ CaseIndentation:
23
+ IndentWhenRelativeTo: case
24
+ IndentOneStep: true
25
+
26
+ MethodLength:
27
+ CountComments: false
28
+ Max: 20
29
+
30
+ SignalException:
31
+ Enabled: false
32
+
33
+ ColonMethodCall:
34
+ Enabled: false
35
+
36
+ AsciiComments:
37
+ Enabled: false
38
+
39
+ Lambda:
40
+ Enabled: false
41
+
42
+ RegexpLiteral:
43
+ Enabled: false
44
+
45
+ AssignmentInCondition:
46
+ Enabled: false
47
+
48
+ Metrics/AbcSize:
49
+ Enabled: false
50
+
51
+ ClassLength:
52
+ Enabled: false
data/README.md CHANGED
@@ -1,8 +1,16 @@
1
1
  ## Introduction
2
2
 
3
- Aigu processes Rails YAML localization files and generates a single JSON file
4
- that can be imported into the Accent service. It can also process a JSON file
5
- generated by Accent and convert it back to multiple YAML files.
3
+ Aigu is a set of utility to process localization files to genarate JSON files
4
+ to push to Accent service. Pulling from Accent to update localization files
5
+ is also supported.
6
+
7
+ The following localization file formats are supported: Rails YAML, Android
8
+ XML, Java Enum Core, iOS strings and stringsdict.
9
+
10
+ Merge and unmerge commands allows to merge different source files into a
11
+ single JSON file to send as a single Accent project.
12
+
13
+ Push and pull can be used to interact directly with Accent.
6
14
 
7
15
  ## Installation
8
16
 
@@ -36,6 +44,13 @@ $ aigu export --locale=fr --input-directory=config/locales --output-file=my-proj
36
44
  | `output-file` | The path to the JSON file that will be written by `aigu` |
37
45
  | `ignore` | The patterns `aigu` will use to skip ignored files (eg. `routes.yml`)
38
46
 
47
+ | Command | File format |
48
+ |-------------------|-------------------------------------------------------|
49
+ | `export` | Rails YAML |
50
+ | `andoird_export` | Android XML |
51
+ | `core_export` | Java Enum Core |
52
+ | `ios_export` | iOS strings & stringsdict |
53
+
39
54
  ### Importing the JSON file from Accent
40
55
 
41
56
  The `import` command takes a generated JSON file from Accent and generates
@@ -53,10 +68,67 @@ $ aigu import --locale=fr --input-file=file-from-accent.json --output-directory=
53
68
  | `input-file` | The path to the Accent-generated JSON file |
54
69
  | `output-directory` | The directory where the localization YAML files will be generated |
55
70
 
56
- ### Using `aigu.yml`
71
+ | Command | File format |
72
+ |-------------------|-------------------------------------------------------|
73
+ | `import` | Rails YAML |
74
+ | `andoird_import` | Android XML |
75
+ | `core_import` | Java Enum Core |
76
+ | `ios_import` | iOS strings & stringsdict |
77
+
78
+ ### Merge
79
+
80
+ The `merge` command will combine several JSON file into a single one to send into a single Accent project. It will prefix keys with
81
+ a pattern embedding the filename to allow later unmerging. It works from an input directory, mergin all json files in it.
82
+ A file can be named `default.json` to avoid prefixes in the resulting json.
83
+
84
+ #### Options
85
+
86
+ | Option | Description |
87
+ |--------------------|---------------------------------------------------------------------------------------------------|
88
+ | `input-directory` | The directory containing json files to merge |
89
+ | `output-file` | The path to the JSON file that will be written to |
90
+
91
+
92
+ ### Unmerge
93
+
94
+ The `unmerge` will split the Accent JSON file into several json files, for further processing. It will look for the prefix pattern
95
+ of the merge command and will extract the filename or use default.json if pattern is not found.
96
+
97
+ #### Options
98
+
99
+ | Option | Description |
100
+ |--------------------|---------------------------------------------------------------------------------------------------|
101
+ | `input-file` | The path to the Accent-generated JSON file |
102
+ | `output-directory` | The directory where the unmerged files will be written |
103
+
104
+ ### Sending to Accent
105
+
106
+ The `push` command takes a JSON file exported by Aigu and send it to Accent
107
+
108
+ #### Options
109
+
110
+ | Option | Description |
111
+ |--------------------|---------------------------------------------------------------------------------------------------|
112
+ | `input-file` | The path to the Aigu-generated JSON file |
113
+ | `accent-api-key` | The API key of the Accent project. (See project config in Accent) |
114
+ | `accent-url` | The URL to the instance of Accent to use (Prod: http://accent.mirego.com, QA: https://mirego-accent-qa.herokuapp.com ) |
115
+
116
+ ### Pulling from Accent
117
+
118
+ The `pull` command GET the Accent generated JSON file
119
+
120
+ #### Options
121
+
122
+ | Option | Description |
123
+ |--------------------|---------------------------------------------------------------------------------------------------|
124
+ | `output-file` | The path to write the Accent JSON file to |
125
+ | `accent-api-key` | The API key of the Accent project. (See project config in Accent) |
126
+ | `accent-url` | The URL to the instance of Accent to use (Prod: http://accent.mirego.com, QA: https://mirego-accent-qa.herokuapp.com ) |
127
+
128
+ ### Using `.aigu.yml`
57
129
 
58
130
  Instead of using command-line arguments when running the `aigu` command, you
59
- can create a `aigu.yml` file at the root of your project and hard-code options
131
+ can create a `.aigu.yml` file at the root of your project and hard-code options
60
132
  in that file.
61
133
 
62
134
  ```yaml
@@ -67,6 +139,18 @@ output-directory: config/locales
67
139
  input-directory: config/locales
68
140
  ```
69
141
 
142
+ ## Contributing
143
+
144
+ We’re using `phare` to make sure all Ruby code adheres to our coding style
145
+ guide. The best way to use `phare` is as a pre-commit hook:
146
+
147
+ ```bash
148
+ $ ln -s "`pwd`/bin/phare" .git/hooks/pre-commit
149
+ ```
150
+
151
+ That way, whenever `git commit` is ran, `phare` will be executed and will abort
152
+ the commit if there are errors.
153
+
70
154
  ## License
71
155
 
72
156
  `Aigu` is © 2014 [Mirego](http://www.mirego.com) and may be freely distributed under the [New BSD license](http://opensource.org/licenses/BSD-3-Clause). See the [`LICENSE.md`](https://github.com/mirego/aigu/blob/master/LICENSE.md) file.
data/Rakefile CHANGED
@@ -12,7 +12,7 @@ end
12
12
 
13
13
  desc 'Start an IRB session with the gem'
14
14
  task :console do
15
- $:.unshift File.expand_path('..', __FILE__)
15
+ $LOAD_PATH.unshift File.expand_path('..', __FILE__)
16
16
  require 'aigu'
17
17
  require 'irb'
18
18
 
@@ -6,19 +6,22 @@ require 'aigu/version'
6
6
  Gem::Specification.new do |spec|
7
7
  spec.name = 'aigu'
8
8
  spec.version = Aigu::VERSION
9
- spec.authors = ['Rémi Prévost']
10
- spec.email = ['rprevost@mirego.com']
9
+ spec.authors = ['Rémi Prévost', 'William Jobin']
10
+ spec.email = ['rprevost@mirego.com', 'wjobin@mirego.com']
11
11
  spec.description = 'Aigu converts a directory of Rails localization files into a single JSON file to import in Accent.'
12
12
  spec.summary = spec.description
13
13
  spec.homepage = 'https://github.com/mirego/aigu'
14
14
  spec.license = 'BSD 3-Clause'
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.executables = 'aigu'
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ['lib']
20
20
 
21
21
  spec.add_development_dependency 'bundler', '~> 1.3'
22
22
  spec.add_development_dependency 'rake'
23
- spec.add_development_dependency 'rspec', '~> 3.0.0.beta2'
23
+ spec.add_development_dependency 'rspec', '~> 3.0'
24
+ spec.add_development_dependency 'phare', '~> 0.6'
25
+ spec.add_development_dependency 'rubocop', '~> 0.27.0'
26
+ spec.add_development_dependency 'nokogiri', '~> 1.6.3'
24
27
  end
data/bin/aigu CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
- $LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w[.. lib])
3
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), *%w(.. lib))
4
4
 
5
5
  require 'aigu'
6
6
 
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'phare' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('phare', 'phare')
@@ -4,11 +4,22 @@ require 'optparse'
4
4
  require 'fileutils'
5
5
  require 'json'
6
6
  require 'yaml'
7
+ require 'nokogiri'
7
8
 
8
9
  require 'aigu/extensions/hash'
9
10
  require 'aigu/cli'
10
11
  require 'aigu/importer'
11
12
  require 'aigu/exporter'
13
+ require 'aigu/android_exporter'
14
+ require 'aigu/android_importer'
15
+ require 'aigu/core_exporter'
16
+ require 'aigu/core_importer'
17
+ require 'aigu/ios_exporter'
18
+ require 'aigu/ios_importer'
19
+ require 'aigu/unmergeer'
20
+ require 'aigu/mergeer'
21
+ require 'aigu/puller'
22
+ require 'aigu/pusher'
12
23
 
13
24
  module Aigu
14
25
  end
@@ -0,0 +1,165 @@
1
+ module Aigu
2
+ class AndroidExporter
3
+ def initialize(opts = {})
4
+ @output_file = opts[:'output-file']
5
+ @input_directory = opts[:'input-directory']
6
+ @locale = opts[:locale]
7
+ @ignore = opts[:ignore]
8
+ end
9
+
10
+ def process!
11
+ puts "Generating Accent JSON file `#{@output_file}` based on Android resources files in `#{@input_directory}` directory"
12
+
13
+ if @ignore
14
+ print 'Ignoring '
15
+ puts @ignore.join(', ')
16
+ end
17
+
18
+ puts '---'
19
+
20
+ build_output
21
+ write_json_file
22
+
23
+ puts '---'
24
+ puts 'Done'
25
+ end
26
+
27
+ protected
28
+
29
+ def build_output
30
+ @output = {}
31
+
32
+ pattern = File.join(@input_directory, '*', 'strings.xml')
33
+ Dir[pattern].each do |filepath|
34
+ next unless supported_resource_for_language?(filepath, @locale)
35
+
36
+ if ignored_filepath?(filepath)
37
+ puts "Ignoring #{filepath}"
38
+ else
39
+ puts "Processing #{filepath}"
40
+
41
+ _, _, qualifiers = get_resource_type(filepath)
42
+ puts "Qualifiers: #{qualifiers}"
43
+
44
+ content = File.open(filepath, 'r:bom|utf-8').read
45
+ base_key = qualifiers
46
+
47
+ content = parse_xml(content, base_key)
48
+ @output.merge! content
49
+ end
50
+ end
51
+
52
+ @output
53
+ end
54
+
55
+ def write_json_file
56
+ file_path = @output_file
57
+ puts "Generating #{file_path}"
58
+ FileUtils.mkdir_p(File.dirname(file_path))
59
+
60
+ File.open(file_path, 'w+') do |file|
61
+ file << JSON.pretty_generate(JSON.parse(@output.to_json))
62
+ end
63
+ end
64
+
65
+ def sanitize_value_to_string(value)
66
+ case value
67
+ when true
68
+ '___TRUE___'
69
+ when false
70
+ '___FALSE___'
71
+ when nil
72
+ '___NULL___'
73
+ else
74
+ value
75
+ end
76
+ end
77
+
78
+ def parse_xml(xml, base_key = '')
79
+ xml = cleanup_input_xml(xml)
80
+ doc = Nokogiri::XML(xml) { |config| config.options = Nokogiri::XML::ParseOptions::RECOVER }
81
+ resources = doc.root.children
82
+ string_hash = {}
83
+
84
+ resources.each do |node|
85
+ if node.name == 'string'
86
+ string_hash.merge!(hash_for_key_value(node['name'], node.content, base_key))
87
+ elsif node.name == 'string-array'
88
+ resource_string_prefix = node['name'] + '__ARRAY_ITEM__#'
89
+
90
+ item_index = 0
91
+ node.children.each do |array_item|
92
+ next unless array_item.name == 'item'
93
+
94
+ hash = hash_for_key_value("#{resource_string_prefix}#{item_index}", array_item.content, base_key)
95
+ string_hash.merge! hash
96
+
97
+ item_index += 1
98
+ end
99
+ end
100
+ end
101
+
102
+ string_hash
103
+ end
104
+
105
+ def build_key_name(key_name, base_key)
106
+ base_key.empty? ? key_name : "#{key_name}__@TYPE_#{base_key}"
107
+ end
108
+
109
+ def hash_for_key_value(key, value, base_key)
110
+ { build_key_name(key, base_key) => value }
111
+ end
112
+
113
+ # Try to fix issue
114
+ def cleanup_input_xml(xml)
115
+ replace_html_name(xml, 'quot', '"')
116
+ replace_html_name(xml, 'amp', '&')
117
+ replace_html_name(xml, 'apos', "'")
118
+ replace_html_name(xml, 'lt', '<')
119
+ replace_html_name(xml, 'gt', '>')
120
+ xml
121
+ end
122
+
123
+ def replace_html_name(xml, html_name, character)
124
+ replace_string = "&##{character.ord};"
125
+ xml.gsub!(/&#{html_name};/, replace_string)
126
+ end
127
+
128
+ # Parse out resource meta from filename
129
+ # Assumes first qualifer is locale if 2 chars
130
+ def get_resource_type(filepath)
131
+ filepath_parts = filepath.split(File::SEPARATOR)
132
+ filepath = filepath_parts[-2]
133
+
134
+ resource_qualifiers = filepath.split('-')
135
+
136
+ resource_type, first_qualifier = resource_qualifiers
137
+
138
+ first_qualifier_is_locale = !first_qualifier || (first_qualifier.length == 2)
139
+
140
+ if first_qualifier_is_locale
141
+ resource_qualifiers.shift(2)
142
+ locale = first_qualifier
143
+ else
144
+ resource_qualifiers.shift(1)
145
+ locale = nil
146
+ end
147
+
148
+ [resource_type, locale, resource_qualifiers.join(' ')]
149
+ end
150
+
151
+ def supported_resource_for_language?(filepath, expected_locale)
152
+ _, locale = get_resource_type(filepath)
153
+ expected_locale == locale
154
+ end
155
+
156
+ def ignored_filepath?(filepath)
157
+ @ignore && @ignore.any? do |pattern|
158
+ File.fnmatch(pattern, filepath, File::FNM_PATHNAME | File::FNM_DOTMATCH)
159
+ end
160
+
161
+ _, locale = get_resource_type(filepath)
162
+ !(@locale == locale)
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,130 @@
1
+ module Aigu
2
+ class AndroidImporter
3
+ ARRAY_REGEX = /__ARRAY_ITEM__#(?<index>\d+)$/
4
+ TYPE_REGEX = /__@TYPE_(?<type_key>.+)$/
5
+
6
+ def initialize(opts = {})
7
+ @input_file = opts[:'input-file']
8
+ @output_directory = opts[:'output-directory']
9
+ @locale = opts[:locale]
10
+ end
11
+
12
+ def process!
13
+ puts "Generating Android XML files in `#{@output_directory}` based on Accent-generated `#{@input_file}` file"
14
+ puts '---'
15
+
16
+ parse_json
17
+ @blob = split_res_types(@object)
18
+ write_xml_files
19
+
20
+ puts '---'
21
+ puts 'Done'
22
+ end
23
+
24
+ protected
25
+
26
+ def parse_json
27
+ json = File.read(@input_file)
28
+ @object = JSON.parse(json)
29
+ @object = expand_content_values(@object)
30
+ end
31
+
32
+ def write_xml_files
33
+ @blob.each_pair do |type, hash|
34
+ # TODO: add locale to filename
35
+ qualifier = type.gsub(/_/, '-')
36
+ if qualifier != ''
37
+ qualifier.prepend('-')
38
+ end
39
+ if @locale
40
+ language = '-' + @locale
41
+ else
42
+ language = ''
43
+ end
44
+
45
+ file_path = File.join(@output_directory, "values#{language}#{qualifier}", 'strings.xml')
46
+ puts "Generating #{file_path}"
47
+ FileUtils.mkdir_p(File.dirname(file_path))
48
+
49
+ File.open(file_path, 'w+:bom|utf-8') do |file|
50
+ file << res_to_xml(hash)
51
+ end
52
+ end
53
+ end
54
+
55
+ # TODO: Use XML tooling
56
+ def res_to_xml(content)
57
+ xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
58
+ xml << "<resources>\n"
59
+
60
+ content.each_pair do |key, value|
61
+ if value.is_a?(Array)
62
+ xml << ' <string-array name=' << key.encode(xml: :attr) << ">\n"
63
+ value.each do |item|
64
+ xml << ' <item>' << item.encode(xml: :text) << "</item>\n"
65
+ end
66
+ xml << " </string-array>\n"
67
+ elsif value.is_a?(String)
68
+ xml << ' <string name=' << key.encode(xml: :attr) << '>' << value.encode(xml: :text) << "</string>\n"
69
+ end
70
+ end
71
+
72
+ xml << "</resources>\n"
73
+ xml
74
+ end
75
+
76
+ def localize_content_keys(content)
77
+ content.reduce({}) do |memo, (key, value)|
78
+ localized_key = key.gsub(/^([^|]+)\|/, "\\1.#{@locale}|#{@locale}.")
79
+ memo.merge localized_key => value
80
+ end
81
+ end
82
+
83
+ def expand_content_values(content)
84
+ content.each_with_object({}) do |(key, value), memo|
85
+ match_data = key.match(ARRAY_REGEX)
86
+
87
+ if match_data
88
+ canonical_key = key.gsub(ARRAY_REGEX, '')
89
+ value_index = match_data[:index].to_i
90
+ memo[canonical_key] ||= []
91
+ memo[canonical_key][value_index] = sanitize_string_to_value(value)
92
+ else
93
+ memo[key] = sanitize_string_to_value(value)
94
+ end
95
+ end
96
+ end
97
+
98
+ def split_res_types(content)
99
+ res_types = {}
100
+
101
+ content.each_pair do |key, value|
102
+ match_data = key.match(TYPE_REGEX)
103
+ if match_data
104
+ canonical_key = key.gsub(TYPE_REGEX, '')
105
+ res_type = match_data[:type_key]
106
+ else
107
+ canonical_key = key
108
+ res_type = ''
109
+ end
110
+ res_types[res_type] ||= {}
111
+ res_types[res_type][canonical_key] = value
112
+ end
113
+
114
+ res_types
115
+ end
116
+
117
+ def sanitize_string_to_value(string)
118
+ case string
119
+ when '___TRUE___'
120
+ true
121
+ when '___FALSE___'
122
+ false
123
+ when '___NULL___'
124
+ nil
125
+ else
126
+ string
127
+ end
128
+ end
129
+ end
130
+ end