ios_polyglot_cli 2.0.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.
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