whirled_peas 0.7.1 → 0.8.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 +4 -4
- data/.gitignore +1 -0
- data/CHANGELOG.md +7 -0
- data/README.md +116 -88
- data/Rakefile +1 -20
- data/bin/reset_cursor +11 -0
- data/bin/screen_test +68 -0
- data/examples/intro.rb +3 -3
- data/examples/scrolling.rb +5 -4
- data/lib/whirled_peas.rb +2 -4
- data/lib/whirled_peas/animator.rb +5 -0
- data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
- data/lib/whirled_peas/animator/easing.rb +72 -0
- data/lib/whirled_peas/animator/frame.rb +5 -0
- data/lib/whirled_peas/animator/frameset.rb +33 -0
- data/lib/whirled_peas/animator/producer.rb +35 -0
- data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
- data/lib/whirled_peas/command.rb +5 -0
- data/lib/whirled_peas/command/base.rb +86 -0
- data/lib/whirled_peas/command/config_command.rb +44 -0
- data/lib/whirled_peas/command/debug.rb +21 -0
- data/lib/whirled_peas/command/fonts.rb +22 -0
- data/lib/whirled_peas/command/frame_command.rb +34 -0
- data/lib/whirled_peas/command/frames.rb +24 -0
- data/lib/whirled_peas/command/help.rb +38 -0
- data/lib/whirled_peas/command/play.rb +108 -0
- data/lib/whirled_peas/command/record.rb +57 -0
- data/lib/whirled_peas/command/still.rb +29 -0
- data/lib/whirled_peas/command_line.rb +22 -212
- data/lib/whirled_peas/config.rb +56 -6
- data/lib/whirled_peas/device.rb +5 -0
- data/lib/whirled_peas/device/null_device.rb +8 -0
- data/lib/whirled_peas/device/output_file.rb +19 -0
- data/lib/whirled_peas/device/screen.rb +26 -0
- data/lib/whirled_peas/graphics/container_painter.rb +91 -0
- data/lib/whirled_peas/graphics/painter.rb +10 -0
- data/lib/whirled_peas/graphics/renderer.rb +8 -2
- data/lib/whirled_peas/utils/ansi.rb +13 -0
- data/lib/whirled_peas/utils/file_handler.rb +57 -0
- data/lib/whirled_peas/version.rb +1 -1
- data/tools/whirled_peas/tools/screen_tester.rb +117 -65
- metadata +27 -8
- data/lib/whirled_peas/frame.rb +0 -6
- data/lib/whirled_peas/frame/consumer.rb +0 -30
- data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
- data/lib/whirled_peas/frame/event_loop.rb +0 -90
- data/lib/whirled_peas/frame/producer.rb +0 -67
- data/lib/whirled_peas/graphics/screen.rb +0 -70
@@ -0,0 +1,24 @@
|
|
1
|
+
require_relative 'config_command'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Command
|
5
|
+
class Frames < ConfigCommand
|
6
|
+
def self.description
|
7
|
+
'Print out list of frames generated by application'
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
super
|
12
|
+
|
13
|
+
require 'whirled_peas/animator/debug_consumer'
|
14
|
+
require 'whirled_peas/animator/producer'
|
15
|
+
|
16
|
+
Animator::Producer.produce(
|
17
|
+
Animator::DebugConsumer.new, WhirledPeas.config.refresh_rate
|
18
|
+
) do |producer|
|
19
|
+
config.application.start(producer)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require_relative 'base'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Command
|
5
|
+
class Help < Base
|
6
|
+
def self.description
|
7
|
+
'Show detailed help for a command'
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
class_name = cmd.split('_').map(&:capitalize).join
|
12
|
+
klass = Command.const_get(class_name)
|
13
|
+
klass.print_usage
|
14
|
+
rescue NameError
|
15
|
+
puts "Unrecognized command: #{cmd}"
|
16
|
+
exit(1)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :cmd
|
22
|
+
|
23
|
+
def validate!
|
24
|
+
super
|
25
|
+
cmd = args.shift
|
26
|
+
if cmd.nil?
|
27
|
+
@error_text = "#{command_name} requires a command"
|
28
|
+
else
|
29
|
+
@cmd = cmd
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def options_usage
|
34
|
+
[*super, '<command>'].join(' ')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,108 @@
|
|
1
|
+
|
2
|
+
require_relative 'base'
|
3
|
+
|
4
|
+
module WhirledPeas
|
5
|
+
module Command
|
6
|
+
# Start the animation
|
7
|
+
class Play < Base
|
8
|
+
class NullPlayer
|
9
|
+
def play
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
class ApplicationPlayer
|
14
|
+
def initialize(app_config_file, config, logger)
|
15
|
+
@app_config_file = app_config_file
|
16
|
+
@config = config
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def play
|
21
|
+
require app_config_file
|
22
|
+
|
23
|
+
require 'whirled_peas/animator/renderer_consumer'
|
24
|
+
require 'whirled_peas/animator/producer'
|
25
|
+
require 'whirled_peas/device/screen'
|
26
|
+
require 'whirled_peas/utils/ansi'
|
27
|
+
|
28
|
+
Utils::Ansi.with_screen do |width, height|
|
29
|
+
consumer = Animator::RendererConsumer.new(
|
30
|
+
WhirledPeas.config.template_factory,
|
31
|
+
Device::Screen.new(WhirledPeas.config.refresh_rate),
|
32
|
+
width,
|
33
|
+
height
|
34
|
+
)
|
35
|
+
Animator::Producer.produce(
|
36
|
+
consumer, WhirledPeas.config.refresh_rate
|
37
|
+
) do |producer|
|
38
|
+
config.application.start(producer)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_reader :app_config_file, :config, :logger
|
46
|
+
end
|
47
|
+
|
48
|
+
class FilePlayer
|
49
|
+
def initialize(wpz_file)
|
50
|
+
@wpz_file = wpz_file
|
51
|
+
end
|
52
|
+
|
53
|
+
def play
|
54
|
+
require 'whirled_peas/device/screen'
|
55
|
+
require 'whirled_peas/utils/ansi'
|
56
|
+
require 'whirled_peas/utils/file_handler'
|
57
|
+
|
58
|
+
Utils::Ansi.with_screen do
|
59
|
+
screen = Device::Screen.new(WhirledPeas.config.refresh_rate)
|
60
|
+
renders = Utils::FileHandler.read(wpz_file)
|
61
|
+
screen.handle_renders(renders)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
attr_reader :wpz_file
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.description
|
71
|
+
'Play an animation from an application or prerecorded file'
|
72
|
+
end
|
73
|
+
|
74
|
+
def start
|
75
|
+
super
|
76
|
+
player.play
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
attr_reader :player
|
82
|
+
|
83
|
+
def validate!
|
84
|
+
super
|
85
|
+
@player = NullPlayer
|
86
|
+
file = args.shift
|
87
|
+
if file.nil?
|
88
|
+
@error_text = "#{command_name} requires an config file or frames file file"
|
89
|
+
elsif !File.exist?(file)
|
90
|
+
@error_text = "File not found: #{file}"
|
91
|
+
else
|
92
|
+
full_path_file = file[0] == '/' ? file : File.join(Dir.pwd, file)
|
93
|
+
if full_path_file.end_with?('.wpz')
|
94
|
+
@player = FilePlayer.new(full_path_file)
|
95
|
+
elsif full_path_file.end_with?('.rb')
|
96
|
+
@player = ApplicationPlayer.new(full_path_file, config, build_logger)
|
97
|
+
else
|
98
|
+
@error_text = "Unsupported file type: .#{file.split('.').last}, epxecting .rb or .wpz"
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def options_usage
|
104
|
+
'<config/wpz file>'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require_relative 'config_command'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Command
|
5
|
+
class Record < ConfigCommand
|
6
|
+
def self.description
|
7
|
+
'Record animation to a file'
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
super
|
12
|
+
require 'highline'
|
13
|
+
require 'whirled_peas/animator/renderer_consumer'
|
14
|
+
require 'whirled_peas/animator/producer'
|
15
|
+
require 'whirled_peas/device/output_file'
|
16
|
+
|
17
|
+
width, height = HighLine.new.terminal.terminal_size
|
18
|
+
consumer = Animator::RendererConsumer.new(
|
19
|
+
WhirledPeas.config.template_factory,
|
20
|
+
Device::OutputFile.new(out_file),
|
21
|
+
width,
|
22
|
+
height
|
23
|
+
)
|
24
|
+
Animator::Producer.produce(
|
25
|
+
consumer, WhirledPeas.config.refresh_rate
|
26
|
+
) do |producer|
|
27
|
+
config.application.start(producer)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :out_file
|
34
|
+
|
35
|
+
def validate!
|
36
|
+
super
|
37
|
+
return unless @error_text.nil?
|
38
|
+
|
39
|
+
out_file = args.shift
|
40
|
+
if out_file.nil?
|
41
|
+
@error_text = "#{command_name} requires an output file"
|
42
|
+
elsif !out_file.end_with?('.wpz')
|
43
|
+
if out_file.split('/').last =~ /\./
|
44
|
+
extra = ", found: .#{out_file.split('.').last}"
|
45
|
+
end
|
46
|
+
@error_text = "Expecting output file with .wpz extension#{extra}"
|
47
|
+
else
|
48
|
+
@out_file = out_file[0] == '/' ? out_file : File.join(Dir.pwd, out_file)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def options_usage
|
53
|
+
[*super, '<output file>'].join(' ')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative 'frame_command'
|
2
|
+
|
3
|
+
module WhirledPeas
|
4
|
+
module Command
|
5
|
+
# Display a still frame with the specified arguments.
|
6
|
+
class Still < FrameCommand
|
7
|
+
def self.description
|
8
|
+
'Show the specified still frame'
|
9
|
+
end
|
10
|
+
|
11
|
+
def start
|
12
|
+
super
|
13
|
+
|
14
|
+
require 'whirled_peas/device/screen'
|
15
|
+
require 'whirled_peas/graphics/renderer'
|
16
|
+
require 'whirled_peas/utils/ansi'
|
17
|
+
|
18
|
+
Utils::Ansi.with_screen do |width, height|
|
19
|
+
rendered = Graphics::Renderer.new(
|
20
|
+
WhirledPeas.config.template_factory.build(frame, frame_args),
|
21
|
+
width,
|
22
|
+
height
|
23
|
+
).paint
|
24
|
+
Device::Screen.new(10000).handle_renders([rendered])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,217 +1,21 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
require 'whirled_peas/
|
4
|
-
require 'whirled_peas/
|
5
|
-
require 'whirled_peas/
|
6
|
-
require 'whirled_peas/
|
7
|
-
require 'whirled_peas/
|
8
|
-
require 'whirled_peas/frame/producer'
|
1
|
+
require 'whirled_peas/command/debug'
|
2
|
+
require 'whirled_peas/command/fonts'
|
3
|
+
require 'whirled_peas/command/frames'
|
4
|
+
require 'whirled_peas/command/help'
|
5
|
+
require 'whirled_peas/command/play'
|
6
|
+
require 'whirled_peas/command/record'
|
7
|
+
require 'whirled_peas/command/still'
|
9
8
|
|
10
9
|
module WhirledPeas
|
11
|
-
class Command
|
12
|
-
DEFAULT_LOG_LEVEL = Logger::INFO
|
13
|
-
DEFAULT_FORMATTER = proc do |severity, datetime, progname, msg|
|
14
|
-
if msg.is_a?(Exception)
|
15
|
-
msg = %Q(#{msg.class}: #{msg.to_s}\n #{msg.backtrace.join("\n ")})
|
16
|
-
end
|
17
|
-
"[#{severity}] #{datetime.strftime('%Y-%m-%dT%H:%M:%S.%L')} (#{progname}) - #{msg}\n"
|
18
|
-
end
|
19
|
-
|
20
|
-
def self.command_name
|
21
|
-
self.name.split('::').last.sub(/Command$/, '').gsub(/([a-z])([A-Z])/, '\1_\2').downcase
|
22
|
-
end
|
23
|
-
|
24
|
-
def self.build_logger(output, level=DEFAULT_LOG_LEVEL, formatter=DEFAULT_FORMATTER)
|
25
|
-
logger = Logger.new(output)
|
26
|
-
logger.level = level
|
27
|
-
logger.formatter = formatter
|
28
|
-
logger
|
29
|
-
end
|
30
|
-
|
31
|
-
attr_reader :args
|
32
|
-
|
33
|
-
def initialize(args)
|
34
|
-
@args = args
|
35
|
-
end
|
36
|
-
|
37
|
-
def valid?
|
38
|
-
@error_text = nil
|
39
|
-
validate!
|
40
|
-
@error_text.nil?
|
41
|
-
end
|
42
|
-
|
43
|
-
def print_error
|
44
|
-
puts @error_text if @error_text
|
45
|
-
print_usage
|
46
|
-
end
|
47
|
-
|
48
|
-
def start
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
def print_usage
|
54
|
-
puts "Usage: #{$0} #{self.class.command_name}"
|
55
|
-
end
|
56
|
-
|
57
|
-
def validate!
|
58
|
-
# Set @error_text if the options are not valid
|
59
|
-
end
|
60
|
-
end
|
61
|
-
|
62
|
-
class TitleFontsCommand < Command
|
63
|
-
def start
|
64
|
-
require 'whirled_peas/utils/title_font'
|
65
|
-
|
66
|
-
Utils::TitleFont.fonts.keys.each do |key|
|
67
|
-
puts Utils::TitleFont.to_s(key.to_s, key)
|
68
|
-
puts key.inspect
|
69
|
-
puts
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
class ConfigCommand < Command
|
75
|
-
def start
|
76
|
-
require config
|
77
|
-
end
|
78
|
-
|
79
|
-
private
|
80
|
-
|
81
|
-
attr_reader :config
|
82
|
-
|
83
|
-
def validate!
|
84
|
-
if args.length == 0
|
85
|
-
@error_text = "#{self.class.command_name} requires a config file"
|
86
|
-
elsif !File.exist?(args[0])
|
87
|
-
@error_text = "File not found: #{args[0]}"
|
88
|
-
elsif args[0][-3..-1] != '.rb'
|
89
|
-
@error_text = 'Config file should be a .rb file'
|
90
|
-
else
|
91
|
-
@config = args[0][0] == '/' ? args[0] : File.join(Dir.pwd, args[0])
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def print_usage
|
96
|
-
puts "Usage: #{$0} #{self.class.command_name} <config file>"
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
|
-
class StartCommand < ConfigCommand
|
101
|
-
LOGGER_ID = 'MAIN'
|
102
|
-
|
103
|
-
def start
|
104
|
-
super
|
105
|
-
|
106
|
-
logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
|
107
|
-
|
108
|
-
consumer = Frame::EventLoop.new(
|
109
|
-
WhirledPeas.config.template_factory,
|
110
|
-
WhirledPeas.config.loading_template_factory,
|
111
|
-
logger: logger
|
112
|
-
)
|
113
|
-
Frame::Producer.produce(consumer, logger) do |producer|
|
114
|
-
begin
|
115
|
-
WhirledPeas.config.driver.start(producer)
|
116
|
-
rescue => e
|
117
|
-
logger.warn(LOGGER_ID) { 'Driver exited with error, terminating producer...' }
|
118
|
-
logger.error(LOGGER_ID) { e }
|
119
|
-
raise
|
120
|
-
end
|
121
|
-
end
|
122
|
-
end
|
123
|
-
end
|
124
|
-
|
125
|
-
class ListFramesCommand < ConfigCommand
|
126
|
-
def start
|
127
|
-
super
|
128
|
-
|
129
|
-
Frame::Producer.produce(Frame::DebugConsumer.new) do |producer|
|
130
|
-
WhirledPeas.config.driver.start(producer)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
class PlayFrameCommand < ConfigCommand
|
136
|
-
def start
|
137
|
-
super
|
138
|
-
|
139
|
-
if args.last == '--template'
|
140
|
-
template = WhirledPeas.config.template_factory.build(frame, frame_args)
|
141
|
-
puts Graphics::Debugger.new(template).debug
|
142
|
-
else
|
143
|
-
logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
|
144
|
-
consumer = Frame::EventLoop.new(
|
145
|
-
WhirledPeas.config.template_factory,
|
146
|
-
logger: logger
|
147
|
-
)
|
148
|
-
Frame::Producer.produce(consumer, logger) do |producer|
|
149
|
-
producer.send_frame(frame, args: frame_args)
|
150
|
-
end
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
|
-
private
|
155
|
-
|
156
|
-
attr_reader :frame, :frame_args
|
157
|
-
|
158
|
-
def validate!
|
159
|
-
super
|
160
|
-
if !@error_text.nil?
|
161
|
-
return
|
162
|
-
elsif args.length < 2
|
163
|
-
@error_text = "#{self.class.command_name} requires a frame name"
|
164
|
-
else
|
165
|
-
@frame = args[1]
|
166
|
-
@frame_args = {}
|
167
|
-
return if args.length < 3 || args[2][0..1] == '--'
|
168
|
-
JSON.parse(args[2] || '{}').each do |key, value|
|
169
|
-
@frame_args[key.to_sym] = value
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
def print_usage
|
175
|
-
puts "Usage: #{$0} #{self.class.command_name} <config file> <frame> [args as a JSON string] [--template]"
|
176
|
-
end
|
177
|
-
end
|
178
|
-
|
179
|
-
class LoadingCommand < ConfigCommand
|
180
|
-
def start
|
181
|
-
super
|
182
|
-
unless WhirledPeas.config.loading_template_factory
|
183
|
-
puts 'No loading screen configured'
|
184
|
-
exit(1)
|
185
|
-
end
|
186
|
-
|
187
|
-
if args.last == '--template'
|
188
|
-
template = WhirledPeas.config.loading_template_factory.build
|
189
|
-
puts Graphics::Debugger.new(template).debug
|
190
|
-
else
|
191
|
-
logger = self.class.build_logger(File.open('whirled_peas.log', 'a'))
|
192
|
-
consumer = Frame::EventLoop.new(
|
193
|
-
WhirledPeas.config.template_factory,
|
194
|
-
WhirledPeas.config.loading_template_factory,
|
195
|
-
logger: logger
|
196
|
-
)
|
197
|
-
Frame::Producer.produce(consumer, logger) { sleep(5) }
|
198
|
-
end
|
199
|
-
end
|
200
|
-
|
201
|
-
private
|
202
|
-
|
203
|
-
def print_usage
|
204
|
-
puts "Usage: #{$0} #{self.class.command_name} [--template]"
|
205
|
-
end
|
206
|
-
end
|
207
|
-
|
208
10
|
class CommandLine
|
209
11
|
COMMANDS = [
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
12
|
+
Command::Debug,
|
13
|
+
Command::Fonts,
|
14
|
+
Command::Frames,
|
15
|
+
Command::Help,
|
16
|
+
Command::Play,
|
17
|
+
Command::Record,
|
18
|
+
Command::Still
|
215
19
|
].map.with_object({}) { |c, h| h[c.command_name] = c }
|
216
20
|
|
217
21
|
def initialize(args)
|
@@ -232,7 +36,7 @@ module WhirledPeas
|
|
232
36
|
exit(1)
|
233
37
|
end
|
234
38
|
|
235
|
-
cmd = COMMANDS[command].new(args)
|
39
|
+
cmd = COMMANDS[command].new(args, WhirledPeas.config)
|
236
40
|
|
237
41
|
unless cmd.valid?
|
238
42
|
cmd.print_error
|
@@ -249,7 +53,13 @@ module WhirledPeas
|
|
249
53
|
def print_usage
|
250
54
|
puts "Usage: #{$0} <command> [command options]"
|
251
55
|
puts
|
252
|
-
puts
|
56
|
+
puts 'Available commands:'
|
57
|
+
puts
|
58
|
+
max_name_length = 0
|
59
|
+
COMMANDS.keys.each { |c| max_name_length = c.length if c.length > max_name_length }
|
60
|
+
COMMANDS.each do |name, klass|
|
61
|
+
puts " #{name.ljust(max_name_length, ' ')} #{klass.description}"
|
62
|
+
end
|
253
63
|
end
|
254
64
|
end
|
255
65
|
end
|