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