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.
Files changed (40) hide show
  1. checksums.yaml +6 -14
  2. data/lib/#zillabyte-cli.rb# +5 -0
  3. data/lib/zillabyte/api/apps.rb +16 -132
  4. data/lib/zillabyte/api/components.rb +115 -0
  5. data/lib/zillabyte/api/flows.rb +121 -0
  6. data/lib/zillabyte/api/keys.rb +70 -0
  7. data/lib/zillabyte/api.rb +15 -2
  8. data/lib/zillabyte/auth.rb +43 -16
  9. data/lib/zillabyte/cli/#logs.rb# +12 -0
  10. data/lib/zillabyte/cli/#repl.rb# +43 -0
  11. data/lib/zillabyte/cli/apps.rb +52 -893
  12. data/lib/zillabyte/cli/auth.rb +3 -8
  13. data/lib/zillabyte/cli/base.rb +28 -7
  14. data/lib/zillabyte/cli/components.rb +245 -0
  15. data/lib/zillabyte/cli/flows.rb +549 -0
  16. data/lib/zillabyte/cli/git.rb +38 -0
  17. data/lib/zillabyte/cli/help.rb +11 -4
  18. data/lib/zillabyte/cli/keys.rb +177 -0
  19. data/lib/zillabyte/cli/query.rb +0 -1
  20. data/lib/zillabyte/cli/relations.rb +2 -1
  21. data/lib/zillabyte/cli/templates/{js → apps/js}/simple_function.js +0 -0
  22. data/lib/zillabyte/cli/templates/{js → apps/js}/zillabyte.conf.yaml +0 -0
  23. data/lib/zillabyte/cli/templates/apps/python/app.py +17 -0
  24. data/lib/zillabyte/cli/templates/{python → apps/python}/requirements.txt +0 -0
  25. data/lib/zillabyte/cli/templates/{python → apps/python}/zillabyte.conf.yaml +1 -1
  26. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/Gemfile +0 -0
  27. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/app.rb +1 -1
  28. data/lib/zillabyte/cli/templates/{ruby → apps/ruby}/zillabyte.conf.yaml +0 -0
  29. data/lib/zillabyte/cli/templates/python/{simple_function.py → #simple_function.py#} +3 -6
  30. data/lib/zillabyte/common/session.rb +3 -1
  31. data/lib/zillabyte/helpers.rb +64 -1
  32. data/lib/zillabyte/runner/app_runner.rb +226 -0
  33. data/lib/zillabyte/runner/component_operation.rb +529 -0
  34. data/lib/zillabyte/runner/component_runner.rb +244 -0
  35. data/lib/zillabyte/runner/multilang_operation.rb +1133 -0
  36. data/lib/zillabyte/runner/operation.rb +11 -0
  37. data/lib/zillabyte/runner.rb +6 -0
  38. data/lib/zillabyte-cli/version.rb +1 -1
  39. data/zillabyte-cli.gemspec +1 -0
  40. metadata +83 -52
@@ -0,0 +1,549 @@
1
+ require "zillabyte/cli/base"
2
+ require "zillabyte/cli/config"
3
+ require "zillabyte/common"
4
+ require "pty"
5
+ require 'indentation'
6
+ require 'open3'
7
+ require 'securerandom'
8
+ require 'colorize'
9
+ require 'time_difference'
10
+ require 'net/http'
11
+ require "zillabyte/runner/component_runner"
12
+ require "zillabyte/runner/app_runner"
13
+
14
+
15
+ # HIDDEN: superclass for components and apps
16
+ #
17
+ class Zillabyte::Command::Flows < Zillabyte::Command::Base
18
+
19
+ # flows:status [DIR]
20
+ #
21
+ # fetches detailed status of the flow
22
+ #
23
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
24
+ # --directory DIR # Directory of the flow
25
+ #
26
+ def status
27
+
28
+ id = options[:id] || shift_argument
29
+ type = options[:output_type]
30
+
31
+ if id.nil?
32
+ id = read_name_from_conf(options)
33
+ options[:is_name] = true
34
+ elsif !(id =~ /^\d*$/)
35
+ options[:is_name] = true
36
+ end
37
+
38
+ res = api.request(
39
+ :expects => 200,
40
+ :method => :get,
41
+ :path => "/flows/#{id}"
42
+ )
43
+
44
+ res.body
45
+ if type == "json"
46
+ display res.body.to_json
47
+ else
48
+ # TODO
49
+ display res.body.to_json
50
+ end
51
+
52
+ end
53
+
54
+ # flows:pull ID DIR
55
+ #
56
+ # pulls a flow source to a directory.
57
+ #
58
+ # --force # pulls even if the directory exists
59
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
60
+ # --directory DIR # Directory of the flow
61
+ #
62
+ # Examples:
63
+ #
64
+ # $ zillabyte flows:pull .
65
+ #
66
+ def pull
67
+
68
+ flow_id = options[:id] || shift_argument
69
+ type = options[:output_type]
70
+
71
+ if !(flow_id =~ /^\d*$/)
72
+ options[:is_name] = true
73
+ end
74
+
75
+ dir = options[:directory] || shift_argument
76
+ error("no directory given", type) if dir.nil?
77
+ dir = File.expand_path(dir)
78
+
79
+ error("no id given", type) if flow_id.nil?
80
+
81
+ # Create if not exists..
82
+ if File.exists?(dir)
83
+ if Dir.entries(dir).size != 2 and options[:force].nil?
84
+ error("target directory not empty. use --force to override", type)
85
+ end
86
+ else
87
+ FileUtils.mkdir_p(dir)
88
+ end
89
+
90
+ res = api.flows.pull_to_directory flow_id, dir, session, options
91
+
92
+ if res['error']
93
+ error("error: #{res['error_message']}", type)
94
+ else
95
+ if type == "json"
96
+ display "{}"
97
+ else
98
+ display "flow ##{res['id']} pulled to #{dir}"
99
+ end
100
+ end
101
+
102
+ end
103
+ alias_command "pull", "flows:pull"
104
+
105
+ # flows:prep [DIR]
106
+ #
107
+ # performs any necessary initialization for the flow
108
+ #
109
+ # --directory DIR # flow directory
110
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
111
+ #
112
+ def prep
113
+
114
+ type = options[:output_type]
115
+ dir = options[:directory] || shift_argument
116
+ if dir.nil?
117
+ dir = Dir.pwd
118
+ else
119
+ dir = File.expand_path(dir)
120
+ end
121
+ options[:directory] = dir
122
+ meta = Zillabyte::CLI::Config.get_config_info(dir)
123
+
124
+ if meta.nil?
125
+ error("The specified directory (#{dir}) does not appear to contain a valid Zillabyte configuration file.", type)
126
+ end
127
+
128
+ case meta["language"]
129
+ when "ruby"
130
+
131
+ # Execute in the bundler context
132
+ full_script = File.join(dir, meta["script"])
133
+ cmd = "cd \"#{meta['home_dir']}\"; unset BUNDLE_GEMFILE; unset RUBYOPT; bundle install"
134
+ exec(cmd)
135
+
136
+ when "python"
137
+ vDir = "#{meta['home_dir']}/vEnv"
138
+ lock_file = meta['home_dir']+"/zillabyte_thread_lock_file"
139
+ if File.exists?(lock_file)
140
+ sleep(1) while File.exists?(lock_file)
141
+ else
142
+ begin
143
+ cmd = "touch #{lock_file}; virtualenv --clear --system-site-packages #{vDir}; PYTHONPATH=~/zb1/multilang/python/Zillabyte #{vDir}/bin/pip install -r #{meta['home_dir']}/requirements.txt"
144
+ system cmd, :out => :out
145
+ ensure
146
+ File.delete(lock_file)
147
+ end
148
+ end
149
+
150
+ when "js"
151
+ end
152
+
153
+ end
154
+ alias_command "prep", "flows:prep"
155
+
156
+ # flows:logs ID [OPERATION_NAME]
157
+ #
158
+ # streams logs from the distributed workers
159
+ #
160
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
161
+ # --operation OPERATION_NAME # specify the operation to show logs for
162
+ # -v, --verbose LEVEL # sets the verbosity (error, info, debug) (default: info)
163
+ #
164
+ def logs
165
+
166
+ flow_id = options[:id] || shift_argument
167
+ if flow_id.nil?
168
+ flow_id = read_name_from_conf(options)
169
+ options[:is_name] = true
170
+ elsif !(flow_id =~ /^\d*$/)
171
+ options[:is_name] = true
172
+ end
173
+
174
+ operation_id = options[:operation] || shift_argument || '_ALL_'
175
+ category = options[:verbose] || '_ALL_'
176
+ type = options[:output_type]
177
+
178
+ carry_settings = {
179
+ :category => category
180
+ }
181
+
182
+ display "Retrieving logs for flow ##{flow_id}...please wait..." if type.nil?
183
+ hash = self.api.logs.get(flow_id, operation_id, options)
184
+
185
+ fetch_logs(hash, operation_id)
186
+
187
+ end
188
+ alias_command "logs", "flows:logs"
189
+
190
+
191
+ # flows:errors ID
192
+ #
193
+ # show recent errors generated by the flow
194
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
195
+ #
196
+ def errors
197
+
198
+ # Init
199
+ flow_id = options[:id] || shift_argument
200
+
201
+ # No name?
202
+ if flow_id.nil?
203
+ flow_id = read_name_from_conf(options)
204
+ options[:is_name] = true
205
+ elsif !(flow_id =~ /^\d*$/)
206
+ options[:is_name] = true
207
+ end
208
+
209
+ type = options[:output_type]
210
+
211
+ # Make the request
212
+ res = api.request(
213
+ :expects => 200,
214
+ :method => :get,
215
+ :body => options.to_json,
216
+ :path => "/flows/#{CGI.escape(flow_id)}/errors"
217
+ )
218
+
219
+ # Render
220
+ display "Recent errors:" if type.nil?
221
+ headings = ["operation", "date", "error"]
222
+ rows = (res.body["recent_errors"] || []).map do |row|
223
+ if row['date']
224
+ d = Time.at(row['date']/1000)
225
+ else
226
+ d = nil
227
+ end
228
+ [row['name'], d, row['message']]
229
+ end
230
+ rows.sort! do |a,b|
231
+ a[1] <=> b[1]
232
+ end
233
+ color_map = {}
234
+ colors = LogFormatter::COLORS.clone
235
+ rows.each do |row|
236
+ name = row[0]
237
+ time = row[1]
238
+ message = row[2].strip
239
+ color_map[name] ||= colors.shift
240
+ if time
241
+ display "#{"* #{name} - #{time_ago_in_words(time)} ago".colorize(color_map[name])}:" if type.nil?
242
+ else
243
+ display "#{"* #{name}".colorize(color_map[name])}:" if type.nil?
244
+ end
245
+ message.split('\n').each do |sub_line|
246
+ display " #{sub_line}" if type.nil?
247
+ end
248
+ end
249
+
250
+ end
251
+ alias_command "errors", "flows:errors"
252
+
253
+
254
+ # flows:live_run [OPERATION_NAME] [PIPE_NAME] [DIR]
255
+ #
256
+ # runs a local flow with live data
257
+ #
258
+ # --config CONFIG_FILE # use the given config file
259
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
260
+ # --directory DIR # Directory of the flow
261
+ #
262
+ def live_run
263
+
264
+ name = options[:name] || shift_argument
265
+ type = options[:output_type]
266
+
267
+ thread_id = options[:thread] || shift_argument || ""
268
+ dir = options[:directory] || shift_argument
269
+ if dir.nil?
270
+ dir = Dir.pwd
271
+ else
272
+ dir = File.expand_path(dir)
273
+ end
274
+ options[:directory] = dir
275
+
276
+ meta = Zillabyte::CLI::Config.get_config_info(dir, options)
277
+
278
+ if meta.nil?
279
+ error("could not find meta information for: #{dir}", type)
280
+ end
281
+
282
+ if(thread_id == "")
283
+ exec(command("--execute_live --name #{name.to_s}",type, dir))
284
+ else
285
+ exec(command("--execute_live --name #{name.to_s} --pipe #{dir}/#{thread_id}",type, dir))
286
+ end
287
+ end
288
+ alias_command "live_run", "flows:live_run"
289
+
290
+ # flows:info [DIR]
291
+ #
292
+ # outputs the info for the flow in the dir.
293
+ #
294
+ # --pretty # Pretty prints the info output
295
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
296
+ # --directory DIR # Directory of the flow
297
+ #
298
+ def info
299
+
300
+ dir = options[:directory] || shift_argument
301
+ if dir.nil?
302
+ dir = Dir.pwd
303
+ else
304
+ dir = File.expand_path(dir)
305
+ end
306
+ options[:directory] = dir
307
+
308
+ info_file = "#{dir}/#{SecureRandom.uuid}"
309
+ type = options[:output_type]
310
+
311
+ cmd = command("--info --file #{info_file}", type, dir)
312
+ flow_info = Zillabyte::Command::Apps.get_info(cmd, info_file, dir, options)
313
+
314
+ if type == "json"
315
+ puts flow_info
316
+ else
317
+ if options[:pretty]
318
+ puts JSON.pretty_generate(JSON.parse(flow_info))
319
+ else
320
+ puts flow_info
321
+ end
322
+ end
323
+
324
+ exit
325
+ end
326
+ alias_command "info", "flows:info"
327
+
328
+ # flows:delete ID
329
+ #
330
+ # deletes a flow. if the flow is running, this command will kill it.
331
+ #
332
+ # -f, --force # don't ask for confirmation
333
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
334
+ #
335
+ def delete
336
+ flow_id = options[:id] || shift_argument
337
+
338
+ if flow_id.nil?
339
+ flow_id = read_name_from_conf(options)
340
+ options[:is_name] = true
341
+ elsif !(flow_id =~ /^\d*$/)
342
+ options[:is_name] = true
343
+ end
344
+ forced = options[:force]
345
+ type = options[:output_type]
346
+
347
+ if not forced
348
+
349
+ if !type.nil?
350
+ error("specify -f, --force to confirm deletion", type)
351
+ end
352
+
353
+ while true
354
+ display "This operation cannot be undone. Are you sure you want to delete this flow? (yes/no):", false
355
+ confirm = ask
356
+ break if confirm == "yes" || confirm == "no"
357
+ display "Please enter 'yes' to delete the flow or 'no' to exit"
358
+ end
359
+
360
+ end
361
+
362
+ confirmed = forced || confirm == "yes"
363
+
364
+ if confirmed
365
+ response = api.flows.delete(flow_id, options)
366
+ if type == "json"
367
+ display "{}"
368
+ else
369
+ display response["body"]
370
+ end
371
+ end
372
+
373
+ end
374
+
375
+
376
+ #
377
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
378
+ #
379
+ def self.get_info(cmd, info_file, dir, options = {})
380
+ type = options[:output_type]
381
+
382
+ response = `#{cmd}`
383
+ if($?.exitstatus == 1)
384
+
385
+ File.delete("#{info_file}") if File.exists?(info_file)
386
+
387
+ if options[:output_type].nil?
388
+ exit(1)
389
+ else
390
+ Zillabyte::Helpers.error("error: #{response}", type)
391
+ end
392
+ end
393
+
394
+ flow_info = {}
395
+ File.open("#{info_file}", "r+").each do |line|
396
+ line = JSON.parse(line)
397
+ if(line["class"] == "node")
398
+ flow_info["nodes"] << line["info"]
399
+ elsif(line["class"] == "arc")
400
+ flow_info["arcs"] << line["info"]
401
+ else
402
+ flow_info = line
403
+ flow_info["nodes"] = []
404
+ flow_info["arcs"] = []
405
+ end
406
+ end
407
+ File.delete("#{info_file}")
408
+
409
+ flow_info = flow_info.to_json
410
+ if(File.exists?("#{dir}/info_to_java.in"))
411
+ java_pipe = open("#{dir}/info_to_java.in","w+")
412
+ java_pipe.puts(flow_info+"\n")
413
+ java_pipe.flush
414
+ java_pipe.close()
415
+ end
416
+
417
+ flow_info
418
+ end
419
+
420
+
421
+
422
+
423
+ # flows:test
424
+ #
425
+ # tests a local flow with sample data
426
+ #
427
+ # --config CONFIG_FILE # use the given config file
428
+ # --input INPUT_FILE # writes sink output to a file
429
+ # --output OUTPUT_FILE # writes sink output to a file
430
+ # --cycles CYCLES # number of cycles to emit (default 1)
431
+ # --directory DIR # app directory
432
+ # -i, --interactive # allow user to control input and read output
433
+ #
434
+ def test
435
+ dir = options[:directory]
436
+ if dir.nil?
437
+ dir = Dir.pwd
438
+ else
439
+ dir = File.expand_path(dir)
440
+ end
441
+ options[:directory] = dir
442
+
443
+
444
+ # Get the class
445
+ flow_class = read_class_from_conf(options)
446
+
447
+ case flow_class
448
+ when "component"
449
+ runner = Zillabyte::Runner::ComponentRunner.new()
450
+ when "app"
451
+ runner = Zillabyte::Runner::AppRunner.new()
452
+ else
453
+ # Default to App
454
+ runner = Zillabyte::Runner::AppRunner.new()
455
+ end
456
+
457
+ # Start Runner
458
+ runner.run(dir, self, options)
459
+
460
+ end
461
+ alias_command "test", "flows:test"
462
+
463
+
464
+ private
465
+
466
+ #
467
+ # --output_type OUTPUT_TYPE # specify an output type i.e. json
468
+ #
469
+ def command(arg="--execute_live", type = nil, dir = Dir.pwd, ignore_stderr = false)
470
+ meta = Zillabyte::CLI::Config.get_config_info(dir, self, options)
471
+
472
+ #meta = Zillabyte::API::Functions.get_rich_meta_info_from_script(dir, self)
473
+ error("could not extract meta information. missing zillabyte.conf.yml?", type) if meta.nil?
474
+ error(meta["error_message"], type) if meta['status'] == "error"
475
+ full_script = File.join(dir, meta["script"])
476
+ stderr_opt = "2> /dev/null" if ignore_stderr
477
+
478
+ case meta["language"]
479
+ when "ruby"
480
+ # Execute in the bundler context
481
+ cmd = "cd \"#{dir}\"; unset BUNDLE_GEMFILE; ZILLABYTE_HARNESS=1 bundle exec ruby \"#{full_script}\" #{arg} #{stderr_opt}"
482
+
483
+ when "python"#{
484
+ if(File.directory?("#{dir}/vEnv"))
485
+ cmd = "cd \"#{dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte #{dir}/vEnv/bin/python \"#{full_script}\" #{arg} #{stderr_opt}"
486
+ else
487
+ cmd = "cd \"#{dir}\"; PYTHONPATH=~/zb1/multilang/python/Zillabyte python \"#{full_script}\" #{arg} #{stderr_opt}"
488
+ end
489
+
490
+ when "js"
491
+ # cmd = "#{Zillabyte::API::CASPERJS_BIN} #{Zillabyte::API::API_CLIENT_JS} \"#{full_script}\" #{arg}"
492
+ cmd = "cd \"#{dir}\"; NODE_PATH=~/zb1/multilang/js/src/lib #{Zillabyte::API::NODEJS_BIN} \"#{full_script}\" #{arg} #{stderr_opt}"
493
+
494
+ else
495
+ error("no language specified", type)
496
+ end
497
+
498
+ return cmd
499
+
500
+ end
501
+
502
+ def read_name_from_conf(options = {})
503
+ type = options[:output_type]
504
+ hash = Zillabyte::API::Apps.get_rich_meta_info_from_script Dir.pwd, options
505
+ error("No id given and current directory does not contain a valid Zillabyte configuration file. Please specify a flow id or run command from the directory containing the flow.",type) if hash["error"]
506
+ hash["name"]
507
+ end
508
+
509
+ def read_class_from_conf(options = {})
510
+ type = options[:output_type]
511
+ hash = Zillabyte::API::Apps.get_rich_meta_info_from_script Dir.pwd, options
512
+ error("No id given and current directory does not contain a valid Zillabyte configuration file. Please specify a flow id or run command from the directory containing the flow.",type) if hash["error"]
513
+ hash["class"]
514
+ end
515
+
516
+ def fetch_logs(hash, operation=nil, exit_on=nil)
517
+ def color_for(operation_name)
518
+ @color_map[operation_name] ||= @all_colors[ @color_map.size % @all_colors.size ]
519
+ @color_map[operation_name]
520
+ end
521
+
522
+ @color_map = {}
523
+ @all_colors = [:green, :yellow, :magenta, :cyan, :light_black, :light_green, :light_yellow, :light_blue, :light_magenta, :light_cyan]
524
+ if(hash["server"] == "localhost")
525
+ cmd = "tail -n 500 -f /tmp/flows/f#{hash["flow"]}/flow_logs/flow_#{hash["flow"]}.log"
526
+ else
527
+ cmd = "curl -G http://#{hash["flow"]}:#{hash["token"]}@#{hash["server"]}:#{hash["port"]}/getLogs?flow=#{hash["flow"]}"
528
+ end
529
+
530
+ begin
531
+ PTY.spawn( cmd ) do |r, w, pid|
532
+ r.each do |line|
533
+ begin
534
+ op = line.match(/\[(.*?)\]/)[0].to_s[1...-1]
535
+ op_name = op.split(".")[0]
536
+ next if op_name != operation and operation != "_ALL_"
537
+ line_split = line.split("[")
538
+ print line_split[0] + "[" + op.colorize(color_for(op)) + line_split[1..-1].join("[")[op.length..-1]
539
+ break if exit_on and line.include? exit_on
540
+ rescue Exception => e
541
+ next
542
+ end
543
+ end
544
+ end
545
+ rescue PTY::ChildExited => e
546
+ end
547
+ end
548
+
549
+ end
@@ -0,0 +1,38 @@
1
+ require "zillabyte/cli/base"
2
+
3
+ # manage git for apps
4
+ #
5
+ class Zillabyte::Command::Git < Zillabyte::Command::Base
6
+
7
+ # git:remote [OPTIONS]
8
+ #
9
+ # adds a git remote to an app repo
10
+ #
11
+ # if OPTIONS are specified they will be passed to git remote add
12
+ #
13
+ # -r, --remote REMOTE # the git remote to create, default "Zillabyte"
14
+ # -n, --name NAME # the name of the app, default current directory's name
15
+ #
16
+ #Examples:
17
+ #
18
+ # $ zillabyte git:remote -a example
19
+ # Git remote zillabyte added
20
+ #
21
+ # $ zillabyte git:remote -a example
22
+ # ! Git remote zillabyte already exists
23
+ #
24
+ def remote
25
+ git_options = args.join(" ")
26
+ remote = options[:remote] || 'zillabyte'
27
+ app_name = options[:name] || File.basename(Dir.pwd)
28
+
29
+ if git('remote').split("\n").include?(remote)
30
+ # already exists.. remove it.
31
+ git("remote remove #{remote}")
32
+ end
33
+
34
+ git_url = "git@#{Zillabyte::Auth.git_host}:#{Zillabyte::Auth.user}/#{app_name}"
35
+ create_git_remote(remote, git_url)
36
+ end
37
+
38
+ end
@@ -4,7 +4,7 @@ require "zillabyte/cli/base"
4
4
  #
5
5
  class Zillabyte::Command::Help < Zillabyte::Command::Base
6
6
 
7
- PRIMARY_NAMESPACES = %w( relations query apps logs )
7
+ PRIMARY_NAMESPACES = %w( relations query apps components logs )
8
8
 
9
9
  # help
10
10
  def index(*direct_args)
@@ -85,13 +85,20 @@ private
85
85
 
86
86
  def summary_for_aliases(command_aliases)
87
87
  used_aliases = command_aliases.select{|c,a| !c.include?("-") and !c.include?(":") and !skip_command?(commands[a])}
88
- a_size = longest(used_aliases.map { |al,al_to| al }) + 2
89
- a_to_size = longest(used_aliases.map { |al,al_to| al_to }) + 2
88
+ a_size = longest(used_aliases.map { |al,al_to| al }) + 4
89
+ a_to_size = longest(used_aliases.map { |al,al_to| al_to }) + 4
90
90
  used_aliases.sort_by{|k,v| v}.each do |command_alias, alias_to|
91
+
91
92
  if command_alias.include?("-") or command_alias.include?(":")
92
93
  next #skip -h, -help and live-run etc.
93
94
  end
94
- puts "#{command_alias.ljust(a_size)} --> #{alias_to.ljust(a_to_size)} # #{commands[alias_to][:summary]}"
95
+
96
+ if alias_to.include?("flows")
97
+ puts "#{command_alias.ljust(a_size)} --> #{alias_to.gsub("flows", "apps").ljust(a_to_size)} # #{commands[alias_to][:summary].gsub("flow", "app")}"
98
+ puts "#{" ".ljust(a_size)} --> #{alias_to.gsub("flows", "components").ljust(a_to_size)} # #{commands[alias_to][:summary].gsub("flow", "component")}"
99
+ else
100
+ puts "#{command_alias.ljust(a_size)} --> #{alias_to.ljust(a_to_size)} # #{commands[alias_to][:summary]}"
101
+ end
95
102
  end
96
103
  end
97
104