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
@@ -1,3 +1,3 @@
1
1
  module WhirledPeas
2
- VERSION = '0.7.1'
2
+ VERSION = '0.8.0'
3
3
  end
@@ -1,34 +1,53 @@
1
1
  require 'bundler/setup'
2
2
  require 'whirled_peas'
3
- require 'whirled_peas/frame/event_loop'
4
- require 'whirled_peas/frame/producer'
3
+ require 'whirled_peas/animator/producer'
4
+ require 'whirled_peas/animator/renderer_consumer'
5
+ require 'whirled_peas/device/screen'
5
6
  require 'whirled_peas/graphics/debugger'
6
7
  require 'whirled_peas/graphics/renderer'
7
- require 'whirled_peas/graphics/screen'
8
8
  require 'whirled_peas/settings/text_color'
9
+ require 'whirled_peas/utils/ansi'
9
10
  require 'whirled_peas/utils/formatted_string'
10
11
 
11
12
  module WhirledPeas
13
+ # Useful code for managing the repository that should not be distributed with the gem
12
14
  module Tools
13
- # levels_up = 0 returns the directory of the current file. If the current file is a
14
- # directory, then it will return a parent directory
15
+ # Return the parent directory N-levels up from the current file. If levels_up = 0 return
16
+ # the directory of the current file. E.g.
17
+ #
18
+ # parent_directory("/path/to/file.rb")
19
+ # # => "/path/to"
20
+ #
21
+ # parent_directory("really/long/path/to/another/file.rb", 3)
22
+ # # => "really/long"
23
+ #
15
24
  def self.parent_directory(file, levels_up=0)
16
25
  return File.dirname(file) if levels_up == 0
17
26
  parent_directory(File.dirname(file), levels_up - 1)
18
27
  end
19
28
 
20
29
  class ScreenTester
30
+ # The output of a rendered frame is depenend upon the width and height of the screen.
31
+ # Thus for the sake of reproduceability, the same width and height should be used for
32
+ # all runs of the test. The values should be relatively small to discourage testing
33
+ # complicated templates.
21
34
  SCREEN_WIDTH = 32
22
35
  SCREEN_HEIGHT = 24
23
36
 
37
+ # Path to screen tests relative to the base of this repository
38
+ SCREEN_TEST_DIR = 'screen_test'
39
+
40
+ # Return the base directory of this repository
24
41
  def self.base_dir
25
42
  Tools.parent_directory(__FILE__, 3)
26
43
  end
27
44
 
45
+ # Return all screen test files
28
46
  def self.test_files
29
- Dir.glob(File.join(base_dir, 'screen_test', '**', '*.rb'))
47
+ Dir.glob(File.join(base_dir, SCREEN_TEST_DIR, '**', '*.rb'))
30
48
  end
31
49
 
50
+ # Run all screen tests that exist in the screen test directory
32
51
  def self.run_all
33
52
  failures = {}
34
53
  pendings = Set.new
@@ -50,71 +69,135 @@ module WhirledPeas
50
69
  puts
51
70
  puts if pendings.count > 0
52
71
  pendings.each do |file|
53
- puts Utils::FormattedString.new("PENDING: #{file}", Settings::TextColor::YELLOW)
72
+ puts Utils::FormattedString.new(
73
+ "PENDING: #{file}",
74
+ Settings::TextColor::YELLOW
75
+ )
54
76
  end
55
77
  puts if failures.count > 0
56
78
  failures.each do |file, error|
57
- puts Utils::FormattedString.new("Failed: #{file}:\n\n #{error}\n", Settings::TextColor::RED)
79
+ puts Utils::FormattedString.new(
80
+ "Failed: #{file}:\n\n #{error}\n",
81
+ Settings::TextColor::RED
82
+ )
58
83
  end
59
84
 
60
85
  if failures.count == 0
61
86
  puts
62
- puts Utils::FormattedString.new('No failures', Settings::TextColor::GREEN)
87
+ puts Utils::FormattedString.new(
88
+ 'No failures',
89
+ Settings::TextColor::GREEN
90
+ )
91
+ exit(0)
63
92
  else
64
93
  exit(1)
65
94
  end
66
95
  end
67
96
 
68
- def self.update_all
97
+ # View rendered output for each pending test, giving the user the option to save the output
98
+ # as the expected output
99
+ def self.view_pending
100
+ num_attempted = 0
101
+ test_files.map do |file|
102
+ tester = new(file)
103
+ if tester.pending?
104
+ puts "File: #{tester.test_file}"
105
+ puts Utils::FormattedString.new(
106
+ 'Frame file does not exist', Settings::TextColor::YELLOW
107
+ )
108
+ print 'View rendered frame? [Y/n/q] '
109
+ STDOUT.flush
110
+ response = STDIN.gets.strip.downcase
111
+ case response
112
+ when 'q'
113
+ exit
114
+ when 'n'
115
+ return
116
+ end
117
+ tester.ask_pending
118
+ num_attempted += 1
119
+ end
120
+ end
121
+ if num_attempted == 0
122
+ puts 'No pending tests.'
123
+ end
124
+ end
125
+
126
+ # View expected and rendered output for each failed test, giving the user the option
127
+ # to overwrite the expected output with the rendered output
128
+ def self.view_failed
69
129
  num_attempted = 0
70
130
  test_files.map do |file|
71
- num_attempted += 1 if new(file).update(false)
131
+ tester = new(file)
132
+ if tester.failed?
133
+ puts "File: #{tester.test_file}"
134
+ puts Utils::FormattedString.new(
135
+ 'Output from test does not match saved output', Settings::TextColor::RED
136
+ )
137
+ print 'View expected output? [Y/q] '
138
+ STDOUT.flush
139
+ exit if 'q' == STDIN.gets.strip.downcase
140
+ tester.ask_failed
141
+ num_attempted += 1
142
+ end
72
143
  end
73
144
  if num_attempted == 0
74
145
  puts 'All rendered output matched expected.'
75
146
  end
76
147
  end
77
148
 
78
- attr_reader :error
149
+ attr_reader :error, :test_file
79
150
 
80
151
  def initialize(test_file)
152
+ # Convert the input file path to an absolute path (if necessary)
81
153
  @full_test_file = test_file[0] == '/' ? test_file : File.join(Dir.pwd, test_file)
82
154
  raise ArgumentError, "File not found: #{test_file}" unless File.exist?(@full_test_file)
155
+
156
+ # Remove the base directory from the full file path for a more readable file name
83
157
  @test_file = @full_test_file.sub(self.class.base_dir, '').sub(/^\//, '')
158
+
159
+ # The output file has the same base name as the test file, but ends in `.frame`
84
160
  @full_output_file = @full_test_file.sub(/\.rb$/, '.frame')
161
+
162
+ # Remove the base directory from the full file path for a more readable file name
85
163
  @output_file = @full_output_file.sub(self.class.base_dir, '').sub(/^\//, '')
86
164
  end
87
165
 
88
- def update(report_passing=true)
166
+ # Display the rendered output of the test and
167
+ #
168
+ # * If the expected ouptput matches the rendered output, simply display that output
169
+ # * If the expected ouptput does not match the rendered output, show the expect, then
170
+ # show the actual and give the user the option to overwrite expected with actual
171
+ # * If no expected output is saved for the test, give the user the option to save the
172
+ # rendered output as the expected output
173
+ def view
89
174
  if pending?
90
175
  ask_pending
91
- return true
92
176
  elsif failed?
93
177
  ask_failed
94
- return true
95
- elsif report_passing
96
- puts "Actual rendered output for #{test_file} matches expected."
178
+ else
179
+ puts rendered
180
+ puts "Rendered output for #{test_file} matches expected."
97
181
  end
98
- false
99
182
  end
100
183
 
184
+ # Return true if no output file exists for the test
101
185
  def pending?
102
186
  !File.exist?(full_output_file)
103
187
  end
104
188
 
189
+ # Return true if the rendered output does not match the expected outptu
105
190
  def failed?
191
+ return false if pending?
106
192
  if rendered != File.read(full_output_file)
107
- @error = 'Rendered output does not match saved output'
193
+ @error = 'Rendered output does not match expected.'
108
194
  end
109
195
  !@error.nil?
110
196
  end
111
197
 
112
- def view
113
- with_template_factory do |template_factory|
114
- render_screen(template_factory, STDOUT)
115
- end
116
- end
117
-
198
+ # Enable the Graphics debugger to print out debug information. Also render the template,
199
+ # but do not display it as rendering will clear the screen of debug output before the user
200
+ # can read it.
118
201
  def debug
119
202
  with_template_factory do |template_factory|
120
203
  orig_debug = Graphics.debug
@@ -124,6 +207,7 @@ module WhirledPeas
124
207
  end
125
208
  end
126
209
 
210
+ # Print out the template tree
127
211
  def template
128
212
  with_template_factory do |template_factory|
129
213
  template = template_factory.build('Test', {})
@@ -131,44 +215,13 @@ module WhirledPeas
131
215
  end
132
216
  end
133
217
 
134
- def save
135
- if File.exist?(full_output_file)
136
- puts "Existing output file found: #{full_output_file}"
137
- puts "overwriting..."
138
- else
139
- puts "Writing output to file: #{full_output_file}"
140
- end
141
-
142
- with_template_factory do |template_factory|
143
- File.open(full_output_file, 'w') do |file|
144
- render_screen(template_factory, file)
145
- end
146
- end
147
- end
148
-
149
218
  def ask_pending
150
- puts "File: #{test_file}"
151
- puts Utils::FormattedString.new('Frame file does not exist', Settings::TextColor::YELLOW)
152
- print 'View rendered frame? [Y/n/q] '
153
- STDOUT.flush
154
- response = STDIN.gets.strip.downcase
155
- case response
156
- when 'q'
157
- exit
158
- when 'n'
159
- return
160
- end
161
219
  puts rendered
162
220
  STDOUT.flush
163
221
  ask_to_save
164
222
  end
165
223
 
166
224
  def ask_failed
167
- puts "File: #{test_file}"
168
- puts Utils::FormattedString.new('Output from test does not match saved output', Settings::TextColor::RED)
169
- print 'View expected output? [Y/q] '
170
- STDOUT.flush
171
- exit if 'q' == STDIN.gets.strip.downcase
172
225
  puts File.read(full_output_file)
173
226
  puts "File: #{test_file}"
174
227
  print 'View actual output? [Y/q] '
@@ -181,15 +234,13 @@ module WhirledPeas
181
234
 
182
235
  private
183
236
 
184
- attr_reader :full_test_file, :test_file, :full_output_file, :output_file
237
+ attr_reader :full_test_file, :full_output_file, :output_file
185
238
 
186
239
  def ask_to_save
187
240
  puts "File: #{test_file}"
188
241
  print 'Save actual as the expected test output? [y/N/q] '
189
242
  STDOUT.flush
190
243
  response = STDIN.gets.strip.downcase
191
- print Utils::Ansi.cursor_pos(left: 0, top: 0)
192
- print Utils::Ansi.clear_down
193
244
  if response == 'y'
194
245
  File.open(full_output_file, 'w') { |file| file.print(rendered) }
195
246
  puts "Saved to #{output_file}"
@@ -220,12 +271,13 @@ module WhirledPeas
220
271
  end
221
272
 
222
273
  def render_screen(template_factory, output)
223
- screen = Graphics::Screen.new(output: output, width: SCREEN_WIDTH, height: SCREEN_HEIGHT)
224
- consumer = Frame::EventLoop.new(
225
- template_factory, screen: screen, refresh_rate: 100000
226
- )
227
- Frame::Producer.produce(consumer) do |producer|
228
- producer.send_frame('test', args: {})
274
+ Utils::Ansi.with_screen(output, width: SCREEN_WIDTH, height: SCREEN_HEIGHT) do
275
+ rendered = Graphics::Renderer.new(
276
+ template_factory.build('test', {}),
277
+ SCREEN_WIDTH,
278
+ SCREEN_HEIGHT
279
+ ).paint
280
+ Device::Screen.new(10000, output: output).handle_renders([rendered])
229
281
  end
230
282
  end
231
283
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: whirled_peas
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.1
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tom Collier
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-01-27 00:00:00.000000000 Z
11
+ date: 2021-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: highline
@@ -70,19 +70,38 @@ files:
70
70
  - README.md
71
71
  - Rakefile
72
72
  - bin/console
73
+ - bin/reset_cursor
74
+ - bin/screen_test
73
75
  - bin/setup
74
76
  - examples/intro.rb
75
77
  - examples/scrolling.rb
76
78
  - exe/whirled_peas
77
79
  - lib/whirled_peas.rb
80
+ - lib/whirled_peas/animator.rb
81
+ - lib/whirled_peas/animator/debug_consumer.rb
82
+ - lib/whirled_peas/animator/easing.rb
83
+ - lib/whirled_peas/animator/frame.rb
84
+ - lib/whirled_peas/animator/frameset.rb
85
+ - lib/whirled_peas/animator/producer.rb
86
+ - lib/whirled_peas/animator/renderer_consumer.rb
87
+ - lib/whirled_peas/command.rb
88
+ - lib/whirled_peas/command/base.rb
89
+ - lib/whirled_peas/command/config_command.rb
90
+ - lib/whirled_peas/command/debug.rb
91
+ - lib/whirled_peas/command/fonts.rb
92
+ - lib/whirled_peas/command/frame_command.rb
93
+ - lib/whirled_peas/command/frames.rb
94
+ - lib/whirled_peas/command/help.rb
95
+ - lib/whirled_peas/command/play.rb
96
+ - lib/whirled_peas/command/record.rb
97
+ - lib/whirled_peas/command/still.rb
78
98
  - lib/whirled_peas/command_line.rb
79
99
  - lib/whirled_peas/config.rb
100
+ - lib/whirled_peas/device.rb
101
+ - lib/whirled_peas/device/null_device.rb
102
+ - lib/whirled_peas/device/output_file.rb
103
+ - lib/whirled_peas/device/screen.rb
80
104
  - lib/whirled_peas/errors.rb
81
- - lib/whirled_peas/frame.rb
82
- - lib/whirled_peas/frame/consumer.rb
83
- - lib/whirled_peas/frame/debug_consumer.rb
84
- - lib/whirled_peas/frame/event_loop.rb
85
- - lib/whirled_peas/frame/producer.rb
86
105
  - lib/whirled_peas/graphics.rb
87
106
  - lib/whirled_peas/graphics/box_painter.rb
88
107
  - lib/whirled_peas/graphics/canvas.rb
@@ -95,7 +114,6 @@ files:
95
114
  - lib/whirled_peas/graphics/mock_screen.rb
96
115
  - lib/whirled_peas/graphics/painter.rb
97
116
  - lib/whirled_peas/graphics/renderer.rb
98
- - lib/whirled_peas/graphics/screen.rb
99
117
  - lib/whirled_peas/graphics/text_dimensions.rb
100
118
  - lib/whirled_peas/graphics/text_painter.rb
101
119
  - lib/whirled_peas/null_logger.rb
@@ -121,6 +139,7 @@ files:
121
139
  - lib/whirled_peas/settings/vert_alignment.rb
122
140
  - lib/whirled_peas/utils.rb
123
141
  - lib/whirled_peas/utils/ansi.rb
142
+ - lib/whirled_peas/utils/file_handler.rb
124
143
  - lib/whirled_peas/utils/formatted_string.rb
125
144
  - lib/whirled_peas/utils/title_font.rb
126
145
  - lib/whirled_peas/version.rb
@@ -1,6 +0,0 @@
1
- module WhirledPeas
2
- module Frame
3
- end
4
-
5
- private_constant :Frame
6
- end
@@ -1,30 +0,0 @@
1
- module WhirledPeas
2
- module Frame
3
- # Abstract class for consuming frame events.
4
- class Consumer
5
- EOF = '__EOF__'
6
-
7
- def enqueue(name, duration, args)
8
- raise NotImplemented, "#{self.class} must implement #enqueue"
9
- end
10
-
11
- def running?
12
- @running == true
13
- end
14
-
15
- def start
16
- self.running = true
17
- end
18
-
19
- def stop
20
- enqueue(EOF, nil, {})
21
- end
22
-
23
- private
24
-
25
- attr_writer :running
26
- end
27
-
28
- private_constant :Consumer
29
- end
30
- end
@@ -1,30 +0,0 @@
1
- require 'whirled_peas/null_logger'
2
-
3
- require_relative 'consumer'
4
-
5
- module WhirledPeas
6
- module Frame
7
- class DebugConsumer < Consumer
8
- LOGGER_ID = 'PRINTER'
9
-
10
- def initialize(output=STDOUT, logger=NullLogger.new)
11
- @output = output
12
- @logger = logger
13
- end
14
-
15
- def enqueue(name, duration, args)
16
- if name == EOF
17
- output.puts "EOF frame detected"
18
- else
19
- displayed_for = duration ? "#{duration} second(s)" : '1 frame'
20
- args_str = args.empty? ? '' : " '#{JSON.generate(args)}'"
21
- output.puts "Frame '#{name}' displayed for #{displayed_for}#{args_str}"
22
- end
23
- end
24
-
25
- private
26
-
27
- attr_reader :output, :logger
28
- end
29
- end
30
- end