bash-visual 1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,60 @@
1
+ require 'font'
2
+
3
+ class Builder
4
+
5
+ def initialize (font = Font.new)
6
+ @default_font = font
7
+ end
8
+
9
+ def clear
10
+ set_position(0, 0) + "\e[2J"
11
+ end
12
+
13
+ def erase_to_end_line
14
+ "\e[K"
15
+ end
16
+
17
+ def restore_position
18
+ "\e[u"
19
+ end
20
+
21
+ def save_position
22
+ "\e[s"
23
+ end
24
+
25
+ def set_position (x, y)
26
+ "\e[#{y.to_i};#{x.to_i}H"
27
+ end
28
+
29
+ def move_position(offset_x, offset_y)
30
+ bash = ''
31
+ if (offset_y > 0)
32
+ bash << "\e[#{offset_y}B"
33
+ else
34
+ bash << "\e[#{offset_y.abs}A"
35
+ end
36
+ if (offset_x > 0)
37
+ bash << "\e[#{offset_x}C"
38
+ else
39
+ bash << "\e[#{offset_x.abs}D"
40
+ end
41
+ bash
42
+ end
43
+
44
+ def write_to_position (x, y, text, font = @default_font)
45
+ bash = ''
46
+ bash << save_position()
47
+ bash << move_position(x, y)
48
+ bash << write(text, font)
49
+ bash << restore_position()
50
+ bash
51
+ end
52
+
53
+ def write (text, font = @default_font)
54
+ font.to_bash + text + Font::RESET
55
+ end
56
+
57
+ def write_ln (text, font = @default_font)
58
+ font.to_bash + text << "\n" << Font::RESET
59
+ end
60
+ end
@@ -0,0 +1,143 @@
1
+ # coding: utf-8
2
+ $:.unshift(File.dirname(__FILE__))
3
+ require 'font'
4
+ require 'builder'
5
+
6
+ class Console
7
+ @@mutex = Mutex.new
8
+
9
+ OUTPUT_STRING = 0
10
+ OUTPUT_WITHOUT_BLOCK = 1
11
+ OUPUT_WITH_BLOCK = 2
12
+ # see: http://en.wikipedia.org/wiki/Box-drawing_characters
13
+ BORDER_UTF = ["\u250C","\u2500","\u2510",
14
+ "\u2502", "\u2502",
15
+ "\u2514","\u2500","\u2518"]
16
+
17
+ BORDER_UTF_ROUND = ["\u256D","\u2500","\u256E",
18
+ "\u2502", "\u2502",
19
+ "\u2570","\u2500","\u256F"]
20
+
21
+ BORDER_UTF_DOUBLE = ["\u2554","\u2550","\u2557",
22
+ "\u2551", "\u2551",
23
+ "\u255A","\u2550","\u255D"]
24
+
25
+ attr_reader :current_x, :current_y, :font;
26
+
27
+ def initialize (font = Font.new, way_outnput = OUTPUT_WITHOUT_BLOCK, builder = Builder.new)
28
+ @current_x = 0
29
+ @current_y = 0
30
+ @font = font
31
+ @way_output = way_outnput
32
+ @builder = builder
33
+ end
34
+
35
+ def position= coord
36
+ @current_x , @current_y = *coord
37
+ print @builder.set_position(@current_x, @current_y)
38
+ end
39
+
40
+ def move_position(offset_x, offset_y)
41
+ @current_x += offset_x
42
+ @current_y += offset_y
43
+ print @builder.move_position(offset_x, offset_y)
44
+ end
45
+
46
+ # Записать что-то в определенной позиции, а потом вернуться на текущую
47
+ # Если необходимо сохранить позицию после записи - используйте связку
48
+ # move_position/position= и write
49
+ def write_to_position (x, y, text, font = @font)
50
+ print @builder.write_to_position(x, y, text, font)
51
+ end
52
+
53
+ def write (text, font = @font)
54
+ print @builder.write(text, font)
55
+ end
56
+
57
+ def write_ln (text, font = @font)
58
+ print @builder.write_ln(text, font)
59
+ end
60
+
61
+ def erase_to_end_line
62
+ print @builder.erase_to_end_line
63
+ end
64
+
65
+ def draw_rectangle(x, y, width, height, font = nil)
66
+ bash = ''
67
+ bash << @builder.save_position()
68
+ bash << @builder.set_position(x,y)
69
+ height.times do
70
+ bash << @builder.write(' ' * width, font)
71
+ bash << @builder.move_position(-width, 1)
72
+ end
73
+ bash << @builder.restore_position()
74
+ print @builder.write(bash, font)
75
+ end
76
+
77
+ def draw_border(x, y, width, height, font = nil, border = BORDER_UTF)
78
+ raise 'width,height must be great than 1' if (width < 2 or height < 2)
79
+
80
+ bash = ''
81
+ bash << @builder.save_position()
82
+ bash << @builder.set_position(x,y)
83
+
84
+ bash << @builder.write(border[0] + border[1] * (width - 2) + border[2], font)
85
+ (height - 2).times do
86
+ bash << @builder.move_position(-width, 1)
87
+ bash << @builder.write(border[3] + ' ' * (width - 2) + border[4], font)
88
+ end
89
+ bash << @builder.move_position(-width, 1)
90
+ bash << @builder.write(border[5] + border[6] * (width - 2) + border[7], font)
91
+
92
+ bash << @builder.restore_position()
93
+ print @builder.write(bash, font)
94
+ end
95
+
96
+ def draw_window(x, y, width, height, text = '', font = nil, border = BORDER_UTF_DOUBLE, text_wrap = ["\u2561","\u255E"])
97
+ raise 'width,height must be great than 2' if (width < 3 or height < 3)
98
+
99
+ text = text.slice(0, width - 2)
100
+ if text.size < (width - 3) && (text_wrap.instance_of? Array)
101
+ text = text_wrap[0] + text + text_wrap[1]
102
+ end
103
+ text = text.center(width - 2, border[1])
104
+ bash = ''
105
+ bash << @builder.save_position()
106
+ bash << @builder.set_position(x,y)
107
+
108
+ bash << @builder.write(border[0] + text + border[2], font)
109
+ (height - 2).times do
110
+ bash << @builder.move_position(-width, 1)
111
+ bash << @builder.write(border[3] + ' ' * (width - 2) + border[4], font)
112
+ end
113
+ bash << @builder.move_position(-width, 1)
114
+ bash << @builder.write(border[5] + border[6] * (width - 2) + border[7], font)
115
+
116
+ bash << @builder.restore_position()
117
+ print @builder.write(bash, font)
118
+ end
119
+
120
+ # Clear the screen and move to (0,0)
121
+ def clear
122
+ @current_x = 0
123
+ @current_y = 0
124
+ print @builder.clear
125
+ end
126
+
127
+ def font= font
128
+ @font = font
129
+ end
130
+
131
+ private
132
+ def print string
133
+
134
+ return string if OUTPUT_STRING == @way_output
135
+
136
+ if OUPUT_WITH_BLOCK == @way_output
137
+ @@mutex.synchronize {super}
138
+ else
139
+ super
140
+ end
141
+ end
142
+
143
+ end
data/lib/bash/font.rb ADDED
@@ -0,0 +1,91 @@
1
+
2
+ class Font
3
+ BLACK = 0
4
+ RED = 1
5
+ GREEN = 2
6
+ BROWN = 3
7
+ BLUE = 4
8
+ MAGENTA = 5
9
+ CYAN = 6
10
+ LIGHT_GRAY = 7
11
+
12
+ GRAY = 10
13
+ LIGHT_RED = 11
14
+ LIGHT_GREEN = 12
15
+ YELLOW = 13
16
+ LIGHT_BLUE = 14
17
+ LIGHT_MAGENTA = 15
18
+ LIGHT_CYAN = 16
19
+ WHITE = 17
20
+
21
+ RESET = "\e[0m"
22
+
23
+ #types font
24
+ STD = 0
25
+ BOLD = 1
26
+ UNDERLINE = 2
27
+ BLINK = 4
28
+
29
+ attr_reader :font, :foreground, :background
30
+
31
+ def initialize (font = STD, foreground = WHITE, background = nil)
32
+ raise "not found color #{foreground}" unless (0..7).cover? foreground or (10..17).cover? foreground
33
+ @font, @foreground, @background = font, foreground, background
34
+
35
+ @bash_command = ''
36
+
37
+ @string = "<type=%s, foreground=%s, background=%s>" % [@font, @foreground, @background]
38
+ if font == STD
39
+ if foreground and foreground < 10
40
+ @bash_command << "\e[3#{foreground}m"
41
+ else
42
+ @bash_command << "\e[9#{foreground-10}m"
43
+ end
44
+ end
45
+
46
+ if font & BOLD != 0
47
+ if foreground < 10
48
+ @bash_command << "\e[1;3#{foreground}m"
49
+ else
50
+ @bash_command << "\e[1;9#{foreground-10}m"
51
+ end
52
+ end
53
+
54
+ if font & UNDERLINE != 0
55
+ if foreground < 10
56
+ @bash_command << "\e[4;3#{foreground}m"
57
+ else
58
+ @bash_command << "\e[4;9#{foreground-10}m"
59
+ end
60
+ end
61
+
62
+ if background.instance_of? Fixnum
63
+ if background < 10
64
+ @bash_command << "\e[4#{background}m"
65
+ else
66
+ @bash_command << "\e[10#{background-10}m"
67
+ end
68
+ end
69
+
70
+ end
71
+
72
+ def to_bash
73
+ @bash_command
74
+ end
75
+
76
+ def to_s
77
+ @string
78
+ end
79
+
80
+ class << self
81
+ def rand_color (exclude_color = nil)
82
+ color = rand(14)
83
+ color += 1 if color == exclude_color
84
+ color = 0 if color > 14
85
+ color += 3 if color > 7
86
+ color
87
+ end
88
+ end
89
+
90
+
91
+ end
@@ -0,0 +1,12 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+ class RandomText
3
+ @@text = File.open(File.dirname(__FILE__)+'/utf') { |file| file.read }
4
+ class << self
5
+ def get
6
+ aText = @@text.split(' ')
7
+ len = Random.new.rand(1...70)
8
+ max = Random.new.rand(0...aText.size-len)
9
+ aText[max, len].join(' ')
10
+ end
11
+ end
12
+ end
data/lib/example1.rb ADDED
@@ -0,0 +1,86 @@
1
+ # coding: utf-8
2
+ $:.unshift(File.dirname(__FILE__))
3
+
4
+ require 'bash/console'
5
+ require 'bash/font'
6
+ require 'scroll'
7
+ require 'vertical_scroll'
8
+ require 'horizontal_scroll'
9
+ require 'common/random_text'
10
+
11
+
12
+ def join_all
13
+ main = Thread.main
14
+ current = Thread.current
15
+ all = Thread.list
16
+ all.each {|t| t.join unless t == current or t == main}
17
+ end
18
+
19
+ default_font = Font.new(Font::STD, Font::LIGHT_BLUE)
20
+
21
+
22
+ console = Console.new(default_font)
23
+ console.clear()
24
+
25
+ window_font = Font.new(Font::STD, Font::WHITE, Font::BLUE)
26
+
27
+
28
+ font_scroll = Font.new(Font::STD, Font::YELLOW)
29
+
30
+ scroll = VerticalScroll.new(
31
+ coordinates: [41, 1],
32
+ window_size: [80, 28],
33
+ message_block_size: 23,
34
+ font: font_scroll,
35
+ separator: "\u2508",
36
+ start: Scroll::ENDING,
37
+ adapt_size_message: true,
38
+ prefix: ->{ Time.now.strftime('%H-%M-%S:%3N') << " " }
39
+ )
40
+
41
+
42
+ console.draw_window(4, 31, 122, 6, 'Example List', window_font, Console::BORDER_UTF_DOUBLE)
43
+
44
+ scroll2 = HorizontalScroll.new(
45
+ coordinates: [4, 31],
46
+ window_size: [120, 4],
47
+ message_block_size: 10,
48
+ font: font_scroll,
49
+ separator: true,
50
+ start: Scroll::BEGINNING
51
+ )
52
+
53
+ 1.upto(28) { |y|
54
+ if (10..20).include? y
55
+ console.write_to_position(121, y, "\u2503")
56
+ else
57
+ console.write_to_position(121, y, "\u2502")
58
+ end
59
+
60
+ }
61
+
62
+ Thread.new do
63
+ 1000.times do
64
+ font = Font.new(Font::BOLD, rand(7), nil)
65
+ x = rand(40)
66
+ y = rand(30)
67
+ console.write_to_position(x, y, '.', font)
68
+
69
+ font = Font.new(Font::STD, Font::rand_color(window_font.background), window_font.background)
70
+ scroll.add(RandomText::get(), font)
71
+ scroll2.add(RandomText::get(), font)
72
+
73
+ sleep(0.7)
74
+ end
75
+ end
76
+ Thread.new do
77
+ 1000.times do
78
+ font = Font.new(Font::STD, window_font.foreground, window_font.background)
79
+ scroll2.add(RandomText::get(), font)
80
+
81
+ sleep(0.3)
82
+ end
83
+ end
84
+
85
+
86
+ join_all
@@ -0,0 +1,69 @@
1
+ require 'scroll'
2
+
3
+ class HorizontalScroll < Scroll
4
+
5
+ DEFAULT_SEPARATOR = "\u2502"
6
+
7
+ def initialize options
8
+ super
9
+ @message_block_width = @message_block_size
10
+
11
+ @separator =
12
+ if @separator == true; DEFAULT_SEPARATOR
13
+ elsif @separator.instance_of? String; @separator[0]
14
+ else nil
15
+ end
16
+ end
17
+
18
+ def get_x_position available_area_width, message_width = 0
19
+ if (@start == BEGINNING)
20
+ @x + (@area_width - available_area_width)
21
+ else
22
+ @x + available_area_width - message_width
23
+ end
24
+ end
25
+
26
+ def print_message message, font, available_area
27
+
28
+ available_area_width, available_area_height = available_area
29
+ avail_width = @message_block_width > available_area_width ? available_area_width : @message_block_width
30
+
31
+
32
+ (available_area_height - message.size).times {
33
+ message << ''
34
+ }
35
+
36
+ if @adapt_size_message
37
+ message_width = rows_wrap! message, available_area_width
38
+ else
39
+ rows_wrap! message, avail_width
40
+ message_width = avail_width
41
+ end
42
+
43
+ message = message.slice(0, available_area_height) if message.size > available_area_height
44
+
45
+ message.map! do |row|
46
+ row.strip.ljust(message_width)
47
+ end
48
+
49
+ if @separator and @separator.size.nonzero?
50
+ if @adapt_size_message
51
+ if message_width < available_area_width
52
+ message.map! do |row|
53
+ row << @separator
54
+ end
55
+ message_width += 1
56
+ end
57
+ else
58
+ message.each do |row|
59
+ row[message_width-1] = @separator
60
+ end
61
+ end
62
+ end
63
+
64
+ write(get_x_position(available_area_width, message_width), @y, message, font)
65
+
66
+ [available_area_width - message_width, available_area_height]
67
+ end
68
+
69
+ end
data/lib/main.rb ADDED
@@ -0,0 +1,71 @@
1
+ ####!> coding:
2
+ $:.unshift(File.dirname(__FILE__))
3
+ print "\u2580"
4
+ exit
5
+ 255.downto(0) { |i|
6
+ puts i.chr
7
+ }
8
+
9
+ exit
10
+ require 'bash/console'
11
+ require 'bash/font'
12
+ require 'scroll'
13
+
14
+ font = Font.new(Font::STD, Font::LIGHT_BLUE)
15
+
16
+ #
17
+ console = Console.new(font)
18
+ console.clear()
19
+ #console.write_ln('+' << '-' * 59 << '+')
20
+ #console.write_ln(("| %9s " % 'Thread') * 5 << '|')
21
+ #console.write_ln('+' << '-' * 59 << '+')
22
+
23
+ #####console.draw_rectangle(1, 1, 100, 40, Font::get(Font::STD, Font::STD, nil))
24
+
25
+ font_scroll = Font.new(Font::STD, Font::YELLOW, Font::BLACK)
26
+
27
+ font_scroll1 = Font.new(Font::STD, Font::LIGHT_GREEN, Font::BLACK)
28
+
29
+
30
+ scroll = Scroll.new(
31
+ coordinates: [100, 3],
32
+ size: [80, 20],
33
+ message_block_size: [80, 3],
34
+ message_crop?: true,
35
+ message_keep: 0,
36
+ type: Scroll::VERTICAL,
37
+ font: font_scroll,
38
+ separator: ' ',
39
+ start: Scroll::BEGINNING
40
+ # prefix: ->{ Time.now.strftime('%H-%M-%S:%3N') << "\n" }
41
+ )
42
+
43
+ at_exit do |unusedlocal|
44
+ #console.clear()
45
+ end
46
+
47
+ #Thread.new {
48
+ #
49
+ # scroll.add(gets())
50
+ #}
51
+
52
+ generator = ->(count){
53
+ value = ''
54
+ 25.times {value << (65 + rand(25)).chr}
55
+ value
56
+ }
57
+
58
+ 100000.times {
59
+ font = Font.new(Font::BOLD, rand(7), nil)
60
+ x = rand(100)
61
+ y = rand(40)
62
+ console.write_to_position(x, y, '.', font)
63
+ #
64
+ #scroll.add("\n01234" * 4, font_scroll2)
65
+
66
+ #scroll.add(generator.(50).to_s, font_scroll1)
67
+
68
+ scroll.add('' << '[]' * rand(40) << "\n1111\n1221")
69
+
70
+ sleep(0.4)
71
+ }
data/lib/scroll.rb ADDED
@@ -0,0 +1,120 @@
1
+ $:.unshift(File.dirname(__FILE__))
2
+
3
+ require 'console'
4
+
5
+ class Scroll
6
+
7
+ FORWARD = 1
8
+ BACKWARD = -1
9
+
10
+ BEGINNING = false
11
+ ENDING = true
12
+
13
+ attr_accessor :console
14
+
15
+ def initialize(options)
16
+
17
+ @x, @y = options[:coordinates]
18
+ @x = @x.to_i
19
+ @y = @y.to_i
20
+
21
+ raise ':window_size isn\'t array' unless options[:window_size].instance_of? Array
22
+ raise 'size :window_size must be great than 1' if options[:window_size].size < 2
23
+
24
+ @area_width, @area_height = options[:window_size]
25
+ @area_width = @area_width.to_i
26
+ @area_height = @area_height.to_i
27
+
28
+ @message_block_size = options[:message_block_size] ? options[:message_block_size].to_i : 1
29
+ @message_block_size = 1 if @message_block_size < 1
30
+
31
+ @prefix = options[:prefix] ? options[:prefix] : nil
32
+
33
+ @adapt_size_message = options[:adapt_size_message] ? options[:adapt_size_message] : false
34
+
35
+ @is_wrap = true
36
+ @start = options[:start] ? ENDING : BEGINNING
37
+ @separator = options[:separator] ? options[:separator] : false
38
+ @font = options[:font] ? options[:font] : nil
39
+
40
+ @stack = []
41
+ @console = Console.new @font,Console::OUTPUT_STRING
42
+ @mutex = Mutex.new
43
+ end
44
+
45
+ def scroll(positions = 1, direction = @direction * positions)
46
+
47
+ end
48
+
49
+ def add (message, font = @font)
50
+
51
+ if @stack.size.zero?
52
+ print @console.draw_rectangle(@x + 1, @y + 1, @area_width, @area_height, font)
53
+ end
54
+
55
+ @stack << {
56
+ message: prefix() << message.to_s,
57
+ font: font
58
+ }
59
+
60
+ redraw()
61
+ #@stack.slice!(-index, index)
62
+ end
63
+
64
+ def prefix= (prefix)
65
+ @prefix = prefix
66
+ end
67
+
68
+ def prefix
69
+ if (defined? @prefix.call)
70
+ @prefix[].to_s
71
+ else
72
+ ''
73
+ end
74
+ end
75
+
76
+ private
77
+
78
+ def redraw
79
+ avail_area = [@area_width, @area_height]
80
+ @stack.reverse.each do |item|
81
+
82
+ message = item[:message].dup.lines.to_a
83
+ font = item[:font]
84
+ unless font.background
85
+ font = Font.new font.font, font.foreground, @font.background
86
+ end
87
+
88
+ avail_area = print_message(message, font, avail_area)
89
+ # выходим если закончилось место в области
90
+ return nil if avail_area[0] <= 0 or avail_area[1] <= 0
91
+ end
92
+ end
93
+
94
+ # сделать переносы в массиве строк
95
+ def rows_wrap! arr, max_len
96
+ max_used_len = 1
97
+ arr.each_with_index do |row, i|
98
+ len = row.size
99
+ max_used_len = len if len > max_used_len
100
+ next if len <= max_len
101
+ tail = row.slice!(max_len, len-max_len)
102
+ arr.insert(i+1, tail)
103
+ end
104
+ max_used_len
105
+ end
106
+
107
+ def write (x, y, message, font)
108
+
109
+
110
+ string = ''
111
+ message.each_with_index { |text, i|
112
+ string << @console.write_to_position(x, y + i, text, font)
113
+ }
114
+
115
+ @mutex.synchronize {
116
+ print string
117
+ }
118
+ end
119
+ end
120
+
@@ -0,0 +1,54 @@
1
+ require 'scroll'
2
+
3
+ class VerticalScroll < Scroll
4
+
5
+ def initialize options
6
+ super
7
+ @message_block_height = @message_block_size
8
+ end
9
+
10
+ def get_y_position available_area_height, message_size = 0
11
+ if (@start == BEGINNING)
12
+ @y + (@area_height - available_area_height)
13
+ else
14
+ @y + available_area_height - message_size
15
+ end
16
+ end
17
+
18
+ def print_message message, font, available_area
19
+
20
+ available_area_width, available_area_height = available_area
21
+ avail_height = @message_block_height > available_area_height ? available_area_height : @message_block_height
22
+
23
+ rows_wrap!(message, available_area_width) if @is_wrap
24
+
25
+ if @adapt_size_message
26
+ message = message.slice(0, available_area_height) if message.size > available_area_height
27
+ else
28
+ message = message.slice(0, avail_height) if message.size > avail_height
29
+ end
30
+
31
+ message.map! do |row|
32
+ row.strip.ljust(available_area_width)
33
+ end
34
+
35
+ if @separator == true
36
+ message[message.size-1] = Font.new(Font::UNDERLINE, font.foreground, font.background).to_bash +
37
+ message.last.ljust(available_area_width, ' ')
38
+ end
39
+
40
+ if (@separator.instance_of? String)
41
+ if (message.size > available_area_height)
42
+ message[message.size-1] = @separator[0] * available_area_width
43
+ else
44
+ message << @separator[0] * available_area_width
45
+ end
46
+ end
47
+
48
+ write(@x, get_y_position(available_area_height, message.size), message, font)
49
+
50
+
51
+ [available_area_width, available_area_height - message.size]
52
+ end
53
+
54
+ end
metadata ADDED
@@ -0,0 +1,62 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bash-visual
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "1.0"
6
+ platform: ruby
7
+ authors:
8
+ - Almazko
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-12-25 00:00:00 Z
14
+ dependencies: []
15
+
16
+ description:
17
+ email: a.s.suslov@gmail.com
18
+ executables: []
19
+
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - ./lib/scroll.rb
26
+ - ./lib/example1.rb
27
+ - ./lib/vertical_scroll.rb
28
+ - ./lib/main.rb
29
+ - ./lib/horizontal_scroll.rb
30
+ - ./lib/common/random_text.rb
31
+ - ./lib/bash/builder.rb
32
+ - ./lib/bash/console.rb
33
+ - ./lib/bash/font.rb
34
+ homepage: https://github.com/AlmazKo/BashConsole
35
+ licenses:
36
+ - MIT
37
+ post_install_message:
38
+ rdoc_options: []
39
+
40
+ require_paths:
41
+ - lib
42
+ required_ruby_version: !ruby/object:Gem::Requirement
43
+ none: false
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: "0"
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: "0"
54
+ requirements: []
55
+
56
+ rubyforge_project:
57
+ rubygems_version: 1.8.11
58
+ signing_key:
59
+ specification_version: 3
60
+ summary: Bash visualization tools.
61
+ test_files: []
62
+