Simplify Snapshot and its presenter
parent
34b39d0db3
commit
31a880b7aa
@ -0,0 +1,15 @@
|
||||
class SnapshotDecorator < ApplicationDecorator
|
||||
|
||||
delegate_all
|
||||
|
||||
def lines
|
||||
(0...height).map { |line_no| line(line_no) }
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def line(line_no)
|
||||
(0...width).map { |column_no| model.cell(column_no, line_no) }
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,22 @@
|
||||
class Cell
|
||||
|
||||
attr_reader :text, :brush
|
||||
|
||||
def initialize(text, brush)
|
||||
@text = text
|
||||
@brush = brush
|
||||
end
|
||||
|
||||
def empty?
|
||||
text.blank? && brush.default?
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
text == other.text && brush == other.brush
|
||||
end
|
||||
|
||||
def css_class
|
||||
BrushPresenter.new(brush).to_css_class
|
||||
end
|
||||
|
||||
end
|
@ -1,55 +1,65 @@
|
||||
class Snapshot
|
||||
include Enumerable
|
||||
|
||||
delegate :each, :to => :lines
|
||||
attr_reader :width, :height
|
||||
|
||||
def self.build(lines)
|
||||
lines = lines.map { |fragments| SnapshotLine.build(fragments) }
|
||||
|
||||
new(lines)
|
||||
end
|
||||
|
||||
def initialize(lines = [])
|
||||
@lines = lines
|
||||
def initialize(data, raw = true)
|
||||
@lines = raw ? cellify(data) : data
|
||||
@width = lines.first && lines.first.size || 0
|
||||
@height = lines.size
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.lines == lines
|
||||
def cell(column, line)
|
||||
lines[line][column]
|
||||
end
|
||||
|
||||
def crop(width, height)
|
||||
min_height = [height, lines.size].min
|
||||
new_lines = lines.drop(lines.size - min_height).map { |line|
|
||||
line.crop(width)
|
||||
}
|
||||
def thumbnail(width, height)
|
||||
new_lines = strip_trailing_blank_lines(lines)
|
||||
new_lines = crop_at_bottom_left(new_lines, width, height)
|
||||
new_lines = expand(new_lines, width, height)
|
||||
|
||||
self.class.new(new_lines)
|
||||
self.class.new(new_lines, false)
|
||||
end
|
||||
|
||||
def rstrip
|
||||
protected
|
||||
|
||||
def strip_trailing_blank_lines(lines)
|
||||
i = lines.size - 1
|
||||
|
||||
while i >= 0 && lines[i].empty?
|
||||
while i >= 0 && empty_line?(lines[i])
|
||||
i -= 1
|
||||
end
|
||||
|
||||
new_lines = i > -1 ? lines[0..i] : []
|
||||
|
||||
self.class.new(new_lines)
|
||||
i > -1 ? lines[0..i] : []
|
||||
end
|
||||
|
||||
def expand(height)
|
||||
new_lines = lines
|
||||
def crop_at_bottom_left(lines, width, height)
|
||||
min_height = [height, lines.size].min
|
||||
|
||||
while new_lines.size < height
|
||||
new_lines << []
|
||||
lines.drop(lines.size - min_height).map { |line| line.take(width) }
|
||||
end
|
||||
|
||||
def expand(lines, width, height)
|
||||
while lines.size < height
|
||||
lines << [Cell.new(' ', Brush.new)] * width
|
||||
end
|
||||
|
||||
self.class.new(new_lines)
|
||||
lines
|
||||
end
|
||||
|
||||
protected
|
||||
private
|
||||
|
||||
attr_reader :lines
|
||||
|
||||
def cellify(lines)
|
||||
lines.map { |cells|
|
||||
cells.map { |cell|
|
||||
Cell.new(cell[0], Brush.new(cell[1]))
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def empty_line?(cells)
|
||||
cells.all?(&:empty?)
|
||||
end
|
||||
|
||||
end
|
||||
|
@ -1,28 +0,0 @@
|
||||
class SnapshotFragment # TODO: rename to Cell or SnapshotCell
|
||||
|
||||
attr_reader :text, :brush
|
||||
|
||||
delegate :size, :to => :text
|
||||
|
||||
def initialize(text, brush)
|
||||
@text = text
|
||||
@brush = brush
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.text == text && other.brush == brush
|
||||
end
|
||||
|
||||
def crop(size)
|
||||
if size >= text.size
|
||||
self
|
||||
else
|
||||
self.class.new(text[0...size], brush)
|
||||
end
|
||||
end
|
||||
|
||||
def empty?
|
||||
text.blank? && brush.default?
|
||||
end
|
||||
|
||||
end
|
@ -1,48 +0,0 @@
|
||||
class SnapshotLine
|
||||
include Enumerable
|
||||
|
||||
delegate :each, :to => :fragments
|
||||
|
||||
def self.build(blocks)
|
||||
fragments = blocks.map { |block|
|
||||
SnapshotFragment.new(block[0], Brush.new(block[1]))
|
||||
}
|
||||
|
||||
new(fragments)
|
||||
end
|
||||
|
||||
def initialize(fragments)
|
||||
@fragments = fragments
|
||||
end
|
||||
|
||||
def ==(other)
|
||||
other.fragments == fragments
|
||||
end
|
||||
|
||||
def crop(size)
|
||||
new_fragments = []
|
||||
current_size = 0
|
||||
|
||||
fragments.each do |fragment|
|
||||
break if current_size == size
|
||||
|
||||
if current_size + fragment.size > size
|
||||
fragment = fragment.crop(size - current_size)
|
||||
end
|
||||
|
||||
new_fragments << fragment
|
||||
current_size += fragment.size
|
||||
end
|
||||
|
||||
self.class.new(new_fragments)
|
||||
end
|
||||
|
||||
def empty?
|
||||
fragments.all?(&:empty?)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
attr_reader :fragments
|
||||
|
||||
end
|
@ -1,15 +0,0 @@
|
||||
class SnapshotFragmentPresenter < Draper::Decorator
|
||||
|
||||
delegate :text, :brush
|
||||
|
||||
def to_html
|
||||
h.content_tag(:span, text, :class => css_class)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def css_class
|
||||
BrushPresenter.new(brush).to_css_class
|
||||
end
|
||||
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
class SnapshotLinePresenter < Draper::Decorator
|
||||
|
||||
delegate :map
|
||||
|
||||
def to_html
|
||||
h.content_tag(:span, fragment_strings.html_safe, :class => 'line')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def fragment_strings
|
||||
map { |fragment| fragment_string(fragment) }.join
|
||||
end
|
||||
|
||||
def fragment_string(fragment)
|
||||
SnapshotFragmentPresenter.new(fragment).to_html
|
||||
end
|
||||
|
||||
end
|
@ -1,19 +0,0 @@
|
||||
class SnapshotPresenter < Draper::Decorator
|
||||
|
||||
delegate :map
|
||||
|
||||
def to_html
|
||||
h.content_tag(:pre, lines_html.html_safe, :class => 'terminal')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def lines_html
|
||||
map { |line| line_html(line) }.join("\n") + "\n"
|
||||
end
|
||||
|
||||
def line_html(line)
|
||||
SnapshotLinePresenter.new(line).to_html
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,6 @@
|
||||
pre.terminal
|
||||
- for line in thumbnail.lines
|
||||
span.line
|
||||
- for cell in line
|
||||
span class=cell.css_class = cell.text
|
||||
= "\n"
|
@ -0,0 +1,22 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotDecorator do
|
||||
|
||||
let(:decorator) { described_class.new(snapshot) }
|
||||
let(:snapshot) { double('snapshot', :width => 2, :height => 2) }
|
||||
let(:cells) { [
|
||||
[:a, :b],
|
||||
[:c, :d]
|
||||
] }
|
||||
|
||||
describe '#lines' do
|
||||
subject { decorator.lines }
|
||||
|
||||
before do
|
||||
allow(snapshot).to receive(:cell) { |x, y| cells[y][x] }
|
||||
end
|
||||
|
||||
it { should eq(cells) }
|
||||
end
|
||||
|
||||
end
|
@ -0,0 +1,74 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Cell do
|
||||
|
||||
let(:cell) { described_class.new('a', brush) }
|
||||
let(:brush) { double('brush') }
|
||||
|
||||
describe '#empty?' do
|
||||
let(:cell) { described_class.new(text, brush) }
|
||||
|
||||
subject { cell.empty? }
|
||||
|
||||
context "when text is not blank" do
|
||||
let(:text) { 'a' }
|
||||
let(:brush) { double('brush', :default? => true) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context "when brush is not default" do
|
||||
let(:text) { ' ' }
|
||||
let(:brush) { double('brush', :default? => false) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context "when text is blank and brush is default" do
|
||||
let(:text) { ' ' }
|
||||
let(:brush) { double('brush', :default? => true) }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#==' do
|
||||
let(:other) { described_class.new(text, other_brush) }
|
||||
|
||||
subject { cell == other }
|
||||
|
||||
context "when text differs" do
|
||||
let(:text) { 'b' }
|
||||
let(:other_brush) { double('brush', :== => true) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context "when brush differs" do
|
||||
let(:text) { 'a' }
|
||||
let(:other_brush) { double('brush', :== => false) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context "when text and brush are equal" do
|
||||
let(:text) { 'a' }
|
||||
let(:other_brush) { double('brush', :== => true) }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#css_class' do
|
||||
let(:brush_presenter) { double('brush_presenter', :to_css_class => 'kls') }
|
||||
|
||||
subject { cell.css_class }
|
||||
|
||||
before do
|
||||
allow(BrushPresenter).to receive(:new).with(brush) { brush_presenter }
|
||||
end
|
||||
|
||||
it { should eq('kls') }
|
||||
end
|
||||
|
||||
end
|
@ -1,67 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotFragment do
|
||||
|
||||
describe '#==' do
|
||||
let(:snapshot_fragment) { SnapshotFragment.new('foo', brush_1) }
|
||||
let(:brush_1) { double('brush_1') }
|
||||
let(:brush_2) { double('brush_2') }
|
||||
|
||||
subject { snapshot_fragment == other }
|
||||
|
||||
context "when fragments have the same texts and brushes" do
|
||||
let(:other) { SnapshotFragment.new('foo', brush_1) }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context "when fragments have different texts" do
|
||||
let(:other) { SnapshotFragment.new('bar', brush_1) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
|
||||
context "when fragments have different brushes" do
|
||||
let(:other) { SnapshotFragment.new('foo', brush_2) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#crop' do
|
||||
let(:snapshot_fragment) { SnapshotFragment.new('foobar', brush) }
|
||||
let(:brush) { double('brush') }
|
||||
|
||||
context "when size is smaller than fragment's size" do
|
||||
subject { snapshot_fragment.crop(3) }
|
||||
|
||||
it 'returns a new instance of SnapshotFragment' do
|
||||
expect(subject).to be_kind_of(SnapshotFragment)
|
||||
expect(subject).to_not be(snapshot_fragment)
|
||||
end
|
||||
|
||||
it 'trims the text to the requested size' do
|
||||
expect(subject.text).to eq('foo')
|
||||
end
|
||||
|
||||
it 'returns SnapshotFragment with the same brush' do
|
||||
expect(subject.brush).to be(brush)
|
||||
end
|
||||
end
|
||||
|
||||
context "when size is equal or larger than the fragment's size" do
|
||||
it 'returns self' do
|
||||
expect(snapshot_fragment.crop(6)).to be(snapshot_fragment)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe '#size' do
|
||||
let(:snapshot_fragment) { SnapshotFragment.new('f' * 100, Brush.new) }
|
||||
|
||||
subject { snapshot_fragment.size }
|
||||
|
||||
it { should eq(100) }
|
||||
end
|
||||
|
||||
end
|
@ -1,97 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotLine do
|
||||
|
||||
describe '.build' do
|
||||
let(:line) { SnapshotLine.build(input) }
|
||||
let(:input) { [input_fragment_1, input_fragment_2] }
|
||||
let(:input_fragment_1) { ['foo', { :fg => 1, :bold => true }] }
|
||||
let(:input_fragment_2) { ['bar', { :bg => 2 }] }
|
||||
|
||||
it 'returns an instance of SnapshotLine' do
|
||||
expect(line).to be_kind_of(SnapshotLine)
|
||||
end
|
||||
|
||||
it 'returns properly joined fragments' do
|
||||
fragment_1 = line.to_a[0]
|
||||
fragment_2 = line.to_a[1]
|
||||
|
||||
expect(fragment_1.text).to eq('foo')
|
||||
expect(fragment_1.brush).to eq(Brush.new(:fg => 1, :bold => true))
|
||||
expect(fragment_2.text).to eq('bar')
|
||||
expect(fragment_2.brush).to eq(Brush.new(:bg => 2))
|
||||
end
|
||||
end
|
||||
|
||||
describe '#each' do
|
||||
let(:snapshot_line) { SnapshotLine.new([:fragment_1, :fragment_2]) }
|
||||
|
||||
it 'yields to the given block for each fragment' do
|
||||
fragments = []
|
||||
|
||||
snapshot_line.each do |fragment|
|
||||
fragments << fragment
|
||||
end
|
||||
|
||||
expect(fragments).to eq([:fragment_1, :fragment_2])
|
||||
end
|
||||
end
|
||||
|
||||
describe '#==' do
|
||||
let(:snapshot_line) { SnapshotLine.new([:foo]) }
|
||||
|
||||
subject { snapshot_line == other }
|
||||
|
||||
context "when lines have the same fragments" do
|
||||
let(:other) { SnapshotLine.new([:foo]) }
|
||||
|
||||
it { should be(true) }
|
||||
end
|
||||
|
||||
context "when lines have different fragments" do
|
||||
let(:other) { SnapshotLine.new([:foo, :bar]) }
|
||||
|
||||
it { should be(false) }
|
||||
end
|
||||
end
|
||||
|
||||
describe '#crop' do
|
||||
let(:snapshot_line) { SnapshotLine.new(fragments) }
|
||||
let(:fragments) { [fragment_1, fragment_2] }
|
||||
let(:fragment_1) { double('fragment_1', :size => 2, :crop => nil) }
|
||||
let(:fragment_2) { double('fragment_2', :size => 3,
|
||||
:crop => cropped_fragment_2) }
|
||||
let(:fragment_3) { double('fragment_3', :size => 4, :crop => nil) }
|
||||
let(:cropped_fragment_2) { double('cropped_fragment_2', :size => 2) }
|
||||
|
||||
context "when cropping point is at the end of the first fragment" do
|
||||
it 'crops none of the fragments' do
|
||||
snapshot_line.crop(2)
|
||||
|
||||
expect(fragment_1).to_not have_received(:crop)
|
||||
expect(fragment_2).to_not have_received(:crop)
|
||||
expect(fragment_3).to_not have_received(:crop)
|
||||
end
|
||||
|
||||
it 'returns a new SnapshotLine with only the first fragment' do
|
||||
expect(snapshot_line.crop(2)).to eq(SnapshotLine.new([fragment_1]))
|
||||
end
|
||||
end
|
||||
|
||||
context "when cropping point is inside of the second fragment" do
|
||||
it 'crops only the second fragment' do
|
||||
snapshot_line.crop(4)
|
||||
|
||||
expect(fragment_1).to_not have_received(:crop)
|
||||
expect(fragment_2).to have_received(:crop).with(2)
|
||||
expect(fragment_3).to_not have_received(:crop)
|
||||
end
|
||||
|
||||
it 'returns a new SnapshotLine with first two fragments cropped' do
|
||||
expect(snapshot_line.crop(4)).
|
||||
to eq(SnapshotLine.new([fragment_1, cropped_fragment_2]))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
@ -1,27 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotFragmentPresenter do
|
||||
let(:snapshot_fragment_presenter) { described_class.new(snapshot_fragment) }
|
||||
let(:snapshot_fragment) { SnapshotFragment.new('foo > bar', brush) }
|
||||
let(:brush) { double('brush') }
|
||||
let(:brush_presenter) { double('presenter', :to_css_class => css_class) }
|
||||
let(:css_class) { 'qux' }
|
||||
|
||||
describe '#to_html' do
|
||||
subject { snapshot_fragment_presenter.to_html }
|
||||
|
||||
before do
|
||||
allow(BrushPresenter).to receive(:new).with(brush).
|
||||
and_return(brush_presenter)
|
||||
end
|
||||
|
||||
it { should be_kind_of(ActiveSupport::SafeBuffer) }
|
||||
it { should eq('<span class="qux">foo > bar</span>') }
|
||||
|
||||
context "when css class is nil" do
|
||||
let(:css_class) { nil }
|
||||
|
||||
it { should eq('<span>foo > bar</span>') }
|
||||
end
|
||||
end
|
||||
end
|
@ -1,24 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotLinePresenter do
|
||||
let(:snapshot_line_presenter) { SnapshotLinePresenter.new(snapshot_line) }
|
||||
let(:snapshot_line) { SnapshotLine.new([fragment_1, fragment_2]) }
|
||||
let(:fragment_1) { double('fragment_1') }
|
||||
let(:fragment_2) { double('fragment_2') }
|
||||
let(:fragment_1_presenter) { double(:to_html => '<fragment_1>') }
|
||||
let(:fragment_2_presenter) { double(:to_html => '<fragment_2>') }
|
||||
|
||||
describe '#to_html' do
|
||||
subject { snapshot_line_presenter.to_html }
|
||||
|
||||
before do
|
||||
allow(SnapshotFragmentPresenter).to receive(:new).with(fragment_1).
|
||||
and_return(fragment_1_presenter)
|
||||
allow(SnapshotFragmentPresenter).to receive(:new).with(fragment_2).
|
||||
and_return(fragment_2_presenter)
|
||||
end
|
||||
|
||||
it { should be_kind_of(ActiveSupport::SafeBuffer) }
|
||||
it { should eq('<span class="line"><fragment_1><fragment_2></span>') }
|
||||
end
|
||||
end
|
@ -1,24 +0,0 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe SnapshotPresenter do
|
||||
let(:snapshot_presenter) { SnapshotPresenter.new(snapshot) }
|
||||
let(:snapshot) { Snapshot.new([line_1, line_2]) }
|
||||
let(:line_1) { double('line_1') }
|
||||
let(:line_2) { double('line_2') }
|
||||
let(:line_1_presenter) { double(:to_html => '<line_1>') }
|
||||
let(:line_2_presenter) { double(:to_html => '<line_2>') }
|
||||
|
||||
describe '#to_html' do
|
||||
subject { snapshot_presenter.to_html }
|
||||
|
||||
before do
|
||||
allow(SnapshotLinePresenter).to receive(:new).with(line_1).
|
||||
and_return(line_1_presenter)
|
||||
allow(SnapshotLinePresenter).to receive(:new).with(line_2).
|
||||
and_return(line_2_presenter)
|
||||
end
|
||||
|
||||
it { should be_kind_of(ActiveSupport::SafeBuffer) }
|
||||
it { should eq(%(<pre class="terminal"><line_1>\n<line_2>\n</pre>)) }
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue