applebot 0.0.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8abd1f581c359df890dca0e9ed9a458d6244a41d
4
+ data.tar.gz: 1af37a02a80c482bc8da15de976853215d4f203b
5
+ SHA512:
6
+ metadata.gz: e5eb30d8e6205b5d25347ea4c16953d6540ba276fa92ccab84ea659d019f2dfd3386c7cf06fddb461b98a2f5dac4c4bcbf3a1b2b6e7a12d5c837433135d3b41b
7
+ data.tar.gz: 8078823f751ddf30c2ffc578fd3273c151c9e01a735e8f1f719ada1dd7117e4ba0808436475dd3fc7721e58752fc62e6e41a047f8247bec527a6155f8437a9c1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,38 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ applebot (0.0.1)
5
+ activesupport (>= 3.2)
6
+ commander (~> 4.1.2)
7
+ terminal-table (>= 1.4.0)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ activesupport (4.0.3)
13
+ i18n (~> 0.6, >= 0.6.4)
14
+ minitest (~> 4.2)
15
+ multi_json (~> 1.3)
16
+ thread_safe (~> 0.1)
17
+ tzinfo (~> 0.3.37)
18
+ atomic (1.1.14)
19
+ commander (4.1.6)
20
+ highline (~> 1.6.11)
21
+ dotenv (0.10.0)
22
+ highline (1.6.21)
23
+ i18n (0.6.9)
24
+ minitest (4.7.5)
25
+ multi_json (1.9.3)
26
+ rake (10.1.0)
27
+ terminal-table (1.4.5)
28
+ thread_safe (0.1.3)
29
+ atomic
30
+ tzinfo (0.3.38)
31
+
32
+ PLATFORMS
33
+ ruby
34
+
35
+ DEPENDENCIES
36
+ applebot!
37
+ dotenv
38
+ rake
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2013-2014 Turboprop Inc (https://usepropeller.com/)
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,153 @@
1
+ # AppleBot - The Apple Robot
2
+
3
+ A CLI and Ruby library to manage Apple Developer Center and iTunes Connect tasks. Uses [CasperJS](http://casperjs.org/) for easy maintenance and flexibility.
4
+
5
+ ## Requirements
6
+
7
+ AppleBot requires [CasperJS](http://casperjs.org/), which can be installed using [Homebrew](http://brew.sh/):
8
+
9
+ ```
10
+ $ brew install casperjs --devel
11
+ ```
12
+
13
+ ## Installation
14
+
15
+ ```
16
+ $ gem install applebot
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ### Commands
22
+
23
+ These commands run using iTunes Connect:
24
+
25
+ - `app:create` - Creates an entry for a new app on iTunes Connect
26
+ - `app:update` - Creates an entry for a new update to an existing app on iTunes Connect
27
+ - `app:reject_binary` - Rejects the binary for a pending release (either new or an update)
28
+ - `app:remove_from_sale` - Removes the app from sale in all territories
29
+
30
+ These commands run using the Apple Developer Center:
31
+
32
+ - `app_id:create` - Creates a bundle identifier
33
+ - `app_id:delete` - Removes a bundle identifier
34
+ - `profile:create` - Creates a provisioning profile
35
+ - `profile:download` - Downloads an existing provisioning profile
36
+ - `profile:list` - Lists all provisioning profiles
37
+
38
+ These commands can run on either iTunes Connect or Apple Developer Center:
39
+
40
+ - `app_id:list` - Lists all bundle identifiers
41
+
42
+ #### JSON Manifests
43
+
44
+ Most commands take in options - you can either pass them individually, or use a JSON manifest file like this:
45
+
46
+ ```json
47
+ {
48
+ "name": "My new app",
49
+ "app_id": "com.usepropeller.mynewapp"
50
+ }
51
+ ```
52
+
53
+ For example, the following commands are equivalent usng this JSON manifest:
54
+
55
+ ```shell
56
+ $ applebot app_id:create --name 'My new app' --app_id 'com.usepropeller.mynewapp'
57
+ $ applebot app_id:create --manifest ./manifest.json
58
+ ```
59
+
60
+ ```ruby
61
+ > AppleBot.app_id.create(name: 'My new app', app_id: 'com.usepropeller.mynewapp')
62
+ > AppleBot.app_id.create(manifest './manifest.json')
63
+ ```
64
+
65
+ ### CLI
66
+
67
+ AppleBot installs an `applebot` command, which you can explore with `-h` flags:
68
+
69
+ ```shell
70
+ $ applebot -h
71
+
72
+ Commands:
73
+ app:create Create App
74
+ app:reject_binary Reject the binary of an pending release
75
+ app:remove_from_sale Remove app from sale
76
+ app:update Update App
77
+ app_id:create Create App ID
78
+ app_id:delete Delete App ID
79
+ app_id:list List App IDs
80
+ help Display global or [command] help documentation.
81
+ profile:create Create Provisioning Profile
82
+ profile:download Download a Provisioning Profile
83
+ profile:list List Provisioning Profiles
84
+
85
+ Global Options:
86
+ --manifest FILE.json Use a JSON file to load options for each command
87
+ --username USERNAME Username to login to Apple Service, or $APPLEBOT_USERNAME
88
+ --password PASSWORD Password to login to Apple Service, or $APPLEBOT_PASSWORD
89
+ --format FORMAT Output format - ['json', 'pretty']
90
+ --verbose Verbose output
91
+ ```
92
+
93
+ #### Authentication
94
+
95
+ For every command, you can pass `--username` and `--password` flags to enter you auth credentials; you can also set `APPLEBOT_USERNAME` and `APPLEBOT_PASSWORD` environment variables.
96
+
97
+ ### Ruby
98
+
99
+ The Ruby library uses an `AppleBot` module, and its methods map to the CLI commands:
100
+
101
+ ```ruby
102
+ > require 'applebot'
103
+ => true
104
+ > AppleBot.app.create(options: here)
105
+ ```
106
+
107
+ #### Authentication
108
+
109
+ The Ruby library has a few shortcuts for logging in to Apple services:
110
+
111
+ ```ruby
112
+ # pass as options
113
+ AppleBot.app.create(username: "username", password: "password")
114
+
115
+ # run in block
116
+ AppleBot.with_credentials(username: "username", password: "password") do
117
+ AppleBot.app.create(options)
118
+ end
119
+
120
+ # set globally
121
+ AppleBot.set_credentials(username: "username", password: "password")
122
+ ```
123
+
124
+
125
+ ### Output
126
+
127
+ The `:list` commands are meant to return some data. If you're using the Ruby library, you'll receive an `Array` when the command is done; if you're using the CLI, the last line will output a JSON object with one entry.
128
+
129
+ ```ruby
130
+ AppleBot.app_ids.all
131
+ => ["com.usepropeller.myapp"]
132
+ ```
133
+
134
+ ```bash
135
+ $ applebot app_ids:all
136
+ {"app_ids": ["com.usepropeller.myapp"]}
137
+ ```
138
+
139
+ If you're using any other command (which generally create side-effects), the end result will be `true` in Ruby, or exit code 0 on the CLI.
140
+
141
+ #### Verbose & Pretty Output
142
+
143
+ You can base a `--verbose` flag (or a `verbose: true` option in Ruby) to see all of the output as each script processes. There are two output formats, `json` and `pretty`, which you are set with either the `--format` flag or `format: 'format_string'` in Ruby.
144
+
145
+ ## Contact
146
+
147
+ [Clay Allsopp](http://clayallsopp.com/)
148
+ - [clay@usepropeller.com](mailto:clay@usepropeller.com)
149
+ - [@clayallsopp](https://twitter.com/clayallsopp)
150
+
151
+ ## License
152
+
153
+ AppleBot is available under the MIT license. See the [LICENSE](LICENSE) file for more info.
data/Rakefile ADDED
@@ -0,0 +1,13 @@
1
+ require "bundler/gem_tasks"
2
+ require "bundler/setup"
3
+
4
+ require './lib/applebot.rb'
5
+
6
+ require 'dotenv'
7
+ Dotenv.load
8
+
9
+ task :console do
10
+ require 'irb'
11
+ ARGV.clear
12
+ IRB.start
13
+ end
data/applebot.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+
4
+ require "applebot/version"
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = "applebot"
8
+ s.license = "MIT"
9
+ s.authors = ["Clay Allsopp"]
10
+ s.email = "clay@usepropeller.com"
11
+ s.homepage = AppleBot::WEBSITE
12
+ s.version = AppleBot::VERSION
13
+ s.summary = "AppleBot"
14
+ s.description = AppleBot::DESCRIPTION
15
+
16
+ s.add_dependency "commander", "~> 4.1.2"
17
+ s.add_dependency "activesupport", ">= 3.2"
18
+ s.add_dependency "terminal-table", ">= 1.4.0"
19
+
20
+ s.add_development_dependency "rake"
21
+ s.add_development_dependency "dotenv"
22
+
23
+ s.files = Dir["./**/*"].reject { |file| file =~ /\.\/(bin|log|pkg|script|spec|test|vendor)/ }
24
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
25
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
26
+ s.require_paths = ["lib"]
27
+ end
data/bin/applebot ADDED
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'commander/import'
4
+
5
+ applebot = File.join(File.dirname(__FILE__), '..', 'lib', 'applebot')
6
+ require applebot
7
+
8
+ HighLine.track_eof = false # Fix for built-in Ruby
9
+ Signal.trap("INT") {} # Suppress backtrace when exiting command
10
+
11
+ program :name, 'AppleBot'
12
+ program :version, AppleBot::VERSION
13
+ program :description, AppleBot::DESCRIPTION
14
+
15
+ program :help, 'Author', 'Clay Allsopp <clay@usepropeller.com>'
16
+ program :help, 'Website', AppleBot::WEBSITE
17
+ program :help_formatter, :compact
18
+
19
+ default_command :help
20
+
21
+ global_option('--manifest FILE.json', 'Use a JSON file to load options for each command') { |file|
22
+ $manifest_file = file
23
+ }
24
+
25
+ global_option('--username USERNAME', 'Username to login to Apple Service, or $APPLEBOT_USERNAME') { |username|
26
+ $username = username
27
+ }
28
+
29
+ global_option('--password PASSWORD', 'Password to login to Apple Service, or $APPLEBOT_PASSWORD') { |password|
30
+ $password = password
31
+ }
32
+
33
+ global_option('--format FORMAT', "Output format - 'json' or 'pretty'") { |format|
34
+ $format = format
35
+ }
36
+
37
+ global_option('--verbose', "Verbose output") {|verbose|
38
+ $verbose = verbose
39
+ }
40
+
41
+ AppleBot.commands.each do |apple_command|
42
+ command apple_command.cli_command.to_sym do |c|
43
+ c.syntax = "applebot #{apple_command.cli_command} [options]"
44
+ c.description = apple_command.description
45
+ c.summary = apple_command.description
46
+
47
+ (apple_command.options.required + apple_command.options.optional).each do |apple_option|
48
+ c.option "--#{apple_option.key} VALUE", apple_option.cli_description
49
+ end
50
+
51
+ c.action do |args, options|
52
+ user_options = {
53
+ manifest: $manifest_file,
54
+ format: $format || 'pretty',
55
+ verbose: $verbose,
56
+ print_result: true
57
+ }.merge(options.__hash__)
58
+
59
+ credentials = {username: $username, password: $password}
60
+
61
+ ret_value = false
62
+ AppleBot.with_credentials(credentials) do |ab|
63
+ ret_value = ab.run_command(apple_command.ruby_method, user_options)
64
+ end
65
+ $username = nil
66
+ $password = nil
67
+ $manifest_file = nil
68
+ $format = nil
69
+ $verbose = nil
70
+ ret_value
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "Test App 9007",
3
+ "username": "clay+applestore@usepropeller.com",
4
+ "password": "B0nnyB3ar",
5
+ "app_id": "com.usepropeller.test.test9007"
6
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "username": "clay+applestore@usepropeller.com",
3
+ "password": "B0nnyB3ar",
4
+ "title": "Test App 9007",
5
+ "sku": "TEST_APP_9007",
6
+ "id": "com.usepropeller.test.test9007",
7
+ "price_tier": "free",
8
+ "app_version": "1.0.0",
9
+ "keywords": "this,thing,cool",
10
+ "support_url": "http://usepropeller.com",
11
+ "description": "My cool app",
12
+ "simulated_gambling": "none",
13
+ "horror_fear_themes": "none",
14
+ "primary_category": "book",
15
+ "secondary_category": "catalogs",
16
+ "first_name": "Clay",
17
+ "last_name": "Allsopp",
18
+ "email": "Clay.allsopp@gmail.com",
19
+ "phone": "9197937595",
20
+ "copyright": "Thing 2013",
21
+ "large_icon": "/Users/clayallsopp/Desktop/itunes_1.jpg",
22
+ "screenshots_35": "[\"/Users/clayallsopp/Projects/Apptory/iOS/profiles/apps/705/screenshots/products_35.png\"]",
23
+ "screenshots_4": "[\"/Users/clayallsopp/Projects/Apptory/iOS/profiles/apps/705/screenshots/products_4.png\"]"
24
+ }
@@ -0,0 +1,44 @@
1
+ module AppleBot
2
+ class CommandProxy
3
+ class_attribute :proxies
4
+ self.proxies = {}
5
+
6
+ attr_accessor :namespace, :commands
7
+
8
+ def self.for(command)
9
+ proxies[command.namespace] ||= new(command.namespace)
10
+ proxy = proxies[command.namespace]
11
+ proxy.add_command(command)
12
+ proxy
13
+ end
14
+
15
+ def initialize(namespace)
16
+ @namespace = namespace
17
+ @commands = []
18
+ end
19
+
20
+ def add_command(command)
21
+ return if @commands.include?(command.action)
22
+ @commands << command.action
23
+ define_singleton_method(command.action, ->(options = {}) {
24
+ AppleBot.run_command(command.file_name, options)
25
+ })
26
+ end
27
+
28
+ def attach_to(klass)
29
+ return if klass.respond_to?(self.namespace)
30
+ namespace = self.namespace
31
+ klass.define_singleton_method(namespace) {
32
+ return AppleBot::CommandProxy.proxies[namespace]
33
+ }
34
+ end
35
+
36
+ def inspect
37
+ s = self.to_s.gsub(">", " ")
38
+ s << "commands: #{@commands.join(', ')}"
39
+ s << ">"
40
+ s
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,74 @@
1
+ module AppleBot
2
+ mattr_accessor :commands
3
+
4
+ class Command < Struct.new(:file_name, :namespace, :action, :description, :options)
5
+ def initialize(*properties)
6
+ options = properties.delete_at -1
7
+ super(*properties)
8
+ self.options = CommandOptionSet.new(options['required'], options['optional'])
9
+ end
10
+
11
+ def self.load_all!
12
+ commands_file_path = File.join(AppleBot.phantom_scripts_path, '_commands.json')
13
+ commands = JSON.parse(IO.read(commands_file_path))
14
+ AppleBot.commands = commands.keys.map {|file|
15
+ command_config = commands[file]
16
+ args = [file] + [
17
+ command_config['namespace'], command_config['action'],
18
+ command_config['description'], command_config['options']
19
+ ]
20
+ Command.new(*args)
21
+ }
22
+ end
23
+
24
+ def cli_command
25
+ "#{namespace}:#{action}"
26
+ end
27
+
28
+ def ruby_method
29
+ "#{action}_#{namespace}"
30
+ end
31
+ end
32
+
33
+ class CommandOptionSet < Struct.new(:required, :optional)
34
+ def initialize(required, optional)
35
+ fix_batch_options = lambda { |set, extras|
36
+ set.map {|o|
37
+ if o['batch']
38
+ o['keys'].map {|k|
39
+ h = {
40
+ 'key' => k
41
+ }.merge(o)
42
+ h.delete 'keys'
43
+ h.delete 'batch'
44
+ h
45
+ }
46
+ else
47
+ o
48
+ end
49
+ }.flatten.map {|o|
50
+ o = o.merge(extras)
51
+ CommandOption.new(o['key'], o['description'], o['values'], o['default'], o['required'])
52
+ }
53
+ }
54
+ required = fix_batch_options.call(required, {'required' => true}) if required
55
+ required ||= []
56
+ optional = fix_batch_options.call(optional, {'required' => false}) if optional
57
+ optional ||= []
58
+ super(required, optional)
59
+ end
60
+ end
61
+
62
+ class CommandOption < Struct.new(:key, :description, :values, :default, :required)
63
+ def cli_description
64
+ s = ""
65
+ s << "REQUIRED " if required
66
+ s << "#{description}"
67
+ s.tap do |d|
68
+ d << " - VALUES: #{values}" if values
69
+ d << " - DEFAULT: #{default}" if default
70
+ end
71
+ end
72
+ end
73
+ end
74
+ AppleBot::Command.load_all!
@@ -0,0 +1,97 @@
1
+ module AppleBot
2
+ class AppleBotError < StandardError
3
+ class_attribute :message
4
+ self.message = "Unspecified AppleBot error occured"
5
+ attr_reader :output, :html
6
+
7
+ def initialize(error, output = [], message = nil)
8
+ super(self.class.message)
9
+ output ||= []
10
+ output = output.map(&:strip).reject(&:blank?)
11
+ @html = output.select {|n|
12
+ is_debug_html?(n)
13
+ }.map { |s|
14
+ JSON.parse(s)['html']
15
+ }.first
16
+
17
+ @output = output.reject {|n|
18
+ is_debug_html?(n)
19
+ }
20
+
21
+ full_backtrace = (@output.map { |o|
22
+ "#{o}:in `AppleBot'"
23
+ } + error.backtrace).compact
24
+ set_backtrace(full_backtrace)
25
+ end
26
+
27
+ def is_debug_html?(line)
28
+ line.include?('"event":"debug_html"')
29
+ end
30
+ end
31
+
32
+ class AppIdNotFound < AppleBotError
33
+ self.message = "Could not find App Bundle ID in the HTML"
34
+ end
35
+
36
+ class AppleMaintenanceMode < AppleBotError
37
+ self.message = "Apple is currently in maintenance mode"
38
+ end
39
+
40
+ class AuthenticationError < AppleBotError
41
+ self.message = "Provided Apple ID credentials were incorrect"
42
+ end
43
+
44
+ class MultipleProfileError < AppleBotError
45
+ self.message = "Attempted to re-create an existing profile"
46
+ end
47
+
48
+ class AppNameTakenError < AppleBotError
49
+ self.message = "The app name you entered is taken"
50
+ end
51
+
52
+ class SignalTermination < StandardError
53
+ def initialize(status)
54
+ super("The process was terminated with signal #{status.termsig} (#{Signal.signame(status.termsig)})")
55
+ end
56
+ end
57
+
58
+ MESSAGE_FRAGMENT_TO_ERROR = {
59
+ "could not find App ID in options" => AppIdNotFound,
60
+ 'class="maintenance"' => AppleMaintenanceMode,
61
+ 'iTunes Connect is temporarily unavailable' => AppleMaintenanceMode,
62
+ 'Your Apple ID or password was entered incorrectly' => AuthenticationError,
63
+ 'Multiple profiles found with the name' => MultipleProfileError,
64
+ 'The App Name you entered has already been used' => AppNameTakenError,
65
+ }
66
+
67
+ class AppleBotError
68
+ def self.for_output(output = [])
69
+ output ||= []
70
+
71
+ error_class = nil
72
+
73
+ output.each { |line|
74
+ MESSAGE_FRAGMENT_TO_ERROR.each { |message, klass|
75
+ if line.include?(message)
76
+ error_class = klass
77
+ break
78
+ end
79
+ }
80
+ break if error_class
81
+ }
82
+ error_class ||= AppleBotError
83
+
84
+ begin
85
+ # capture the Ruby-level stack trace
86
+ raise StandardError
87
+ rescue Exception => e
88
+ error_class.new(e, output)
89
+ end
90
+ end
91
+
92
+ def self.test
93
+ raise for_output(["something", "happened"])
94
+ end
95
+ end
96
+
97
+ end
@@ -0,0 +1,25 @@
1
+ module AppleBot
2
+ module_function
3
+
4
+ # ripped from mkmf source
5
+ def find_executable(bin, path = nil)
6
+ ext = ""
7
+ if File.expand_path(bin) == bin
8
+ return bin if File.executable?(bin)
9
+ ext and File.executable?(file = bin + ext) and return file
10
+ return nil
11
+ end
12
+ if path ||= ENV['PATH']
13
+ path = path.split(File::PATH_SEPARATOR)
14
+ else
15
+ path = %w[/usr/local/bin /usr/ucb /usr/bin /bin]
16
+ end
17
+ file = nil
18
+ path.each do |dir|
19
+ return file if File.executable?(file = File.join(dir, bin))
20
+ return file if ext and File.executable?(file << ext)
21
+ end
22
+ nil
23
+ end
24
+
25
+ end
@@ -0,0 +1,79 @@
1
+ module AppleBot
2
+ class Shell
3
+ def command(sys_command, verbose, format)
4
+ output = []
5
+ Open3.popen3(sys_command) do |stdin, stdout, stderr, wait_thr|
6
+ while line = (stdout.gets || stderr.gets)
7
+ output << line
8
+ if verbose === true
9
+ puts_format_line(line, format)
10
+ end
11
+ end
12
+ exit_status = wait_thr.value
13
+ output << JSON.generate(process_status: exit_status.inspect, thread: wait_thr.inspect)
14
+ if exit_status.termsig
15
+ raise SignalTermination.new(exit_status)
16
+ end
17
+ unless exit_status.success?
18
+ raise AppleBotError.for_output(output)
19
+ end
20
+ end
21
+
22
+ output.select {|o|
23
+ o.include?('"result":')
24
+ }.last
25
+ end
26
+
27
+ def puts_format_line(line, format)
28
+ json = JSON.parse(line)
29
+
30
+ case format.to_s
31
+ when 'json'
32
+ puts JSON.generate(json.except('normal_output'))
33
+ else
34
+ normal_output = json['normal_output']
35
+ return if normal_output.blank?
36
+ return if normal_output.include?("[phantom]")
37
+ puts(normal_output)
38
+ end
39
+ end
40
+
41
+ def result(output, format, print_result)
42
+ result = JSON.parse(output)['result']
43
+
44
+ case format.to_s
45
+ when 'pretty'
46
+ table(result).tap do |t|
47
+ puts t if print_result
48
+ end
49
+ true
50
+ else
51
+ result.tap do |json|
52
+ puts json if print_result
53
+ end
54
+ end
55
+ end
56
+
57
+ def table(data)
58
+ table = nil
59
+ if data.first && data.first[-1].is_a?(Hash)
60
+ headings = ['key'] + data.first[-1].keys
61
+ rows = data.to_a.map { |key_and_value|
62
+ row = []
63
+ headings.each do |heading|
64
+ if heading == 'key'
65
+ row << key_and_value[0]
66
+ else
67
+ row << key_and_value[1][heading]
68
+ end
69
+ end
70
+ row
71
+ }
72
+ table = Terminal::Table.new headings: headings, rows: rows
73
+ else
74
+ table = Terminal::Table.new rows: data.to_a
75
+ end
76
+ table
77
+ end
78
+ end
79
+ end