ios_polyglot_cli 2.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +129 -0
  3. data/.ruby-version +1 -0
  4. data/Gemfile +3 -0
  5. data/Gemfile.lock +91 -0
  6. data/LICENSE +21 -0
  7. data/README.md +63 -0
  8. data/Rakefile +2 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/exe/polyglot +41 -0
  12. data/ios-polyglot-cli.gemspec +35 -0
  13. data/lib/ios_polyglot_cli/api/base.rb +19 -0
  14. data/lib/ios_polyglot_cli/api/language.rb +20 -0
  15. data/lib/ios_polyglot_cli/api/project.rb +7 -0
  16. data/lib/ios_polyglot_cli/api/session.rb +9 -0
  17. data/lib/ios_polyglot_cli/api/translation.rb +8 -0
  18. data/lib/ios_polyglot_cli/api/translation_key.rb +24 -0
  19. data/lib/ios_polyglot_cli/commands/login.rb +32 -0
  20. data/lib/ios_polyglot_cli/commands/pull.rb +81 -0
  21. data/lib/ios_polyglot_cli/commands/setup.rb +80 -0
  22. data/lib/ios_polyglot_cli/error_handler.rb +28 -0
  23. data/lib/ios_polyglot_cli/helpers/depaginate.rb +18 -0
  24. data/lib/ios_polyglot_cli/helpers/general.rb +79 -0
  25. data/lib/ios_polyglot_cli/helpers/terminal.rb +17 -0
  26. data/lib/ios_polyglot_cli/io/config.rb +20 -0
  27. data/lib/ios_polyglot_cli/io/token.rb +25 -0
  28. data/lib/ios_polyglot_cli/serializers/languages/languages_serializer.rb +27 -0
  29. data/lib/ios_polyglot_cli/serializers/languages/languages_serializer_objc.rb +90 -0
  30. data/lib/ios_polyglot_cli/serializers/languages/languages_serializer_swift.rb +41 -0
  31. data/lib/ios_polyglot_cli/serializers/sources/sources_serializer_swift.rb +63 -0
  32. data/lib/ios_polyglot_cli/serializers/sources/swift_helpers/translation_case.rb +23 -0
  33. data/lib/ios_polyglot_cli/serializers/sources/swift_helpers/translation_enum.rb +84 -0
  34. data/lib/ios_polyglot_cli/serializers/translations/translations_serializer.rb +33 -0
  35. data/lib/ios_polyglot_cli/version.rb +3 -0
  36. data/lib/ios_polyglot_cli.rb +42 -0
  37. metadata +192 -0
@@ -0,0 +1,80 @@
1
+ require 'commander/blank'
2
+ require 'commander/command'
3
+
4
+ module PolyglotIos
5
+ module Command
6
+ class Setup
7
+ include Helper::Terminal
8
+ include Helper::General
9
+
10
+ def self.init(options = Commander::Command::Options.new)
11
+ new(options).call
12
+ end
13
+
14
+ def initialize(options = Commander::Command::Options.new)
15
+ @options = options
16
+ end
17
+
18
+ def call
19
+ project_id = project_id_prompt
20
+ language = language_prompt
21
+ translations_path = translations_path_prompt
22
+ sources_path = sources_path_prompt
23
+ project = {
24
+ id: project_id,
25
+ path: translations_path,
26
+ sourceFilesPath: sources_path
27
+ }
28
+ config = {
29
+ language: language,
30
+ projects: [project]
31
+ }
32
+ PolyglotIos::IO::Config.write(config)
33
+ success
34
+ end
35
+
36
+ private
37
+
38
+ # Project ID
39
+
40
+ def project_id_prompt
41
+ prompt.select('Choose a project: ', filtered_projects)
42
+ end
43
+
44
+ def projects
45
+ prompt.say('Getting projects...')
46
+ PolyglotIos::Resource::Project.token(token).depaginate.each_with_object({}) do |r, hash|
47
+ hash[r.name] = r.id
48
+ end
49
+ end
50
+
51
+ def filtered_projects
52
+ projects.select { |key, _id| key[/^(.*?(#{option_query})[^$]*)$/i] }.sort_by { |p| p[0].downcase }.to_h
53
+ end
54
+
55
+ def option_query
56
+ @option_query ||= @options.__hash__.fetch(:query, '')
57
+ end
58
+
59
+ # Language
60
+
61
+ def language_prompt
62
+ languages = {'Swift' => 'swift', 'Objective-C' => 'objc'}
63
+ prompt.select('Choose a language: ', languages)
64
+ end
65
+
66
+ # Translations path
67
+
68
+ def translations_path_prompt
69
+ prompt.ask('Where would you like to store translation files (.strings)?', default: './Resources/Translations/')
70
+ end
71
+
72
+ # Sources path
73
+
74
+ def sources_path_prompt
75
+ prompt.ask('Where would you like to store source files?', default: './Common/Translations/')
76
+ end
77
+
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,28 @@
1
+ module PolyglotIos
2
+ class ErrorHandler
3
+ extend Helper::Terminal
4
+
5
+ class << self
6
+ def rescuable
7
+ yield
8
+ rescue => e
9
+ handle(e)
10
+ end
11
+
12
+ def handle(e)
13
+ prompt.error(
14
+ case e
15
+ when JsonApiClient::Errors::NotAuthorized
16
+ "You are not authorized. Please call `polyglot login` and provide correct credentials."
17
+ when JsonApiClient::Errors::AccessDenied
18
+ "You don't have the permission to access requested project."
19
+ when Errno::ENOENT
20
+ "We could not find a file that we need:\n\n#{e.message}"
21
+ else
22
+ "An error happened. This might help:\n\n#{e.message}"
23
+ end
24
+ )
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module PolyglotIos
2
+ module Helper
3
+ module Depaginate
4
+ PER_PAGE = 100
5
+
6
+ def depaginate_query(query)
7
+ depaginated_array = []
8
+ page = query.page(1).per(PER_PAGE).all
9
+ until page.nil?
10
+ depaginated_array += page
11
+ page = page.pages.next
12
+ end
13
+ depaginated_array
14
+ end
15
+
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,79 @@
1
+ module PolyglotIos
2
+ module Helper
3
+ module General
4
+ ESCAPE_KEYWORDS = ["associatedtype", "class", "deinit", "enum", "extension", "fileprivate", "func",
5
+ "import", "init", "inout", "internal", "let", "operator", "private", "protocol",
6
+ "public", "static", "struct", "subscript", "typealias", "var", "break", "case",
7
+ "continue", "default", "defer", "do", "else", "fallthrough", "for", "guard", "if",
8
+ "in", "repeat", "return", "switch", "where", "while", "as", "Any", "catch", "false",
9
+ "is", "nil", "rethrows", "super", "self", "Self", "throw", "throws", "true", "try", "_"]
10
+ def token
11
+ @token ||= PolyglotIos::IO::Token.read
12
+ if @token.to_s.empty?
13
+ PolyglotIos::Command::Login.init
14
+ @token = PolyglotIos::IO::Token.read
15
+ end
16
+ @token
17
+ end
18
+
19
+ def config
20
+ @config ||= PolyglotIos::IO::Config.read.with_indifferent_access
21
+ end
22
+
23
+ def project_configs
24
+ @project_configs ||= config[:projects]
25
+ end
26
+
27
+ def programming_language
28
+ @programming_language ||= config[:language]
29
+ end
30
+
31
+ def use_old_naming
32
+ @useOldNaming ||= config[:useOldNaming]
33
+ end
34
+
35
+ def indent(level = 0, initial = "")
36
+ (1..level)
37
+ .to_a.reduce("") { |result, value| result + " " }
38
+ .concat(initial)
39
+ end
40
+
41
+ def clean_enum_name(name)
42
+ clean_name = name
43
+ .split(/[^0-9a-zA-Z]/)
44
+ .reject { |c| c.empty? }
45
+ .map { |value| value.capitalize }
46
+ .join
47
+
48
+ escape_with_underscore_if_needed(clean_name)
49
+ end
50
+
51
+ def clean_case_name(name)
52
+ clean_variable_name(name)
53
+ end
54
+
55
+ def clean_variable_name(name)
56
+ clean_name = name
57
+ .split(/[^0-9a-zA-Z]/)
58
+ .reject { |c| c.empty? }
59
+ .each_with_index
60
+ .map { |value, index| index == 0 ? value.downcase : value.capitalize }
61
+ .join
62
+
63
+ escaped_underscore = escape_with_underscore_if_needed(clean_name)
64
+ escape_keyword_if_needed(escaped_underscore)
65
+ end
66
+
67
+ def escape_with_underscore_if_needed(name)
68
+ return name if name.match(/^[A-Za-z_]/)
69
+ "_" + name
70
+ end
71
+
72
+ def escape_keyword_if_needed(name)
73
+ return name if !ESCAPE_KEYWORDS.include? name
74
+ "`#{name}`"
75
+ end
76
+
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,17 @@
1
+ require 'tty-prompt'
2
+
3
+ module PolyglotIos
4
+ module Helper
5
+ module Terminal
6
+
7
+ def success(message = 'Success!')
8
+ prompt.ok(message)
9
+ end
10
+
11
+ def prompt
12
+ @prompt ||= TTY::Prompt.new(interrupt: :exit)
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,20 @@
1
+ module PolyglotIos
2
+ module IO
3
+ class Config
4
+
5
+ CONFIG_FILE_PATH = "#{Dir.pwd}/polyglot.yml".freeze
6
+
7
+ class << self
8
+ def write(data)
9
+ File.open(CONFIG_FILE_PATH, 'w') { |file| YAML.dump(data, file) }
10
+ end
11
+
12
+ def read
13
+ YAML.load_file(CONFIG_FILE_PATH)
14
+ end
15
+ end
16
+
17
+ end
18
+ end
19
+ end
20
+
@@ -0,0 +1,25 @@
1
+ module PolyglotIos
2
+ module IO
3
+ class Token
4
+
5
+ KEYCHAIN_SERVICE = 'polyglot'.freeze
6
+ KEYCHAIN_TOKEN_KEY = 'polyglot_token'.freeze
7
+
8
+ class << self
9
+ def write(token)
10
+ keychain[KEYCHAIN_SERVICE, KEYCHAIN_TOKEN_KEY] = token
11
+ end
12
+
13
+ def read
14
+ keychain[KEYCHAIN_SERVICE, KEYCHAIN_TOKEN_KEY]
15
+ end
16
+
17
+ def keychain
18
+ @keychain ||= OSXKeychain.new
19
+ end
20
+
21
+ end
22
+ end
23
+ end
24
+ end
25
+
@@ -0,0 +1,27 @@
1
+ module PolyglotIos
2
+ module Serializer
3
+ module Language
4
+ class Base
5
+ include ERB::Util
6
+ attr_accessor :languages
7
+
8
+ def initialize(languages)
9
+ @languages = languages
10
+ end
11
+
12
+ def render()
13
+ ERB.new(template, nil, '-').result(binding)
14
+ end
15
+
16
+ def template()
17
+ fail NotImplementedError, 'Abstract Method'
18
+ end
19
+
20
+ def save(path)
21
+ fail NotImplementedError, 'Abstract Method'
22
+ end
23
+
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,90 @@
1
+ require_relative 'languages_serializer'
2
+
3
+ module PolyglotIos
4
+ module Serializer
5
+ module Language
6
+ class ObjC < Base
7
+
8
+ def render()
9
+ h_file = ERB.new(h_template, nil, '-').result(binding)
10
+ m_file = ERB.new(m_template, nil, '-').result(binding)
11
+ return h_file, m_file
12
+ end
13
+
14
+ def save(sources_path)
15
+ FileUtils.mkdir_p sources_path unless File.exist? sources_path
16
+ h_file, m_file = render()
17
+ File.write(File.join(sources_path, "Language.h"), h_file)
18
+ File.write(File.join(sources_path, "Language.m"), m_file)
19
+ end
20
+
21
+ def h_template()
22
+ <<-H_TEMPLATE
23
+ #import <Foundation/Foundation.h>
24
+
25
+ @interface Language : NSObject
26
+
27
+ @property (nonatomic, strong, readonly) NSString *name;
28
+ @property (nonatomic, strong, readonly) NSString *localName;
29
+ @property (nonatomic, strong, readonly) NSString *locale;
30
+ @property (nonatomic, strong, readonly) NSString *languageCode;
31
+
32
+ @property (readonly, class) NSArray<Language *> *allLanguages;
33
+ <% @languages.each do |language| -%>
34
+ @property (readonly, class) Language *<%= language.clean_name %>;
35
+ <% end -%>
36
+
37
+ @end
38
+ H_TEMPLATE
39
+ end
40
+
41
+ def m_template()
42
+ <<-M_TEMPLATE
43
+ #import "Language.h"
44
+
45
+ @interface Language()
46
+
47
+ @property (nonatomic, strong) NSString *name;
48
+ @property (nonatomic, strong) NSString *localName;
49
+ @property (nonatomic, strong) NSString *locale;
50
+ @property (nonatomic, strong) NSString *languageCode;
51
+
52
+ @end
53
+
54
+ @implementation Language
55
+
56
+ - (instancetype)initWithName:(NSString *)name localName:(NSString *)localName locale:(NSString *)locale languageCode:(NSString *)languageCode
57
+ {
58
+ if (self = [super init]) {
59
+ _name = name;
60
+ _localName = localName;
61
+ _locale = locale;
62
+ _languageCode = languageCode;
63
+ }
64
+ return self;
65
+ }
66
+
67
+ <% @languages.each do |language| -%>
68
+ + (Language *)<%= language.clean_name %>
69
+ {
70
+ return [[Language alloc] initWithName:@"<%= language.name %>" localName:@"<%= language.local_name %>" locale:@"<%= language.locale %>" languageCode:@"<%= language.code %>"];
71
+ }
72
+
73
+ <% end -%>
74
+ + (NSArray<Language *> *)allLanguages
75
+ {
76
+ return @[
77
+ <% @languages.each_with_index do |language, i| -%>
78
+ Language.<%= language.clean_name %><%= i == (@languages.size - 1) ? "" : "," %>
79
+ <% end -%>
80
+ ];
81
+ }
82
+
83
+ @end
84
+ M_TEMPLATE
85
+ end
86
+
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,41 @@
1
+ require_relative 'languages_serializer'
2
+
3
+ module PolyglotIos
4
+ module Serializer
5
+ module Language
6
+ class Swift < Base
7
+
8
+ def save(sources_path)
9
+ FileUtils.mkdir_p sources_path unless File.exist? sources_path
10
+ output_path = File.join(sources_path, "Language.swift")
11
+ File.write(output_path, render)
12
+ end
13
+
14
+ def template()
15
+ <<-TEMPLATE
16
+ import Foundation
17
+
18
+ public struct Language {
19
+
20
+ public let name: String
21
+ public let localName: String
22
+ public let locale: String
23
+ public let languageCode: String
24
+
25
+ <% @languages.each do |language| -%>
26
+ public static let <%= language.clean_name %> = Language(name: "<%= language.name %>", localName: "<%= language.local_name %>", locale: "<%= language.locale %>", languageCode: "<%= language.code %>")
27
+ <% end -%>
28
+
29
+ public static let all = [
30
+ <% @languages.each_with_index do |language, i| -%>
31
+ Language.<%= language.clean_name %><%= i == (@languages.size - 1) ? "" : "," %>
32
+ <% end -%>
33
+ ]
34
+
35
+ }
36
+ TEMPLATE
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,63 @@
1
+ require_relative 'swift_helpers/translation_case'
2
+ require_relative 'swift_helpers/translation_enum'
3
+
4
+ module PolyglotIos
5
+ module Serializer
6
+ module Source
7
+ class Swift
8
+ include ERB::Util
9
+
10
+ def initialize(translation_keys)
11
+ @translation_keys = translation_keys
12
+ end
13
+
14
+ def render()
15
+ @root_enum = build_translations()
16
+ ERB.new(template, nil, '-').result(binding)
17
+ end
18
+
19
+ def template()
20
+ <<-TEMPLATE
21
+ import Foundation
22
+
23
+ <%= @root_enum.serialized() %>
24
+ public protocol StringsProtocol: RawRepresentable {
25
+ var localized: String { get }
26
+ func localized(with args: CVarArg...) -> String
27
+ }
28
+
29
+ public extension StringsProtocol where RawValue == String {
30
+ var localized: String {
31
+ return self.rawValue.localized
32
+ }
33
+
34
+ func localized(with args: CVarArg...) -> String {
35
+ return String(format: self.rawValue.localized, arguments: args)
36
+ }
37
+ }
38
+ TEMPLATE
39
+ end
40
+
41
+ def build_translations()
42
+ root_enum = TranslationEnum.new("Strings")
43
+
44
+ @translation_keys.each do |translation_key|
45
+ key_name = translation_key.name
46
+ next if key_name.nil?
47
+ components = key_name.split(".")
48
+ root_enum.insert(components, key_name)
49
+ end
50
+
51
+ return root_enum
52
+ end
53
+
54
+ def save(sources_path)
55
+ FileUtils.mkdir_p sources_path unless File.exist? sources_path
56
+ output_path = File.join(sources_path, "Strings.swift")
57
+ File.write(output_path, render)
58
+ end
59
+
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,23 @@
1
+ module PolyglotIos
2
+ module Serializer
3
+ module Source
4
+ class TranslationCase
5
+ include Helper::General
6
+
7
+ attr_accessor :name
8
+ attr_accessor :value
9
+
10
+ def initialize(name, value)
11
+ @name = name
12
+ @value = value
13
+ end
14
+
15
+ def serialized()
16
+ clean_name = clean_case_name(name)
17
+ "case #{clean_name} = \"#{value}\""
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,84 @@
1
+ require_relative 'translation_case'
2
+
3
+ module PolyglotIos
4
+ module Serializer
5
+ module Source
6
+ class TranslationEnum
7
+ include Helper::General
8
+ attr_accessor :name
9
+ attr_accessor :translations
10
+ attr_accessor :child_enums
11
+
12
+ def initialize(name)
13
+ @name = name
14
+ @translations = []
15
+ @child_enums = []
16
+ end
17
+
18
+ def insert(components, key_name)
19
+ if components.count > 1
20
+ insert_to_nested_enum(components, key_name)
21
+ elsif components.count == 1
22
+ translation_case = TranslationCase.new(components.first, key_name)
23
+ translations.push(translation_case)
24
+ end
25
+ end
26
+
27
+ def serialized(indent_level = 0)
28
+ output = indent(indent_level)
29
+ clean_name = clean_enum_name(name)
30
+
31
+ if translations.count == 0
32
+ # enum without any case statement cannot be RawRepresentable
33
+ output.concat("public enum #{clean_name} {\n")
34
+ else
35
+ output.concat("public enum #{clean_name}: String, StringsProtocol {\n")
36
+ end
37
+
38
+ translations
39
+ .sort_by { |translation| translation.name.downcase }
40
+ .each do |translation|
41
+ output
42
+ .concat(indent(indent_level + 1, translation.serialized()))
43
+ .concat("\n")
44
+ end
45
+
46
+ if translations.count > 0 && child_enums.count > 0
47
+ output.concat("\n")
48
+ end
49
+
50
+ serialized_enums = child_enums
51
+ .sort_by { |child_enum| child_enum.name.downcase }
52
+ .map { |child_enum| child_enum.serialized(indent_level + 1) }
53
+ .join("\n")
54
+ output.concat(serialized_enums)
55
+
56
+ output.concat(indent(indent_level, "}\n"))
57
+ return output
58
+ end
59
+
60
+ private
61
+
62
+ def insert_to_nested_enum(components, key_name)
63
+ _components = components.clone
64
+
65
+ enum_name = _components.shift
66
+ nested_enum = nested_enum_with_name(enum_name)
67
+
68
+ if !nested_enum.nil?
69
+ nested_enum.insert(_components, key_name)
70
+ else
71
+ child = TranslationEnum.new(enum_name)
72
+ child.insert(_components, key_name)
73
+ @child_enums.push(child)
74
+ end
75
+ end
76
+
77
+ def nested_enum_with_name(name)
78
+ child_enums.find { |enum| enum.name == name }
79
+ end
80
+
81
+ end
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,33 @@
1
+ module PolyglotIos
2
+ module Serializer
3
+ class Translation
4
+ class << self
5
+
6
+ def write(translation_keys, language, translations_path, use_old_naming = false)
7
+ translations = extract_translations(translation_keys, language)
8
+
9
+ data = translations
10
+ .sort
11
+ .map { |key, value| "\"#{key}\" = \"#{value}\";" }
12
+ .join("\n")
13
+ .concat("\n")
14
+
15
+ FileUtils.mkdir_p translations_path unless File.exist? translations_path
16
+ locale_code = language.code(use_old_naming)
17
+ output_path = File.join(translations_path, "#{locale_code}.strings")
18
+ File.write(output_path, data)
19
+ end
20
+
21
+ def extract_translations(translation_keys, language)
22
+ translation_keys.each_with_object({}) do |translation_key, strings|
23
+ key_name = translation_key.name
24
+ next if key_name.nil?
25
+ strings[key_name] = translation_key.clean_translation(language)
26
+ end
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
33
+
@@ -0,0 +1,3 @@
1
+ module PolyglotIos
2
+ VERSION = '2.0.1'.freeze
3
+ end
@@ -0,0 +1,42 @@
1
+ # Parsing
2
+ require 'yaml'
3
+ require 'osx_keychain'
4
+ require 'erb'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+
7
+ # Helpers
8
+ require 'ios_polyglot_cli/helpers/depaginate'
9
+ require 'ios_polyglot_cli/helpers/terminal'
10
+ require 'ios_polyglot_cli/helpers/general'
11
+
12
+ # JSON API Resources
13
+ require 'json_api_client'
14
+ require 'ios_polyglot_cli/api/base'
15
+ require 'ios_polyglot_cli/api/language'
16
+ require 'ios_polyglot_cli/api/project'
17
+ require 'ios_polyglot_cli/api/session'
18
+ require 'ios_polyglot_cli/api/translation'
19
+ require 'ios_polyglot_cli/api/translation_key'
20
+
21
+ # File Accessors
22
+ require 'ios_polyglot_cli/io/token'
23
+ require 'ios_polyglot_cli/io/config'
24
+
25
+ # Serializers
26
+ require 'ios_polyglot_cli/serializers/translations/translations_serializer'
27
+ require 'ios_polyglot_cli/serializers/languages/languages_serializer_objc'
28
+ require 'ios_polyglot_cli/serializers/languages/languages_serializer_swift'
29
+ require 'ios_polyglot_cli/serializers/sources/sources_serializer_swift'
30
+
31
+ # Commands
32
+ require 'ios_polyglot_cli/commands/login'
33
+ require 'ios_polyglot_cli/commands/setup'
34
+ require 'ios_polyglot_cli/commands/pull'
35
+
36
+ # Error Handler
37
+ require 'ios_polyglot_cli/error_handler'
38
+ require 'ios_polyglot_cli/version'
39
+
40
+ module PolyglotIos
41
+
42
+ end