shelr 0.11.9 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/Gemfile +13 -12
- data/Gemfile.lock +21 -11
- data/Guardfile +24 -0
- data/lib/shelr/publisher.rb +15 -15
- data/lib/shelr/recorder.rb +15 -33
- data/lib/shelr/terminal.rb +9 -0
- data/lib/shelr/version.rb +1 -1
- data/lib/shelr.rb +5 -0
- data/spec/shelr/publisher_spec.rb +8 -6
- data/spec/shelr/recorder_spec.rb +43 -4
- data/spec/shelr/terminal_spec.rb +11 -0
- data/spec/shelr_spec.rb +7 -3
- data/spec/spec_helper.rb +4 -7
- data/spec/support/fixture.rb +6 -0
- metadata +14 -5
data/Gemfile
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
-
gem '
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
3
|
+
gem 'rake'
|
4
|
+
|
5
|
+
gem "pry"
|
6
|
+
gem "pry-nav"
|
7
|
+
|
8
|
+
gem "rspec"
|
9
|
+
gem "aruba", :git => 'git://github.com/cucumber/aruba.git'
|
10
|
+
gem "simplecov"
|
11
|
+
|
12
|
+
gem "guard"
|
13
|
+
gem 'guard-rspec'
|
14
|
+
gem 'guard-bundler'
|
15
|
+
gem 'guard-ctags-bundler', git: 'git://github.com/antono/guard-ctags-bundler.git'
|
data/Gemfile.lock
CHANGED
@@ -1,3 +1,10 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/antono/guard-ctags-bundler.git
|
3
|
+
revision: 819284437cd2b98b219675bedf42f9aa36838096
|
4
|
+
specs:
|
5
|
+
guard-ctags-bundler (0.0.5)
|
6
|
+
guard (>= 0.4.0)
|
7
|
+
|
1
8
|
GIT
|
2
9
|
remote: git://github.com/cucumber/aruba.git
|
3
10
|
revision: fabc92edc45bf42b5747a0937dd31511d4ab774a
|
@@ -25,12 +32,14 @@ GEM
|
|
25
32
|
ffi (1.0.11)
|
26
33
|
gherkin (2.9.0)
|
27
34
|
json (>= 1.4.6)
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
35
|
+
guard (1.0.1)
|
36
|
+
ffi (>= 0.5.0)
|
37
|
+
thor (~> 0.14.6)
|
38
|
+
guard-bundler (0.1.3)
|
39
|
+
bundler (>= 1.0.0)
|
40
|
+
guard (>= 0.2.2)
|
41
|
+
guard-rspec (0.7.0)
|
42
|
+
guard (>= 0.10.0)
|
34
43
|
json (1.6.5)
|
35
44
|
method_source (0.7.1)
|
36
45
|
multi_json (1.1.0)
|
@@ -41,8 +50,6 @@ GEM
|
|
41
50
|
pry-nav (0.1.0)
|
42
51
|
pry (~> 0.9.8.1)
|
43
52
|
rake (0.9.2.2)
|
44
|
-
rdoc (3.12)
|
45
|
-
json (~> 1.4)
|
46
53
|
rspec (2.8.0)
|
47
54
|
rspec-core (~> 2.8.0)
|
48
55
|
rspec-expectations (~> 2.8.0)
|
@@ -57,16 +64,19 @@ GEM
|
|
57
64
|
simplecov-html (0.5.3)
|
58
65
|
slop (2.4.4)
|
59
66
|
term-ansicolor (1.0.7)
|
67
|
+
thor (0.14.6)
|
60
68
|
|
61
69
|
PLATFORMS
|
62
70
|
ruby
|
63
71
|
|
64
72
|
DEPENDENCIES
|
65
73
|
aruba!
|
66
|
-
|
67
|
-
|
68
|
-
|
74
|
+
guard
|
75
|
+
guard-bundler
|
76
|
+
guard-ctags-bundler!
|
77
|
+
guard-rspec
|
69
78
|
pry
|
70
79
|
pry-nav
|
80
|
+
rake
|
71
81
|
rspec
|
72
82
|
simplecov
|
data/Guardfile
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'bundler' do
|
5
|
+
watch('Gemfile')
|
6
|
+
# Uncomment next line if Gemfile contain `gemspec' command
|
7
|
+
# watch(/^.+\.gemspec/)
|
8
|
+
end
|
9
|
+
|
10
|
+
guard 'ctags-bundler' do
|
11
|
+
watch(%r{^(app|lib|spec/support)/.*\.rb$}) { ["app", "lib", "spec/support"] }
|
12
|
+
watch('Gemfile.lock')
|
13
|
+
end
|
14
|
+
|
15
|
+
guard 'rspec', :version => 2 do
|
16
|
+
watch(%r{^spec/.+_spec\.rb$})
|
17
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
18
|
+
watch('spec/spec_helper.rb') { "spec" }
|
19
|
+
|
20
|
+
# Rails example
|
21
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
22
|
+
watch(%r{^lib/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
23
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
24
|
+
end
|
data/lib/shelr/publisher.rb
CHANGED
@@ -18,7 +18,7 @@ module Shelr
|
|
18
18
|
File.open(dump_filename, 'w+') do |f|
|
19
19
|
f.puts(prepare(id))
|
20
20
|
end
|
21
|
-
puts "=> record dumped to #{dump_filename}"
|
21
|
+
STDOUT.puts "=> record dumped to #{dump_filename}"
|
22
22
|
end
|
23
23
|
end
|
24
24
|
|
@@ -27,18 +27,18 @@ module Shelr
|
|
27
27
|
def with_exception_handler(&block)
|
28
28
|
yield
|
29
29
|
rescue => e
|
30
|
-
puts "=> Something went wrong..."
|
31
|
-
puts e.message
|
32
|
-
puts e.backtrace.join("\n")
|
30
|
+
STDOUT.puts "=> Something went wrong..."
|
31
|
+
STDOUT.puts e.message
|
32
|
+
STDOUT.puts e.backtrace.join("\n")
|
33
33
|
end
|
34
34
|
|
35
35
|
def handle_response(res)
|
36
36
|
res = JSON.parse(res.body)
|
37
37
|
if res['ok']
|
38
|
-
puts res['message']
|
39
|
-
puts Shelr::API_URL + '/records/' + res['id']
|
38
|
+
STDOUT.puts res['message']
|
39
|
+
STDOUT.puts Shelr::API_URL + '/records/' + res['id']
|
40
40
|
else
|
41
|
-
puts res['message']
|
41
|
+
STDOUT.puts res['message']
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
@@ -48,7 +48,7 @@ module Shelr
|
|
48
48
|
|
49
49
|
def api_key
|
50
50
|
unless Shelr.api_key
|
51
|
-
print 'Paste your API KEY [or Enter to publish as Anonymous]: '
|
51
|
+
STDOUT.print 'Paste your API KEY [or Enter to publish as Anonymous]: '
|
52
52
|
key = STDIN.gets.strip
|
53
53
|
Shelr.api_key = key unless key.empty?
|
54
54
|
end
|
@@ -56,11 +56,11 @@ module Shelr
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def prepare(id)
|
59
|
-
puts
|
60
|
-
puts 'Your record will be published under terms of'
|
61
|
-
puts 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
62
|
-
puts 'See http://creativecommons.org/licenses/by-sa/3.0/ for details.'
|
63
|
-
puts
|
59
|
+
STDOUT.puts
|
60
|
+
STDOUT.puts 'Your record will be published under terms of'
|
61
|
+
STDOUT.puts 'Creative Commons Attribution-ShareAlike 3.0 Unported'
|
62
|
+
STDOUT.puts 'See http://creativecommons.org/licenses/by-sa/3.0/ for details.'
|
63
|
+
STDOUT.puts
|
64
64
|
|
65
65
|
out = {}
|
66
66
|
['meta', 'timing', 'typescript'].each do |file|
|
@@ -69,9 +69,9 @@ module Shelr
|
|
69
69
|
|
70
70
|
meta = JSON.parse(out.delete('meta'))
|
71
71
|
meta.each { |k,v| out[k] = v }
|
72
|
-
print 'Description: '
|
72
|
+
STDOUT.print 'Description: '
|
73
73
|
out['description'] = STDIN.gets.strip
|
74
|
-
print 'Tags (ex: howto, linux): '
|
74
|
+
STDOUT.print 'Tags (ex: howto, linux): '
|
75
75
|
out['tags'] = STDIN.gets.strip
|
76
76
|
return out.to_json
|
77
77
|
end
|
data/lib/shelr/recorder.rb
CHANGED
@@ -2,23 +2,7 @@
|
|
2
2
|
module Shelr
|
3
3
|
class Recorder
|
4
4
|
|
5
|
-
|
6
|
-
____ _ _
|
7
|
-
| _ \ ___ ___ ___ _ __ __| (_)_ __ __ _
|
8
|
-
| |_) / _ \/ __/ _ \| '__/ _` | | '_ \ / _` |
|
9
|
-
| _ < __/ (_| (_) | | | (_| | | | | | (_| |
|
10
|
-
|_| \_\___|\___\___/|_| \__,_|_|_| |_|\__, |
|
11
|
-
|___/
|
12
|
-
EOH
|
13
|
-
|
14
|
-
FOOTER = <<-EOF
|
15
|
-
_____ _ _ _ _
|
16
|
-
| ___(_)_ __ (_)___| |__ ___ __| |
|
17
|
-
| |_ | | '_ \| / __| '_ \ / _ \/ _` |
|
18
|
-
| _| | | | | | \__ \ | | | __/ (_| |
|
19
|
-
|_| |_|_| |_|_|___/_| |_|\___|\__,_|
|
20
|
-
|
21
|
-
EOF
|
5
|
+
attr_accessor :meta, :user_rows, :user_columns
|
22
6
|
|
23
7
|
def self.record!
|
24
8
|
new.record!
|
@@ -30,27 +14,28 @@ module Shelr
|
|
30
14
|
|
31
15
|
def record!
|
32
16
|
check_record_dir
|
17
|
+
init_terminal
|
33
18
|
request_metadata
|
34
|
-
puts
|
35
|
-
puts "Your session started"
|
36
|
-
puts "
|
19
|
+
STDOUT.puts "-=" * (Shelr.terminal.size[:width] / 2)
|
20
|
+
STDOUT.puts "=> Your session started"
|
21
|
+
STDOUT.puts "=> Please, do not resize your terminal while recording"
|
22
|
+
STDOUT.puts "=> Press Ctrl+D or 'exit' to finish recording"
|
37
23
|
system(recorder_cmd)
|
38
|
-
restore_terminal
|
39
24
|
save_as_typescript if Shelr.backend == 'ttyrec'
|
40
|
-
puts
|
41
|
-
puts
|
42
|
-
puts
|
43
|
-
puts "
|
25
|
+
STDOUT.puts "-=" * (Shelr.terminal.size[:width] / 2)
|
26
|
+
STDOUT.puts "=> Session finished"
|
27
|
+
STDOUT.puts
|
28
|
+
STDOUT.puts "Replay : #{Shelr::APP_NAME} play last"
|
29
|
+
STDOUT.puts "Publish : #{Shelr::APP_NAME} push last"
|
44
30
|
end
|
45
31
|
|
46
32
|
def request_metadata
|
47
|
-
|
48
|
-
print "Provide HUMAN NAME for Your record: "
|
33
|
+
STDOUT.print "Provide HUMAN NAME for Your record: "
|
49
34
|
@meta["title"] = STDIN.gets.strip
|
50
35
|
@meta["created_at"] = record_id
|
51
36
|
@meta["columns"] = @user_columns
|
52
37
|
@meta["rows"] = @user_rows
|
53
|
-
puts record_file('meta')
|
38
|
+
STDOUT.puts record_file('meta')
|
54
39
|
File.open(record_file('meta'), 'w+') do |meta|
|
55
40
|
meta.puts @meta.to_json
|
56
41
|
end
|
@@ -77,11 +62,8 @@ module Shelr
|
|
77
62
|
end
|
78
63
|
|
79
64
|
def init_terminal
|
80
|
-
@user_rows, @user_columns =
|
81
|
-
|
82
|
-
|
83
|
-
def restore_terminal
|
84
|
-
# system("stty columns #{@user_columns} rows #{@user_rows}")
|
65
|
+
@user_rows, @user_columns =
|
66
|
+
Shelr.terminal.size[:height], Shelr.terminal.size[:width]
|
85
67
|
end
|
86
68
|
|
87
69
|
def record_dir
|
data/lib/shelr/version.rb
CHANGED
data/lib/shelr.rb
CHANGED
@@ -16,6 +16,7 @@ module Shelr
|
|
16
16
|
autoload :Player, 'shelr/player.rb'
|
17
17
|
autoload :Publisher, 'shelr/publisher.rb'
|
18
18
|
autoload :TTYRec, 'shelr/ttyrec.rb'
|
19
|
+
autoload :Terminal, 'shelr/terminal.rb'
|
19
20
|
autoload :VERSION, 'shelr/version.rb'
|
20
21
|
|
21
22
|
class << self
|
@@ -51,6 +52,10 @@ module Shelr
|
|
51
52
|
File.basename(Dir[File.join(DATA_DIR, '*')].sort.last)
|
52
53
|
end
|
53
54
|
|
55
|
+
def terminal
|
56
|
+
@terminal ||= Shelr::Terminal.new
|
57
|
+
end
|
58
|
+
|
54
59
|
private
|
55
60
|
|
56
61
|
def ensure_config_dir_exist
|
@@ -1,16 +1,18 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Shelr::Publisher do
|
4
|
-
before
|
4
|
+
before do
|
5
|
+
STDOUT.stub(:puts)
|
6
|
+
STDOUT.stub(:print)
|
5
7
|
File.stub(:open)
|
6
8
|
Net::HTTP.stub(:post_form)
|
7
9
|
end
|
8
10
|
|
9
11
|
describe "#publish(id)" do
|
10
|
-
it "
|
12
|
+
it "prepares record as json" do
|
11
13
|
STDIN.stub(:gets).and_return('something')
|
12
14
|
subject.stub(:handle_response)
|
13
|
-
subject.stub(:prepare).and_return(
|
15
|
+
subject.stub(:prepare).and_return(Fixture::load('record1.json'))
|
14
16
|
subject.should_receive(:prepare).with('hello')
|
15
17
|
|
16
18
|
subject.publish('hello')
|
@@ -18,18 +20,18 @@ describe Shelr::Publisher do
|
|
18
20
|
end
|
19
21
|
|
20
22
|
describe "#dump_filename" do
|
21
|
-
it "
|
23
|
+
it "returns `pwd` + /shelr-record.json" do
|
22
24
|
subject.send(:dump_filename).should == File.join(Dir.getwd, 'shelr-record.json')
|
23
25
|
end
|
24
26
|
end
|
25
27
|
|
26
28
|
describe "#dump(id)" do
|
27
|
-
before
|
29
|
+
before do
|
28
30
|
@file = mock('dump file')
|
29
31
|
File.stub(:open).and_yield @file
|
30
32
|
end
|
31
33
|
|
32
|
-
it "
|
34
|
+
it "saves prepared dump to #dump_filename" do
|
33
35
|
File.should_receive(:open).with(subject.send(:dump_filename), 'w+')
|
34
36
|
subject.should_receive(:prepare).with('hello').and_return('dump')
|
35
37
|
@file.should_receive(:puts).with('dump')
|
data/spec/shelr/recorder_spec.rb
CHANGED
@@ -2,19 +2,58 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
describe Shelr::Recorder do
|
4
4
|
|
5
|
-
before
|
5
|
+
before do
|
6
6
|
STDIN.stub(:gets).and_return('my shellcast')
|
7
7
|
Shelr.backend = 'script'
|
8
|
+
STDOUT.stub(:puts)
|
9
|
+
STDOUT.stub(:print)
|
8
10
|
end
|
9
11
|
|
10
12
|
describe "#record!" do
|
11
|
-
before
|
13
|
+
before do
|
12
14
|
subject.stub(:system).with(anything).and_return(true)
|
13
15
|
end
|
14
16
|
|
15
|
-
it "
|
16
|
-
subject.should_receive(
|
17
|
+
it "starts script session" do
|
18
|
+
subject.should_receive(:system).with(/script/)
|
17
19
|
subject.record!
|
18
20
|
end
|
19
21
|
end
|
22
|
+
|
23
|
+
describe "#request_metadata" do
|
24
|
+
before do
|
25
|
+
STDIN.stub(:gets => 'Hello')
|
26
|
+
subject.stub(:record_id => "1")
|
27
|
+
File.stub(:open => true)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "adds columns and rows to @meta" do
|
31
|
+
subject.user_rows = 10
|
32
|
+
subject.user_columns = 20
|
33
|
+
subject.request_metadata
|
34
|
+
subject.meta["rows"].should == 10
|
35
|
+
subject.meta["columns"].should == 20
|
36
|
+
end
|
37
|
+
|
38
|
+
it "adds record_id to @meta as created_at" do
|
39
|
+
subject.stub(:record_id => 'ololo')
|
40
|
+
subject.request_metadata
|
41
|
+
subject.meta["created_at"].should == 'ololo'
|
42
|
+
end
|
43
|
+
|
44
|
+
it "reads title from stdin" do
|
45
|
+
STDIN.stub(:gets => 'C00l title')
|
46
|
+
subject.request_metadata
|
47
|
+
subject.meta["title"].should == 'C00l title'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "#init_terminal" do
|
52
|
+
it "gets user_columns and user_rows from system" do
|
53
|
+
Shelr.stub(:terminal).and_return(mock(:size => { :width => 10, :height => 20 }))
|
54
|
+
subject.send :init_terminal
|
55
|
+
subject.user_rows.should == 20
|
56
|
+
subject.user_columns.should == 10
|
57
|
+
end
|
58
|
+
end
|
20
59
|
end
|
data/spec/shelr_spec.rb
CHANGED
@@ -1,15 +1,19 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Shelr do
|
4
|
-
it "
|
4
|
+
it "has ::APP_NAME defined" do
|
5
5
|
Shelr::APP_NAME.should_not be_nil
|
6
6
|
end
|
7
7
|
|
8
|
-
it "
|
8
|
+
it "provides XDG and APP_NAME based ::DATA_DIR" do
|
9
9
|
Shelr::DATA_DIR.should == File.join(ENV['XDG_DATA_HOME'], Shelr::APP_NAME)
|
10
10
|
end
|
11
11
|
|
12
|
-
it "
|
12
|
+
it "provides XDG config path" do
|
13
13
|
Shelr::CONFIG_DIR.should == File.join(ENV['XDG_CONFIG_HOME'], Shelr::APP_NAME)
|
14
14
|
end
|
15
|
+
|
16
|
+
it "provides terminal info" do
|
17
|
+
Shelr.terminal.should be_a(Shelr::Terminal)
|
18
|
+
end
|
15
19
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -26,13 +26,10 @@ require "bundler/setup"
|
|
26
26
|
require 'pry'
|
27
27
|
require 'pry-nav'
|
28
28
|
|
29
|
+
RSpec.configure do |config|
|
30
|
+
config.mock_with :rspec
|
31
|
+
end
|
32
|
+
|
29
33
|
# Requires supporting files with custom matchers and macros, etc,
|
30
34
|
# in ./support/ and its subdirectories.
|
31
35
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
32
|
-
|
33
|
-
def fixture(name)
|
34
|
-
file = File.join(File.expand_path("../fixtures/#{name}", __FILE__))
|
35
|
-
require 'pp'
|
36
|
-
pp file
|
37
|
-
JSON.parse(File.read(file))
|
38
|
-
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shelr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.12.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -11,11 +11,11 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2012-
|
14
|
+
date: 2012-04-01 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: json
|
18
|
-
requirement:
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
19
|
none: false
|
20
20
|
requirements:
|
21
21
|
- - ! '>='
|
@@ -23,7 +23,12 @@ dependencies:
|
|
23
23
|
version: 1.6.5
|
24
24
|
type: :runtime
|
25
25
|
prerelease: false
|
26
|
-
version_requirements:
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ! '>='
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: 1.6.5
|
27
32
|
description: Screencast utility for unix shell junkies
|
28
33
|
email: self@antono.info
|
29
34
|
executables:
|
@@ -37,6 +42,7 @@ files:
|
|
37
42
|
- .rvmrc
|
38
43
|
- Gemfile
|
39
44
|
- Gemfile.lock
|
45
|
+
- Guardfile
|
40
46
|
- LICENSE.txt
|
41
47
|
- README.md
|
42
48
|
- Rakefile
|
@@ -49,6 +55,7 @@ files:
|
|
49
55
|
- lib/shelr/player.rb
|
50
56
|
- lib/shelr/publisher.rb
|
51
57
|
- lib/shelr/recorder.rb
|
58
|
+
- lib/shelr/terminal.rb
|
52
59
|
- lib/shelr/ttyrec.rb
|
53
60
|
- lib/shelr/version.rb
|
54
61
|
- shelr.1
|
@@ -58,8 +65,10 @@ files:
|
|
58
65
|
- spec/shelr/player_spec.rb
|
59
66
|
- spec/shelr/publisher_spec.rb
|
60
67
|
- spec/shelr/recorder_spec.rb
|
68
|
+
- spec/shelr/terminal_spec.rb
|
61
69
|
- spec/shelr_spec.rb
|
62
70
|
- spec/spec_helper.rb
|
71
|
+
- spec/support/fixture.rb
|
63
72
|
homepage: http://shelr.tv/
|
64
73
|
licenses:
|
65
74
|
- GPLv3
|
@@ -81,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
81
90
|
version: '0'
|
82
91
|
requirements: []
|
83
92
|
rubyforge_project:
|
84
|
-
rubygems_version: 1.8.
|
93
|
+
rubygems_version: 1.8.21
|
85
94
|
signing_key:
|
86
95
|
specification_version: 3
|
87
96
|
summary: Screencasts for Shell Ninjas
|