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.
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "termplot/version"
4
+ require "termplot/cli"
5
+
6
+ module Termplot
7
+ end
8
+
9
+
@@ -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
@@ -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,11 @@
1
+ module Termplot
2
+ module ControlChars
3
+ CR = "\r"
4
+ NEWLINE = "\n"
5
+ UP = "\e[A"
6
+ DOWN = "\e[B"
7
+ FORWARD = "\e\[C"
8
+ BACK = "\e\[D"
9
+ end
10
+ end
11
+
@@ -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