Expose stdout of old and new format asciicasts via Asciicast#stdout

private-asciicasts
Marcin Kulik 9 years ago
parent 8f56d019ec
commit dad5f40c17

@ -58,8 +58,8 @@ class Asciicast < ActiveRecord::Base
end
def stdout
@stdout ||= BufferedStdout.new(stdout_data.decompressed_path,
stdout_timing.decompressed_path).lazy
return @stdout if @stdout
@stdout = Stdout::Buffered.new(get_stdout)
end
def with_terminal
@ -73,4 +73,16 @@ class Asciicast < ActiveRecord::Base
theme_name.presence && Theme.for_name(theme_name)
end
private
def get_stdout
if version == 0
Stdout::MultiFile.new(stdout_data.decompressed_path,
stdout_timing.decompressed_path)
else
file.cache!
Stdout::SingleFile.new(file.current_path)
end
end
end

@ -1,22 +0,0 @@
class BufferedStdout < Stdout
MIN_FRAME_LENGTH = 1.0 / 60
def each
buffered_delay, buffered_data = 0.0, []
super do |delay, data|
if buffered_delay + delay < MIN_FRAME_LENGTH
buffered_delay += delay
buffered_data << data
else
yield(buffered_delay, buffered_data.join)
buffered_delay = delay
buffered_data = [data]
end
end
yield(buffered_delay, buffered_data.join) unless buffered_data.empty?
end
end

@ -1,28 +1,98 @@
class Stdout
include Enumerable
attr_reader :data_path, :timing_path
class SingleFile < self
attr_reader :path
def initialize(path)
@path = path
end
def each(&blk)
File.open(path, 'r') do |f|
Oj.sc_parse(FrameIterator.new(blk), f)
end
end
class FrameIterator < ::Oj::ScHandler
def initialize(callback)
@callback = callback
end
def array_start
if @top # we're already inside top level array
[] # <- this will hold pair [delay, data]
else
@top = []
end
end
def array_append(a, v)
if a.equal?(@top)
@callback.call(*v)
else
a << v
end
end
end
def initialize(data_path, timing_path)
@data_path = data_path
@timing_path = timing_path
end
def each
File.open(data_path, 'rb') do |file|
File.foreach(timing_path) do |line|
yield(*delay_and_data_for_line(file, line))
class MultiFile < self
attr_reader :data_path, :timing_path
def initialize(data_path, timing_path)
@data_path = data_path
@timing_path = timing_path
end
def each
File.open(data_path, 'rb') do |file|
File.foreach(timing_path) do |line|
yield(*delay_and_data_for_line(file, line))
end
end
end
private
def delay_and_data_for_line(file, line)
delay, size = TimingParser.parse_line(line)
data = file.read(size).to_s.force_encoding('utf-8')
[delay, data]
end
end
private
class Buffered < self
MIN_FRAME_LENGTH = 1.0 / 60
def delay_and_data_for_line(file, line)
delay, size = TimingParser.parse_line(line)
data = file.read(size).to_s.force_encoding('utf-8')
attr_reader :stdout
def initialize(stdout)
@stdout = stdout
end
def each
buffered_delay, buffered_data = 0.0, []
stdout.each do |delay, data|
if buffered_delay + delay < MIN_FRAME_LENGTH || buffered_data.empty?
buffered_delay += delay
buffered_data << data
else
yield(buffered_delay, buffered_data.join)
buffered_delay = delay
buffered_data = [data]
end
end
yield(buffered_delay, buffered_data.join) unless buffered_data.empty?
end
[delay, data]
end
end

@ -7,6 +7,7 @@ FactoryGirl.define do
}
factory :asciicast do
version 1
association :user
title "bashing"
duration 11.146430015564
@ -21,6 +22,7 @@ FactoryGirl.define do
end
factory :legacy_asciicast, parent: :asciicast do
version 0
file nil
stdout_data { fixture_file['0.9.9/stdout', 'application/octet-stream'] }
stdout_timing { fixture_file['0.9.9/stdout.time', 'application/octet-stream'] }

@ -11,6 +11,7 @@
},
"stdout": [
[1.234567, "foo bar"],
[5.678987, "baz qux"]
[5.678987, "baz qux"],
[3.456789, "żółć jaźń"]
]
}

@ -1 +0,0 @@
abcdefghijklmnopqrstuwxyz

@ -1,13 +0,0 @@
0.010000 1
0.006000 1
0.000600 1
0.000060 1
0.000006 1
1.000000 1
0.016665 1
0.000002 1
0.016664 1
0.000002 1
0.016666 1
0.016667 1
0.000001 1

@ -75,30 +75,32 @@ describe Asciicast do
end
describe '#stdout' do
let(:asciicast) { Asciicast.new }
let(:data_uploader) { double('data_uploader',
:decompressed_path => '/foo') }
let(:timing_uploader) { double('timing_uploader',
:decompressed_path => '/bar') }
let(:stdout) { double('stdout', :lazy => lazy_stdout) }
let(:lazy_stdout) { double('lazy_stdout') }
subject { asciicast.stdout }
before do
allow(BufferedStdout).to receive(:new) { stdout }
allow(StdoutDataUploader).to receive(:new) { data_uploader }
allow(StdoutTimingUploader).to receive(:new) { timing_uploader }
end
context 'for single-file, JSON asciicast' do
let(:asciicast) { create(:asciicast) }
it 'creates a new BufferedStdout instance' do
subject
subject { asciicast.stdout.to_a }
expect(BufferedStdout).to have_received(:new).with('/foo', '/bar')
it 'is enumerable with [delay, data] pair as every item' do
expect(subject).to eq([
[1.234567, "foo bar"],
[5.678987, "baz qux"],
[3.456789, "żółć jaźń"],
])
end
end
it 'returns lazy instance of stdout' do
expect(subject).to be(lazy_stdout)
context 'for multi-file, legacy asciicast' do
let(:asciicast) { create(:legacy_asciicast) }
subject { asciicast.stdout.to_a }
it 'is enumerable with [delay, data] pair as every item' do
expect(subject).to eq([
[1.234567, "foobar"],
[0.123456, "baz"],
[2.345678, "qux"],
])
end
end
end

@ -1,25 +0,0 @@
require 'rails_helper'
describe BufferedStdout do
let(:stdout) { described_class.new('spec/fixtures/high-freq-stdout',
'spec/fixtures/high-freq-stdout.time') }
describe '#each' do
let(:yield_args) { [
[0.016666, 'abcde'],
[1.000000, 'f'],
[0.016665, 'g'],
[0.016666, 'hi'],
[0.000002, 'j'],
[0.016666, 'k'],
[0.016667, 'l'],
[0.000001, 'm']
] }
it 'yields for each frame with delay and data at 60hz freq tops' do
expect { |b| stdout.each(&b) }.to yield_successive_args(*yield_args)
end
end
end

@ -2,9 +2,9 @@
require 'rails_helper'
describe Stdout do
let(:stdout) { Stdout.new('spec/fixtures/stdout.decompressed',
'spec/fixtures/stdout.time.decompressed') }
describe Stdout::MultiFile do
let(:stdout) { Stdout::MultiFile.new('spec/fixtures/stdout.decompressed',
'spec/fixtures/stdout.time.decompressed') }
describe '#each' do
it 'yields for each frame with delay and data' do
@ -16,3 +16,59 @@ describe Stdout do
end
end
describe Stdout::SingleFile do
let(:stdout) { Stdout::SingleFile.new('spec/fixtures/1/asciicast.json') }
describe '#each' do
it 'yields for each frame with delay and data' do
expect { |b| stdout.each(&b) }.
to yield_successive_args([1.234567, 'foo bar'],
[5.678987, 'baz qux'],
[3.456789, 'żółć jaźń'])
end
end
end
describe Stdout::Buffered do
let(:inner) { double('inner') }
let(:stdout) { Stdout::Buffered.new(inner) }
before do
allow(inner).to receive(:each)
.and_yield(0.200000, '!')
.and_yield(0.010000, 'a')
.and_yield(0.006000, 'b')
.and_yield(0.000600, 'c')
.and_yield(0.000060, 'd')
.and_yield(0.000006, 'e')
.and_yield(1.000000, 'f')
.and_yield(0.016665, 'g')
.and_yield(0.000002, 'h')
.and_yield(0.016664, 'i')
.and_yield(0.000002, 'j')
.and_yield(0.016666, 'k')
.and_yield(0.016667, 'l')
.and_yield(0.000001, 'm')
end
describe '#each' do
let(:yield_args) { [
[0.200000, '!'],
[0.016666, 'abcde'],
[1.000000, 'f'],
[0.016665, 'g'],
[0.016666, 'hi'],
[0.000002, 'j'],
[0.016666, 'k'],
[0.016667, 'l'],
[0.000001, 'm']
] }
it 'yields for each frame with delay and data at 60hz freq tops' do
expect { |b| stdout.each(&b) }.to yield_successive_args(*yield_args)
end
end
end

Loading…
Cancel
Save