zillabyte-cli 0.0.24 → 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 +6 -14
- data/lib/#zillabyte-cli.rb# +5 -0
- data/lib/zillabyte/api/apps.rb +16 -132
- data/lib/zillabyte/api/components.rb +115 -0
- data/lib/zillabyte/api/flows.rb +121 -0
- data/lib/zillabyte/api/keys.rb +70 -0
- data/lib/zillabyte/api.rb +15 -2
- data/lib/zillabyte/auth.rb +43 -16
- data/lib/zillabyte/cli/#logs.rb# +12 -0
- data/lib/zillabyte/cli/#repl.rb# +43 -0
- data/lib/zillabyte/cli/apps.rb +52 -893
- data/lib/zillabyte/cli/auth.rb +3 -8
- data/lib/zillabyte/cli/base.rb +28 -7
- data/lib/zillabyte/cli/components.rb +245 -0
- data/lib/zillabyte/cli/flows.rb +549 -0
- data/lib/zillabyte/cli/git.rb +38 -0
- data/lib/zillabyte/cli/help.rb +11 -4
- data/lib/zillabyte/cli/keys.rb +177 -0
- data/lib/zillabyte/cli/query.rb +0 -1
- data/lib/zillabyte/cli/relations.rb +2 -1
- data/lib/zillabyte/cli/templates/{js → apps/js}/simple_function.js +0 -0
- data/lib/zillabyte/cli/templates/{js → apps/js}/zillabyte.conf.yaml +0 -0
- data/lib/zillabyte/cli/templates/apps/python/app.py +17 -0
- data/lib/zillabyte/cli/templates/{python → apps/python}/requirements.txt +0 -0
- data/lib/zillabyte/cli/templates/{python → apps/python}/zillabyte.conf.yaml +1 -1
- data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/Gemfile +0 -0
- data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/app.rb +1 -1
- data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/zillabyte.conf.yaml +0 -0
- data/lib/zillabyte/cli/templates/python/{simple_function.py → #simple_function.py#} +3 -6
- data/lib/zillabyte/common/session.rb +3 -1
- data/lib/zillabyte/helpers.rb +64 -1
- data/lib/zillabyte/runner/app_runner.rb +226 -0
- data/lib/zillabyte/runner/component_operation.rb +529 -0
- data/lib/zillabyte/runner/component_runner.rb +244 -0
- data/lib/zillabyte/runner/multilang_operation.rb +1133 -0
- data/lib/zillabyte/runner/operation.rb +11 -0
- data/lib/zillabyte/runner.rb +6 -0
- data/lib/zillabyte-cli/version.rb +1 -1
- data/zillabyte-cli.gemspec +1 -0
- metadata +83 -52
@@ -0,0 +1,177 @@
|
|
1
|
+
require "zillabyte/cli/base"
|
2
|
+
require "zillabyte/cli/helpers/table_output_builder"
|
3
|
+
require "cgi"
|
4
|
+
require "csv"
|
5
|
+
require "open-uri"
|
6
|
+
require "json"
|
7
|
+
|
8
|
+
|
9
|
+
# manage authentication keys
|
10
|
+
#
|
11
|
+
class Zillabyte::Command::Keys < Zillabyte::Command::Base
|
12
|
+
|
13
|
+
SHORT_KEY_SPLIT = 18
|
14
|
+
SHORT_KEY_MAX = 64
|
15
|
+
|
16
|
+
# keys:add NAME [KEY]
|
17
|
+
#
|
18
|
+
# add a key for the current user
|
19
|
+
#
|
20
|
+
# if no KEY is specified, will try to find ~/.ssh/id_rsa.pub
|
21
|
+
#
|
22
|
+
# --output_type OUTPUT_TYPE # The output format type
|
23
|
+
#
|
24
|
+
def add()
|
25
|
+
type = options[:output_type] || nil
|
26
|
+
name = shift_argument()
|
27
|
+
if name.nil?
|
28
|
+
error("no name given", type)
|
29
|
+
end
|
30
|
+
keypath = shift_argument() || "~/.ssh/id_rsa.pub"
|
31
|
+
begin
|
32
|
+
key = File.binread(File.expand_path(keypath))
|
33
|
+
rescue => e
|
34
|
+
error(e.message, type)
|
35
|
+
end
|
36
|
+
message = api.keys.add(name, key)
|
37
|
+
if type == "json"
|
38
|
+
display("{}")
|
39
|
+
else
|
40
|
+
display(message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# keys:show NAME
|
45
|
+
#
|
46
|
+
# show a key for the current user
|
47
|
+
#
|
48
|
+
# --output_type OUTPUT_TYPE # The output format type
|
49
|
+
#
|
50
|
+
def show()
|
51
|
+
type = options[:output_type] || nil
|
52
|
+
name = shift_argument()
|
53
|
+
if name.nil?()
|
54
|
+
error("no name given", type)
|
55
|
+
end
|
56
|
+
|
57
|
+
key = api.keys.show(name)
|
58
|
+
if type == "json"
|
59
|
+
display({"key" => key}.to_json())
|
60
|
+
else
|
61
|
+
display(key)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
# keys:remove [NAME]
|
66
|
+
#
|
67
|
+
# remove a key from the current user
|
68
|
+
#
|
69
|
+
# --output_type OUTPUT_TYPE # The output format type
|
70
|
+
# -l, --long # display extended information for each key
|
71
|
+
# -f, --force # Force -- remove key without prompting
|
72
|
+
#
|
73
|
+
def remove()
|
74
|
+
type = options[:output_type] || nil
|
75
|
+
long = options[:long]
|
76
|
+
force = options[:force]
|
77
|
+
name = shift_argument()
|
78
|
+
key_data = if name.nil?()
|
79
|
+
if force
|
80
|
+
error("no name given", type)
|
81
|
+
end
|
82
|
+
keys = api.keys.list()
|
83
|
+
display("=== Keys")
|
84
|
+
display(format_keys(keys, long, nil))
|
85
|
+
# NOTE: it may not be the most efficient thing to use a hash here, unless
|
86
|
+
# we expect users to routinely try to remove invalid keys.
|
87
|
+
keymap = Hash[*keys.flatten()]
|
88
|
+
loop do
|
89
|
+
display("Please enter the name of the key you wish to remove (case-sensitive):")
|
90
|
+
name = ask().chomp()
|
91
|
+
if name.empty?()
|
92
|
+
error("Aborting.", type)
|
93
|
+
end
|
94
|
+
if keymap.has_key?(name)
|
95
|
+
break(keymap[name])
|
96
|
+
end
|
97
|
+
display("Key not found: `#{name}`")
|
98
|
+
end
|
99
|
+
else
|
100
|
+
key = api.keys.show(name)
|
101
|
+
if force
|
102
|
+
key
|
103
|
+
else
|
104
|
+
display(format_keys([[name, key]], long, nil))
|
105
|
+
loop do
|
106
|
+
display("Proceed with removal? (yes or no)")
|
107
|
+
choice = ask().chomp()
|
108
|
+
if choice == "y" || choice == "yes"
|
109
|
+
break(key)
|
110
|
+
elsif choice == "n" || choice == "no"
|
111
|
+
error("Aborting.", type)
|
112
|
+
else
|
113
|
+
display("Invalid response.")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
key
|
118
|
+
end
|
119
|
+
|
120
|
+
message = api.keys.remove(name, key_data)
|
121
|
+
if type == "json"
|
122
|
+
display("{}")
|
123
|
+
else
|
124
|
+
display(message)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# keys
|
129
|
+
#
|
130
|
+
# display keys for the current user
|
131
|
+
#
|
132
|
+
# --output_type OUTPUT_TYPE # The output format type
|
133
|
+
# -l, --long # display extended information for each key
|
134
|
+
def index()
|
135
|
+
type = options[:output_type] || nil
|
136
|
+
long = options[:long]
|
137
|
+
|
138
|
+
keys = api.keys.list()
|
139
|
+
if type.nil?()
|
140
|
+
display("=== Keys")
|
141
|
+
end
|
142
|
+
display(format_keys(keys, long, type))
|
143
|
+
end
|
144
|
+
|
145
|
+
# keys:clear
|
146
|
+
#
|
147
|
+
# remove all authentication keys from the current user
|
148
|
+
#
|
149
|
+
# --output_type OUTPUT_TYPE # The output format type
|
150
|
+
#
|
151
|
+
def clear()
|
152
|
+
type = options[:output_type] || nil
|
153
|
+
|
154
|
+
message = api.keys.clear()
|
155
|
+
if type == "json"
|
156
|
+
display("{}")
|
157
|
+
else
|
158
|
+
display(message)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
private
|
163
|
+
def format_keys(keys, long, type)
|
164
|
+
outlong = long || type == "json"
|
165
|
+
rows = keys.map() { |row|
|
166
|
+
name = row[0]
|
167
|
+
key = row[1]
|
168
|
+
if outlong || key.length() < SHORT_KEY_MAX
|
169
|
+
[key, name]
|
170
|
+
else
|
171
|
+
["#{key[0...SHORT_KEY_SPLIT]}...#{key[SHORT_KEY_SPLIT - SHORT_KEY_MAX + 2..-1]}", name]
|
172
|
+
end
|
173
|
+
}
|
174
|
+
headings = ["key", "name"]
|
175
|
+
TableOutputBuilder.build_table(headings, rows, type)
|
176
|
+
end
|
177
|
+
end
|
data/lib/zillabyte/cli/query.rb
CHANGED
@@ -520,6 +520,7 @@ class Zillabyte::Command::Relations < Zillabyte::Command::Base
|
|
520
520
|
case filetype
|
521
521
|
when "csv"
|
522
522
|
begin
|
523
|
+
|
523
524
|
CSV.foreach(file) do |row|
|
524
525
|
if row.size != n_columns
|
525
526
|
error("relation expect #{n_columns} column(s). Found a row with #{row.size}::\n #{row}", type)
|
@@ -539,7 +540,7 @@ class Zillabyte::Command::Relations < Zillabyte::Command::Base
|
|
539
540
|
rows << row
|
540
541
|
end
|
541
542
|
rescue Exception => e
|
542
|
-
error("
|
543
|
+
error("error when parsing csv : #{e}", type)
|
543
544
|
end
|
544
545
|
|
545
546
|
else
|
File without changes
|
File without changes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import zillabyte
|
2
|
+
|
3
|
+
def prep(controller):
|
4
|
+
return
|
5
|
+
|
6
|
+
# This is the heart of your algorithm. It's processed on every
|
7
|
+
# web page. This algorithm is run in parallel on possibly hundreds
|
8
|
+
# of machines.
|
9
|
+
def execute(controller, tup):
|
10
|
+
if("hello world" in tup["html"]):
|
11
|
+
controller.emit("has_hello_world",{"url":tup["url"]})
|
12
|
+
return
|
13
|
+
|
14
|
+
app = zillabyte.app(name = "hello_world")
|
15
|
+
app.source(matches = "select * from web_pages")\
|
16
|
+
.each(execute = execute)\
|
17
|
+
.sink(name = "has_hello_world", columns = [{"url":"string"}])
|
File without changes
|
File without changes
|
File without changes
|
@@ -7,18 +7,15 @@ def prep(controller):
|
|
7
7
|
# web page. This algorithm is run in parallel on possibly hundreds
|
8
8
|
# of machines.
|
9
9
|
def execute(controller, tup):
|
10
|
-
if("hello world" in tup["html"]):
|
11
|
-
controller.emit("has_hello_world",{"url":tup["url"]})
|
10
|
+
if("hello world" in tup.values["html"]):
|
11
|
+
controller.emit("has_hello_world",{"url":tup.values["url"]})
|
12
12
|
return
|
13
13
|
|
14
14
|
zillabyte.simple_function(\
|
15
|
-
# This specifies the function's name and is mandatory.
|
16
|
-
name = "simple_function", \
|
17
|
-
|
18
15
|
# This directive instructs zillabyte to give your function every
|
19
16
|
# web page in our known universe. Your function will have access
|
20
17
|
# to two fields: URL and HTML
|
21
|
-
matches = "select * from
|
18
|
+
matches = "select * from web_pa", \
|
22
19
|
|
23
20
|
# This directive tells Zillabyte what kind of data your function
|
24
21
|
# produces. In this case, we're saying we will emit a tuple that
|
data/lib/zillabyte/helpers.rb
CHANGED
@@ -7,7 +7,7 @@ module Zillabyte
|
|
7
7
|
|
8
8
|
def display(msg="", new_line=true)
|
9
9
|
if new_line
|
10
|
-
|
10
|
+
puts(msg)
|
11
11
|
else
|
12
12
|
print(msg)
|
13
13
|
$stdout.flush
|
@@ -50,6 +50,69 @@ module Zillabyte
|
|
50
50
|
# fails on windows
|
51
51
|
end
|
52
52
|
end
|
53
|
+
|
54
|
+
def has_git?
|
55
|
+
%x{ git --version }
|
56
|
+
$?.success?
|
57
|
+
end
|
58
|
+
|
59
|
+
def git(args)
|
60
|
+
return "" unless has_git?
|
61
|
+
flattened_args = [args].flatten.compact.join(" ")
|
62
|
+
%x{ git #{flattened_args} 2>&1 }.strip
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_git_remote(remote, url)
|
66
|
+
return if git('remote').split("\n").include?(remote)
|
67
|
+
return unless File.exists?(".git")
|
68
|
+
git "remote add #{remote} #{url}"
|
69
|
+
display "Git remote #{remote} added"
|
70
|
+
end
|
71
|
+
|
72
|
+
|
73
|
+
|
74
|
+
def app
|
75
|
+
@app ||= if options[:confirm].is_a?(String)
|
76
|
+
if options[:app] && (options[:app] != options[:confirm])
|
77
|
+
error("Mismatch between --app and --confirm")
|
78
|
+
end
|
79
|
+
options[:confirm]
|
80
|
+
elsif options[:app].is_a?(String)
|
81
|
+
options[:app]
|
82
|
+
elsif ENV.has_key?('ZILLABYTE_APP')
|
83
|
+
ENV['ZILLABYTE_APP']
|
84
|
+
elsif app_from_dir = extract_app_in_dir(Dir.pwd)
|
85
|
+
app_from_dir
|
86
|
+
else
|
87
|
+
# raise instead of using error command to enable rescuing when app is optional
|
88
|
+
raise Zillabyte::Command::CommandFailed.new("No app specified.\nRun this command from an app folder or specify which app to use with --app APP.") unless options[:ignore_no_app]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
def extract_app_in_dir(dir)
|
95
|
+
return unless remotes = git_remotes(dir)
|
96
|
+
|
97
|
+
if remote = options[:remote]
|
98
|
+
remotes[remote]
|
99
|
+
elsif remote = extract_app_from_git_config
|
100
|
+
remotes[remote]
|
101
|
+
else
|
102
|
+
apps = remotes.values.uniq
|
103
|
+
if apps.size == 1
|
104
|
+
apps.first
|
105
|
+
else
|
106
|
+
raise(Heroku::Command::CommandFailed, "Multiple apps in folder and no app specified.\nSpecify app with --app APP.") unless options[:ignore_no_app]
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def extract_app_from_git_config
|
112
|
+
remote = git("config zillabyte.remote")
|
113
|
+
remote == "" ? nil : remote
|
114
|
+
end
|
115
|
+
|
53
116
|
|
54
117
|
end
|
55
118
|
end
|
@@ -0,0 +1,226 @@
|
|
1
|
+
require "zillabyte/cli/base"
|
2
|
+
require "zillabyte/runner"
|
3
|
+
require "zillabyte/runner/multilang_operation"
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
|
7
|
+
# HIDDEN:
|
8
|
+
class Zillabyte::Runner::AppRunner < Zillabyte::Command::Base
|
9
|
+
include Zillabyte::Helpers
|
10
|
+
|
11
|
+
def run (dir = Dir.pwd, session = nil, options = {})
|
12
|
+
|
13
|
+
if session.nil?
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
@session = session
|
18
|
+
@colors = {}
|
19
|
+
output = options[:output]
|
20
|
+
otype = options[:output_type]
|
21
|
+
interactive = options[:interactive]
|
22
|
+
cycles = (options[:cycles] || "1").to_i
|
23
|
+
|
24
|
+
# Get app metadata
|
25
|
+
meta = Zillabyte::API::Flows.get_rich_meta_info_from_script(dir, @session, {:test => true})
|
26
|
+
if meta.nil? || meta["nodes"].nil?
|
27
|
+
error "this is not a valid zillabyte app directory"
|
28
|
+
exit
|
29
|
+
end
|
30
|
+
|
31
|
+
# Check that multilang version is atleast 0.1.0
|
32
|
+
version = meta["multilang_version"] || "0.0.0"
|
33
|
+
version_arr = version.split('.').map {|v| v.to_i}
|
34
|
+
if version_arr.empty? || (version_arr[0] == 0 && version_arr[1] < 1)
|
35
|
+
display "The version of zillabyte used in your application is outdated."
|
36
|
+
display "Please use upgrade your zillabyte gem via 'bundle update zillabyte; gem cleanup zillabyte'"
|
37
|
+
return
|
38
|
+
end
|
39
|
+
|
40
|
+
# Show the user what we know about their app...
|
41
|
+
display "inferring your app details..."
|
42
|
+
describe_app(meta)
|
43
|
+
|
44
|
+
# Setup streams
|
45
|
+
@nodes = meta["nodes"]
|
46
|
+
|
47
|
+
# Index stream consummers and emitters by stream name
|
48
|
+
@arcs = meta["arcs"]
|
49
|
+
|
50
|
+
# Organize component pipes
|
51
|
+
@operations = {}
|
52
|
+
@operation_pipes = {}
|
53
|
+
|
54
|
+
# On each cycle, setup and tear down a test harness
|
55
|
+
(1..cycles).each do |cycle|
|
56
|
+
|
57
|
+
display "starting cycle #{cycle}" unless cycles == 1
|
58
|
+
|
59
|
+
begin
|
60
|
+
|
61
|
+
# Setup component pipes
|
62
|
+
@nodes.each do |n|
|
63
|
+
|
64
|
+
name = n["name"]
|
65
|
+
type = n["type"]
|
66
|
+
emits = n["emits"]
|
67
|
+
if n["type"] == "source"
|
68
|
+
options[:end_cycle_policy] = n["end_cycle_policy"]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Create two new pipes in the parent.
|
72
|
+
rd_child, wr_parent = IO.pipe()
|
73
|
+
rd_parent, wr_child = IO.pipe()
|
74
|
+
|
75
|
+
@operation_pipes[name] = {
|
76
|
+
:rd_child => rd_child,
|
77
|
+
:wr_child => wr_child,
|
78
|
+
:rd_parent => rd_parent,
|
79
|
+
:wr_parent => wr_parent
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
# Maps origin => {stream => [destinations]}
|
84
|
+
@arc_map = {}
|
85
|
+
@arcs.each do |a|
|
86
|
+
origin = a["origin"]
|
87
|
+
name = a["name"]
|
88
|
+
dest = a["dest"]
|
89
|
+
@arc_map[origin] ||= {}
|
90
|
+
@arc_map[origin][name] ||= []
|
91
|
+
@arc_map[origin][name] << a["dest"]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Spawn component threads
|
95
|
+
@nodes.each do |n|
|
96
|
+
|
97
|
+
name = n["name"]
|
98
|
+
type = n["type"]
|
99
|
+
emits = n["emits"]
|
100
|
+
|
101
|
+
pipes = @operation_pipes[name]
|
102
|
+
rd_child = pipes[:rd_child]
|
103
|
+
wr_child = pipes[:wr_child]
|
104
|
+
rd_parent = pipes[:rd_parent]
|
105
|
+
wr_parent = pipes[:wr_parent]
|
106
|
+
|
107
|
+
# Fork.
|
108
|
+
pid = fork()
|
109
|
+
if pid # In parent
|
110
|
+
# Close the reading end of the child so we can write to the child.
|
111
|
+
rd_child.close()
|
112
|
+
# Close the writing end of the child so we can read from the child.
|
113
|
+
wr_child.close()
|
114
|
+
|
115
|
+
else # in child
|
116
|
+
# Close the writing end of the parent so we can read from the parent.
|
117
|
+
wr_parent.close()
|
118
|
+
# Close the reading end of the parent so we can write to the parent.
|
119
|
+
rd_parent.close()
|
120
|
+
begin
|
121
|
+
|
122
|
+
|
123
|
+
# Setup reading and writing pipes for communicating with consumee component
|
124
|
+
in_pipe = {:rd_child => @operation_pipes[name][:rd_child], :wr_child => @operation_pipes[name][:wr_child]}
|
125
|
+
|
126
|
+
# Index consumer pipes by stream name, consumer_name
|
127
|
+
out_pipes = {}
|
128
|
+
|
129
|
+
|
130
|
+
if type != "sink"
|
131
|
+
@arc_map[name].each_pair do |stream, destinations|
|
132
|
+
out_pipes[stream] ||= {}
|
133
|
+
destinations.each do |dest|
|
134
|
+
out_pipes[stream][dest] = {:wr_parent => @operation_pipes[dest][:wr_parent], :rd_parent => @operation_pipes[dest][:rd_parent] }
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Run the child process
|
140
|
+
Zillabyte::Runner::MultilangOperation.run(n, dir, in_pipe, out_pipes, self, meta, options)
|
141
|
+
|
142
|
+
ensure
|
143
|
+
# Close the reading end of the child
|
144
|
+
rd_child.close()
|
145
|
+
# Close the writing end of the child
|
146
|
+
wr_child.close()
|
147
|
+
exit!(-1)
|
148
|
+
end
|
149
|
+
|
150
|
+
end #end child
|
151
|
+
end
|
152
|
+
|
153
|
+
if interactive
|
154
|
+
display ""
|
155
|
+
while true
|
156
|
+
display "Enter an input tuple in JSON format i.e.{ \"url\" : \"foo.com\", \"html\" : \"bar.html\" }"
|
157
|
+
msg = ask
|
158
|
+
|
159
|
+
begin
|
160
|
+
JSON.parse(msg)
|
161
|
+
rescue JSON::ParserError
|
162
|
+
display "Received invalid JSON object"
|
163
|
+
next
|
164
|
+
end
|
165
|
+
# Send tuple to source
|
166
|
+
@operation_pipes["source_1"][:wr_parent].puts msg
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
ensure
|
171
|
+
Process.waitall()
|
172
|
+
@operation_pipes.each do |name, pipes|
|
173
|
+
#Close the writing end of the parent
|
174
|
+
pipes[:wr_parent].close()
|
175
|
+
# Close the reading end of the parent
|
176
|
+
pipes[:rd_parent].close()
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
def session
|
184
|
+
@session
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def cdisplay(name, message)
|
189
|
+
|
190
|
+
color = @colors[name] || :default
|
191
|
+
if message == ""
|
192
|
+
display ""
|
193
|
+
else
|
194
|
+
display "#{name} - #{message}".colorize(color)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
|
199
|
+
def query_agnostic(query)
|
200
|
+
@session.api.query.agnostic(query)
|
201
|
+
end
|
202
|
+
|
203
|
+
def display(message, newline = true)
|
204
|
+
@session.display(message, newline)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
def describe_app(meta)
|
209
|
+
colors ||= [:green, :yellow, :magenta, :cyan, :white, :blue, :light_yellow, :light_blue, :red, :light_magenta, :light_cyan]
|
210
|
+
rjust = 20
|
211
|
+
display "#{'app name'.rjust(rjust)}: #{meta['name']}"
|
212
|
+
display "#{'app language'.rjust(rjust)}: #{meta['language']}"
|
213
|
+
meta['nodes'].each_with_index do |node, index|
|
214
|
+
@colors[node['name']] ||= colors[index % colors.length]
|
215
|
+
color = @colors[node['name']]
|
216
|
+
display (("="*rjust + " operation ##{index}").colorize(color))
|
217
|
+
display "#{"name".rjust(rjust)}: #{node['name'].to_s.colorize(color)}"
|
218
|
+
display "#{"type".rjust(rjust)}: #{node['type'].to_s.colorize(color)}"
|
219
|
+
display "#{"matches".rjust(rjust)}: #{JSON.pretty_generate(node['matches']).indent(rjust+2).lstrip.colorize(color)}" if node['matches']
|
220
|
+
display "#{"consumes".rjust(rjust)}: #{node['consumes'].to_s.colorize(color)}" if node['consumes']
|
221
|
+
display "#{"emits".rjust(rjust)}: #{JSON.pretty_generate(node['emits']).indent(rjust+2).lstrip.colorize(color)}" if node['emits']
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
end
|