terrestrial-cli 0.1.0

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 (61) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +10 -0
  3. data/.rspec +1 -0
  4. data/.ruby-gemset +1 -0
  5. data/.ruby-version +1 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +134 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +7 -0
  12. data/bin/terrestrial +44 -0
  13. data/circle.yml +11 -0
  14. data/lib/terrestrial/cli/android_xml_formatter.rb +43 -0
  15. data/lib/terrestrial/cli/android_xml_parser.rb +49 -0
  16. data/lib/terrestrial/cli/bootstrapper.rb +184 -0
  17. data/lib/terrestrial/cli/command.rb +20 -0
  18. data/lib/terrestrial/cli/detects_project_type.rb +16 -0
  19. data/lib/terrestrial/cli/dot_strings_formatter.rb +53 -0
  20. data/lib/terrestrial/cli/dot_strings_parser.rb +139 -0
  21. data/lib/terrestrial/cli/editor/android_xml.rb +64 -0
  22. data/lib/terrestrial/cli/editor/base_editor.rb +36 -0
  23. data/lib/terrestrial/cli/editor/objc.rb +66 -0
  24. data/lib/terrestrial/cli/editor/printer.rb +47 -0
  25. data/lib/terrestrial/cli/editor/storyboard.rb +98 -0
  26. data/lib/terrestrial/cli/editor/swift.rb +92 -0
  27. data/lib/terrestrial/cli/editor.rb +42 -0
  28. data/lib/terrestrial/cli/engine_mapper.rb +30 -0
  29. data/lib/terrestrial/cli/entry_collection_differ.rb +22 -0
  30. data/lib/terrestrial/cli/file_finder.rb +65 -0
  31. data/lib/terrestrial/cli/file_picker.rb +58 -0
  32. data/lib/terrestrial/cli/flight/ios_workflow.rb +81 -0
  33. data/lib/terrestrial/cli/flight/table_workflow.rb +77 -0
  34. data/lib/terrestrial/cli/flight.rb +93 -0
  35. data/lib/terrestrial/cli/ignite.rb +73 -0
  36. data/lib/terrestrial/cli/init.rb +133 -0
  37. data/lib/terrestrial/cli/mixpanel_client.rb +56 -0
  38. data/lib/terrestrial/cli/parser/android_xml.rb +82 -0
  39. data/lib/terrestrial/cli/parser/base_parser.rb +42 -0
  40. data/lib/terrestrial/cli/parser/objc.rb +127 -0
  41. data/lib/terrestrial/cli/parser/storyboard.rb +166 -0
  42. data/lib/terrestrial/cli/parser/string_analyser.rb +115 -0
  43. data/lib/terrestrial/cli/parser/swift.rb +102 -0
  44. data/lib/terrestrial/cli/parser.rb +25 -0
  45. data/lib/terrestrial/cli/photoshoot.rb +65 -0
  46. data/lib/terrestrial/cli/pull.rb +110 -0
  47. data/lib/terrestrial/cli/push.rb +40 -0
  48. data/lib/terrestrial/cli/scan.rb +72 -0
  49. data/lib/terrestrial/cli/string_registry.rb +30 -0
  50. data/lib/terrestrial/cli/terminal_ui.rb +25 -0
  51. data/lib/terrestrial/cli/variable_normalizer.rb +34 -0
  52. data/lib/terrestrial/cli/version.rb +5 -0
  53. data/lib/terrestrial/cli.rb +82 -0
  54. data/lib/terrestrial/config.rb +99 -0
  55. data/lib/terrestrial/creates_terrestrial_yml.rb +9 -0
  56. data/lib/terrestrial/web/response.rb +17 -0
  57. data/lib/terrestrial/web.rb +78 -0
  58. data/lib/terrestrial/yaml_helper.rb +48 -0
  59. data/lib/terrestrial.rb +7 -0
  60. data/terrestrial-cli.gemspec +29 -0
  61. metadata +188 -0
@@ -0,0 +1,82 @@
1
+ module Terrestrial
2
+ module Cli
3
+ module Parser
4
+ class AndroidXML < BaseParser
5
+ LANGUAGE = :android_xml
6
+
7
+ def self.find_strings(file)
8
+ self.new(file).find_strings
9
+ end
10
+
11
+ def self.find_api_calls(file)
12
+ self.new(file).find_api_calls
13
+ end
14
+
15
+ def initialize(file)
16
+ @path = file
17
+ @file = File.new(file)
18
+ @document = REXML::Document.new(@file)
19
+ end
20
+
21
+ def find_strings
22
+ result = []
23
+ REXML::XPath.each(@document, "//resources/string") do |node|
24
+ result << build_new_string_entry(node)
25
+ end
26
+ result
27
+ end
28
+
29
+ def find_api_calls
30
+ result = []
31
+ REXML::XPath.each(@document, "//resources/string[@terrestrial=\"true\"]") do |node|
32
+ result << build_registry_entry_hash(node)
33
+ end
34
+ result
35
+ end
36
+
37
+ def build_new_string_entry(node)
38
+ Hash.new.tap do |entry|
39
+ entry["language"] = LANGUAGE
40
+ entry["file"] = @path
41
+ entry["string"] = get_string_from_node(node)
42
+ entry["type"] = "android-strings-xml"
43
+ entry["line_number"] = nil
44
+ # entry.variables = get_variables_from_string(entry.string)
45
+ entry["identifier"] = node.attributes["name"]
46
+ end
47
+ end
48
+
49
+ def build_registry_entry_hash(node)
50
+ Hash.new.tap do |entry|
51
+ entry["string"] = get_string_from_node(node)
52
+ entry["context"] = node.attributes["context"] || ""
53
+ entry["file"] = @path
54
+ entry["line_number"] = nil
55
+ entry["type"] = "android-strings-xml"
56
+ entry["id"] = node.attributes["name"]
57
+ end
58
+ end
59
+
60
+ def get_string_from_node(node)
61
+ # Why could the text be nil?
62
+ # - If it contains valid XML!
63
+ #
64
+ # We assume anything inside the string tag is actually
65
+ # what should be shown in the UI, so we just parse it
66
+ # as a string if we realise that the parser thinks it
67
+ # is XML.
68
+
69
+ if !node.get_text.nil?
70
+ node.get_text.value
71
+ else
72
+ node.children.first.to_s
73
+ end
74
+ end
75
+
76
+ def get_variables_from_string(string)
77
+ string.scan(/(\%\d\$[dsf])/).map {|match| match[0] }
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,42 @@
1
+ module Terrestrial
2
+ module Cli
3
+ module Parser
4
+ class BaseParser
5
+
6
+ # Interface for finding locations in files where the Terrestrial
7
+ # API will be accessing strings
8
+ #
9
+ # file - path to source file
10
+ #
11
+ # Expected to return an array of
12
+ # Bootstrapper::NewStringEntry
13
+ # objects
14
+ def self.find_api_calls(file)
15
+ raise "Not implemented"
16
+ end
17
+
18
+ # Interface for finding strings in a source file
19
+ #
20
+ # file - path to source file
21
+ #
22
+ # Expected to return an array of
23
+ # hashes TODO: make return an object
24
+ def self.find_string(file)
25
+ raise "Not implemented"
26
+ end
27
+
28
+ def self.find_nslocalizedstrings(file)
29
+ raise "Not implemented"
30
+ end
31
+
32
+
33
+
34
+ def self.scan_lines(path)
35
+ File.readlines(file).each_with_index do |line, index|
36
+ yield line, index
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,127 @@
1
+ module Terrestrial
2
+ module Cli
3
+ module Parser
4
+ class ObjC < BaseParser
5
+ LANGUAGE = :objc
6
+
7
+ STRING_REGEX = /@"(.*?)"/
8
+ NSLOCALIZEDSTRING_REGEX = /NSLocalizedString\(.*@"(.*)".*\)/
9
+ DOT_TRANSLATED_REGEX = /@"([^"]*)".translated\W/
10
+ TRANSLATED_WITH_CONTEXT_REGEX = /@"([^"]*)"\stranslatedWithContext:/
11
+
12
+ def self.find_strings(file)
13
+ results = []
14
+ if is_view_controller?(file)
15
+ File.readlines(file, :encoding => "UTF-8").each_with_index do |line, index|
16
+ line.encode!('UTF-16', :undef => :replace, :invalid => :replace, :replace => "")
17
+ line.encode!('UTF-8')
18
+ results.concat(analyse_line_for_strings(line,index, file))
19
+ end
20
+ end
21
+ results
22
+ end
23
+
24
+ def self.analyse_line_for_strings(line, index, file_path)
25
+ results = []
26
+ line.scan(STRING_REGEX).each do |match|
27
+ unless looks_suspicious(line)
28
+ results.push(Hash.new.tap do |entry|
29
+ entry["language"] = LANGUAGE
30
+ entry["file"] = file_path
31
+ entry["line_number"] = index + 1
32
+ entry["string"] = match[0]
33
+ entry["type"] = guess_type(line)
34
+ # entry.variables = get_variable_names(line) if entry.type == "stringWithFormat"
35
+
36
+ end)
37
+ end
38
+ end
39
+ results
40
+ end
41
+
42
+ def self.find_api_calls(file)
43
+ results = []
44
+ File.readlines(file).each_with_index do |line, index|
45
+ results.concat(analyse_line_for_dot_translated(line, index, file))
46
+ results.concat(analyse_line_for_translatedWithContext(line, index, file))
47
+ end
48
+ results
49
+ end
50
+
51
+ def self.analyse_line_for_translatedWithContext(line, index, file_path)
52
+ results = []
53
+ line.scan(TRANSLATED_WITH_CONTEXT_REGEX).each do |match|
54
+ results.push(Hash.new.tap do |h|
55
+ h["file"] = file_path
56
+ h["line_number"] = index + 1
57
+ h["string"] = match[0]
58
+ h["type"] = "translatedWithContext"
59
+ h["context"] = get_context(line, h["string"])
60
+ end)
61
+ end
62
+ results
63
+ end
64
+
65
+ def self.analyse_line_for_dot_translated(line, index, file_path)
66
+ results = []
67
+ line.scan(DOT_TRANSLATED_REGEX).each do |match|
68
+ results.push(Hash.new.tap do |h|
69
+ h["file"] = file_path
70
+ h["line_number"] = index + 1
71
+ h["string"] = match[0]
72
+ h["type"] = ".translated"
73
+ h["context"] = ""
74
+ end)
75
+ end
76
+ results
77
+ end
78
+
79
+ def self.get_context(line, match)
80
+ line.match(/"#{match}" translatedWithContext:\s?@"([^"]*)"/)[1]
81
+ end
82
+
83
+ def self.guess_type(line)
84
+ if line.include? "stringWithFormat"
85
+ "stringWithFormat"
86
+ else
87
+ "unknown"
88
+ end
89
+ end
90
+
91
+ def self.get_variable_names(line)
92
+ line
93
+ .scan(/stringWithFormat:\s?@"[^"]+",\s?(.*?)\][^\s*,]/)
94
+ .first.first # Array of arrays Yo.
95
+ .split(",")
96
+ .map {|var| var.gsub(/\s+/, "")}
97
+ end
98
+
99
+ def self.looks_suspicious(line)
100
+ without_strings = line.gsub(STRING_REGEX, "")
101
+ without_strings.include?("_LOG") ||
102
+ without_strings.include?("DLog") ||
103
+ without_strings.include?("NSLog") ||
104
+ without_strings.include?("NSAssert") ||
105
+ without_strings.downcase.include?("uistoryboard") ||
106
+ without_strings.downcase.include?("instantiateviewcontrollerwithidentifier") ||
107
+ without_strings.downcase.include?("uiimage") ||
108
+ without_strings.downcase.include?("nsentitydescription") ||
109
+ without_strings.downcase.include?("nspredicate") ||
110
+ without_strings.downcase.include?("dateformat") ||
111
+ without_strings.downcase.include?("datefromstring") ||
112
+ without_strings.downcase.include?("==") ||
113
+ without_strings.downcase.include?("isequaltostring") ||
114
+ without_strings.downcase.include?("valueforkey") ||
115
+ without_strings.downcase.include?("cellidentifier") ||
116
+ without_strings.downcase.include?("uifont") ||
117
+ without_strings.downcase.include?("static ") ||
118
+ without_strings.downcase.include?("print(")
119
+ end
120
+
121
+ def self.is_view_controller?(file)
122
+ !file.match(/ViewController/).nil?
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,166 @@
1
+ require 'rexml/document'
2
+
3
+ module Terrestrial
4
+ module Cli
5
+ module Parser
6
+ class Storyboard
7
+ LANGUAGE = :ios_storyboard
8
+ attr_reader :result
9
+
10
+ include REXML
11
+ QUERIES = {
12
+ "storyboard-label" => "//label",
13
+ "storyboard-text-field" => "//textField",
14
+ "storyboard-button" => "//button/state",
15
+ "storyboard-bar-button-item" => "//barButtonItem",
16
+ "storyboard-navbar-item" => "//navigationItem",
17
+ "storyboard-text-view" => "//textView"
18
+ }
19
+
20
+ TEXT_ATTRIBUTE = {
21
+ "storyboard-label" => "text",
22
+ "storyboard-text-field" => "placeholder",
23
+ "storyboard-button" => "title",
24
+ "storyboard-bar-button-item" => "title",
25
+ "storyboard-navbar-item" => "title",
26
+ "storyboard-text-view" => "text"
27
+ }
28
+
29
+ TYPES = QUERIES.keys
30
+
31
+ def initialize(file)
32
+ @path = file
33
+ @file = File.new(file)
34
+ @document = Document.new(@file)
35
+ @result = []
36
+ end
37
+
38
+ def self.find_strings(file)
39
+ self.new(file).find_strings
40
+ end
41
+
42
+ def self.find_api_calls(file)
43
+ self.new(file).find_api_calls
44
+ end
45
+
46
+ def find_api_calls
47
+ labels = []
48
+ XPath.each(@document, api_calls_query) do |node|
49
+ type = type_for(node.name)
50
+ string = get_string(node)
51
+ context = get_context(node)
52
+
53
+ labels << build_registry_entry_hash(string, context, type)
54
+ end
55
+ @result = labels
56
+ @result
57
+ end
58
+
59
+ def find_strings
60
+ TYPES.each do |type|
61
+ @result.concat(find_entries_for_type(type))
62
+ end
63
+ @result
64
+ end
65
+
66
+
67
+
68
+ def type_for(name)
69
+ {
70
+ "label" => "storyboard-label",
71
+ "textField" => "storyboard-text-field",
72
+ "button" => "storyboard-button",
73
+ "barButtonItem" => "storyboard-bar-button-item",
74
+ "navigationItem" => "storyboard-navbar-item",
75
+ "textView" => "storyboard-text-view",
76
+ }[name]
77
+ end
78
+
79
+ def find_entries_for_type(type)
80
+ labels = []
81
+ XPath.each(@document, QUERIES[type]) do |node|
82
+ labels << new_entry(node.attributes[TEXT_ATTRIBUTE[type]],
83
+ type: type,
84
+ id: get_id(node))
85
+ end
86
+ labels
87
+ end
88
+
89
+ def get_id(node)
90
+ # Why? Because the button's text is not in the
91
+ # button, but in a child element, that doesn't have
92
+ # an ID. So for most situations you'll just pick the
93
+ # ID off the element, but sometimes we'll have to
94
+ # traverse back up to get the ID. If that element
95
+ # doesn't have an ID, it's a new situation and
96
+ # we should get an exception down the line.
97
+
98
+ target = node
99
+ if target.attributes["id"].nil?
100
+ target.parent.attributes["id"]
101
+ else
102
+ target.attributes["id"]
103
+ end
104
+ end
105
+
106
+ def get_context(node)
107
+ # //textView/userDefinedRuntimeAttributes/userDefinedRuntimeAttribute[@keyPath="contextInfo"]
108
+ context = ""
109
+ attributes = node.elements.select {|e| e.name == "userDefinedRuntimeAttributes"}.first
110
+ attributes.each_element_with_attribute("keyPath", "contextInfo") do |e|
111
+ context = e.attributes["value"]
112
+ end
113
+ context
114
+ end
115
+
116
+ def get_string(node)
117
+ type = type_for(node.name)
118
+ if type == "storyboard-button"
119
+ node.elements.select {|e| e.name == "state"}.first.attributes[TEXT_ATTRIBUTE[type]]
120
+ else
121
+ node.attributes[TEXT_ATTRIBUTE[type]]
122
+ end
123
+ end
124
+
125
+ def api_calls_query
126
+ # Finds all the attributes that say that an element is
127
+ # translated by Terrestrial, and we then traverse
128
+ # two parents up:
129
+ #
130
+ # <*targetElement*>
131
+ # <userDefinedRuntimeAttributes>
132
+ # <userDefinedRuntimeAttribute ... /> <- these are what we find
133
+
134
+ '//userDefinedRuntimeAttribute[@type="boolean" and @value="YES"]/../..'
135
+ end
136
+
137
+ def new_entry(string, opts)
138
+ defaults = { type: "storyboard" }
139
+ values = defaults.merge(opts)
140
+
141
+ Hash.new.tap do |entry|
142
+ entry["file"] = @path
143
+ entry["language"] = LANGUAGE
144
+ entry["string"] = string.to_s
145
+ entry["type"] = values.fetch(:type)
146
+ entry["line_number"] = nil
147
+ entry["metadata"] = {
148
+ "storyboard_element_id" => values.fetch(:id)
149
+ }
150
+ end
151
+ end
152
+
153
+ def build_registry_entry_hash(string, context, type)
154
+ Hash.new.tap do |entry|
155
+ entry["string"] = string.to_s
156
+ entry["language"] = LANGUAGE
157
+ entry["context"] = context || ""
158
+ entry["file"] = @path
159
+ entry["line_number"] = nil
160
+ entry["type"] = type
161
+ end
162
+ end
163
+ end
164
+ end
165
+ end
166
+ end
@@ -0,0 +1,115 @@
1
+ module Terrestrial
2
+ module Cli
3
+ module Parser
4
+ class StringAnalyser
5
+
6
+ def self.is_string_for_humans?(string, language, variables = [])
7
+ self.new(string, language, variables).decide
8
+ end
9
+
10
+ def initialize(string, language, variables = [])
11
+ @string = string
12
+ @variables = variables || [] # TODO: Find out what was passing in variables as nil instead of empty array
13
+ @language = language
14
+ end
15
+
16
+ def decide
17
+ if @variables.any?
18
+ looks_like_string_without_variables?
19
+ else
20
+ if has_camel_case_words? || looks_like_sql? || is_number? || has_snake_case_words?
21
+ false
22
+ elsif number_of_words > 1 && percentage_of_none_alphanumeric < 0.15
23
+ true
24
+ elsif number_of_words == 1 && is_capitalised? && percentage_of_none_alphanumeric < 0.1
25
+ true
26
+ else
27
+ false
28
+ end
29
+ end
30
+ end
31
+
32
+ def is_number?
33
+ !(@string =~ /\A[-+]?[0-9]*\.?[0-9]+\Z/).nil?
34
+ end
35
+
36
+ def number_of_words
37
+ @string.split(" ").length
38
+ end
39
+
40
+ def percentage_of_none_alphanumeric
41
+ total = @string.split("").length.to_f
42
+ non_alphanumeric = @string
43
+ .split("")
44
+ .select {|c| /[0-9a-zA-Z i\s]/.match(c).nil? }
45
+ .length
46
+ .to_f
47
+
48
+ non_alphanumeric / total
49
+ end
50
+
51
+ def looks_like_sql?
52
+ # Handle SQL with clever regex
53
+ !@string.match(/(ALTER|CREATE|DROP) TABLE/).nil? ||
54
+ !@string.match(/(DELETE|SELECT|INSERT|UPDATE).+(FROM|INTO|SET)/).nil? ||
55
+ !@string.match(/(delete|select|insert|update).+(from|into|set)/).nil?
56
+ end
57
+
58
+ def has_weird_characters?
59
+ (@string.split("") & ["<",">","\\", "/","*"]).length > 0
60
+ end
61
+
62
+ def has_punctuation?
63
+ (@string.split("") & [".",",","=","&"]).length > 0
64
+ end
65
+
66
+ def has_camel_case_words?
67
+ @string.split(" ")
68
+ .select {|word| !word.match(/([a-zA-Z][a-z]+[A-Z][a-zA-Z]+)/).nil? }
69
+ .any?
70
+ end
71
+
72
+ def has_camel_case_words?
73
+ @string.split(" ")
74
+ .select {|word| !word.match(/([a-zA-Z][a-z]+[A-Z][a-zA-Z]+)/).nil? }
75
+ .any?
76
+ end
77
+
78
+ def has_snake_case_words?
79
+ @string.split(" ")
80
+ .select {|word| !word.match(/\b\w*(_\w*)+\b/).nil? }
81
+ .any?
82
+ end
83
+
84
+ def is_capitalised?
85
+ @string == @string.capitalize
86
+ end
87
+
88
+ def looks_like_string_without_variables?
89
+ # Strip away the variables, remove extra whitespace,
90
+ # and feed that string back into the system to see
91
+ # if it now looks human readable or not.
92
+
93
+ if @language == ObjC::LANGUAGE
94
+ new_string = @string
95
+ .gsub(/(%@)|(%d)/, "")
96
+ .gsub(/\s\s/, " ")
97
+ elsif @language == Swift::LANGUAGE
98
+ new_string = @string
99
+ .gsub(/\\\(.*\)/, "")
100
+ .gsub(/\s\s/, " ")
101
+ elsif @language == AndroidXML::LANGUAGE
102
+ new_string = @string
103
+
104
+ @variables.each do |v|
105
+ new_string = new_string.gsub(v, "")
106
+ new_string = new_string.gsub(/\s\s/, " ")
107
+ end
108
+ end
109
+
110
+ self.class.new(new_string, @language).decide
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,102 @@
1
+ module Terrestrial
2
+ module Cli
3
+ module Parser
4
+ class Swift
5
+ LANGUAGE = :swift
6
+
7
+ NSLOCALIZEDSTRING_REGEX = /NSLocalizedString\(.*"(.*)".*\)/
8
+ STRING_REGEX = /"([^"]*)"/
9
+ DOT_TRANSLATED_REGEX = /"([^"]*)".translated\W/
10
+ TRANSLATED_WITH_CONTEXT_REGEX = /"([^"]*)".translatedWithContext/
11
+ VARIABLE_REGEX = /\\\((.*?)\)/
12
+
13
+ def self.find_strings(file)
14
+ results = []
15
+ if is_view_controller?(file)
16
+ File.readlines(file).each_with_index do |line, index|
17
+ results.concat analyse_line_for_strings(line, index, file)
18
+ end
19
+ end
20
+ results
21
+ end
22
+
23
+ def self.analyse_line_for_strings(line, index, file_path)
24
+ results = []
25
+ line.scan(STRING_REGEX).each do |match|
26
+ unless looks_suspicious(line)
27
+ results.push(Hash.new.tap do |entry|
28
+ entry["language"] = LANGUAGE
29
+ entry["file"] = file_path
30
+ entry["line_number"] = index + 1
31
+ entry["string"] = match[0]
32
+ entry["type"] = find_variables(match[0]).any? ? "stringWithFormat" : "unknown"
33
+ # entry.variables = find_variables(match[0])
34
+ end)
35
+ end
36
+ end
37
+ results
38
+ end
39
+
40
+ def self.find_api_calls(file)
41
+ results = []
42
+ File.readlines(file).each_with_index do |line, index|
43
+ line.scan(DOT_TRANSLATED_REGEX).each do |match|
44
+ results.push(Hash.new.tap do |h|
45
+ h["file"] = file
46
+ h["line_number"] = index + 1
47
+ h["string"] = match[0]
48
+ h["type"] = ".translated"
49
+ h["context"] = ""
50
+ end)
51
+ end
52
+
53
+ line.scan(TRANSLATED_WITH_CONTEXT_REGEX).each do |match|
54
+ results.push(Hash.new.tap do |h|
55
+ h["file"] = file
56
+ h["line_number"] = index + 1
57
+ h["string"] = match[0]
58
+ h["type"] = "translatedWithContext"
59
+ h["context"] = get_context(line, h["string"])
60
+ end)
61
+ end
62
+ end
63
+ results
64
+ end
65
+
66
+ def self.get_context(line, match)
67
+ line.match(/"#{match}"\.translatedWithContext\("([^"]*)"\)/)[1]
68
+ end
69
+
70
+ def self.find_variables(string)
71
+ # tries to find \(asd) inside the string itself
72
+ string.scan(VARIABLE_REGEX).map {|matches| matches[0]}
73
+ end
74
+
75
+ def self.is_view_controller?(file)
76
+ !file.match(/ViewController/).nil?
77
+ end
78
+
79
+ def self.looks_suspicious(line)
80
+ without_strings = line.gsub(STRING_REGEX, "")
81
+ without_strings.include?("_LOG") ||
82
+ without_strings.include?("DLog") ||
83
+ without_strings.include?("NSLog") ||
84
+ without_strings.include?("NSAssert") ||
85
+ without_strings.downcase.include?("uistoryboard") ||
86
+ without_strings.downcase.include?("instantiateviewcontrollerwithidentifier") ||
87
+ without_strings.downcase.include?("uiimage") ||
88
+ without_strings.downcase.include?("nsentitydescription") ||
89
+ without_strings.downcase.include?("nspredicate") ||
90
+ without_strings.downcase.include?("dateformat") ||
91
+ without_strings.downcase.include?("datefromstring") ||
92
+ without_strings.downcase.include?("==") ||
93
+ without_strings.downcase.include?("isequaltostring") ||
94
+ without_strings.downcase.include?("valueforkey") ||
95
+ without_strings.downcase.include?("cellidentifier") ||
96
+ without_strings.downcase.include?("uifont") ||
97
+ without_strings.downcase.include?("print(")
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,25 @@
1
+ require 'terrestrial/cli/parser/base_parser'
2
+ require 'terrestrial/cli/parser/objc'
3
+ require 'terrestrial/cli/parser/swift'
4
+ require 'terrestrial/cli/parser/storyboard'
5
+ require 'terrestrial/cli/parser/android_xml'
6
+ require 'terrestrial/cli/parser/string_analyser'
7
+
8
+ module Terrestrial
9
+ module Cli
10
+ module Parser
11
+
12
+ def self.find_strings(file)
13
+ EngineMapper.parser_for(File.extname(file)).find_strings(file)
14
+ end
15
+
16
+ def self.find_api_calls(file)
17
+ EngineMapper.parser_for(File.extname(file)).find_api_calls(file)
18
+ end
19
+
20
+ def self.find_nslocalizedstrings(file)
21
+ EngineMapper.parser_for(File.extname(file)).find_nslocalizedstrings(file)
22
+ end
23
+ end
24
+ end
25
+ end