whirled_peas 0.4.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/.travis.yml +1 -0
- data/CHANGELOG.md +37 -0
- data/README.md +529 -156
- data/Rakefile +9 -3
- data/bin/reset_cursor +11 -0
- data/bin/screen_test +68 -0
- data/examples/intro.rb +52 -0
- data/examples/scrolling.rb +54 -0
- data/lib/whirled_peas.rb +6 -12
- 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 +23 -228
- 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/errors.rb +2 -0
- data/lib/whirled_peas/graphics.rb +19 -0
- data/lib/whirled_peas/graphics/box_painter.rb +101 -0
- data/lib/whirled_peas/graphics/canvas.rb +118 -0
- data/lib/whirled_peas/graphics/composer.rb +80 -0
- data/lib/whirled_peas/graphics/container_coords.rb +72 -0
- data/lib/whirled_peas/graphics/container_dimensions.rb +93 -0
- data/lib/whirled_peas/graphics/container_painter.rb +363 -0
- data/lib/whirled_peas/graphics/debugger.rb +52 -0
- data/lib/whirled_peas/graphics/grid_painter.rb +69 -0
- data/lib/whirled_peas/graphics/mock_screen.rb +26 -0
- data/lib/whirled_peas/graphics/painter.rb +33 -0
- data/lib/whirled_peas/graphics/renderer.rb +32 -0
- data/lib/whirled_peas/graphics/text_dimensions.rb +15 -0
- data/lib/whirled_peas/graphics/text_painter.rb +40 -0
- data/lib/whirled_peas/settings.rb +5 -0
- data/lib/whirled_peas/settings/alignment.rb +24 -0
- data/lib/whirled_peas/settings/bg_color.rb +24 -0
- data/lib/whirled_peas/settings/border.rb +101 -0
- data/lib/whirled_peas/settings/box_settings.rb +8 -0
- data/lib/whirled_peas/settings/color.rb +68 -0
- data/lib/whirled_peas/settings/container_settings.rb +223 -0
- data/lib/whirled_peas/settings/debugger.rb +96 -0
- data/lib/whirled_peas/settings/display_flow.rb +27 -0
- data/lib/whirled_peas/settings/element_settings.rb +61 -0
- data/lib/whirled_peas/settings/grid_settings.rb +19 -0
- data/lib/whirled_peas/settings/margin.rb +8 -0
- data/lib/whirled_peas/settings/padding.rb +8 -0
- data/lib/whirled_peas/settings/position.rb +15 -0
- data/lib/whirled_peas/settings/scrollbar.rb +15 -0
- data/lib/whirled_peas/settings/sizing.rb +19 -0
- data/lib/whirled_peas/settings/spacing.rb +58 -0
- data/lib/whirled_peas/settings/text_color.rb +21 -0
- data/lib/whirled_peas/settings/text_settings.rb +15 -0
- data/lib/whirled_peas/settings/vert_alignment.rb +24 -0
- data/lib/whirled_peas/utils/ansi.rb +19 -56
- data/lib/whirled_peas/utils/file_handler.rb +57 -0
- data/lib/whirled_peas/utils/formatted_string.rb +64 -0
- data/lib/whirled_peas/utils/title_font.rb +1 -1
- data/lib/whirled_peas/version.rb +1 -1
- data/screen_test/elements/box.frame +1 -0
- data/screen_test/elements/box.rb +20 -0
- data/screen_test/elements/grid.frame +1 -0
- data/screen_test/elements/grid.rb +13 -0
- data/screen_test/elements/screen_overflow_x.frame +1 -0
- data/screen_test/elements/screen_overflow_x.rb +9 -0
- data/screen_test/elements/screen_overflow_y.frame +1 -0
- data/screen_test/elements/screen_overflow_y.rb +9 -0
- data/screen_test/elements/text.frame +1 -0
- data/screen_test/elements/text.rb +9 -0
- data/screen_test/elements/text_multiline.frame +1 -0
- data/screen_test/elements/text_multiline.rb +9 -0
- data/screen_test/settings/align/box_around.frame +1 -0
- data/screen_test/settings/align/box_around.rb +16 -0
- data/screen_test/settings/align/box_between.frame +1 -0
- data/screen_test/settings/align/box_between.rb +16 -0
- data/screen_test/settings/align/box_center.frame +1 -0
- data/screen_test/settings/align/box_center.rb +21 -0
- data/screen_test/settings/align/box_default.frame +1 -0
- data/screen_test/settings/align/box_default.rb +20 -0
- data/screen_test/settings/align/box_evenly.frame +1 -0
- data/screen_test/settings/align/box_evenly.rb +16 -0
- data/screen_test/settings/align/box_left.frame +1 -0
- data/screen_test/settings/align/box_left.rb +21 -0
- data/screen_test/settings/align/box_right.frame +1 -0
- data/screen_test/settings/align/box_right.rb +21 -0
- data/screen_test/settings/align/children_center.frame +1 -0
- data/screen_test/settings/align/children_center.rb +15 -0
- data/screen_test/settings/align/children_left.frame +1 -0
- data/screen_test/settings/align/children_left.rb +15 -0
- data/screen_test/settings/align/children_right.frame +1 -0
- data/screen_test/settings/align/children_right.rb +15 -0
- data/screen_test/settings/align/grid_center.frame +1 -0
- data/screen_test/settings/align/grid_center.rb +18 -0
- data/screen_test/settings/align/grid_default.frame +1 -0
- data/screen_test/settings/align/grid_default.rb +17 -0
- data/screen_test/settings/align/grid_left.frame +1 -0
- data/screen_test/settings/align/grid_left.rb +18 -0
- data/screen_test/settings/align/grid_right.frame +1 -0
- data/screen_test/settings/align/grid_right.rb +18 -0
- data/screen_test/settings/ansi/bold.frame +1 -0
- data/screen_test/settings/ansi/bold.rb +14 -0
- data/screen_test/settings/ansi/color.frame +1 -0
- data/screen_test/settings/ansi/color.rb +37 -0
- data/screen_test/settings/ansi/underline.frame +1 -0
- data/screen_test/settings/ansi/underline.rb +14 -0
- data/screen_test/settings/border.frame +1 -0
- data/screen_test/settings/border.rb +13 -0
- data/screen_test/settings/flow/box_b2t.frame +1 -0
- data/screen_test/settings/flow/box_b2t.rb +26 -0
- data/screen_test/settings/flow/box_l2r.frame +1 -0
- data/screen_test/settings/flow/box_l2r.rb +26 -0
- data/screen_test/settings/flow/box_r2l.frame +1 -0
- data/screen_test/settings/flow/box_r2l.rb +26 -0
- data/screen_test/settings/flow/box_t2b.frame +1 -0
- data/screen_test/settings/flow/box_t2b.rb +26 -0
- data/screen_test/settings/flow/grid_b2t.frame +1 -0
- data/screen_test/settings/flow/grid_b2t.rb +14 -0
- data/screen_test/settings/flow/grid_l2r.frame +1 -0
- data/screen_test/settings/flow/grid_l2r.rb +14 -0
- data/screen_test/settings/flow/grid_r2l.frame +1 -0
- data/screen_test/settings/flow/grid_r2l.rb +14 -0
- data/screen_test/settings/flow/grid_t2b.frame +1 -0
- data/screen_test/settings/flow/grid_t2b.rb +14 -0
- data/screen_test/settings/height/box.frame +1 -0
- data/screen_test/settings/height/box.rb +13 -0
- data/screen_test/settings/height/box_border_sizing.frame +1 -0
- data/screen_test/settings/height/box_border_sizing.rb +15 -0
- data/screen_test/settings/height/grid.frame +1 -0
- data/screen_test/settings/height/grid.rb +14 -0
- data/screen_test/settings/height/overflow_box.frame +1 -0
- data/screen_test/settings/height/overflow_box.rb +13 -0
- data/screen_test/settings/height/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/height/overflow_box_l2r.rb +17 -0
- data/screen_test/settings/height/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/height/overflow_box_t2b.rb +16 -0
- data/screen_test/settings/height/overflow_grid.frame +1 -0
- data/screen_test/settings/height/overflow_grid.rb +16 -0
- data/screen_test/settings/margin.frame +1 -0
- data/screen_test/settings/margin.rb +16 -0
- data/screen_test/settings/padding.frame +1 -0
- data/screen_test/settings/padding.rb +13 -0
- data/screen_test/settings/position/box_left.frame +1 -0
- data/screen_test/settings/position/box_left.rb +17 -0
- data/screen_test/settings/position/box_left_negative.frame +1 -0
- data/screen_test/settings/position/box_left_negative.rb +17 -0
- data/screen_test/settings/position/box_top.frame +1 -0
- data/screen_test/settings/position/box_top.rb +17 -0
- data/screen_test/settings/position/box_top_negative.frame +1 -0
- data/screen_test/settings/position/box_top_negative.rb +17 -0
- data/screen_test/settings/position/grid_left.frame +1 -0
- data/screen_test/settings/position/grid_left.rb +18 -0
- data/screen_test/settings/position/grid_left_negative.frame +1 -0
- data/screen_test/settings/position/grid_left_negative.rb +18 -0
- data/screen_test/settings/position/grid_top.frame +1 -0
- data/screen_test/settings/position/grid_top.rb +18 -0
- data/screen_test/settings/position/grid_top_negative.frame +1 -0
- data/screen_test/settings/position/grid_top_negative.rb +18 -0
- data/screen_test/settings/scroll/horiz_box.frame +1 -0
- data/screen_test/settings/scroll/horiz_box.rb +17 -0
- data/screen_test/settings/scroll/horiz_box_align_center.rb +18 -0
- data/screen_test/settings/scroll/horiz_box_align_right.rb +18 -0
- data/screen_test/settings/scroll/vert_box.frame +1 -0
- data/screen_test/settings/scroll/vert_box.rb +20 -0
- data/screen_test/settings/title_font.frame +1 -0
- data/screen_test/settings/title_font.rb +12 -0
- data/screen_test/settings/valign/box_around.frame +1 -0
- data/screen_test/settings/valign/box_around.rb +17 -0
- data/screen_test/settings/valign/box_between.frame +1 -0
- data/screen_test/settings/valign/box_between.rb +17 -0
- data/screen_test/settings/valign/box_bottom.frame +1 -0
- data/screen_test/settings/valign/box_bottom.rb +17 -0
- data/screen_test/settings/valign/box_default.frame +1 -0
- data/screen_test/settings/valign/box_default.rb +16 -0
- data/screen_test/settings/valign/box_evenly.frame +1 -0
- data/screen_test/settings/valign/box_evenly.rb +17 -0
- data/screen_test/settings/valign/box_middle.frame +1 -0
- data/screen_test/settings/valign/box_middle.rb +17 -0
- data/screen_test/settings/valign/box_top.frame +1 -0
- data/screen_test/settings/valign/box_top.rb +17 -0
- data/screen_test/settings/valign/grid_bottom.frame +1 -0
- data/screen_test/settings/valign/grid_bottom.rb +15 -0
- data/screen_test/settings/valign/grid_default.frame +1 -0
- data/screen_test/settings/valign/grid_default.rb +14 -0
- data/screen_test/settings/valign/grid_middle.frame +1 -0
- data/screen_test/settings/valign/grid_middle.rb +15 -0
- data/screen_test/settings/valign/grid_top.frame +1 -0
- data/screen_test/settings/valign/grid_top.rb +15 -0
- data/screen_test/settings/width/box_border_sizing.frame +1 -0
- data/screen_test/settings/width/box_border_sizing.rb +15 -0
- data/screen_test/settings/width/box_content.frame +1 -0
- data/screen_test/settings/width/box_content.rb +15 -0
- data/screen_test/settings/width/box_default.frame +1 -0
- data/screen_test/settings/width/box_default.rb +14 -0
- data/screen_test/settings/width/grid.frame +1 -0
- data/screen_test/settings/width/grid.rb +14 -0
- data/screen_test/settings/width/overflow_align_center.frame +1 -0
- data/screen_test/settings/width/overflow_align_center.rb +14 -0
- data/screen_test/settings/width/overflow_align_right.frame +1 -0
- data/screen_test/settings/width/overflow_align_right.rb +14 -0
- data/screen_test/settings/width/overflow_box.frame +1 -0
- data/screen_test/settings/width/overflow_box.rb +13 -0
- data/screen_test/settings/width/overflow_box_l2r.frame +1 -0
- data/screen_test/settings/width/overflow_box_l2r.rb +16 -0
- data/screen_test/settings/width/overflow_box_t2b.frame +1 -0
- data/screen_test/settings/width/overflow_box_t2b.rb +17 -0
- data/screen_test/settings/width/overflow_grid.frame +1 -0
- data/screen_test/settings/width/overflow_grid.rb +14 -0
- data/tools/whirled_peas/tools/screen_tester.rb +285 -0
- metadata +213 -15
- data/bin/title_fonts +0 -6
- data/lib/whirled_peas/frame.rb +0 -7
- data/lib/whirled_peas/frame/event_loop.rb +0 -91
- data/lib/whirled_peas/frame/print_consumer.rb +0 -33
- data/lib/whirled_peas/frame/producer.rb +0 -61
- data/lib/whirled_peas/template.rb +0 -5
- data/lib/whirled_peas/template/element.rb +0 -230
- data/lib/whirled_peas/template/settings.rb +0 -530
- data/lib/whirled_peas/ui.rb +0 -5
- data/lib/whirled_peas/ui/canvas.rb +0 -68
- data/lib/whirled_peas/ui/painter.rb +0 -287
- data/lib/whirled_peas/ui/screen.rb +0 -63
- data/lib/whirled_peas/utils/color.rb +0 -101
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━┓[2;1H┃ ┃[3;1H┃ ┃[4;1H┃ ┃[5;1H┗━┛[2;2HA[0m[24;1H[?25h
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_grid('Alignment') do |composer, settings|
         | 
| 7 | 
            +
                    settings.num_cols = 1
         | 
| 8 | 
            +
                    settings.full_border
         | 
| 9 | 
            +
                    settings.height = 3
         | 
| 10 | 
            +
                    settings.valign = :top
         | 
| 11 | 
            +
                    composer.add_text { 'A' }
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━━━━┓[2;1H┃             ┃[3;1H┗━━━━━━━━━━━━━┛[2;5H15/bor[0m[24;1H[?25h
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box('Wide') do |_, settings|
         | 
| 7 | 
            +
                    settings.full_border
         | 
| 8 | 
            +
                    settings.set_padding(left: 3, right: 4)
         | 
| 9 | 
            +
                    settings.width = 15
         | 
| 10 | 
            +
                    settings.sizing = :border
         | 
| 11 | 
            +
                    '15/border'
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━━━━━━━━━━━━━┓[2;1H┃                      ┃[3;1H┗━━━━━━━━━━━━━━━━━━━━━━┛[2;5H15/content[0m[24;1H[?25h
         | 
| @@ -0,0 +1,15 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box('Wide') do |_, settings|
         | 
| 7 | 
            +
                    settings.full_border
         | 
| 8 | 
            +
                    settings.set_padding(left: 3, right: 4)
         | 
| 9 | 
            +
                    settings.width = 15
         | 
| 10 | 
            +
                    settings.sizing = :content
         | 
| 11 | 
            +
                    '15/content'
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
              end
         | 
| 15 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━━━━━━━━━━━━━┓[2;1H┃                      ┃[3;1H┗━━━━━━━━━━━━━━━━━━━━━━┛[2;5H15/default[0m[24;1H[?25h
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box('Wide') do |_, settings|
         | 
| 7 | 
            +
                    settings.full_border
         | 
| 8 | 
            +
                    settings.set_padding(left: 3, right: 4)
         | 
| 9 | 
            +
                    settings.width = 15
         | 
| 10 | 
            +
                    '15/default'
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━┳━━━━━━┓[2;1H┃      ┃      ┃[3;1H┣━━━━━━╋━━━━━━┫[4;1H┃      ┃      ┃[5;1H┣━━━━━━╋━━━━━━┫[6;1H┃      ┃      ┃[7;1H┗━━━━━━┻━━━━━━┛[2;2H6[2;9H6[4;2H6[4;9H6[6;2H6[6;9H6[0m[24;1H[?25h
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_grid('Wide') do |_, settings|
         | 
| 7 | 
            +
                    settings.full_border
         | 
| 8 | 
            +
                    settings.width = 6
         | 
| 9 | 
            +
                    settings.num_cols = 2
         | 
| 10 | 
            +
                    6.times.map { '6' }
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━┓[2;1H┃   ┃[3;1H┗━━━┛[2;2H345[0m[24;1H[?25h
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box('Container') do |composer, settings|
         | 
| 7 | 
            +
                    settings.width = 3
         | 
| 8 | 
            +
                    settings.align = :center
         | 
| 9 | 
            +
                    settings.full_border
         | 
| 10 | 
            +
                    '123456'
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━┓[2;1H┃   ┃[3;1H┗━━━┛[2;2H456[0m[24;1H[?25h
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box do |composer, settings|
         | 
| 7 | 
            +
                    settings.width = 3
         | 
| 8 | 
            +
                    settings.align = :right
         | 
| 9 | 
            +
                    settings.full_border
         | 
| 10 | 
            +
                    '123456'
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━┓[2;1H┃          ┃[3;1H┗━━━━━━━━━━┛[2;2HABCDEFGHIJ[0m[24;1H[?25h
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━┓[2;1H┃          ┃[3;1H┗━━━━━━━━━━┛[2;2H0|[2;4H0|1|[2;8H0|1|[0m[24;1H[?25h
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box do |composer, settings|
         | 
| 7 | 
            +
                    settings.width = 10
         | 
| 8 | 
            +
                    settings.full_border
         | 
| 9 | 
            +
                    str = ''
         | 
| 10 | 
            +
                    10.times do |i|
         | 
| 11 | 
            +
                      composer.add_text { str += "#{i}|" }
         | 
| 12 | 
            +
                    end
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━┓[2;1H┃          ┃[3;1H┃          ┃[4;1H┃          ┃[5;1H┃          ┃[6;1H┃          ┃[7;1H┃          ┃[8;1H┃          ┃[9;1H┃          ┃[10;1H┃          ┃[11;1H┃          ┃[12;1H┗━━━━━━━━━━┛[2;2HXO[3;2HXOXO[4;2HXOXOXO[5;2HXOXOXOXO[6;2HXOXOXOXOXO[7;2HXOXOXOXOXO[8;2HXOXOXOXOXO[9;2HXOXOXOXOXO[10;2HXOXOXOXOXO[11;2HXOXOXOXOXO[0m[24;1H[?25h
         | 
| @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer|
         | 
| 6 | 
            +
                  composer.add_box do |composer, settings|
         | 
| 7 | 
            +
                    settings.width = 10
         | 
| 8 | 
            +
                    settings.flow = :t2b
         | 
| 9 | 
            +
                    settings.full_border
         | 
| 10 | 
            +
                    str = ''
         | 
| 11 | 
            +
                    10.times do
         | 
| 12 | 
            +
                      composer.add_text { str += 'XO' }
         | 
| 13 | 
            +
                    end
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
              end
         | 
| 17 | 
            +
            end
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            [?25l[1;1H[J[1;1H┏━━━━━━━━━━┓[2;1H┃          ┃[3;1H┗━━━━━━━━━━┛[2;2HABCDEFGHIJ[0m[24;1H[?25h
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            require 'whirled_peas'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            class TemplateFactory
         | 
| 4 | 
            +
              def build(*)
         | 
| 5 | 
            +
                WhirledPeas.template do |composer, settings|
         | 
| 6 | 
            +
                  composer.add_grid('grid') do |_, settings|
         | 
| 7 | 
            +
                    settings.width = 10
         | 
| 8 | 
            +
                    settings.full_border
         | 
| 9 | 
            +
                    settings.num_cols = 1
         | 
| 10 | 
            +
                    ["ABCDEFGHIJKLMNOPQRSTUVWZYZ"]
         | 
| 11 | 
            +
                  end
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
            end
         | 
| @@ -0,0 +1,285 @@ | |
| 1 | 
            +
            require 'bundler/setup'
         | 
| 2 | 
            +
            require 'whirled_peas'
         | 
| 3 | 
            +
            require 'whirled_peas/animator/producer'
         | 
| 4 | 
            +
            require 'whirled_peas/animator/renderer_consumer'
         | 
| 5 | 
            +
            require 'whirled_peas/device/screen'
         | 
| 6 | 
            +
            require 'whirled_peas/graphics/debugger'
         | 
| 7 | 
            +
            require 'whirled_peas/graphics/renderer'
         | 
| 8 | 
            +
            require 'whirled_peas/settings/text_color'
         | 
| 9 | 
            +
            require 'whirled_peas/utils/ansi'
         | 
| 10 | 
            +
            require 'whirled_peas/utils/formatted_string'
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            module WhirledPeas
         | 
| 13 | 
            +
              # Useful code for managing the repository that should not be distributed with the gem
         | 
| 14 | 
            +
              module Tools
         | 
| 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 | 
            +
                #
         | 
| 24 | 
            +
                def self.parent_directory(file, levels_up=0)
         | 
| 25 | 
            +
                  return File.dirname(file) if levels_up == 0
         | 
| 26 | 
            +
                  parent_directory(File.dirname(file), levels_up - 1)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 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.
         | 
| 34 | 
            +
                  SCREEN_WIDTH = 32
         | 
| 35 | 
            +
                  SCREEN_HEIGHT = 24
         | 
| 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
         | 
| 41 | 
            +
                  def self.base_dir
         | 
| 42 | 
            +
                    Tools.parent_directory(__FILE__, 3)
         | 
| 43 | 
            +
                  end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  # Return all screen test files
         | 
| 46 | 
            +
                  def self.test_files
         | 
| 47 | 
            +
                    Dir.glob(File.join(base_dir, SCREEN_TEST_DIR, '**', '*.rb'))
         | 
| 48 | 
            +
                  end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                  # Run all screen tests that exist in the screen test directory
         | 
| 51 | 
            +
                  def self.run_all
         | 
| 52 | 
            +
                    failures = {}
         | 
| 53 | 
            +
                    pendings = Set.new
         | 
| 54 | 
            +
                    puts "Running #{test_files.length} rendered screen test(s)"
         | 
| 55 | 
            +
                    puts
         | 
| 56 | 
            +
                    test_files.each do |file|
         | 
| 57 | 
            +
                      tester = new(file)
         | 
| 58 | 
            +
                      if tester.pending?
         | 
| 59 | 
            +
                        print Utils::FormattedString.new('.', Settings::TextColor::YELLOW)
         | 
| 60 | 
            +
                        pendings << file
         | 
| 61 | 
            +
                      elsif tester.failed?
         | 
| 62 | 
            +
                        print Utils::FormattedString.new('.', Settings::TextColor::RED)
         | 
| 63 | 
            +
                        failures[file] = tester.error
         | 
| 64 | 
            +
                      else
         | 
| 65 | 
            +
                        print Utils::FormattedString.new('.', Settings::TextColor::GREEN)
         | 
| 66 | 
            +
                      end
         | 
| 67 | 
            +
                      STDOUT.flush
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                    puts
         | 
| 70 | 
            +
                    puts if pendings.count > 0
         | 
| 71 | 
            +
                    pendings.each do |file|
         | 
| 72 | 
            +
                      puts Utils::FormattedString.new(
         | 
| 73 | 
            +
                        "PENDING: #{file}",
         | 
| 74 | 
            +
                        Settings::TextColor::YELLOW
         | 
| 75 | 
            +
                        )
         | 
| 76 | 
            +
                    end
         | 
| 77 | 
            +
                    puts if failures.count > 0
         | 
| 78 | 
            +
                    failures.each do |file, error|
         | 
| 79 | 
            +
                      puts Utils::FormattedString.new(
         | 
| 80 | 
            +
                        "Failed: #{file}:\n\n    #{error}\n",
         | 
| 81 | 
            +
                        Settings::TextColor::RED
         | 
| 82 | 
            +
                      )
         | 
| 83 | 
            +
                    end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                    if failures.count == 0
         | 
| 86 | 
            +
                      puts
         | 
| 87 | 
            +
                      puts Utils::FormattedString.new(
         | 
| 88 | 
            +
                        'No failures',
         | 
| 89 | 
            +
                        Settings::TextColor::GREEN
         | 
| 90 | 
            +
                      )
         | 
| 91 | 
            +
                      exit(0)
         | 
| 92 | 
            +
                    else
         | 
| 93 | 
            +
                      exit(1)
         | 
| 94 | 
            +
                    end
         | 
| 95 | 
            +
                  end
         | 
| 96 | 
            +
             | 
| 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
         | 
| 129 | 
            +
                    num_attempted = 0
         | 
| 130 | 
            +
                    test_files.map do |file|
         | 
| 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
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
                    if num_attempted == 0
         | 
| 145 | 
            +
                      puts 'All rendered output matched expected.'
         | 
| 146 | 
            +
                    end
         | 
| 147 | 
            +
                  end
         | 
| 148 | 
            +
             | 
| 149 | 
            +
                  attr_reader :error, :test_file
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                  def initialize(test_file)
         | 
| 152 | 
            +
                    # Convert the input file path to an absolute path (if necessary)
         | 
| 153 | 
            +
                    @full_test_file = test_file[0] == '/' ? test_file : File.join(Dir.pwd, test_file)
         | 
| 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
         | 
| 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`
         | 
| 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
         | 
| 163 | 
            +
                    @output_file = @full_output_file.sub(self.class.base_dir, '').sub(/^\//, '')
         | 
| 164 | 
            +
                  end
         | 
| 165 | 
            +
             | 
| 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
         | 
| 174 | 
            +
                    if pending?
         | 
| 175 | 
            +
                      ask_pending
         | 
| 176 | 
            +
                    elsif failed?
         | 
| 177 | 
            +
                      ask_failed
         | 
| 178 | 
            +
                    else
         | 
| 179 | 
            +
                      puts rendered
         | 
| 180 | 
            +
                      puts "Rendered output for #{test_file} matches expected."
         | 
| 181 | 
            +
                    end
         | 
| 182 | 
            +
                  end
         | 
| 183 | 
            +
             | 
| 184 | 
            +
                  # Return true if no output file exists for the test
         | 
| 185 | 
            +
                  def pending?
         | 
| 186 | 
            +
                    !File.exist?(full_output_file)
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
             | 
| 189 | 
            +
                  # Return true if the rendered output does not match the expected outptu
         | 
| 190 | 
            +
                  def failed?
         | 
| 191 | 
            +
                    return false if pending?
         | 
| 192 | 
            +
                    if rendered != File.read(full_output_file)
         | 
| 193 | 
            +
                      @error = 'Rendered output does not match expected.'
         | 
| 194 | 
            +
                    end
         | 
| 195 | 
            +
                    !@error.nil?
         | 
| 196 | 
            +
                  end
         | 
| 197 | 
            +
             | 
| 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.
         | 
| 201 | 
            +
                  def debug
         | 
| 202 | 
            +
                    with_template_factory do |template_factory|
         | 
| 203 | 
            +
                      orig_debug = Graphics.debug
         | 
| 204 | 
            +
                      Graphics.debug = true
         | 
| 205 | 
            +
                      render_screen(template_factory, StringIO.new)
         | 
| 206 | 
            +
                      Graphics.debug = orig_debug
         | 
| 207 | 
            +
                    end
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  # Print out the template tree
         | 
| 211 | 
            +
                  def template
         | 
| 212 | 
            +
                    with_template_factory do |template_factory|
         | 
| 213 | 
            +
                      template = template_factory.build('Test', {})
         | 
| 214 | 
            +
                      puts Graphics::Debugger.new(template).debug
         | 
| 215 | 
            +
                    end
         | 
| 216 | 
            +
                  end
         | 
| 217 | 
            +
             | 
| 218 | 
            +
                  def ask_pending
         | 
| 219 | 
            +
                    puts rendered
         | 
| 220 | 
            +
                    STDOUT.flush
         | 
| 221 | 
            +
                    ask_to_save
         | 
| 222 | 
            +
                  end
         | 
| 223 | 
            +
             | 
| 224 | 
            +
                  def ask_failed
         | 
| 225 | 
            +
                    puts File.read(full_output_file)
         | 
| 226 | 
            +
                    puts "File: #{test_file}"
         | 
| 227 | 
            +
                    print 'View actual output? [Y/q] '
         | 
| 228 | 
            +
                    STDOUT.flush
         | 
| 229 | 
            +
                    exit if 'q' == STDIN.gets.strip.downcase
         | 
| 230 | 
            +
                    puts rendered
         | 
| 231 | 
            +
                    STDOUT.flush
         | 
| 232 | 
            +
                    ask_to_save
         | 
| 233 | 
            +
                  end
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                  private
         | 
| 236 | 
            +
             | 
| 237 | 
            +
                  attr_reader :full_test_file, :full_output_file, :output_file
         | 
| 238 | 
            +
             | 
| 239 | 
            +
                  def ask_to_save
         | 
| 240 | 
            +
                    puts "File: #{test_file}"
         | 
| 241 | 
            +
                    print 'Save actual as the expected test output? [y/N/q] '
         | 
| 242 | 
            +
                    STDOUT.flush
         | 
| 243 | 
            +
                    response = STDIN.gets.strip.downcase
         | 
| 244 | 
            +
                    if response == 'y'
         | 
| 245 | 
            +
                      File.open(full_output_file, 'w') { |file| file.print(rendered) }
         | 
| 246 | 
            +
                      puts "Saved to #{output_file}"
         | 
| 247 | 
            +
                    elsif response == 'q'
         | 
| 248 | 
            +
                      puts 'Exiting'
         | 
| 249 | 
            +
                      exit
         | 
| 250 | 
            +
                    else
         | 
| 251 | 
            +
                      puts 'Skipped'
         | 
| 252 | 
            +
                    end
         | 
| 253 | 
            +
                  end
         | 
| 254 | 
            +
             | 
| 255 | 
            +
                  def rendered
         | 
| 256 | 
            +
                    @rendered ||= begin
         | 
| 257 | 
            +
                      string_io = StringIO.new
         | 
| 258 | 
            +
                      with_template_factory do |template_factory|
         | 
| 259 | 
            +
                        render_screen(template_factory, string_io)
         | 
| 260 | 
            +
                      end
         | 
| 261 | 
            +
                      string_io.string
         | 
| 262 | 
            +
                    end
         | 
| 263 | 
            +
                  end
         | 
| 264 | 
            +
             | 
| 265 | 
            +
                  def with_template_factory(&block)
         | 
| 266 | 
            +
                    require full_test_file
         | 
| 267 | 
            +
                    raise 'TemplateFactory must be defined' unless defined?(TemplateFactory)
         | 
| 268 | 
            +
                    yield TemplateFactory.new
         | 
| 269 | 
            +
                  ensure
         | 
| 270 | 
            +
                    Object.send(:remove_const, :TemplateFactory)
         | 
| 271 | 
            +
                  end
         | 
| 272 | 
            +
             | 
| 273 | 
            +
                  def render_screen(template_factory, output)
         | 
| 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])
         | 
| 281 | 
            +
                    end
         | 
| 282 | 
            +
                  end
         | 
| 283 | 
            +
                end
         | 
| 284 | 
            +
              end
         | 
| 285 | 
            +
            end
         |