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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/CHANGELOG.md +7 -0
  4. data/README.md +116 -88
  5. data/Rakefile +1 -20
  6. data/bin/reset_cursor +11 -0
  7. data/bin/screen_test +68 -0
  8. data/examples/intro.rb +3 -3
  9. data/examples/scrolling.rb +5 -4
  10. data/lib/whirled_peas.rb +2 -4
  11. data/lib/whirled_peas/animator.rb +5 -0
  12. data/lib/whirled_peas/animator/debug_consumer.rb +17 -0
  13. data/lib/whirled_peas/animator/easing.rb +72 -0
  14. data/lib/whirled_peas/animator/frame.rb +5 -0
  15. data/lib/whirled_peas/animator/frameset.rb +33 -0
  16. data/lib/whirled_peas/animator/producer.rb +35 -0
  17. data/lib/whirled_peas/animator/renderer_consumer.rb +31 -0
  18. data/lib/whirled_peas/command.rb +5 -0
  19. data/lib/whirled_peas/command/base.rb +86 -0
  20. data/lib/whirled_peas/command/config_command.rb +44 -0
  21. data/lib/whirled_peas/command/debug.rb +21 -0
  22. data/lib/whirled_peas/command/fonts.rb +22 -0
  23. data/lib/whirled_peas/command/frame_command.rb +34 -0
  24. data/lib/whirled_peas/command/frames.rb +24 -0
  25. data/lib/whirled_peas/command/help.rb +38 -0
  26. data/lib/whirled_peas/command/play.rb +108 -0
  27. data/lib/whirled_peas/command/record.rb +57 -0
  28. data/lib/whirled_peas/command/still.rb +29 -0
  29. data/lib/whirled_peas/command_line.rb +22 -212
  30. data/lib/whirled_peas/config.rb +56 -6
  31. data/lib/whirled_peas/device.rb +5 -0
  32. data/lib/whirled_peas/device/null_device.rb +8 -0
  33. data/lib/whirled_peas/device/output_file.rb +19 -0
  34. data/lib/whirled_peas/device/screen.rb +26 -0
  35. data/lib/whirled_peas/graphics/container_painter.rb +91 -0
  36. data/lib/whirled_peas/graphics/painter.rb +10 -0
  37. data/lib/whirled_peas/graphics/renderer.rb +8 -2
  38. data/lib/whirled_peas/utils/ansi.rb +13 -0
  39. data/lib/whirled_peas/utils/file_handler.rb +57 -0
  40. data/lib/whirled_peas/version.rb +1 -1
  41. data/tools/whirled_peas/tools/screen_tester.rb +117 -65
  42. metadata +27 -8
  43. data/lib/whirled_peas/frame.rb +0 -6
  44. data/lib/whirled_peas/frame/consumer.rb +0 -30
  45. data/lib/whirled_peas/frame/debug_consumer.rb +0 -30
  46. data/lib/whirled_peas/frame/event_loop.rb +0 -90
  47. data/lib/whirled_peas/frame/producer.rb +0 -67
  48. 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 'json'
2
-
3
- require 'whirled_peas/graphics/debugger'
4
- require 'whirled_peas/graphics/renderer'
5
- require 'whirled_peas/graphics/screen'
6
- require 'whirled_peas/frame/debug_consumer'
7
- require 'whirled_peas/frame/event_loop'
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
- StartCommand,
211
- ListFramesCommand,
212
- PlayFrameCommand,
213
- LoadingCommand,
214
- TitleFontsCommand
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 "Available commands: #{COMMANDS.keys.join(', ')}"
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