termplot 0.1.0 → 0.3.1
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 +4 -4
- data/.gitignore +3 -0
- data/README.md +134 -58
- data/Rakefile +28 -13
- data/doc/dash.png +0 -0
- data/doc/demo.png +0 -0
- data/doc/file.png +0 -0
- data/doc/memory.png +0 -0
- data/doc/ping.png +0 -0
- data/doc/sin.png +0 -0
- data/doc/tcp.png +0 -0
- data/examples/sample.rb +17 -0
- data/lib/termplot/character_map.rb +15 -4
- data/lib/termplot/cli.rb +14 -48
- data/lib/termplot/colors.rb +36 -29
- data/lib/termplot/commands.rb +27 -0
- data/lib/termplot/consumers/base_consumer.rb +132 -0
- data/lib/termplot/consumers/command_consumer.rb +14 -0
- data/lib/termplot/consumers/multi_source_consumer.rb +33 -0
- data/lib/termplot/consumers/single_source_consumer.rb +36 -0
- data/lib/termplot/consumers/stdin_consumer.rb +11 -0
- data/lib/termplot/consumers.rb +12 -0
- data/lib/termplot/{cursors/control_chars.rb → control_chars.rb} +0 -0
- data/lib/termplot/cursors/buffered_console_cursor.rb +51 -52
- data/lib/termplot/cursors/virtual_cursor.rb +64 -58
- data/lib/termplot/cursors.rb +7 -0
- data/lib/termplot/dsl/panels.rb +80 -0
- data/lib/termplot/dsl/widgets.rb +128 -0
- data/lib/termplot/file_config.rb +37 -0
- data/lib/termplot/message_broker.rb +111 -0
- data/lib/termplot/options.rb +211 -0
- data/lib/termplot/positioned_widget.rb +8 -0
- data/lib/termplot/producer_options.rb +3 -0
- data/lib/termplot/producers/base_producer.rb +32 -0
- data/lib/termplot/producers/command_producer.rb +42 -0
- data/lib/termplot/producers/stdin_producer.rb +11 -0
- data/lib/termplot/producers.rb +7 -0
- data/lib/termplot/renderable.rb +35 -0
- data/lib/termplot/renderer.rb +16 -257
- data/lib/termplot/renderers/border_renderer.rb +48 -0
- data/lib/termplot/renderers/text_renderer.rb +73 -0
- data/lib/termplot/renderers.rb +6 -0
- data/lib/termplot/shell.rb +13 -9
- data/lib/termplot/utils/ansi_safe_string.rb +68 -0
- data/lib/termplot/version.rb +1 -1
- data/lib/termplot/widgets/base_widget.rb +79 -0
- data/lib/termplot/widgets/border.rb +6 -0
- data/lib/termplot/widgets/dataset.rb +50 -0
- data/lib/termplot/widgets/histogram_widget.rb +196 -0
- data/lib/termplot/widgets/statistics.rb +21 -0
- data/lib/termplot/widgets/statistics_widget.rb +104 -0
- data/lib/termplot/widgets/time_series_widget.rb +248 -0
- data/lib/termplot/widgets.rb +8 -0
- data/lib/termplot/window.rb +29 -9
- data/termplot.gemspec +1 -6
- metadata +46 -30
- data/doc/cpu.png +0 -0
- data/doc/demo.cast +0 -638
- data/doc/demo.gif +0 -0
- data/lib/termplot/consumer.rb +0 -71
- data/lib/termplot/cursors/console_cursor.rb +0 -56
- data/lib/termplot/series.rb +0 -37
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "termplot/renderer"
|
4
|
+
require "termplot/message_broker"
|
5
|
+
require "termplot/shell"
|
6
|
+
|
7
|
+
module Termplot
|
8
|
+
module Consumers
|
9
|
+
class BaseConsumer
|
10
|
+
def initialize(options)
|
11
|
+
@options = options
|
12
|
+
@broker_pool = MessageBrokerPool.new
|
13
|
+
@producer_pool = ProducerPool.new
|
14
|
+
@renderer = Renderer.new(
|
15
|
+
cols: options.cols,
|
16
|
+
rows: options.rows,
|
17
|
+
widgets: positioned_widgets,
|
18
|
+
debug: options.debug
|
19
|
+
)
|
20
|
+
|
21
|
+
@renderer_thread = RendererThread.new(
|
22
|
+
renderer: renderer,
|
23
|
+
broker_pool: broker_pool
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def run
|
28
|
+
register_producers_and_brokers
|
29
|
+
broker_pool.on_message { renderer_thread.continue }
|
30
|
+
|
31
|
+
Shell.init(clear: options.full_screen)
|
32
|
+
renderer_thread.start
|
33
|
+
|
34
|
+
# Blocks main thread to produce values from the producer pool
|
35
|
+
producer_pool.start_and_block
|
36
|
+
|
37
|
+
# At this point producer threads have all exited, tell renderer to
|
38
|
+
# consume all messages left on the queue
|
39
|
+
renderer_thread.continue while !broker_pool.empty?
|
40
|
+
|
41
|
+
# Close queues
|
42
|
+
broker_pool.shutdown
|
43
|
+
|
44
|
+
# Shutdown renderer
|
45
|
+
renderer_thread.join
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
attr_reader :options,
|
50
|
+
:broker_pool,
|
51
|
+
:producer_pool,
|
52
|
+
:renderer,
|
53
|
+
:renderer_thread
|
54
|
+
|
55
|
+
class RendererThread
|
56
|
+
def initialize(renderer:, broker_pool:, &block)
|
57
|
+
@renderer = renderer
|
58
|
+
@broker_pool = broker_pool
|
59
|
+
@block = block
|
60
|
+
@thread = nil
|
61
|
+
end
|
62
|
+
|
63
|
+
def start
|
64
|
+
@thread = Thread.new do
|
65
|
+
# Pause and wait to be woken for rendering
|
66
|
+
pause
|
67
|
+
while !broker_pool.closed?
|
68
|
+
num_samples = broker_pool.pending_message_count
|
69
|
+
|
70
|
+
if num_samples.zero?
|
71
|
+
pause
|
72
|
+
else
|
73
|
+
broker_pool.flush_messages
|
74
|
+
|
75
|
+
if num_samples > 0
|
76
|
+
renderer.render
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def continue
|
84
|
+
thread.run
|
85
|
+
end
|
86
|
+
|
87
|
+
def pause
|
88
|
+
Thread.stop
|
89
|
+
end
|
90
|
+
|
91
|
+
def join
|
92
|
+
thread.join unless thread.stop?
|
93
|
+
end
|
94
|
+
|
95
|
+
private
|
96
|
+
attr_reader :renderer, :broker_pool, :block, :thread
|
97
|
+
end
|
98
|
+
|
99
|
+
class ProducerPool
|
100
|
+
def initialize
|
101
|
+
@producers = []
|
102
|
+
@threads = []
|
103
|
+
end
|
104
|
+
|
105
|
+
def start
|
106
|
+
# Run Producers with producers
|
107
|
+
@threads = producers.map do |producer|
|
108
|
+
Thread.new do
|
109
|
+
producer.run
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def wait
|
115
|
+
threads.each(&:join)
|
116
|
+
end
|
117
|
+
|
118
|
+
def start_and_block
|
119
|
+
start
|
120
|
+
wait
|
121
|
+
end
|
122
|
+
|
123
|
+
def add_producer(producer)
|
124
|
+
producers.push(producer)
|
125
|
+
end
|
126
|
+
|
127
|
+
private
|
128
|
+
attr_reader :producers, :threads
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
require "termplot/positioned_widget"
|
3
|
+
require "termplot/widgets"
|
4
|
+
require "termplot/producers"
|
5
|
+
|
6
|
+
module Termplot
|
7
|
+
module Consumers
|
8
|
+
class CommandConsumer < SingleSourceConsumer
|
9
|
+
def producer_class
|
10
|
+
Termplot::Producers::CommandProducer
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "termplot/file_config"
|
4
|
+
require "termplot/producers"
|
5
|
+
|
6
|
+
module Termplot
|
7
|
+
module Consumers
|
8
|
+
class MultiSourceConsumer < BaseConsumer
|
9
|
+
def positioned_widgets
|
10
|
+
@positioned_widgets ||= config.positioned_widgets
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_producers_and_brokers
|
14
|
+
config.widget_configs.each do |widget_config|
|
15
|
+
producer = build_producer(widget_config)
|
16
|
+
broker_pool.broker(sender: producer, receiver: widget_config.widget)
|
17
|
+
producer_pool.add_producer(producer)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
def config
|
23
|
+
@config ||= FileConfig.new(options).parse_config
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_producer(widget_config)
|
27
|
+
Termplot::Producers::CommandProducer.new(
|
28
|
+
widget_config.producer_options
|
29
|
+
)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require "termplot/positioned_widget"
|
3
|
+
require "termplot/widgets"
|
4
|
+
require "termplot/producers"
|
5
|
+
|
6
|
+
module Termplot
|
7
|
+
module Consumers
|
8
|
+
class SingleSourceConsumer < BaseConsumer
|
9
|
+
def positioned_widgets
|
10
|
+
@positioned_widgets ||= [PositionedWidget.new(row: 0, col: 0, widget: widget)]
|
11
|
+
end
|
12
|
+
|
13
|
+
def register_producers_and_brokers
|
14
|
+
producer = build_producer
|
15
|
+
broker_pool.broker(sender: producer, receiver: widget)
|
16
|
+
producer_pool.add_producer(producer)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
def widget
|
21
|
+
return @widget if defined? @widget
|
22
|
+
wigdet_classes = {
|
23
|
+
"timeseries" => "Termplot::Widgets::TimeSeriesWidget",
|
24
|
+
"stats" => "Termplot::Widgets::StatisticsWidget",
|
25
|
+
"hist" => "Termplot::Widgets::HistogramWidget",
|
26
|
+
}
|
27
|
+
klass = Object.const_get(wigdet_classes[options.type])
|
28
|
+
@widget = klass.new(**options.to_h)
|
29
|
+
end
|
30
|
+
|
31
|
+
def build_producer
|
32
|
+
producer_class.new(options.producer_options)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Termplot
|
4
|
+
module Consumers
|
5
|
+
autoload :BaseConsumer, "termplot/consumers/base_consumer"
|
6
|
+
autoload :MultiSourceConsumer, "termplot/consumers/multi_source_consumer"
|
7
|
+
autoload :SingleSourceConsumer, "termplot/consumers/single_source_consumer"
|
8
|
+
|
9
|
+
autoload :StdinConsumer, "termplot/consumers/stdin_consumer"
|
10
|
+
autoload :CommandConsumer, "termplot/consumers/command_consumer"
|
11
|
+
end
|
12
|
+
end
|
File without changes
|
@@ -1,70 +1,69 @@
|
|
1
|
-
require "termplot/cursors/virtual_cursor"
|
2
|
-
require "termplot/cursors/control_chars"
|
3
|
-
|
4
1
|
module Termplot
|
5
|
-
|
6
|
-
|
7
|
-
|
2
|
+
module Cursors
|
3
|
+
class BufferedConsoleCursor < VirtualCursor
|
4
|
+
include Termplot::ControlChars
|
5
|
+
attr_reader :buffer
|
8
6
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
7
|
+
def initialize(window, buffer)
|
8
|
+
super(window)
|
9
|
+
@buffer = buffer
|
10
|
+
end
|
13
11
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
12
|
+
def write(char)
|
13
|
+
if writeable?
|
14
|
+
buffer << char
|
15
|
+
super(char)
|
16
|
+
end
|
18
17
|
end
|
19
|
-
end
|
20
18
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
19
|
+
def forward(n = 1)
|
20
|
+
moved = super(n)
|
21
|
+
moved.times { buffer << FORWARD }
|
22
|
+
end
|
25
23
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
def back(n = 1)
|
25
|
+
moved = super(n)
|
26
|
+
moved.times { buffer << BACK }
|
27
|
+
end
|
30
28
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
29
|
+
def up(n=1)
|
30
|
+
moved = super(n)
|
31
|
+
moved.times { buffer << UP }
|
32
|
+
end
|
35
33
|
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
def down(n=1)
|
35
|
+
moved = super(n)
|
36
|
+
moved.times { buffer << DOWN }
|
37
|
+
end
|
40
38
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
39
|
+
def beginning_of_line
|
40
|
+
super
|
41
|
+
buffer << CR
|
42
|
+
end
|
45
43
|
|
46
|
-
|
47
|
-
|
48
|
-
|
44
|
+
def new_line
|
45
|
+
buffer << NEWLINE
|
46
|
+
end
|
49
47
|
|
50
|
-
|
51
|
-
|
52
|
-
|
48
|
+
def clear_buffer
|
49
|
+
buffer.clear
|
50
|
+
end
|
53
51
|
|
54
|
-
|
55
|
-
|
56
|
-
|
52
|
+
def flush
|
53
|
+
buffer.join
|
54
|
+
end
|
57
55
|
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
def position=()
|
57
|
+
raise "Cannot set cursor position directly"
|
58
|
+
end
|
61
59
|
|
62
|
-
|
63
|
-
|
64
|
-
|
60
|
+
def row=()
|
61
|
+
raise "Cannot set cursor position directly"
|
62
|
+
end
|
65
63
|
|
66
|
-
|
67
|
-
|
64
|
+
def col=()
|
65
|
+
raise "Cannot set cursor position directly"
|
66
|
+
end
|
68
67
|
end
|
69
68
|
end
|
70
69
|
end
|
@@ -1,76 +1,82 @@
|
|
1
1
|
module Termplot
|
2
|
-
|
3
|
-
|
2
|
+
module Cursors
|
3
|
+
class VirtualCursor
|
4
|
+
attr_reader :position, :window
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
def initialize(window)
|
7
|
+
@window = window
|
8
|
+
@position = 0
|
9
|
+
end
|
9
10
|
|
10
|
-
|
11
|
-
|
12
|
-
|
11
|
+
def write(char)
|
12
|
+
@position += 1 if writeable?
|
13
|
+
end
|
13
14
|
|
14
|
-
|
15
|
-
|
16
|
-
|
15
|
+
def writeable?
|
16
|
+
position < window.buffer.size
|
17
|
+
end
|
17
18
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
19
|
+
def forward(n = 1)
|
20
|
+
movable_chars = window.buffer.size - position
|
21
|
+
chars_to_move = [movable_chars, n].min
|
22
|
+
@position += chars_to_move
|
23
|
+
chars_to_move
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
def back(n = 1)
|
27
|
+
chars_to_move = [position, n].min
|
28
|
+
@position -= chars_to_move
|
29
|
+
chars_to_move
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
32
|
+
def up(n=1)
|
33
|
+
return unless row > 0
|
34
|
+
rows_to_move = [n, row].min
|
35
|
+
@position -= rows_to_move * window.cols
|
36
|
+
rows_to_move
|
37
|
+
end
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
39
|
+
def row
|
40
|
+
(position / window.cols).floor
|
41
|
+
end
|
41
42
|
|
42
|
-
|
43
|
-
|
44
|
-
|
43
|
+
def col
|
44
|
+
position % window.cols
|
45
|
+
end
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
47
|
+
def row=(y)
|
48
|
+
@position = y * window.cols + col
|
49
|
+
end
|
49
50
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
51
|
+
def col=(x)
|
52
|
+
beginning_of_line
|
53
|
+
forward(x)
|
54
|
+
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
56
|
+
def down(n=1)
|
57
|
+
return 0 unless row < (window.rows - 1)
|
58
|
+
rows_to_move = [n, window.rows - 1 - row].min
|
59
|
+
@position += window.cols * rows_to_move
|
60
|
+
rows_to_move
|
61
|
+
end
|
61
62
|
|
62
|
-
|
63
|
-
|
64
|
-
|
63
|
+
def beginning_of_line
|
64
|
+
@position = position - (position % window.cols)
|
65
|
+
end
|
65
66
|
|
66
|
-
|
67
|
-
|
68
|
-
|
67
|
+
def beginning_of_line?
|
68
|
+
@position % window.cols == 0
|
69
|
+
end
|
70
|
+
|
71
|
+
def position=(n)
|
72
|
+
@position = n
|
73
|
+
end
|
69
74
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
75
|
+
def reset_position
|
76
|
+
return if position == 0
|
77
|
+
up(row) # Go up by row num times
|
78
|
+
beginning_of_line
|
79
|
+
end
|
74
80
|
end
|
75
81
|
end
|
76
82
|
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require "termplot/commands"
|
2
|
+
require "termplot/dsl/widgets"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
module DSL
|
6
|
+
class Panel
|
7
|
+
include Termplot::Commands
|
8
|
+
include Termplot::WidgetDSL
|
9
|
+
|
10
|
+
attr_accessor(
|
11
|
+
:rows,
|
12
|
+
:cols,
|
13
|
+
:start_row,
|
14
|
+
:start_col
|
15
|
+
)
|
16
|
+
|
17
|
+
def initialize(options, children = [])
|
18
|
+
@options = options
|
19
|
+
@children = children
|
20
|
+
end
|
21
|
+
|
22
|
+
def set_dimensions(rows, cols, start_row, start_col)
|
23
|
+
raise "Must be implemented"
|
24
|
+
end
|
25
|
+
|
26
|
+
def flatten
|
27
|
+
children.map(&:flatten).flatten
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
attr_reader :options, :children
|
32
|
+
|
33
|
+
def row(&block)
|
34
|
+
new_row = Row.new(options)
|
35
|
+
new_row.instance_eval(&block)
|
36
|
+
children.push(new_row)
|
37
|
+
end
|
38
|
+
|
39
|
+
def col(&block)
|
40
|
+
new_col = Col.new(options)
|
41
|
+
new_col.instance_eval(&block)
|
42
|
+
children.push(new_col)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Row < Panel
|
47
|
+
def set_dimensions(rows, cols, start_row, start_col)
|
48
|
+
@rows = rows
|
49
|
+
@cols = cols
|
50
|
+
child_cols = cols / children.count
|
51
|
+
|
52
|
+
children.each_with_index do |child, index|
|
53
|
+
child.set_dimensions(
|
54
|
+
rows,
|
55
|
+
child_cols,
|
56
|
+
start_row,
|
57
|
+
child_cols * index
|
58
|
+
)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Col < Panel
|
64
|
+
def set_dimensions(rows, cols, start_row, start_col)
|
65
|
+
@rows = rows
|
66
|
+
@cols = cols
|
67
|
+
child_rows = rows / children.count
|
68
|
+
|
69
|
+
children.each_with_index do |child, index|
|
70
|
+
child.set_dimensions(
|
71
|
+
child_rows,
|
72
|
+
cols,
|
73
|
+
child_rows * index,
|
74
|
+
start_col
|
75
|
+
)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|