zillabyte-cli 0.0.24 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|