terrestrial-cli 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.rspec +1 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +134 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/bin/terrestrial +44 -0
- data/circle.yml +11 -0
- data/lib/terrestrial/cli/android_xml_formatter.rb +43 -0
- data/lib/terrestrial/cli/android_xml_parser.rb +49 -0
- data/lib/terrestrial/cli/bootstrapper.rb +184 -0
- data/lib/terrestrial/cli/command.rb +20 -0
- data/lib/terrestrial/cli/detects_project_type.rb +16 -0
- data/lib/terrestrial/cli/dot_strings_formatter.rb +53 -0
- data/lib/terrestrial/cli/dot_strings_parser.rb +139 -0
- data/lib/terrestrial/cli/editor/android_xml.rb +64 -0
- data/lib/terrestrial/cli/editor/base_editor.rb +36 -0
- data/lib/terrestrial/cli/editor/objc.rb +66 -0
- data/lib/terrestrial/cli/editor/printer.rb +47 -0
- data/lib/terrestrial/cli/editor/storyboard.rb +98 -0
- data/lib/terrestrial/cli/editor/swift.rb +92 -0
- data/lib/terrestrial/cli/editor.rb +42 -0
- data/lib/terrestrial/cli/engine_mapper.rb +30 -0
- data/lib/terrestrial/cli/entry_collection_differ.rb +22 -0
- data/lib/terrestrial/cli/file_finder.rb +65 -0
- data/lib/terrestrial/cli/file_picker.rb +58 -0
- data/lib/terrestrial/cli/flight/ios_workflow.rb +81 -0
- data/lib/terrestrial/cli/flight/table_workflow.rb +77 -0
- data/lib/terrestrial/cli/flight.rb +93 -0
- data/lib/terrestrial/cli/ignite.rb +73 -0
- data/lib/terrestrial/cli/init.rb +133 -0
- data/lib/terrestrial/cli/mixpanel_client.rb +56 -0
- data/lib/terrestrial/cli/parser/android_xml.rb +82 -0
- data/lib/terrestrial/cli/parser/base_parser.rb +42 -0
- data/lib/terrestrial/cli/parser/objc.rb +127 -0
- data/lib/terrestrial/cli/parser/storyboard.rb +166 -0
- data/lib/terrestrial/cli/parser/string_analyser.rb +115 -0
- data/lib/terrestrial/cli/parser/swift.rb +102 -0
- data/lib/terrestrial/cli/parser.rb +25 -0
- data/lib/terrestrial/cli/photoshoot.rb +65 -0
- data/lib/terrestrial/cli/pull.rb +110 -0
- data/lib/terrestrial/cli/push.rb +40 -0
- data/lib/terrestrial/cli/scan.rb +72 -0
- data/lib/terrestrial/cli/string_registry.rb +30 -0
- data/lib/terrestrial/cli/terminal_ui.rb +25 -0
- data/lib/terrestrial/cli/variable_normalizer.rb +34 -0
- data/lib/terrestrial/cli/version.rb +5 -0
- data/lib/terrestrial/cli.rb +82 -0
- data/lib/terrestrial/config.rb +99 -0
- data/lib/terrestrial/creates_terrestrial_yml.rb +9 -0
- data/lib/terrestrial/web/response.rb +17 -0
- data/lib/terrestrial/web.rb +78 -0
- data/lib/terrestrial/yaml_helper.rb +48 -0
- data/lib/terrestrial.rb +7 -0
- data/terrestrial-cli.gemspec +29 -0
- metadata +188 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Photoshoot < Command
|
4
|
+
|
5
|
+
WORKING_DIR = '/usr/local/var/terrestrial'
|
6
|
+
|
7
|
+
def run
|
8
|
+
Config.load!
|
9
|
+
MixpanelClient.track("cli-photoshoot-command")
|
10
|
+
|
11
|
+
if Config[:platform] != "ios"
|
12
|
+
abort "Unfortunately photoshoot mode is only supported on iOS at this time."
|
13
|
+
end
|
14
|
+
|
15
|
+
puts "Starting simulator in photoshoot mode..."
|
16
|
+
ensure_var_folder_exists
|
17
|
+
|
18
|
+
workspace = Dir["#{Config[:directory]}/*.xcworkspace"][0]
|
19
|
+
project = Dir["#{Config[:directory]}/*.xcodeproj"][0]
|
20
|
+
|
21
|
+
# Kill simulator and
|
22
|
+
system("killall \"Simulator\" &> /dev/null")
|
23
|
+
`rm -rf #{WORKING_DIR}`
|
24
|
+
|
25
|
+
if workspace
|
26
|
+
# If a workspace exists we want to build it instead of the project.
|
27
|
+
# We assume the scheme we want to use is simply the application name
|
28
|
+
app_name = File.basename(workspace).split(".").first
|
29
|
+
`xcodebuild -workspace "#{workspace}" -arch "i386" ONLY_ACTIVE_ARCH=NO VALID_ARCHS="i386 x86_64" -scheme #{app_name} -sdk iphonesimulator clean`
|
30
|
+
`xcodebuild -workspace "#{workspace}" -arch "i386" ONLY_ACTIVE_ARCH=NO VALID_ARCHS="i386 x86_64" -scheme #{app_name} -sdk iphonesimulator CONFIGURATION_BUILD_DIR=#{WORKING_DIR}`
|
31
|
+
else
|
32
|
+
app_name = File.basename(project).split(".").first
|
33
|
+
`xcodebuild -project "#{project}" -arch i386 -sdk iphonesimulator clean`
|
34
|
+
`xcodebuild -project "#{project}" -arch i386 -sdk iphonesimulator CONFIGURATION_BUILD_DIR=#{WORKING_DIR}`
|
35
|
+
end
|
36
|
+
`open /Applications/Xcode.app/Contents/Developer/Applications/Simulator.app`
|
37
|
+
|
38
|
+
# Here we literally sleep until the Simulator has been booted
|
39
|
+
wait_until_booted = %{
|
40
|
+
count=`xcrun simctl list | grep Booted | wc -l | sed -e 's/ //g'`
|
41
|
+
while [ $count -lt 1 ]
|
42
|
+
do
|
43
|
+
sleep 1
|
44
|
+
count=`xcrun simctl list | grep Booted | wc -l | sed -e 's/ //g'`
|
45
|
+
done
|
46
|
+
}
|
47
|
+
`#{wait_until_booted}`
|
48
|
+
|
49
|
+
# Here we magically find the bundle identifier of the app
|
50
|
+
command = "defaults read \"#{Dir[WORKING_DIR + '/*.app/Info.plist'].first}\" CFBundleIdentifier"
|
51
|
+
bundle_name = `#{command}`.chop
|
52
|
+
|
53
|
+
# Reinstall the app,
|
54
|
+
# Run it with the locale we want
|
55
|
+
`xcrun simctl uninstall booted #{bundle_name}`
|
56
|
+
`xcrun simctl install booted "#{Dir[WORKING_DIR + "/" + app_name + ".app"].first}"`
|
57
|
+
`xcrun simctl launch booted #{bundle_name} --args -TerrestrialScreenShotMode YES -TerrestrialAPIToken "#{Config[:api_key]}" -TerrestrialAppId "#{Config[:app_id]}" -TerrestrialProjectId "#{Config[:project_id]}" -TerrestrialURL "#{Config[:api_url]}"`
|
58
|
+
end
|
59
|
+
|
60
|
+
def ensure_var_folder_exists
|
61
|
+
`mkdir -p #{WORKING_DIR}`
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,110 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module Terrestrial
|
5
|
+
module Cli
|
6
|
+
class Pull < Command
|
7
|
+
|
8
|
+
def run
|
9
|
+
Config.load!
|
10
|
+
MixpanelClient.track("cli-pull-command")
|
11
|
+
|
12
|
+
fetch_translations
|
13
|
+
languages.each do |lang, translations|
|
14
|
+
update_translation_file(lang, translations)
|
15
|
+
end
|
16
|
+
print_confirmation
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def print_confirmation
|
22
|
+
if languages.size == 1
|
23
|
+
puts "Fetched latest translations for '#{languages.keys.first}'"
|
24
|
+
elsif languages.size == 0
|
25
|
+
puts "No translations to fetch..."
|
26
|
+
else
|
27
|
+
puts "Fetched latest translations for #{languages.size} languages: #{languages.keys.map {|l| "'#{l}'"}.join(", ")}."
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def update_translation_file(language, translations)
|
32
|
+
write_translation_file(
|
33
|
+
translation_file_path_for(language),
|
34
|
+
translations
|
35
|
+
.reject {|entry| entry["translation"].nil? || entry["translation"].empty? }
|
36
|
+
.map {|entry| TranslatedString.new(entry["translation"], entry["id"]) }
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
def translation_file_path_for(language)
|
41
|
+
if Config[:platform] == "ios"
|
42
|
+
ios_translation_file_path(language)
|
43
|
+
elsif Config[:platform] == "android"
|
44
|
+
android_translation_file_path(language)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def ios_translation_file_path(language)
|
49
|
+
folder = Pathname.new(Config[:directory] + "/" + Config[:translation_files].first)
|
50
|
+
.parent
|
51
|
+
.parent
|
52
|
+
.to_s + "/#{format_language_code(language)}.lproj"
|
53
|
+
|
54
|
+
FileUtils.mkdir_p(folder)
|
55
|
+
folder + "/Localizable.strings"
|
56
|
+
end
|
57
|
+
|
58
|
+
def android_translation_file_path(language)
|
59
|
+
folder = Pathname.new(Config[:directory] + "/" + Config[:translation_files].first)
|
60
|
+
.parent
|
61
|
+
.parent
|
62
|
+
.to_s + "/values-#{format_language_code(language)}"
|
63
|
+
|
64
|
+
FileUtils.mkdir_p(folder)
|
65
|
+
folder + "/strings.xml"
|
66
|
+
end
|
67
|
+
|
68
|
+
def write_translation_file(path, translations)
|
69
|
+
File.open(path, "w+") do |f|
|
70
|
+
if Config[:platform] == "ios"
|
71
|
+
f.write "// Updated by Terrestrial #{Time.now.to_s}\n\n"
|
72
|
+
f.write DotStringsFormatter.new(translations).format_foreign_translation
|
73
|
+
elsif Config[:platform] == "android"
|
74
|
+
f.write "<!-- Updated by Terrestrial #{Time.now.to_s} -->\n\n"
|
75
|
+
f.write AndroidXmlFormatter.new(translations).format_foreign_translation
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def format_language_code(language)
|
81
|
+
lang, region = language.split("-")
|
82
|
+
|
83
|
+
if region
|
84
|
+
"#{lang}_#{region.upcase}"
|
85
|
+
else
|
86
|
+
lang
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def fetch_translations
|
91
|
+
@response = web_client.get_translations(Config[:project_id], Config[:app_id])
|
92
|
+
end
|
93
|
+
|
94
|
+
def languages
|
95
|
+
response.body["data"]["translations"]
|
96
|
+
end
|
97
|
+
|
98
|
+
def response
|
99
|
+
@response
|
100
|
+
end
|
101
|
+
|
102
|
+
def web_client
|
103
|
+
@web_client ||= Web.new
|
104
|
+
end
|
105
|
+
|
106
|
+
class TranslatedString < Struct.new(:string, :identifier)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class Push < Command
|
4
|
+
|
5
|
+
def run
|
6
|
+
Config.load!
|
7
|
+
MixpanelClient.track("cli-push-command")
|
8
|
+
load_string_registry
|
9
|
+
|
10
|
+
web_client.push(Config[:project_id], Config[:app_id], format_entries)
|
11
|
+
|
12
|
+
puts "Success!"
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def format_entries
|
18
|
+
string_registry.entries.map do |entry|
|
19
|
+
{
|
20
|
+
"string" => entry["string"],
|
21
|
+
"context" => entry["context"],
|
22
|
+
"identifier" => entry["identifier"]
|
23
|
+
}
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def load_string_registry
|
28
|
+
@string_registry = StringRegistry.load
|
29
|
+
end
|
30
|
+
|
31
|
+
def web_client
|
32
|
+
@web_client ||= Web.new
|
33
|
+
end
|
34
|
+
|
35
|
+
def string_registry
|
36
|
+
@string_registry
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'terminal-table'
|
2
|
+
|
3
|
+
module Terrestrial
|
4
|
+
module Cli
|
5
|
+
class Scan < Command
|
6
|
+
|
7
|
+
def run
|
8
|
+
Config.load!
|
9
|
+
MixpanelClient.track("cli-scan-command")
|
10
|
+
|
11
|
+
TerminalUI.show_spinner do
|
12
|
+
@string_registry = StringRegistry.load
|
13
|
+
@remote_registry = fetch_current_strings_from_web
|
14
|
+
end
|
15
|
+
|
16
|
+
print_results
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def print_results
|
22
|
+
puts "New Strings: #{new_strings.count}"
|
23
|
+
puts "Removed Strings: #{removed_strings.count}"
|
24
|
+
|
25
|
+
if opts[:verbose]
|
26
|
+
print_diff
|
27
|
+
else
|
28
|
+
if rand(10) == 1 # Show hint ~10% of the time
|
29
|
+
puts "(Hint: add --verbose to the 'scan' command to view the diff of local and remote strings.)"
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def print_diff
|
35
|
+
puts "--- Diff"
|
36
|
+
puts "- New Strings"
|
37
|
+
print_table(new_strings)
|
38
|
+
puts ""
|
39
|
+
puts "- Removed Strings"
|
40
|
+
print_table(removed_strings)
|
41
|
+
end
|
42
|
+
|
43
|
+
def print_table(strings)
|
44
|
+
puts Terminal::Table.new(headings: ['Identifier', 'String', 'Comment']) do |t|
|
45
|
+
size = strings.count
|
46
|
+
strings.each_with_index do |string, i|
|
47
|
+
t.add_row([string["identifier"], string["string"], string["context"]])
|
48
|
+
t.add_separator unless i == (size - 1)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def new_strings
|
54
|
+
EntryCollectionDiffer.additions(@remote_registry, @string_registry.entries)
|
55
|
+
end
|
56
|
+
|
57
|
+
def removed_strings
|
58
|
+
EntryCollectionDiffer.omissions(@remote_registry, @string_registry.entries)
|
59
|
+
end
|
60
|
+
|
61
|
+
def fetch_current_strings_from_web
|
62
|
+
web_client
|
63
|
+
.get_app_strings(Config[:project_id], Config[:app_id])
|
64
|
+
.body["data"]["strings"]
|
65
|
+
end
|
66
|
+
|
67
|
+
def web_client
|
68
|
+
@web_client ||= Web.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class StringRegistry
|
4
|
+
|
5
|
+
def self.load
|
6
|
+
entries = Config[:translation_files].flat_map do |file|
|
7
|
+
begin
|
8
|
+
if Config[:platform] == "ios"
|
9
|
+
DotStringsParser.parse_file(Config[:directory] + "/#{file}")
|
10
|
+
elsif Config[:platform] == "android"
|
11
|
+
AndroidXmlParser.parse_file(Config[:directory] + "/#{file}")
|
12
|
+
end
|
13
|
+
rescue Errno::ENOENT
|
14
|
+
abort "Could not find #{file}. If the file is no longer in your project, remove it from your tracked files in terrestrial.yml."
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
new(entries)
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize(entries)
|
22
|
+
@entries = entries
|
23
|
+
end
|
24
|
+
|
25
|
+
def entries
|
26
|
+
@entries
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
module TerminalUI
|
4
|
+
|
5
|
+
def self.show_spinner(fps = 10)
|
6
|
+
chars = %w[| / - \\]
|
7
|
+
delay = 1.0/fps
|
8
|
+
iter = 0
|
9
|
+
spinner = Thread.new do
|
10
|
+
while iter do # Keep spinning until told otherwise
|
11
|
+
print chars[(iter+=1) % chars.length]
|
12
|
+
sleep delay
|
13
|
+
print "\b"
|
14
|
+
print " "
|
15
|
+
print "\b"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
yield.tap do # After yielding to the block, save the return value
|
19
|
+
iter = false # Tell the thread to exit, cleaning up after itself…
|
20
|
+
spinner.join # …and wait for it to do so.
|
21
|
+
end # Use the block's return value as the method's
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Cli
|
3
|
+
class VariableNormalizer
|
4
|
+
|
5
|
+
def self.run(string, swift: false)
|
6
|
+
result = string
|
7
|
+
result = format_swift_string(result) if swift
|
8
|
+
result = format_string(result)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.format_swift_string(target_string)
|
12
|
+
formatted_string = target_string
|
13
|
+
regex = /\\\(.*?\)/
|
14
|
+
index = 1
|
15
|
+
while formatted_string.scan(regex).any?
|
16
|
+
formatted_string = formatted_string.sub(regex, "%#{index}$@")
|
17
|
+
index += 1
|
18
|
+
end
|
19
|
+
formatted_string
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.format_string(target_string)
|
23
|
+
formatted_string = target_string
|
24
|
+
regex = /\%@/
|
25
|
+
index = 1
|
26
|
+
while formatted_string.scan(regex).any?
|
27
|
+
formatted_string = formatted_string.sub(regex, "%#{index}$@")
|
28
|
+
index += 1
|
29
|
+
end
|
30
|
+
formatted_string
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require "terrestrial/cli/command"
|
2
|
+
require "terrestrial/cli/init"
|
3
|
+
require "terrestrial/cli/flight"
|
4
|
+
require "terrestrial/cli/scan"
|
5
|
+
require "terrestrial/cli/ignite"
|
6
|
+
require "terrestrial/cli/push"
|
7
|
+
require "terrestrial/cli/pull"
|
8
|
+
require "terrestrial/cli/photoshoot"
|
9
|
+
require "terrestrial/cli/version"
|
10
|
+
require "terrestrial/cli/variable_normalizer"
|
11
|
+
require "terrestrial/cli/terminal_ui"
|
12
|
+
require "terrestrial/cli/entry_collection_differ"
|
13
|
+
require "terrestrial/cli/detects_project_type"
|
14
|
+
require "terrestrial/cli/file_picker"
|
15
|
+
require "terrestrial/cli/file_finder"
|
16
|
+
require "terrestrial/cli/mixpanel_client"
|
17
|
+
require "terrestrial/cli/bootstrapper"
|
18
|
+
require "terrestrial/cli/android_xml_parser"
|
19
|
+
require "terrestrial/cli/android_xml_formatter"
|
20
|
+
require "terrestrial/cli/dot_strings_parser"
|
21
|
+
require "terrestrial/cli/dot_strings_formatter"
|
22
|
+
require "terrestrial/cli/parser"
|
23
|
+
require "terrestrial/cli/editor"
|
24
|
+
require "terrestrial/cli/engine_mapper"
|
25
|
+
require "terrestrial/cli/string_registry"
|
26
|
+
|
27
|
+
module Terrestrial
|
28
|
+
module Cli
|
29
|
+
|
30
|
+
COMMANDS = ["init", "flight", "pull", "push", "scan", "ignite", "photoshoot"]
|
31
|
+
|
32
|
+
def self.start(command, opts = {}, args = [])
|
33
|
+
case command
|
34
|
+
when "init"
|
35
|
+
init(opts)
|
36
|
+
when "flight"
|
37
|
+
flight(opts)
|
38
|
+
when "push"
|
39
|
+
push(opts)
|
40
|
+
when "pull"
|
41
|
+
pull(opts)
|
42
|
+
when "scan"
|
43
|
+
scan(opts)
|
44
|
+
when "ignite"
|
45
|
+
ignite(opts, args)
|
46
|
+
when "photoshoot"
|
47
|
+
photoshoot(opts)
|
48
|
+
else
|
49
|
+
abort "Unknown command #{command}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.init(opts)
|
54
|
+
Terrestrial::Cli::Init.run(opts)
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.push(opts)
|
58
|
+
Terrestrial::Cli::Push.run(opts)
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.pull(opts)
|
62
|
+
Terrestrial::Cli::Pull.run(opts)
|
63
|
+
end
|
64
|
+
|
65
|
+
def self.flight(opts)
|
66
|
+
Terrestrial::Cli::Flight.run(opts)
|
67
|
+
end
|
68
|
+
|
69
|
+
def self.scan(opts)
|
70
|
+
Terrestrial::Cli::Scan.run(opts)
|
71
|
+
end
|
72
|
+
|
73
|
+
def self.ignite(opts, args)
|
74
|
+
opts["language"] = args[0]
|
75
|
+
Terrestrial::Cli::Ignite.run(opts)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.photoshoot(opts)
|
79
|
+
Terrestrial::Cli::Photoshoot.run(opts)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
module Config
|
3
|
+
class << self
|
4
|
+
|
5
|
+
DEFAULTS = {
|
6
|
+
api_url: "https://mission.terrestrial.io",
|
7
|
+
directory: Dir.pwd
|
8
|
+
}
|
9
|
+
|
10
|
+
GLOBAL_KEYS = [
|
11
|
+
:api_key,
|
12
|
+
:user_id
|
13
|
+
]
|
14
|
+
|
15
|
+
PROJECT_KEYS = [
|
16
|
+
:app_id,
|
17
|
+
:project_id,
|
18
|
+
:platform,
|
19
|
+
:translation_files
|
20
|
+
]
|
21
|
+
|
22
|
+
def load(opts = {})
|
23
|
+
values.merge!(opts)
|
24
|
+
end
|
25
|
+
|
26
|
+
def load!(opts = {}, project: true, global: true)
|
27
|
+
load(opts)
|
28
|
+
_load_project_config if project
|
29
|
+
_load_global_config if global
|
30
|
+
end
|
31
|
+
|
32
|
+
def [](key)
|
33
|
+
values[key]
|
34
|
+
end
|
35
|
+
|
36
|
+
def reset!
|
37
|
+
_reset!
|
38
|
+
end
|
39
|
+
|
40
|
+
def inspect
|
41
|
+
"<Terrestrial::Config config=#{values.inspect}>"
|
42
|
+
end
|
43
|
+
|
44
|
+
def project_config_exist?
|
45
|
+
File.exists?(_project_config_path)
|
46
|
+
end
|
47
|
+
|
48
|
+
def update_project_config(fail_if_exists: false)
|
49
|
+
YamlHelper.update(_project_config_path, values.select {|key, val| PROJECT_KEYS.include? key })
|
50
|
+
end
|
51
|
+
|
52
|
+
def update_global_config
|
53
|
+
YamlHelper.update(_global_config_path, values.select {|key, val| GLOBAL_KEYS.include? key })
|
54
|
+
end
|
55
|
+
|
56
|
+
def testing?
|
57
|
+
self[:api_url] != DEFAULTS[:api_url]
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def _load_project_config
|
63
|
+
begin
|
64
|
+
values.merge! _project_config
|
65
|
+
rescue Errno::ENOENT
|
66
|
+
abort "No terrerstrial.yaml found. Are you in the correct folder?"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def _load_global_config
|
71
|
+
values.merge! _global_config
|
72
|
+
end
|
73
|
+
|
74
|
+
def _reset!
|
75
|
+
@values = Hash.new.merge(DEFAULTS)
|
76
|
+
end
|
77
|
+
|
78
|
+
def values
|
79
|
+
@values ||= Hash.new.merge(DEFAULTS)
|
80
|
+
end
|
81
|
+
|
82
|
+
def _global_config
|
83
|
+
YamlHelper.read _global_config_path
|
84
|
+
end
|
85
|
+
|
86
|
+
def _project_config
|
87
|
+
YamlHelper.read _project_config_path
|
88
|
+
end
|
89
|
+
|
90
|
+
def _global_config_path
|
91
|
+
Dir.home + "/.terrestrial"
|
92
|
+
end
|
93
|
+
|
94
|
+
def _project_config_path
|
95
|
+
Dir.pwd + "/terrestrial.yml"
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Terrestrial
|
2
|
+
class Web
|
3
|
+
class Response
|
4
|
+
def initialize(http_response)
|
5
|
+
@inner_response = http_response
|
6
|
+
end
|
7
|
+
|
8
|
+
def success?
|
9
|
+
@inner_response.code.to_s.start_with?("2")
|
10
|
+
end
|
11
|
+
|
12
|
+
def body
|
13
|
+
JSON.parse(@inner_response.body)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'terrestrial/web/response'
|
2
|
+
require 'net/http'
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module Terrestrial
|
6
|
+
class Web
|
7
|
+
|
8
|
+
def initialize(api_token = nil)
|
9
|
+
@url = URI.parse(Config[:api_url])
|
10
|
+
@token = api_token || token
|
11
|
+
end
|
12
|
+
|
13
|
+
def push(project_id, app_id, strings_and_context)
|
14
|
+
post("projects/#{project_id}/apps/#{app_id}/imports",
|
15
|
+
{
|
16
|
+
import: {
|
17
|
+
entries: strings_and_context
|
18
|
+
}
|
19
|
+
})
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_app(project_id, platform)
|
23
|
+
post("projects/#{project_id}/apps",
|
24
|
+
{
|
25
|
+
app: {
|
26
|
+
platform: platform
|
27
|
+
}
|
28
|
+
})
|
29
|
+
end
|
30
|
+
|
31
|
+
def get_app_strings(project_id, app_id)
|
32
|
+
get("projects/#{project_id}/apps/#{app_id}/strings")
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_translations(project_id, app_id)
|
36
|
+
get("projects/#{project_id}/apps/#{app_id}/translations")
|
37
|
+
end
|
38
|
+
|
39
|
+
def get_profile
|
40
|
+
get("me")
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def post(path, payload)
|
46
|
+
http = Net::HTTP.new(@url.host, @url.port)
|
47
|
+
http.use_ssl = true if @url.scheme == "https"
|
48
|
+
|
49
|
+
request = Net::HTTP::Post.new(base_url + path)
|
50
|
+
request.body = payload.to_json
|
51
|
+
request["Content-Type"] = "application/json"
|
52
|
+
request["AUTHENTICATE"] = @token
|
53
|
+
|
54
|
+
Response.new(http.request(request))
|
55
|
+
end
|
56
|
+
|
57
|
+
def get(path)
|
58
|
+
http = Net::HTTP.new(@url.host, @url.port)
|
59
|
+
http.use_ssl = true if @url.scheme == "https"
|
60
|
+
|
61
|
+
request = Net::HTTP::Get.new(base_url + path)
|
62
|
+
request["Content-Type"] = "application/json"
|
63
|
+
request["AUTHENTICATE"] = @token
|
64
|
+
|
65
|
+
Response.new(http.request(request))
|
66
|
+
end
|
67
|
+
|
68
|
+
def base_url
|
69
|
+
@url.request_uri
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
def token
|
75
|
+
Config[:api_key]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|