whirled_peas 0.7.1 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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