termplot 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 +7 -0
- data/.gitignore +10 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +131 -0
- data/Rakefile +18 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/bin/termplot +5 -0
- data/doc/cpu.png +0 -0
- data/doc/demo.cast +638 -0
- data/doc/demo.gif +0 -0
- data/doc/memory.png +0 -0
- data/doc/sin.png +0 -0
- data/doc/tcp.png +0 -0
- data/lib/termplot.rb +9 -0
- data/lib/termplot/character_map.rb +55 -0
- data/lib/termplot/cli.rb +59 -0
- data/lib/termplot/colors.rb +55 -0
- data/lib/termplot/consumer.rb +71 -0
- data/lib/termplot/cursors/buffered_console_cursor.rb +70 -0
- data/lib/termplot/cursors/console_cursor.rb +56 -0
- data/lib/termplot/cursors/control_chars.rb +11 -0
- data/lib/termplot/cursors/virtual_cursor.rb +76 -0
- data/lib/termplot/renderer.rb +279 -0
- data/lib/termplot/series.rb +37 -0
- data/lib/termplot/shell.rb +31 -0
- data/lib/termplot/version.rb +3 -0
- data/lib/termplot/window.rb +69 -0
- data/termplot.gemspec +32 -0
- metadata +91 -0
data/doc/demo.gif
ADDED
Binary file
|
data/doc/memory.png
ADDED
Binary file
|
data/doc/sin.png
ADDED
Binary file
|
data/doc/tcp.png
ADDED
Binary file
|
data/lib/termplot.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module Termplot
|
2
|
+
module CharacterMap
|
3
|
+
LINE = {
|
4
|
+
empty: " ",
|
5
|
+
point: "─",
|
6
|
+
vert_left: "│",
|
7
|
+
vert_right: "│",
|
8
|
+
horz_top: "─",
|
9
|
+
horz_bot: "─",
|
10
|
+
bot_left: "└",
|
11
|
+
top_right: "┐",
|
12
|
+
top_left: "┌",
|
13
|
+
bot_right: "┘",
|
14
|
+
tick_right: "┤",
|
15
|
+
extended: true
|
16
|
+
}
|
17
|
+
DEFAULT = LINE
|
18
|
+
|
19
|
+
HEAVY_LINE = DEFAULT.merge(
|
20
|
+
point: "━",
|
21
|
+
vert_left: "┃",
|
22
|
+
vert_right: "┃",
|
23
|
+
horz_top: "━",
|
24
|
+
horz_bot: "━",
|
25
|
+
bot_left: "┗",
|
26
|
+
top_right: "┓",
|
27
|
+
top_left: "┏",
|
28
|
+
bot_right: "┛",
|
29
|
+
tick_right: "┫"
|
30
|
+
)
|
31
|
+
|
32
|
+
BASIC = {
|
33
|
+
empty: " ",
|
34
|
+
point: "•",
|
35
|
+
extended: false
|
36
|
+
}
|
37
|
+
DOTS = BASIC
|
38
|
+
|
39
|
+
X = BASIC.merge(
|
40
|
+
point: "x"
|
41
|
+
)
|
42
|
+
|
43
|
+
STAR = BASIC.merge(
|
44
|
+
point: "*"
|
45
|
+
)
|
46
|
+
|
47
|
+
LINE_STYLES = {
|
48
|
+
"line" => LINE,
|
49
|
+
"heavy-line" => HEAVY_LINE,
|
50
|
+
"dot" => DOTS,
|
51
|
+
"star" => STAR,
|
52
|
+
"x" => X
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
data/lib/termplot/cli.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
require "optparse"
|
2
|
+
require "termplot/consumer"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
class CLI
|
6
|
+
def self.run
|
7
|
+
opts = self.parse_options
|
8
|
+
Consumer.new(**opts).run
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
def self.parse_options
|
13
|
+
options = {
|
14
|
+
rows: 19,
|
15
|
+
cols: 80,
|
16
|
+
title: "Series",
|
17
|
+
line_style: "line",
|
18
|
+
color: "red",
|
19
|
+
debug: false
|
20
|
+
}
|
21
|
+
OptionParser.new do |opts|
|
22
|
+
opts.banner = "Usage: termplot [OPTIONS]"
|
23
|
+
|
24
|
+
opts.on("-rROWS", "--rows ROWS", "Number of rows in the chart window (default: 19)") do |v|
|
25
|
+
options[:rows] = v.to_i
|
26
|
+
end
|
27
|
+
|
28
|
+
opts.on("-cCOLS", "--cols COLS", "Number of cols in the chart window (default: 80)") do |v|
|
29
|
+
options[:cols] = v.to_i
|
30
|
+
end
|
31
|
+
|
32
|
+
opts.on("-tTITLE", "--title TITLE", "Title of the series (default: Series)") do |v|
|
33
|
+
options[:title] = v
|
34
|
+
end
|
35
|
+
|
36
|
+
opts.on("--line-style STYLE", "Line style. Options are: line [default], heavy-line, dot, star, x") do |v|
|
37
|
+
options[:line_style] = v.downcase
|
38
|
+
end
|
39
|
+
|
40
|
+
opts.on("--color COLOR", "Series color, specified as ansi 16-bit color name",
|
41
|
+
"(i.e. black, red [default], green, yellow, blue, magenta, cyan, white)",
|
42
|
+
"with light versions specified as light_{color}") do |v|
|
43
|
+
options[:color] = v.downcase
|
44
|
+
end
|
45
|
+
|
46
|
+
opts.on("-d", "--debug", "Enable debug mode, Logs window data to stdout instead of rendering") do |v|
|
47
|
+
options[:debug] = v
|
48
|
+
end
|
49
|
+
|
50
|
+
opts.on("-h", "--help", "Display this help message") do
|
51
|
+
puts opts
|
52
|
+
exit(0)
|
53
|
+
end
|
54
|
+
|
55
|
+
end.parse!
|
56
|
+
options
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
module Termplot
|
2
|
+
class Colors
|
3
|
+
class << self
|
4
|
+
COLORS = {
|
5
|
+
black: 0,
|
6
|
+
light_black: 60,
|
7
|
+
red: 1,
|
8
|
+
light_red: 61,
|
9
|
+
green: 2,
|
10
|
+
light_green: 62,
|
11
|
+
yellow: 3,
|
12
|
+
light_yellow: 63,
|
13
|
+
blue: 4,
|
14
|
+
light_blue: 64,
|
15
|
+
magenta: 5,
|
16
|
+
light_magenta: 65,
|
17
|
+
cyan: 6,
|
18
|
+
light_cyan: 66,
|
19
|
+
white: 7,
|
20
|
+
light_white: 67,
|
21
|
+
default: 9
|
22
|
+
}
|
23
|
+
|
24
|
+
MODES = {
|
25
|
+
default: 0,
|
26
|
+
bold: 1,
|
27
|
+
italic: 3,
|
28
|
+
underline: 4,
|
29
|
+
blink: 5,
|
30
|
+
swap: 7,
|
31
|
+
hide: 8
|
32
|
+
}
|
33
|
+
|
34
|
+
COLORS.each do |(color, code)|
|
35
|
+
define_method(color) do |str|
|
36
|
+
escape_color(color) + str + escape_mode(:default)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def fetch(color, default)
|
41
|
+
COLORS.key?(color.to_sym) ? color.to_sym : default.to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
def escape_color(color)
|
47
|
+
"\e[#{COLORS[color] + 30}m"
|
48
|
+
end
|
49
|
+
|
50
|
+
def escape_mode(mode)
|
51
|
+
"\e[#{MODES[mode]}m"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require "termplot/series"
|
2
|
+
require "termplot/renderer"
|
3
|
+
require "termplot/shell"
|
4
|
+
|
5
|
+
module Termplot
|
6
|
+
class Consumer
|
7
|
+
attr_reader :series, :renderer
|
8
|
+
|
9
|
+
def initialize(cols:, rows:, title:, line_style:, color:, debug:)
|
10
|
+
@renderer = Renderer.new(
|
11
|
+
cols: cols,
|
12
|
+
rows: rows,
|
13
|
+
debug: debug
|
14
|
+
)
|
15
|
+
@series = Series.new(
|
16
|
+
title: title,
|
17
|
+
max_data_points: renderer.inner_width,
|
18
|
+
line_style: line_style,
|
19
|
+
color: color,
|
20
|
+
)
|
21
|
+
end
|
22
|
+
|
23
|
+
def run
|
24
|
+
Shell.init
|
25
|
+
queue = Queue.new
|
26
|
+
|
27
|
+
# Consumer thread will process and render any available input in the
|
28
|
+
# queue. If samples are available faster than it can render, multiple
|
29
|
+
# samples will be shifted from the queue so they can be rendered at once.
|
30
|
+
# If no samples are available but stdin is open, it will sleep until
|
31
|
+
# woken to render new input.
|
32
|
+
consumer = Thread.new do
|
33
|
+
while !queue.closed?
|
34
|
+
num_samples = queue.size
|
35
|
+
if num_samples == 0
|
36
|
+
Thread.stop
|
37
|
+
else
|
38
|
+
num_samples.times do
|
39
|
+
series.add_point(queue.shift)
|
40
|
+
end
|
41
|
+
|
42
|
+
renderer.render(series)
|
43
|
+
series.max_data_points = renderer.inner_width
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Main thread will accept samples as fast as they become available from stdin,
|
49
|
+
# and wake the consumer thread to process them if its asleep
|
50
|
+
while n = STDIN.gets&.chomp do
|
51
|
+
if numeric?(n)
|
52
|
+
queue << n.to_f
|
53
|
+
consumer.run
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Queue is closed as soon as stdin is closed, and we wait for the consumer
|
58
|
+
# to finish rendering
|
59
|
+
queue.close
|
60
|
+
consumer.run
|
61
|
+
consumer.join
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
FLOAT_REGEXP = /^[-+]?[0-9]*\.?[0-9]+$/
|
67
|
+
def numeric?(n)
|
68
|
+
n =~ FLOAT_REGEXP
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require "termplot/cursors/virtual_cursor"
|
2
|
+
require "termplot/cursors/control_chars"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
class BufferedConsoleCursor < VirtualCursor
|
6
|
+
include Termplot::ControlChars
|
7
|
+
attr_reader :buffer
|
8
|
+
|
9
|
+
def initialize(window, buffer)
|
10
|
+
super(window)
|
11
|
+
@buffer = buffer
|
12
|
+
end
|
13
|
+
|
14
|
+
def write(char)
|
15
|
+
if writeable?
|
16
|
+
buffer << char
|
17
|
+
super(char)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def forward(n = 1)
|
22
|
+
moved = super(n)
|
23
|
+
moved.times { buffer << FORWARD }
|
24
|
+
end
|
25
|
+
|
26
|
+
def back(n = 1)
|
27
|
+
moved = super(n)
|
28
|
+
moved.times { buffer << BACK }
|
29
|
+
end
|
30
|
+
|
31
|
+
def up(n=1)
|
32
|
+
moved = super(n)
|
33
|
+
moved.times { buffer << UP }
|
34
|
+
end
|
35
|
+
|
36
|
+
def down(n=1)
|
37
|
+
moved = super(n)
|
38
|
+
moved.times { buffer << DOWN }
|
39
|
+
end
|
40
|
+
|
41
|
+
def beginning_of_line
|
42
|
+
super
|
43
|
+
buffer << CR
|
44
|
+
end
|
45
|
+
|
46
|
+
def new_line
|
47
|
+
buffer << NEWLINE
|
48
|
+
end
|
49
|
+
|
50
|
+
def clear_buffer
|
51
|
+
buffer.clear
|
52
|
+
end
|
53
|
+
|
54
|
+
def flush
|
55
|
+
print buffer.join
|
56
|
+
end
|
57
|
+
|
58
|
+
def position=()
|
59
|
+
raise "Cannot set cursor position directly"
|
60
|
+
end
|
61
|
+
|
62
|
+
def row=()
|
63
|
+
raise "Cannot set cursor position directly"
|
64
|
+
end
|
65
|
+
|
66
|
+
def col=()
|
67
|
+
raise "Cannot set cursor position directly"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require "termplot/cursors/virtual_cursor"
|
2
|
+
require "termplot/cursors/control_chars"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
class ConsoleCursor < VirtualCursor
|
6
|
+
include Termplot::ControlChars
|
7
|
+
|
8
|
+
def write(char)
|
9
|
+
if writeable?
|
10
|
+
print(char)
|
11
|
+
super(char)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def forward(n = 1)
|
16
|
+
moved = super(n)
|
17
|
+
moved.times { print FORWARD }
|
18
|
+
end
|
19
|
+
|
20
|
+
def back(n = 1)
|
21
|
+
moved = super(n)
|
22
|
+
moved.times { print BACK }
|
23
|
+
end
|
24
|
+
|
25
|
+
def up(n=1)
|
26
|
+
moved = super(n)
|
27
|
+
moved.times { print UP }
|
28
|
+
end
|
29
|
+
|
30
|
+
def down(n=1)
|
31
|
+
moved = super(n)
|
32
|
+
moved.times { print DOWN }
|
33
|
+
end
|
34
|
+
|
35
|
+
def beginning_of_line
|
36
|
+
super
|
37
|
+
print CR
|
38
|
+
end
|
39
|
+
|
40
|
+
def new_line
|
41
|
+
print NEWLINE
|
42
|
+
end
|
43
|
+
|
44
|
+
def position=()
|
45
|
+
raise "Cannot set cursor position directly"
|
46
|
+
end
|
47
|
+
|
48
|
+
def row=()
|
49
|
+
raise "Cannot set cursor position directly"
|
50
|
+
end
|
51
|
+
|
52
|
+
def col=()
|
53
|
+
raise "Cannot set cursor position directly"
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
module Termplot
|
2
|
+
class VirtualCursor
|
3
|
+
attr_reader :position, :window
|
4
|
+
|
5
|
+
def initialize(window)
|
6
|
+
@window = window
|
7
|
+
@position = 0
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(char)
|
11
|
+
@position += 1 if writeable?
|
12
|
+
end
|
13
|
+
|
14
|
+
def writeable?
|
15
|
+
position < window.buffer.size
|
16
|
+
end
|
17
|
+
|
18
|
+
def forward(n = 1)
|
19
|
+
movable_chars = window.buffer.size - position
|
20
|
+
chars_to_move = [movable_chars, n].min
|
21
|
+
@position += chars_to_move
|
22
|
+
chars_to_move
|
23
|
+
end
|
24
|
+
|
25
|
+
def back(n = 1)
|
26
|
+
chars_to_move = [position, n].min
|
27
|
+
@position -= chars_to_move
|
28
|
+
chars_to_move
|
29
|
+
end
|
30
|
+
|
31
|
+
def up(n=1)
|
32
|
+
return unless row > 0
|
33
|
+
rows_to_move = [n, row].min
|
34
|
+
@position -= rows_to_move * window.cols
|
35
|
+
rows_to_move
|
36
|
+
end
|
37
|
+
|
38
|
+
def row
|
39
|
+
(position / window.cols).floor
|
40
|
+
end
|
41
|
+
|
42
|
+
def col
|
43
|
+
position % window.cols
|
44
|
+
end
|
45
|
+
|
46
|
+
def row=(y)
|
47
|
+
@position = y * window.cols + col
|
48
|
+
end
|
49
|
+
|
50
|
+
def col=(x)
|
51
|
+
beginning_of_line
|
52
|
+
forward(x)
|
53
|
+
end
|
54
|
+
|
55
|
+
def down(n=1)
|
56
|
+
return 0 unless row < (window.rows - 1)
|
57
|
+
rows_to_move = [n, window.rows - 1 - row].min
|
58
|
+
@position += window.cols * rows_to_move
|
59
|
+
rows_to_move
|
60
|
+
end
|
61
|
+
|
62
|
+
def beginning_of_line
|
63
|
+
@position = position - (position % window.cols)
|
64
|
+
end
|
65
|
+
|
66
|
+
def position=(n)
|
67
|
+
@position = n
|
68
|
+
end
|
69
|
+
|
70
|
+
def reset_position
|
71
|
+
return if position == 0
|
72
|
+
up(row) # Go up by row num times
|
73
|
+
beginning_of_line
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|