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,128 @@
|
|
1
|
+
require "termplot/positioned_widget"
|
2
|
+
require "termplot/widgets"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
module WidgetDSL
|
6
|
+
def timeseries(attrs)
|
7
|
+
attrs = merge_defaults(attrs)
|
8
|
+
children.push(TimeSeriesConfig.new(attrs))
|
9
|
+
end
|
10
|
+
|
11
|
+
def statistics(attrs)
|
12
|
+
attrs = merge_defaults(attrs)
|
13
|
+
children.push(StatisticsConfig.new(attrs))
|
14
|
+
end
|
15
|
+
|
16
|
+
def histogram(attrs)
|
17
|
+
attrs = merge_defaults(attrs)
|
18
|
+
children.push(HistogramConfig.new(attrs))
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def merge_defaults(attrs)
|
24
|
+
Termplot::Options.default_options.merge(attrs)
|
25
|
+
end
|
26
|
+
|
27
|
+
class WidgetConfig
|
28
|
+
attr_reader(
|
29
|
+
:title,
|
30
|
+
:command,
|
31
|
+
:interval,
|
32
|
+
:col,
|
33
|
+
:row,
|
34
|
+
:cols,
|
35
|
+
:rows,
|
36
|
+
:debug
|
37
|
+
)
|
38
|
+
|
39
|
+
def initialize(opts)
|
40
|
+
@title = opts[:title]
|
41
|
+
|
42
|
+
@command = opts[:command]
|
43
|
+
@interval = opts[:interval]
|
44
|
+
@debug = opts[:debug]
|
45
|
+
|
46
|
+
post_initialize(opts)
|
47
|
+
end
|
48
|
+
|
49
|
+
def post_initialize(opts)
|
50
|
+
# Implemented in subclasses
|
51
|
+
end
|
52
|
+
|
53
|
+
def set_dimensions(rows, cols, start_row, start_col)
|
54
|
+
@rows = rows
|
55
|
+
@cols = cols
|
56
|
+
@row = start_row
|
57
|
+
@col = start_col
|
58
|
+
end
|
59
|
+
|
60
|
+
def flatten
|
61
|
+
self
|
62
|
+
end
|
63
|
+
|
64
|
+
def positioned_widget
|
65
|
+
@positioned_widget ||= PositionedWidget.new(
|
66
|
+
row: row,
|
67
|
+
col: col,
|
68
|
+
widget: widget
|
69
|
+
)
|
70
|
+
end
|
71
|
+
|
72
|
+
def widget
|
73
|
+
raise "Must be implemented"
|
74
|
+
end
|
75
|
+
|
76
|
+
def producer_options
|
77
|
+
ProducerOptions.new(command: command, interval: interval)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class TimeSeriesConfig < WidgetConfig
|
82
|
+
attr_reader :color, :line_style
|
83
|
+
def post_initialize(opts)
|
84
|
+
@color = opts[:color]
|
85
|
+
@line_style = opts[:line_style]
|
86
|
+
end
|
87
|
+
|
88
|
+
def widget
|
89
|
+
@widget ||= Termplot::Widgets::TimeSeriesWidget.new(
|
90
|
+
title: title,
|
91
|
+
line_style: line_style,
|
92
|
+
color: color,
|
93
|
+
cols: cols,
|
94
|
+
rows: rows,
|
95
|
+
debug: debug
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
class StatisticsConfig < WidgetConfig
|
101
|
+
def widget
|
102
|
+
@widget ||= Termplot::Widgets::StatisticsWidget.new(
|
103
|
+
title: title,
|
104
|
+
cols: cols,
|
105
|
+
rows: rows,
|
106
|
+
debug: debug
|
107
|
+
)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
class HistogramConfig < WidgetConfig
|
112
|
+
attr_reader :color
|
113
|
+
def post_initialize(opts)
|
114
|
+
@color = opts[:color]
|
115
|
+
end
|
116
|
+
|
117
|
+
def widget
|
118
|
+
@widget ||= Termplot::Widgets::HistogramWidget.new(
|
119
|
+
title: title,
|
120
|
+
color: color,
|
121
|
+
cols: cols,
|
122
|
+
rows: rows,
|
123
|
+
debug: debug
|
124
|
+
)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "termplot/dsl/panels"
|
4
|
+
|
5
|
+
module Termplot
|
6
|
+
class FileConfig
|
7
|
+
attr_reader :options, :rows, :cols, :widget_configs
|
8
|
+
def initialize(options)
|
9
|
+
@options = options
|
10
|
+
@path = options.file
|
11
|
+
@rows = options.rows
|
12
|
+
@cols = options.cols
|
13
|
+
@widget_configs = nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse_config
|
17
|
+
code = File.read(path)
|
18
|
+
top_level_panel = Termplot::DSL::Col.new(options)
|
19
|
+
top_level_panel.instance_eval(code)
|
20
|
+
|
21
|
+
@widget_configs = resolve_widget_positions(top_level_panel)
|
22
|
+
self
|
23
|
+
end
|
24
|
+
|
25
|
+
def positioned_widgets
|
26
|
+
widget_configs.map(&:positioned_widget)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
attr_reader :path
|
31
|
+
|
32
|
+
def resolve_widget_positions(top_level_panel)
|
33
|
+
top_level_panel.set_dimensions(rows, cols, 0, 0)
|
34
|
+
top_level_panel.flatten
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require "forwardable"
|
2
|
+
|
3
|
+
module Termplot
|
4
|
+
class MessageBrokerPool
|
5
|
+
def initialize
|
6
|
+
@brokers = []
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@on_message_callbacks = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def broker(sender:, receiver:)
|
12
|
+
mutex.synchronize do
|
13
|
+
broker = MessageBroker.new(sender: sender, receiver: receiver)
|
14
|
+
broker.on_message do |v|
|
15
|
+
on_message_callbacks.each do |block|
|
16
|
+
block.call(v)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
brokers.push(broker)
|
20
|
+
broker
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def on_message(&block)
|
25
|
+
mutex.synchronize do
|
26
|
+
on_message_callbacks.push(block)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def closed?
|
31
|
+
mutex.synchronize do
|
32
|
+
(brokers.count == 0) || brokers.all?(&:closed?)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def shutdown
|
37
|
+
mutex.synchronize do
|
38
|
+
brokers.each(&:close)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def flush_messages
|
43
|
+
mutex.synchronize do
|
44
|
+
brokers.each(&:flush_queue)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def pending_message_count
|
49
|
+
mutex.synchronize do
|
50
|
+
brokers.inject(0) do |sum, broker|
|
51
|
+
sum + broker.pending_message_count
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def empty?
|
57
|
+
pending_message_count == 0
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
attr_reader :brokers, :mutex, :on_message_callbacks
|
62
|
+
end
|
63
|
+
|
64
|
+
# Broker messages in a thread-safe way between a sender and a receiver.
|
65
|
+
class MessageBroker
|
66
|
+
def initialize(sender:, receiver:)
|
67
|
+
@sender = sender
|
68
|
+
@receiver = receiver
|
69
|
+
@queue = Queue.new
|
70
|
+
@on_message_callbacks = []
|
71
|
+
|
72
|
+
register_callbacks
|
73
|
+
end
|
74
|
+
|
75
|
+
def on_message(&block)
|
76
|
+
on_message_callbacks.push(block)
|
77
|
+
end
|
78
|
+
|
79
|
+
def pending_message_count
|
80
|
+
queue.size
|
81
|
+
end
|
82
|
+
|
83
|
+
def flush_queue
|
84
|
+
num_samples = queue.size
|
85
|
+
num_samples.times do
|
86
|
+
receiver << queue.shift
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def close
|
91
|
+
queue.close
|
92
|
+
end
|
93
|
+
|
94
|
+
def closed?
|
95
|
+
queue.closed?
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
attr_reader :sender, :receiver, :queue, :on_message_callbacks
|
100
|
+
|
101
|
+
def register_callbacks
|
102
|
+
on_message_callbacks.push -> (value) { queue << value }
|
103
|
+
|
104
|
+
sender.on_message do |value|
|
105
|
+
on_message_callbacks.each do |block|
|
106
|
+
block.call(value)
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,211 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require "termplot/character_map"
|
5
|
+
require "termplot/colors"
|
6
|
+
require "termplot/producer_options"
|
7
|
+
require "termplot/shell"
|
8
|
+
|
9
|
+
module Termplot
|
10
|
+
class Options
|
11
|
+
attr_reader :rows,
|
12
|
+
:cols,
|
13
|
+
:full_screen,
|
14
|
+
:debug,
|
15
|
+
:file,
|
16
|
+
:command,
|
17
|
+
:interval,
|
18
|
+
:type,
|
19
|
+
:title,
|
20
|
+
:line_style,
|
21
|
+
:color
|
22
|
+
|
23
|
+
def initialize
|
24
|
+
self.class.default_options.each do |(option, value)|
|
25
|
+
instance_variable_set("@#{option}", value)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# 3 input modes supported:
|
30
|
+
# - Read from stdin and render a single chart (default)
|
31
|
+
# - Run a single command at an interval and render a single chart
|
32
|
+
# - Read configuration from a file, run multiple commands at an interval and
|
33
|
+
# render multiple charts in a dashboard
|
34
|
+
def input_mode
|
35
|
+
return :file unless @file.nil?
|
36
|
+
return :command unless @command.nil?
|
37
|
+
:stdin
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.default_options
|
41
|
+
{
|
42
|
+
# General options
|
43
|
+
rows: 19,
|
44
|
+
cols: 100,
|
45
|
+
full_screen: false,
|
46
|
+
debug: false,
|
47
|
+
|
48
|
+
# Input modes
|
49
|
+
file: nil,
|
50
|
+
command: nil,
|
51
|
+
interval: 1000,
|
52
|
+
|
53
|
+
# Widget (only necessary for stdin/command input modes)
|
54
|
+
type: "timeseries",
|
55
|
+
|
56
|
+
# General - All/multiple widget types
|
57
|
+
title: "Series",
|
58
|
+
color: "green",
|
59
|
+
|
60
|
+
# Timeseries
|
61
|
+
line_style: "heavy-line",
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def to_h
|
66
|
+
self.class.default_options.inject({}) do |hash, (k, _)|
|
67
|
+
hash[k] = instance_variable_get("@#{k}")
|
68
|
+
hash
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_options!
|
73
|
+
# Debug option is parsed manually to prevent it from showing up in the
|
74
|
+
# options help
|
75
|
+
parse_debug
|
76
|
+
|
77
|
+
OptionParser.new do |opts|
|
78
|
+
opts.banner = "Usage: termplot [OPTIONS]"
|
79
|
+
|
80
|
+
parse_rows(opts)
|
81
|
+
parse_cols(opts)
|
82
|
+
parse_full_screen(opts)
|
83
|
+
|
84
|
+
parse_file(opts)
|
85
|
+
parse_command(opts)
|
86
|
+
parse_interval(opts)
|
87
|
+
|
88
|
+
parse_type(opts)
|
89
|
+
|
90
|
+
parse_title(opts)
|
91
|
+
parse_color(opts)
|
92
|
+
|
93
|
+
parse_line_style(opts)
|
94
|
+
|
95
|
+
opts.on("-h", "--help", "Display this help message") do
|
96
|
+
puts opts
|
97
|
+
exit(0)
|
98
|
+
end
|
99
|
+
|
100
|
+
end.parse!
|
101
|
+
self
|
102
|
+
end
|
103
|
+
|
104
|
+
def producer_options
|
105
|
+
ProducerOptions.new(command: command, interval: interval)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def parse_rows(opts)
|
111
|
+
opts.on("-r ROWS", "--rows ROWS",
|
112
|
+
"Number of rows in the chart window (default: #{@rows})") do |v|
|
113
|
+
@rows = v.to_i
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def parse_cols(opts)
|
118
|
+
opts.on("-c COLS", "--cols COLS",
|
119
|
+
"Number of cols in the chart window (default: #{@cols})") do |v|
|
120
|
+
@cols = v.to_i
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def parse_full_screen(opts)
|
125
|
+
opts.on("--full-screen", "Render to the full available terminal size") do |v|
|
126
|
+
@rows, @cols = Shell.get_dimensions
|
127
|
+
@full_screen = true
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def parse_file(opts)
|
132
|
+
opts.on("-f FILE", "--file FILE",
|
133
|
+
"Read a dashboard configuration from a file") do |v|
|
134
|
+
@file = v
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def parse_title(opts)
|
139
|
+
opts.on("-tTITLE", "--title TITLE",
|
140
|
+
"Title of the series (default: '#{@title}')") do |v|
|
141
|
+
@title = v
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def parse_line_style(opts)
|
146
|
+
line_style_opts = with_default(Termplot::CharacterMap::LINE_STYLES.keys, @line_style)
|
147
|
+
opts.on("--line-style STYLE",
|
148
|
+
"Line style. Options are: #{line_style_opts.join(", ")}") do |v|
|
149
|
+
@line_style = v.downcase
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def parse_color(opts)
|
154
|
+
color_opts = Termplot::Colors::COLORS.keys.map(&:to_s).reject do |c|
|
155
|
+
c == :default
|
156
|
+
end
|
157
|
+
color_opts = with_default(color_opts, @color)
|
158
|
+
opts.on("--color COLOR",
|
159
|
+
"Series color, specified as ansi 16-bit color name:",
|
160
|
+
"(i.e. #{color_opts.join(", ")})") do |v|
|
161
|
+
@color = v.downcase
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def parse_command(opts)
|
166
|
+
opts.on("--command COMMAND",
|
167
|
+
"Enables command mode, where input is received by executing",
|
168
|
+
"the specified command in intervals rather than from stdin") do |v|
|
169
|
+
@command = v
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
def parse_interval(opts)
|
174
|
+
opts.on("--interval INTERVAL",
|
175
|
+
"The interval at which to run the specified command in",
|
176
|
+
"command mode in milliseconds (default: #{@interval})") do |v|
|
177
|
+
@interval = v.to_i
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def parse_type(opts)
|
182
|
+
widget_types = %w( timeseries stats hist )
|
183
|
+
widget_types_with_default = with_default(widget_types, @type)
|
184
|
+
opts.on("--type TYPE",
|
185
|
+
"The type of chart to render. ",
|
186
|
+
"Options are: #{widget_types_with_default.join(", ")}") do |v|
|
187
|
+
@type = v
|
188
|
+
end
|
189
|
+
|
190
|
+
widget_types.each do |type|
|
191
|
+
opts.on("--#{type}", "Shorthand for --type #{type}") do |_|
|
192
|
+
@type = type
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def parse_debug
|
198
|
+
if ARGV.delete("--debug") || ARGV.delete("-d")
|
199
|
+
@debug = true
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def with_default(opt_arr, default)
|
204
|
+
opt_arr.map do |opt|
|
205
|
+
opt == default ?
|
206
|
+
opt + " (default)" :
|
207
|
+
opt
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Termplot
|
2
|
+
module Producers
|
3
|
+
class BaseProducer
|
4
|
+
def initialize(options)
|
5
|
+
@options = options
|
6
|
+
@on_message_handler = -> {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def on_message(&block)
|
10
|
+
@on_message_handler = block
|
11
|
+
end
|
12
|
+
|
13
|
+
def run
|
14
|
+
raise "Must be implemented"
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
attr_reader :options, :on_message_handler
|
19
|
+
|
20
|
+
def produce(value)
|
21
|
+
if numeric?(value)
|
22
|
+
on_message_handler.call(value.to_f)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
FLOAT_REGEXP = /^[-+]?[0-9]*\.?[0-9]+$/
|
27
|
+
def numeric?(n)
|
28
|
+
n =~ FLOAT_REGEXP
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require "tempfile"
|
2
|
+
require "fileutils"
|
3
|
+
|
4
|
+
module Termplot
|
5
|
+
module Producers
|
6
|
+
class CommandProducer < BaseProducer
|
7
|
+
def run
|
8
|
+
temp_executable = make_executable(options.command)
|
9
|
+
loop do
|
10
|
+
n = `#{temp_executable.path}`.chomp
|
11
|
+
# TODO: Error handling...
|
12
|
+
|
13
|
+
produce(n)
|
14
|
+
|
15
|
+
# Interval is in ms
|
16
|
+
sleep(options.interval / 1000.0)
|
17
|
+
end
|
18
|
+
ensure
|
19
|
+
temp_executable.unlink
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
def make_executable(command)
|
24
|
+
file = Tempfile.new
|
25
|
+
file.write <<~COMMAND
|
26
|
+
#! #{ENV['SHELL']}
|
27
|
+
echo $(#{sanitize_command(command)})
|
28
|
+
COMMAND
|
29
|
+
file.close
|
30
|
+
|
31
|
+
FileUtils.chmod("a=xrw", file.path)
|
32
|
+
file
|
33
|
+
end
|
34
|
+
|
35
|
+
# TODO: Proper sanitization, probably using Shellwords?
|
36
|
+
def sanitize_command(command)
|
37
|
+
# command.gsub('"', "'")
|
38
|
+
command
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Termplot
|
2
|
+
module Renderable
|
3
|
+
# Included in any module that has the ivars:
|
4
|
+
# :window
|
5
|
+
# :errors
|
6
|
+
# :debug
|
7
|
+
# And methods:
|
8
|
+
# #render_tO_window
|
9
|
+
# Provides rendering to string and stdout
|
10
|
+
def render
|
11
|
+
rendered_string = render_to_string
|
12
|
+
if debug?
|
13
|
+
rendered_string.each do |row|
|
14
|
+
print row
|
15
|
+
end
|
16
|
+
else
|
17
|
+
print rendered_string
|
18
|
+
STDOUT.flush
|
19
|
+
end
|
20
|
+
|
21
|
+
if errors.any?
|
22
|
+
window.print_errors(errors)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def render_to_string
|
27
|
+
render_to_window
|
28
|
+
debug? ? window.flush_debug : window.flush
|
29
|
+
end
|
30
|
+
|
31
|
+
def debug?
|
32
|
+
@debug
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|