asciinema-rails 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.rspec +2 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +22 -0
- data/README.md +107 -0
- data/Rakefile +2 -0
- data/asciinema-rails.gemspec +27 -0
- data/lib/asciinema-rails.rb +1 -0
- data/lib/asciinema/asciicast.rb +44 -0
- data/lib/asciinema/asciicast_frames_file_updater.rb +24 -0
- data/lib/asciinema/asciicast_snapshot_updater.rb +20 -0
- data/lib/asciinema/brush.rb +78 -0
- data/lib/asciinema/cell.rb +33 -0
- data/lib/asciinema/cursor.rb +18 -0
- data/lib/asciinema/film.rb +40 -0
- data/lib/asciinema/frame.rb +26 -0
- data/lib/asciinema/frame_diff.rb +20 -0
- data/lib/asciinema/frame_diff_list.rb +26 -0
- data/lib/asciinema/grid.rb +51 -0
- data/lib/asciinema/json_file_writer.rb +21 -0
- data/lib/asciinema/rails.rb +8 -0
- data/lib/asciinema/rails/convertor.rb +97 -0
- data/lib/asciinema/rails/engine.rb +6 -0
- data/lib/asciinema/rails/version.rb +5 -0
- data/lib/asciinema/snapshot.rb +41 -0
- data/lib/asciinema/stdout.rb +101 -0
- data/lib/asciinema/terminal.rb +65 -0
- data/spec/.rspec +2 -0
- data/spec/asciinemosh_spec.rb +63 -0
- data/spec/fixtures/sudosh-script +8 -0
- data/spec/fixtures/sudosh-time +22 -0
- data/spec/spec_helper.rb +50 -0
- data/src/terminal +0 -0
- data/src/terminal.c +307 -0
- data/vendor/assets/javscripts/asciinema-rails.js +4 -0
- data/vendor/assets/javscripts/asciinema.org/asciinema-player.js +1149 -0
- data/vendor/assets/javscripts/asciinema.org/rAF.js +32 -0
- data/vendor/assets/javscripts/asciinema.org/react-0.10.0.js +17228 -0
- data/vendor/assets/javscripts/asciinema.org/screenfull.js +143 -0
- data/vendor/assets/stylesheets/asciinema-rails.css +4 -0
- data/vendor/assets/stylesheets/asciinema.org/asciinema-player.css +1732 -0
- data/vendor/assets/stylesheets/asciinema.org/themes/solarized-dark.css +35 -0
- data/vendor/assets/stylesheets/asciinema.org/themes/solarized-light.css +35 -0
- data/vendor/assets/stylesheets/asciinema.org/themes/tango.css +35 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a77da9f1f221e64eff158e9a6f11bb996728c37f
|
4
|
+
data.tar.gz: c3ccee4badaeb98d419843073baf58aa5823dd1a
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3eda26b09f2775ca6e134bf2082d376aba12c5beba1bf4b1996daa73dc5c3ae11f2c0c79991b85531a980702cb2f139fa2ab9b0ef3c63a89f870454871b1a562
|
7
|
+
data.tar.gz: f4ae9674f9b853ecbdee0880e29a4ab79f699de0e56958a16538f836a082608a24d4d65a86f44768ed81580556f283d37ef131ccb6c4f5d3b834ccede7c63b82
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2015 Eddie Johnston
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
# Asciinema-Rails
|
2
|
+
|
3
|
+
TLDR: Record terminal sessions from local or remote machines and play them back from your own website. See the demo at [http://eddiej.github.io/asciinema-rails](http://eddiej.github.io/asciinema-rails).
|
4
|
+
|
5
|
+
Asciinema-rails is a gem that allows you to generate playback files from Asciinema asciicasts or Sudosh log files and host them from your own website using the bundled player.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'asciinema-rails'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle install
|
18
|
+
|
19
|
+
Or install it manually:
|
20
|
+
|
21
|
+
$ gem install asciinema-rails
|
22
|
+
|
23
|
+
## Prerequisites
|
24
|
+
|
25
|
+
Asciinema-rails gem depends on the binary `terminal` which must be compiled for your platform before using this gem. The source file `terminal.c` is located in the `/src` directory of the gem. Before compiling, you need to install a suitable version of the `libtsm` library for your platform. For Darwin based systems, a reliable source is
|
26
|
+
[https://github.com/skade/libtsm](https://github.com/skade/libtsm). For other distributions, see [http://www.freedesktop.org/wiki/Software/kmscon/libtsm](http://www.freedesktop.org/wiki/Software/kmscon/libtsm).
|
27
|
+
|
28
|
+
Once `libtsm` has been installed, compile `terminal.c`:
|
29
|
+
|
30
|
+
$ gcc -O3 -o terminal terminal.c -ltsm
|
31
|
+
|
32
|
+
The resulting binary file `terminal` should be placed in your binaries folder (e.g. `/usr/bin`), or added to the $PATH environment variable.
|
33
|
+
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Asciinema-rails consists of two parts - a Convertor module for creating player files, and an engine that provides the assets that allow you to playback the files from your own site.
|
38
|
+
|
39
|
+
### Asciinema::Rails::Convertor
|
40
|
+
|
41
|
+
Asciinema::Rails::Convertor has two methods, one to generate player files from Asciinema asciicast files, and another to covert Sudosh log files to the Asciinema asciicast format (which can then be used to create player files.)
|
42
|
+
|
43
|
+
#### Creating playback files from asciicasts
|
44
|
+
From within your Rails application or terminal, create playback files using the `asciicast_to_playback` function:
|
45
|
+
|
46
|
+
Asciinema::Rails::Convertor.asciicast_to_playback(asciicast_path, {playback_path: '/path/to/playback/file'})
|
47
|
+
|
48
|
+
where `asciicast_path` is the path to the file created after running the Asciinema recorder and `playback_path` is the path to save the new file.
|
49
|
+
|
50
|
+
#### Creating asciicasts from Sudosh log files
|
51
|
+
To create a playback file from Sudosh log files, you must first covert them to the Asciinma asciicast format using the `sudosh_to_asciicast` function:
|
52
|
+
|
53
|
+
Asciinema::Rails::Convertor.sudosh_to_asciicast(sudosh_timing_file_path, sudosh_script_file_path, {asciicast_path: /path/to/asciicast/file})
|
54
|
+
|
55
|
+
where `asciicast_path` is the path to save the new asciicast file. If the width and height of the Susodh session terminal are available, these values can be passed in as additional parameters so that the resulting player is the original size of the terminal session. Note that Playback files can be generated from the resulting asciicast using the `asciicast_to_playback` method above.
|
56
|
+
|
57
|
+
### Rails Engine
|
58
|
+
|
59
|
+
To display a player from your app, you need to include the asciineama-rails javascript and stylesheet files in ```app/assets/javascripts/application.js```and ```app/assets/stylesheets/application.css respectively```:
|
60
|
+
|
61
|
+
##### application.js
|
62
|
+
//= require asciinema-rails
|
63
|
+
|
64
|
+
##### application.css
|
65
|
+
*= require asciinema-rails
|
66
|
+
|
67
|
+
Then include the player markup and javascript in any of your views:
|
68
|
+
|
69
|
+
<div id='player-container'></div> <!-- the div that will contain the player -->
|
70
|
+
|
71
|
+
<%= javascript_tag do %>
|
72
|
+
$(function() {
|
73
|
+
function createPlayer() {
|
74
|
+
var source = new asciinema.HttpArraySource("recording.json", 1);
|
75
|
+
var snapshot = []
|
76
|
+
var movie = new asciinema.Movie(180, 43, source, snapshot, 9);
|
77
|
+
|
78
|
+
React.renderComponent(
|
79
|
+
asciinema.Player({ autoPlay: true, movie: movie }),
|
80
|
+
$('#player-container')[0]
|
81
|
+
);
|
82
|
+
}
|
83
|
+
createPlayer();
|
84
|
+
});
|
85
|
+
<% end %>
|
86
|
+
|
87
|
+
In this example, the Asciinema player file `recording.json` has been placed in the public directory of the app. This can be replaced with a fully qualified url, as long as it points to a player file.
|
88
|
+
|
89
|
+
The parameters sent to the `asciinema.Movie` constructor are the terminal width (cols), height (rows), playback file source, snapshot and playback speed.
|
90
|
+
The width, height and snapshot string are returned form the `Asciinema::Rails::Convertor.asciicast_to_playback` method. Typically, player files would be associated with a Rails model, and this information would be saved as model fields when the playback files are generated.
|
91
|
+
|
92
|
+
|
93
|
+
## Testing
|
94
|
+
The test for the gem are written in Rspec:
|
95
|
+
|
96
|
+
bundle exec rspec spec
|
97
|
+
|
98
|
+
## Demo
|
99
|
+
A demo of the player is available at [http://eddiej.github.io/asciinema-rails](http://eddiej.github.io/asciinema-rails). The embedded player shows a recording if the gem being installed and used in a new Rails application.
|
100
|
+
|
101
|
+
## Contributing
|
102
|
+
|
103
|
+
1. Fork it ( https://github.com/[my-github-username]/asciinemosh/fork )
|
104
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
105
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
106
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
107
|
+
5. Create a new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'asciinema/rails/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "asciinema-rails"
|
8
|
+
spec.version = Asciinema::Rails::VERSION
|
9
|
+
spec.authors = ["Eddie Johnston"]
|
10
|
+
spec.email = ["eddie@beanstalk.ie"]
|
11
|
+
spec.summary = %q{Converts sudosh log files to asciinema format. Provides JS and CSS includes to allow you host your own asciicasts.}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
17
|
+
spec.require_paths = ["lib"]
|
18
|
+
|
19
|
+
spec.add_dependency "oj"
|
20
|
+
spec.add_dependency "oj_mimic_json"
|
21
|
+
spec.add_dependency "yajl-ruby", "~> 1.2.1"
|
22
|
+
|
23
|
+
spec.add_development_dependency "bundler", "~> 1.7"
|
24
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
25
|
+
spec.add_development_dependency "rspec", "~> 3"
|
26
|
+
spec.add_development_dependency "rails"
|
27
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require "asciinema/rails"
|
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'asciinema/terminal'
|
2
|
+
require 'asciinema/stdout'
|
3
|
+
|
4
|
+
class Asciicast
|
5
|
+
|
6
|
+
|
7
|
+
attr_reader :terminal_columns, :terminal_lines, :duration, :file, :version
|
8
|
+
attr_accessor :stdout_frames, :snapshot, :snapshot_at
|
9
|
+
|
10
|
+
def initialize(terminal_columns, terminal_lines, duration, file_location)
|
11
|
+
@terminal_columns = terminal_columns
|
12
|
+
@terminal_lines = terminal_lines
|
13
|
+
@duration = duration
|
14
|
+
@file = File.new(file_location)
|
15
|
+
@version = 1
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
def stdout
|
22
|
+
return @stdout if @stdout
|
23
|
+
@stdout = Stdout::Buffered.new(get_stdout)
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_terminal
|
27
|
+
terminal = Terminal.new(terminal_columns, terminal_lines)
|
28
|
+
yield(terminal)
|
29
|
+
ensure
|
30
|
+
terminal.release if terminal
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
|
35
|
+
def get_stdout
|
36
|
+
if version == 0
|
37
|
+
Stdout::MultiFile.new(stdout_data.decompressed_path,
|
38
|
+
stdout_timing.decompressed_path)
|
39
|
+
else
|
40
|
+
Stdout::SingleFile.new(file.path)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'asciinema/json_file_writer'
|
2
|
+
|
3
|
+
class AsciicastFramesFileUpdater
|
4
|
+
|
5
|
+
def initialize(file_writer = JsonFileWriter.new)
|
6
|
+
@file_writer = file_writer
|
7
|
+
end
|
8
|
+
|
9
|
+
def update(asciicast, outfile_location=nil)
|
10
|
+
file = outfile_location.present? ? File.new(outfile_location, 'w') : Tempfile.new('outfile')
|
11
|
+
# file = File.new(new_outfile_location, 'w')
|
12
|
+
|
13
|
+
asciicast.with_terminal do |terminal|
|
14
|
+
film = Film.new(asciicast.stdout, terminal)
|
15
|
+
file_writer.write_enumerable(file, film.frames)
|
16
|
+
end
|
17
|
+
asciicast.stdout_frames = file
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
attr_reader :file_writer
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require 'asciinema/film'
|
2
|
+
|
3
|
+
|
4
|
+
class AsciicastSnapshotUpdater
|
5
|
+
|
6
|
+
def update(asciicast, at_seconds = nil)
|
7
|
+
at_seconds ||= asciicast.snapshot_at || asciicast.duration / 2
|
8
|
+
snapshot = generate_snapshot(asciicast, at_seconds)
|
9
|
+
asciicast.snapshot = snapshot
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def generate_snapshot(asciicast, at_seconds)
|
15
|
+
asciicast.with_terminal do |terminal|
|
16
|
+
Film.new(asciicast.stdout, terminal).snapshot_at(at_seconds)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
class Brush
|
2
|
+
require 'active_support/core_ext/hash'
|
3
|
+
|
4
|
+
ALLOWED_ATTRIBUTES = [:fg, :bg, :bold, :underline, :inverse, :blink]
|
5
|
+
DEFAULT_FG_CODE = 7
|
6
|
+
DEFAULT_BG_CODE = 0
|
7
|
+
|
8
|
+
def initialize(attributes = {})
|
9
|
+
@attributes = attributes.symbolize_keys
|
10
|
+
end
|
11
|
+
|
12
|
+
def ==(other)
|
13
|
+
fg == other.fg &&
|
14
|
+
bg == other.bg &&
|
15
|
+
bold? == other.bold? &&
|
16
|
+
underline? == other.underline? &&
|
17
|
+
blink? == other.blink?
|
18
|
+
end
|
19
|
+
|
20
|
+
def fg
|
21
|
+
inverse? ? bg_code || DEFAULT_BG_CODE : fg_code
|
22
|
+
end
|
23
|
+
|
24
|
+
def bg
|
25
|
+
inverse? ? fg_code || DEFAULT_FG_CODE : bg_code
|
26
|
+
end
|
27
|
+
|
28
|
+
def bold?
|
29
|
+
!!attributes[:bold]
|
30
|
+
end
|
31
|
+
|
32
|
+
def underline?
|
33
|
+
!!attributes[:underline]
|
34
|
+
end
|
35
|
+
|
36
|
+
def inverse?
|
37
|
+
!!attributes[:inverse]
|
38
|
+
end
|
39
|
+
|
40
|
+
def blink?
|
41
|
+
!!attributes[:blink]
|
42
|
+
end
|
43
|
+
|
44
|
+
def default?
|
45
|
+
fg.nil? && bg.nil? && !bold? && !underline? && !inverse? && !blink?
|
46
|
+
end
|
47
|
+
|
48
|
+
def as_json(*)
|
49
|
+
attributes.slice(*ALLOWED_ATTRIBUTES)
|
50
|
+
end
|
51
|
+
|
52
|
+
protected
|
53
|
+
|
54
|
+
attr_reader :attributes
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def fg_code
|
59
|
+
calculate_code(:fg, bold?)
|
60
|
+
end
|
61
|
+
|
62
|
+
def bg_code
|
63
|
+
calculate_code(:bg, blink?)
|
64
|
+
end
|
65
|
+
|
66
|
+
def calculate_code(attr_name, strong)
|
67
|
+
code = attributes[attr_name]
|
68
|
+
|
69
|
+
if code
|
70
|
+
if code < 8 && strong
|
71
|
+
code += 8
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
code
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'active_support/core_ext/module/delegation.rb'
|
2
|
+
|
3
|
+
class Cell
|
4
|
+
|
5
|
+
attr_reader :text, :brush
|
6
|
+
|
7
|
+
delegate :size, :to => :text
|
8
|
+
|
9
|
+
def initialize(text, brush)
|
10
|
+
@text = text
|
11
|
+
@brush = brush
|
12
|
+
end
|
13
|
+
|
14
|
+
def empty?
|
15
|
+
text.blank? && brush.default?
|
16
|
+
end
|
17
|
+
|
18
|
+
def ==(other)
|
19
|
+
text == other.text && brush == other.brush
|
20
|
+
end
|
21
|
+
|
22
|
+
def [](*args)
|
23
|
+
self.class.new(text[*args], brush)
|
24
|
+
end
|
25
|
+
|
26
|
+
def as_json(*)
|
27
|
+
[text, brush.as_json]
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_f
|
31
|
+
size
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class Cursor
|
2
|
+
|
3
|
+
attr_reader :x, :y, :visible
|
4
|
+
|
5
|
+
def initialize(x, y, visible)
|
6
|
+
@x, @y, @visible = x, y, visible
|
7
|
+
end
|
8
|
+
|
9
|
+
def diff(other)
|
10
|
+
diff = {}
|
11
|
+
diff[:x] = x if other && x != other.x || other.nil?
|
12
|
+
diff[:y] = y if other && y != other.y || other.nil?
|
13
|
+
diff[:visible] = visible if other && visible != other.visible || other.nil?
|
14
|
+
|
15
|
+
diff
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'asciinema/frame'
|
2
|
+
require 'asciinema/frame_diff_list'
|
3
|
+
|
4
|
+
class Film
|
5
|
+
|
6
|
+
def initialize(stdout, terminal)
|
7
|
+
@stdout = stdout
|
8
|
+
@terminal = terminal
|
9
|
+
end
|
10
|
+
|
11
|
+
def snapshot_at(time)
|
12
|
+
stdout_each_until(time) do |delay, data|
|
13
|
+
terminal.feed(data)
|
14
|
+
end
|
15
|
+
|
16
|
+
terminal.snapshot
|
17
|
+
end
|
18
|
+
|
19
|
+
def frames
|
20
|
+
frames = stdout.map do |delay, data|
|
21
|
+
terminal.feed(data)
|
22
|
+
[delay, Frame.new(terminal.snapshot, terminal.cursor)]
|
23
|
+
end
|
24
|
+
|
25
|
+
FrameDiffList.new(frames)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def stdout_each_until(seconds)
|
31
|
+
stdout.each do |delay, frame_data|
|
32
|
+
seconds -= delay
|
33
|
+
break if seconds <= 0
|
34
|
+
yield(delay, frame_data)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
attr_reader :stdout, :terminal
|
39
|
+
|
40
|
+
end
|