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
@@ -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