private-asciicasts
Marcin Kulik 9 years ago
parent facee1ba82
commit 2852248989

@ -64,6 +64,7 @@ group :test do
gem 'shoulda-matchers'
gem 'coveralls', require: false
gem 'rspec-activemodel-mocks'
gem 'chunky_png'
end
group :production do

@ -60,6 +60,7 @@ GEM
activesupport (>= 3.2.0)
celluloid (0.15.2)
timers (~> 1.1.0)
chunky_png (1.3.4)
cliver (0.3.2)
coderay (1.1.0)
coercible (1.0.0)
@ -349,6 +350,7 @@ DEPENDENCIES
capistrano-ext (~> 1.2)
capybara (~> 2.4.1)
carrierwave (~> 0.8.0)
chunky_png
coffee-rails (~> 4.0.1)
coveralls
dalli (~> 2.6.2)

@ -37,6 +37,9 @@ dependencies installed:
If you don't install it now the setup script (point 4 below) will try to
install it for you anyway.
* phantomjs 2.0+
`sudo add-apt-repository ppa:tanguy-patte/phantomjs && sudo apt-get update && sudo apt-get install phantomjs`
### 2. Get the source code
Clone git repository:

@ -0,0 +1,12 @@
//= require player
body.screenshot
background-color: transparent
padding: 0
margin: 0
.control-bar
display: none
.asciinema-player-wrapper
text-align: left

@ -26,6 +26,16 @@ class AsciicastsController < ApplicationController
format.json do
render json: asciicast
end
format.png do
if asciicast.image_stale?
with_player_html_file do |html_path|
image_updater.update(asciicast, html_path)
end
end
redirect_to asciicast.image_url
end
end
end
@ -79,4 +89,22 @@ class AsciicastsController < ApplicationController
AsciicastUpdater.new
end
def image_updater
AsciicastImageUpdater.new
end
def with_player_html_file
html = render_to_string(
template: 'asciicasts/screenshot.html.slim',
layout: 'screenshot',
locals: { page: BareAsciicastPagePresenter.build(asciicast, params) }
)
Dir.mktmpdir do |dir|
path = "#{dir}/asciicast.html"
File.open(path, 'w') { |f| f.write(html) }
yield(path)
end
end
end

@ -5,10 +5,27 @@ module AsciicastsHelper
options: options
end
def screenshot_javascript_tag
js = assets.find_asset('embed.js').to_s
content_tag(:script, js.html_safe)
end
def screenshot_stylesheet_tag
css = translate_asset_paths(assets.find_asset('screenshot.css').to_s)
content_tag(:style, css.html_safe)
end
private
def serialized_asciicast(asciicast)
AsciicastSerializer.new(asciicast).to_json
end
def translate_asset_paths(css)
css.gsub(/['"]\/assets\/(.+?)(-\w{32})?\.(.+?)['"]/) { |m|
path = assets.find_asset("#{$1}.#{$3}").pathname
"'#{path}'"
}
end
end

@ -8,6 +8,7 @@ class Asciicast < ActiveRecord::Base
mount_uploader :stdout_timing, StdoutTimingUploader
mount_uploader :stdout_frames, StdoutFramesUploader
mount_uploader :file, AsciicastUploader
mount_uploader :image, ImageUploader
serialize :snapshot, ActiveSupportJsonProxy
@ -73,6 +74,14 @@ class Asciicast < ActiveRecord::Base
theme_name.presence && Theme.for_name(theme_name)
end
def image_filename
"#{image_hash}.png"
end
def image_stale?
!image.file || (image.file.filename != image_filename)
end
private
def get_stdout
@ -84,4 +93,10 @@ class Asciicast < ActiveRecord::Base
end
end
def image_hash
version = 1 # version of screenshot, increment to force regeneration
input = "#{version}/#{id}/#{snapshot_at}"
Digest::SHA1.hexdigest(input)
end
end

@ -0,0 +1,23 @@
class AsciicastImageUpdater
attr_reader :png_generator
def initialize(png_generator = PngGenerator.new)
@png_generator = png_generator
end
def update(asciicast, page_path)
Dir.mktmpdir do |dir|
png_path = "#{dir}/#{asciicast.image_filename}"
png_generator.generate(page_path, png_path)
File.open(png_path) do |f|
asciicast.image = f
end
asciicast.save!
end
end
end

@ -0,0 +1,15 @@
require 'open3'
class PngGenerator
BINARY_PATH = (Rails.root + "bin" + "asciicast2png").to_s
def generate(page_path, png_path)
o, e, t = Open3.capture3("#{BINARY_PATH} #{page_path} #{png_path}")
if t.exitstatus != 0
raise RuntimeError, "Couldn't generate PNG for #{page_path}:\n#{o}\n#{e}"
end
end
end

@ -0,0 +1,2 @@
class ImageUploader < BaseUploader
end

@ -0,0 +1 @@
= player page.asciicast, page.playback_options

@ -0,0 +1,8 @@
doctype html
html[lang="en"]
head
meta[charset="utf-8"]
= screenshot_javascript_tag
= screenshot_stylesheet_tag
body.screenshot
= yield

@ -5,4 +5,4 @@ Rails.application.config.assets.version = '1.0'
# Precompile additional assets.
# application.js, application.css, and all non-JS/CSS in app/assets folder are already added.
Rails.application.config.assets.precompile += %w( player.css player.js embed.css embed.js widget.js )
Rails.application.config.assets.precompile += %w( player.css player.js embed.css embed.js widget.js screenshot.css )

@ -0,0 +1,5 @@
class AddImageToAsciicasts < ActiveRecord::Migration
def change
add_column :asciicasts, :image, :string
end
end

@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150311094819) do
ActiveRecord::Schema.define(version: 20150324103607) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -55,6 +55,7 @@ ActiveRecord::Schema.define(version: 20150311094819) do
t.float "snapshot_at"
t.integer "version", null: false
t.string "file"
t.string "image"
end
add_index "asciicasts", ["created_at"], name: "index_asciicasts_on_created_at", using: :btree

@ -15,4 +15,29 @@ feature "Asciicast page", :js => true do
expect(page).to have_selector('.cinema .play-button')
end
def rgb(color)
[ChunkyPNG::Color.r(color), ChunkyPNG::Color.g(color), ChunkyPNG::Color.b(color)]
end
scenario 'Requesting PNG' do
visit asciicast_path(asciicast, format: :png)
expect(current_path).to match(%r{/uploads/test/asciicast/image/\d+/\w+\.png$})
png = ChunkyPNG::Image.from_file("#{Rails.root}/public/#{current_path}")
# make sure there are black-ish borders
expect(rgb(png[1, 1])).to eq([18, 19, 20])
expect(rgb(png[png.width - 2, png.height - 2])).to eq([18, 19, 20])
# check content color (blue background)
expect(rgb(png[15, 15])).to eq([0, 175, 255])
# make sure white SVG play icon is rendered correctly
expect(rgb(png[png.width / 2, (png.height / 2) - 10])).to eq([255, 255, 255])
# make sure PowerlineSymbols are rendered
expect(rgb(png[144, 795])).to eq([0, 95, 255])
end
end

@ -1 +1,28 @@
[[["code/ascii.io", {"fg": 4}], [" ", {}], ["(color-snapshot", {"fg": 3}], ["*", {"fg": 2}], ["?", {"fg": 4}], [")", {"fg": 3}], [" % ", {}], ["ohai", {"fg": 1, "bold": true}], [" :)", {}], [" ", {"inverse": true}], [" ", {}], ["(2.0.0)", {"fg": 1}], [" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]], [[" ", {}]]]
[
[[" .g/COMMIT_EDITMSG+ ",{"fg":232,"bg":39}],["",{"fg":39,"bg":235}],[" ",{"fg":121,"bg":235}],["",{"fg":214,"bg":235}],[" buffers ",{"fg":232,"bg":214}]],
[[" 1 ",{"fg":241,"bg":233}],["AUI-1325 ",{"fg":196,"bg":235,"bold":true}],["C",{"fg":196,"bg":235,"bold":true,"inverse":true}],["hange the diagram-builder icons to font-",{"fg":196,"bg":235,"bold":true}],["based and remove unused icons CSS ",{"fg":15,"bg":235}]],
[[" 2 ",{"fg":241,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 3 ",{"fg":241,"bg":233}],["Signed-off-by: Henrique Vicente <henriquevicente@gmail.com> ",{"fg":15,"bg":233}]],
[[" 4 ",{"fg":241,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 5 ",{"fg":241,"bg":233}],["# Please enter the commit message for your changes. Lines starting",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 6 ",{"fg":241,"bg":233}],["# with '#' will be ignored, and an empty message aborts the commit.",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 7 ",{"fg":241,"bg":233}],["# rebase in progress; onto 5ea3517",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 8 ",{"fg":241,"bg":233}],["# You are currently editing a commit while rebasing branch 'AUI-1325' on '5ea3517'.",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 9 ",{"fg":241,"bg":233}],["#",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 10 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["Changes to be committed:",{"fg":154,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" 11 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["modified",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/aui-diagram-builder-impl-core.css",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 12 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/anchor-default.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 13 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/anchor-drop-active.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 20 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-end.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 21 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-fork.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 22 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-join.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 23 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-start.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 24 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-state.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 25 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/menu-task.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 26 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/start.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 27 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/state.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 28 ",{"fg":241,"bg":233}],["# ",{"fg":243,"bg":233}],["deleted",{"fg":211,"bg":233}],[": ",{"fg":243,"bg":233}],[" src/aui-diagram-builder/assets/skins/sam/task.png",{"fg":137,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]],
[[" 29 ",{"fg":241,"bg":233}],["#",{"fg":243,"bg":233}],[" ",{"fg":15,"bg":233}]],
[[" ",{"fg":232,"bg":39}],["INSERT",{"fg":232,"bg":39,"bold":true}],[" ",{"fg":232,"bg":39}],["",{"fg":39,"bg":27}],["",{"fg":27,"bg":238}],[" .git/COMMIT_EDITMSG[+] ",{"fg":214,"bg":238}],["",{"fg":235,"bg":238}],[" gitcommit ",{"fg":39,"bg":235}],["",{"fg":27,"bg":235}],[" utf-8[unix] ",{"fg":222,"bg":27}],["",{"fg":39,"bg":27}],[" 2% ",{"fg":232,"bg":39}],[" 1",{"fg":232,"bg":39,"bold":true}],[": 10 ",{"fg":232,"bg":39}]],
[["-- INSERT --",{"fg":222,"bg":233,"bold":true}],[" ",{"fg":15,"bg":233}]]
]

Loading…
Cancel
Save