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,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,5 @@
1
+ module Terrestrial
2
+ module Cli
3
+ VERSION = "0.1.0"
4
+ end
5
+ 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,9 @@
1
+ module Terrestrial
2
+ module Cli
3
+ class CreatesTerrestrialYml
4
+
5
+ def self.run(project_id, app_id, project_type)
6
+ end
7
+ end
8
+ end
9
+ 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