terrestrial-cli 0.1.0

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