headless 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/CHANGELOG +6 -1
- data/Gemfile +4 -0
- data/README.md +31 -3
- data/Rakefile +1 -0
- data/headless.gemspec +18 -0
- data/lib/headless.rb +43 -21
- data/lib/headless/cli_util.rb +52 -0
- data/lib/headless/video/video_recorder.rb +46 -0
- data/spec/headless_spec.rb +125 -0
- data/spec/video_recorder_spec.rb +60 -0
- metadata +33 -11
data/.gitignore
ADDED
data/CHANGELOG
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
-
## 0.1
|
1
|
+
## 0.2.1 (2011-08-26)
|
2
|
+
* added ability to capture screenshots (from https://github.com/iafonov/headless)
|
3
|
+
* added ability to capture video (from https://github.com/iafonov/headless)
|
4
|
+
* fixed issue with stray pidfile
|
5
|
+
|
6
|
+
## 0.1.0 (2010-08-15)
|
2
7
|
* introduced options
|
3
8
|
* make it possible to change virtual screen dimensions and pixel depth
|
data/Gemfile
ADDED
data/README.md
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# Headless
|
2
2
|
|
3
3
|
Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.
|
4
|
+
It can also capture images and video from the virtual framebuffer.
|
4
5
|
|
5
6
|
I created it so I can run Selenium tests in Cucumber without any shell scripting. Even more, you can go headless only when you run tests against Selenium.
|
6
7
|
Other possible uses include pdf generation with `wkhtmltopdf`, or screenshotting.
|
7
8
|
|
9
|
+
Documentation is available at [rdoc.info](http://rdoc.info/projects/leonid-shevtsov/headless)
|
10
|
+
|
8
11
|
## Installation
|
9
12
|
|
10
13
|
On Debian/Ubuntu:
|
@@ -52,12 +55,37 @@ Running cucumber headless is now as simple as adding a before and after hook in
|
|
52
55
|
|
53
56
|
headless = Headless.new
|
54
57
|
headless.start
|
58
|
+
end
|
59
|
+
|
60
|
+
## Capturing video
|
61
|
+
|
62
|
+
Video is captured using `ffmpeg`. You can install it on Debian/Ubuntu via `sudo apt-get install ffmpeg` or on OS X via `brew install ffmpeg`. You can capture video continuously or capture scenarios separately. Here is typical use case:
|
55
63
|
|
56
|
-
|
57
|
-
|
64
|
+
require 'headless'
|
65
|
+
|
66
|
+
headless = Headless.new
|
67
|
+
headless.start
|
68
|
+
|
69
|
+
Before do
|
70
|
+
headless.video.start_capture
|
71
|
+
end
|
72
|
+
|
73
|
+
After do |scenario|
|
74
|
+
if scenario.failed?
|
75
|
+
headless.video.stop_and_save("/tmp/#{BUILD_ID}/#{scenario.name.split.join("_")}.mov")
|
76
|
+
else
|
77
|
+
headless.video.stop_and_discard
|
58
78
|
end
|
59
79
|
end
|
60
80
|
|
81
|
+
## Taking screenshots
|
82
|
+
|
83
|
+
Images are captured using `import` utility which is part of `imagemagick` library. You can install it on Ubuntu via `sudo apt-get install imagemagick`. You can call `headless.take_screenshot` at any time. You have to supply full path to target file. File format is determined by supplied file extension.
|
84
|
+
|
85
|
+
## Contributors
|
86
|
+
|
87
|
+
* [Igor Afonov](http://iafonov.github.com) - video and screenshot capturing functionality.
|
88
|
+
|
61
89
|
---
|
62
90
|
|
63
|
-
©
|
91
|
+
© 2011 Leonid Shevtsov, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/headless.gemspec
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
spec = Gem::Specification.new do |s|
|
2
|
+
s.author = 'Leonid Shevtsov'
|
3
|
+
s.email = 'leonid@shevtsov.me'
|
4
|
+
|
5
|
+
s.name = 'headless'
|
6
|
+
s.version = '0.2.1'
|
7
|
+
s.summary = 'Ruby headless display interface'
|
8
|
+
|
9
|
+
s.description = <<-EOF
|
10
|
+
Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.
|
11
|
+
EOF
|
12
|
+
s.requirements = 'Xvfb'
|
13
|
+
s.homepage = 'http://leonid.shevtsov.me/en/headless'
|
14
|
+
|
15
|
+
s.files = `git ls-files`.split("\n")
|
16
|
+
|
17
|
+
s.add_development_dependency "rspec", "~> 2.6"
|
18
|
+
end
|
data/lib/headless.rb
CHANGED
@@ -1,3 +1,6 @@
|
|
1
|
+
require 'headless/cli_util'
|
2
|
+
require 'headless/video/video_recorder'
|
3
|
+
|
1
4
|
# A class incapsulating the creation and usage of a headless X server
|
2
5
|
#
|
3
6
|
# == Prerequisites
|
@@ -38,7 +41,7 @@
|
|
38
41
|
#++
|
39
42
|
class Headless
|
40
43
|
|
41
|
-
class Exception <
|
44
|
+
class Exception < RuntimeError
|
42
45
|
end
|
43
46
|
|
44
47
|
# The display number
|
@@ -53,29 +56,31 @@ class Headless
|
|
53
56
|
# * +display+ (default 99) - what display number to listen to;
|
54
57
|
# * +reuse+ (default true) - if given display server already exists, should we use it or fail miserably?
|
55
58
|
# * +dimensions+ (default 1280x1024x24) - display dimensions and depth. Not all combinations are possible, refer to +man Xvfb+.
|
59
|
+
# * +destroy_at_exit+ (default true) - if a display is started but not stopped, should it be destroyed when the script finishes?
|
56
60
|
def initialize(options = {})
|
57
|
-
|
61
|
+
CliUtil.ensure_application_exists!('Xvfb', 'Xvfb not found on your system')
|
58
62
|
|
59
63
|
@display = options.fetch(:display, 99).to_i
|
60
64
|
@reuse_display = options.fetch(:reuse, true)
|
61
65
|
@dimensions = options.fetch(:dimensions, '1280x1024x24')
|
66
|
+
@video_capture_options = options.fetch(:video, {})
|
67
|
+
@destroy_at_exit = options.fetch(:destroy_at_exit, true)
|
62
68
|
|
63
69
|
#TODO more logic here, autopicking the display number
|
64
70
|
if @reuse_display
|
65
|
-
launch_xvfb unless
|
66
|
-
elsif
|
67
|
-
raise Exception.new("Display :#{display} is already taken and reuse=false")
|
71
|
+
launch_xvfb unless xvfb_running?
|
72
|
+
elsif xvfb_running?
|
73
|
+
raise Headless::Exception.new("Display :#{display} is already taken and reuse=false")
|
68
74
|
else
|
69
75
|
launch_xvfb
|
70
76
|
end
|
71
|
-
|
72
|
-
raise Exception.new("Xvfb did not launch - something's wrong") unless read_pid
|
73
77
|
end
|
74
78
|
|
75
79
|
# Switches to the headless server
|
76
80
|
def start
|
77
81
|
@old_display = ENV['DISPLAY']
|
78
82
|
ENV['DISPLAY'] = ":#{display}"
|
83
|
+
hook_at_exit
|
79
84
|
end
|
80
85
|
|
81
86
|
# Switches back from the headless server
|
@@ -86,7 +91,7 @@ class Headless
|
|
86
91
|
# Switches back from the headless server and terminates the headless session
|
87
92
|
def destroy
|
88
93
|
stop
|
89
|
-
|
94
|
+
CliUtil.kill_process(pid_filename)
|
90
95
|
end
|
91
96
|
|
92
97
|
# Block syntax:
|
@@ -101,27 +106,44 @@ class Headless
|
|
101
106
|
yield headless
|
102
107
|
headless.destroy
|
103
108
|
end
|
104
|
-
|
105
109
|
class <<self; alias_method :ly, :run; end
|
106
110
|
|
107
|
-
|
108
|
-
|
111
|
+
def video
|
112
|
+
@video_recorder ||= VideoRecorder.new(display, dimensions, @video_capture_options)
|
113
|
+
end
|
114
|
+
|
115
|
+
def take_screenshot(file_path)
|
116
|
+
CliUtil.ensure_application_exists!('import', "imagemagick not found on your system. Please install it using sudo apt-get install imagemagick")
|
109
117
|
|
110
|
-
|
111
|
-
@xvfb = `which Xvfb`.strip
|
112
|
-
raise Exception.new('Xvfb not found on your system') if @xvfb == ''
|
118
|
+
system "#{CliUtil.path_to('import')} -display localhost:#{display} -window root #{file_path}"
|
113
119
|
end
|
114
120
|
|
121
|
+
private
|
122
|
+
|
115
123
|
def launch_xvfb
|
116
124
|
#TODO error reporting
|
117
|
-
system "#{
|
118
|
-
|
125
|
+
result = system "#{CliUtil.path_to("Xvfb")} :#{display} -screen 0 #{dimensions} -ac >/dev/null 2>&1 &"
|
126
|
+
raise Headless::Exception.new("Xvfb did not launch - something's wrong") unless result
|
127
|
+
end
|
128
|
+
|
129
|
+
def xvfb_running?
|
130
|
+
!!read_xvfb_pid
|
119
131
|
end
|
120
132
|
|
121
|
-
def
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
133
|
+
def pid_filename
|
134
|
+
"/tmp/.X#{display}-lock"
|
135
|
+
end
|
136
|
+
|
137
|
+
def read_xvfb_pid
|
138
|
+
CliUtil.read_pid(pid_filename)
|
139
|
+
end
|
140
|
+
|
141
|
+
def hook_at_exit
|
142
|
+
unless @at_exit_hook_installed
|
143
|
+
@at_exit_hook_installed = true
|
144
|
+
at_exit do
|
145
|
+
destroy if @destroy_at_exit
|
146
|
+
end
|
147
|
+
end
|
126
148
|
end
|
127
149
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
class Headless
|
2
|
+
class CliUtil
|
3
|
+
def self.application_exists?(app)
|
4
|
+
`which #{app}`.strip != ""
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.ensure_application_exists!(app, error_message)
|
8
|
+
if !self.application_exists?(app)
|
9
|
+
raise Headless::Exception.new(error_message)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.path_to(app)
|
14
|
+
`which #{app}`.strip
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.read_pid(pid_filename)
|
18
|
+
pid = (File.read(pid_filename) rescue "").strip.to_i
|
19
|
+
pid == 0 ? nil : pid
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.fork_process(command, pid_filename, log_filename='/dev/null')
|
23
|
+
pid = fork do
|
24
|
+
STDERR.reopen(log_filename)
|
25
|
+
exec command
|
26
|
+
exit! 127 # safeguard in case exec fails
|
27
|
+
end
|
28
|
+
Process.detach(pid)
|
29
|
+
|
30
|
+
File.open pid_filename, 'w' do |f|
|
31
|
+
f.puts pid
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.kill_process(pid_filename, options={})
|
36
|
+
if pid = self.read_pid(pid_filename)
|
37
|
+
begin
|
38
|
+
Process.kill 'TERM', pid
|
39
|
+
Process.wait pid if options[:wait]
|
40
|
+
rescue Errno::ESRCH
|
41
|
+
# no such process; assume it's already killed
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
begin
|
46
|
+
FileUtils.rm pid_filename
|
47
|
+
rescue Errno::ENOENT
|
48
|
+
# pid file already removed
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'tempfile'
|
2
|
+
|
3
|
+
class Headless
|
4
|
+
class VideoRecorder
|
5
|
+
attr_accessor :pid_file_path, :tmp_file_path, :log_file_path
|
6
|
+
|
7
|
+
def initialize(display, dimensions, options = {})
|
8
|
+
CliUtil.ensure_application_exists!('ffmpeg', 'Ffmpeg not found on your system. Install it with sudo apt-get install ffmpeg')
|
9
|
+
|
10
|
+
@display = display
|
11
|
+
@dimensions = dimensions
|
12
|
+
|
13
|
+
@pid_file_path = options.fetch(:pid_file_path, "/tmp/.headless_ffmpeg_#{@display}.pid")
|
14
|
+
@tmp_file_path = options.fetch(:tmp_file_path, "/tmp/.headless_ffmpeg_#{@display}.mov")
|
15
|
+
@log_file_path = options.fetch(:log_file_path, "/dev/null")
|
16
|
+
end
|
17
|
+
|
18
|
+
def capture_running?
|
19
|
+
!!@capture_running
|
20
|
+
end
|
21
|
+
|
22
|
+
def start_capture
|
23
|
+
CliUtil.fork_process("#{CliUtil.path_to('ffmpeg')} -y -r 30 -g 600 -s #{@dimensions} -f x11grab -i :#{@display} -vcodec qtrle #{@tmp_file_path}", @pid_file_path, @log_file_path)
|
24
|
+
@capture_running = true
|
25
|
+
at_exit do
|
26
|
+
stop_and_discard if capture_running?
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def stop_and_save(path)
|
31
|
+
@capture_running = false
|
32
|
+
CliUtil.kill_process(@pid_file_path, :wait => true)
|
33
|
+
FileUtils.mv(@tmp_file_path, path)
|
34
|
+
end
|
35
|
+
|
36
|
+
def stop_and_discard
|
37
|
+
@capture_running = false
|
38
|
+
CliUtil.kill_process(@pid_file_path, :wait => true)
|
39
|
+
begin
|
40
|
+
FileUtils.rm(@tmp_file_path)
|
41
|
+
rescue Errno::ENOENT
|
42
|
+
# that's ok if the file doesn't exist
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'lib/headless'
|
2
|
+
|
3
|
+
describe Headless do
|
4
|
+
before do
|
5
|
+
ENV['DISPLAY'] = ":31337"
|
6
|
+
stub_environment
|
7
|
+
end
|
8
|
+
|
9
|
+
context "instaniation" do
|
10
|
+
context "when Xvfb is not installed" do
|
11
|
+
before do
|
12
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "raises an error" do
|
16
|
+
lambda { Headless.new }.should raise_error(Headless::Exception)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
context "when Xvfb not started yet" do
|
21
|
+
it "starts Xvfb" do
|
22
|
+
Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1280x1024x24 -ac >/dev/null 2>&1 &").and_return(true)
|
23
|
+
|
24
|
+
headless = Headless.new
|
25
|
+
end
|
26
|
+
|
27
|
+
it "allows setting screen dimensions" do
|
28
|
+
Headless.any_instance.should_receive(:system).with("/usr/bin/Xvfb :99 -screen 0 1024x768x16 -ac >/dev/null 2>&1 &").and_return(true)
|
29
|
+
|
30
|
+
headless = Headless.new(:dimensions => "1024x768x16")
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context "when Xvfb is already running" do
|
35
|
+
before do
|
36
|
+
Headless::CliUtil.stub!(:read_pid).and_return(31337)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "raises an error if reuse display is not allowed" do
|
40
|
+
lambda { Headless.new(:reuse => false) }.should raise_error(Headless::Exception)
|
41
|
+
end
|
42
|
+
|
43
|
+
it "doesn't raise an error if reuse display is allowed" do
|
44
|
+
lambda { Headless.new(:reuse => true) }.should_not raise_error(Headless::Exception)
|
45
|
+
lambda { Headless.new }.should_not raise_error(Headless::Exception)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context "lifecycle" do
|
51
|
+
let(:headless) { Headless.new }
|
52
|
+
describe "#start" do
|
53
|
+
it "switches to the headless server" do
|
54
|
+
ENV['DISPLAY'].should == ":31337"
|
55
|
+
headless.start
|
56
|
+
ENV['DISPLAY'].should == ":99"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
describe "#stop" do
|
61
|
+
it "switches back from the headless server" do
|
62
|
+
ENV['DISPLAY'].should == ":31337"
|
63
|
+
headless.start
|
64
|
+
ENV['DISPLAY'].should == ":99"
|
65
|
+
headless.stop
|
66
|
+
ENV['DISPLAY'].should == ":31337"
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
describe "#destroy" do
|
71
|
+
before do
|
72
|
+
Headless::CliUtil.stub!(:read_pid).and_return(4444)
|
73
|
+
end
|
74
|
+
|
75
|
+
it "switches back from the headless server and terminates the headless session" do
|
76
|
+
Process.should_receive(:kill).with('TERM', 4444)
|
77
|
+
|
78
|
+
ENV['DISPLAY'].should == ":31337"
|
79
|
+
headless.start
|
80
|
+
ENV['DISPLAY'].should == ":99"
|
81
|
+
headless.destroy
|
82
|
+
ENV['DISPLAY'].should == ":31337"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
context "#video" do
|
88
|
+
let(:headless) { Headless.new }
|
89
|
+
|
90
|
+
it "returns video recorder" do
|
91
|
+
headless.video.should be_a_kind_of(Headless::VideoRecorder)
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns the same instance" do
|
95
|
+
recorder = headless.video
|
96
|
+
headless.video.should be_eql(recorder)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context "#take_screenshot" do
|
101
|
+
let(:headless) { Headless.new }
|
102
|
+
|
103
|
+
it "raises an error if imagemagick is not installed" do
|
104
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
105
|
+
|
106
|
+
lambda { headless.take_screenshot }.should raise_error(Headless::Exception)
|
107
|
+
end
|
108
|
+
|
109
|
+
it "issues command to take screenshot" do
|
110
|
+
headless = Headless.new
|
111
|
+
|
112
|
+
Headless.any_instance.should_receive(:system)
|
113
|
+
|
114
|
+
headless.take_screenshot("/tmp/image.png")
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
private
|
119
|
+
|
120
|
+
def stub_environment
|
121
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(true)
|
122
|
+
Headless::CliUtil.stub!(:read_pid).and_return(nil)
|
123
|
+
Headless::CliUtil.stub!(:path_to).and_return("/usr/bin/Xvfb")
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'lib/headless'
|
2
|
+
|
3
|
+
describe Headless::VideoRecorder do
|
4
|
+
before do
|
5
|
+
stub_environment
|
6
|
+
end
|
7
|
+
|
8
|
+
describe "instaniation" do
|
9
|
+
before do
|
10
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(false)
|
11
|
+
end
|
12
|
+
|
13
|
+
it "throws an error if ffmpeg is not installed" do
|
14
|
+
lambda { Headless::VideoRecorder.new(99, "1024x768x32") }.should raise_error(Headless::Exception)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#capture" do
|
19
|
+
it "starts ffmpeg" do
|
20
|
+
Headless::CliUtil.stub(:path_to, 'ffmpeg').and_return('ffmpeg')
|
21
|
+
Headless::CliUtil.should_receive(:fork_process).with(/ffmpeg -y -r 30 -g 600 -s 1024x768x32 -f x11grab -i :99 -vcodec qtrle/, "/tmp/.headless_ffmpeg_99.pid", '/dev/null')
|
22
|
+
|
23
|
+
recorder = Headless::VideoRecorder.new(99, "1024x768x32")
|
24
|
+
recorder.start_capture
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
context "stopping video recording" do
|
29
|
+
subject do
|
30
|
+
recorder = Headless::VideoRecorder.new(99, "1024x768x32", :pid_file_path => "/tmp/pid", :tmp_file_path => "/tmp/ci.mov")
|
31
|
+
recorder.start_capture
|
32
|
+
recorder
|
33
|
+
end
|
34
|
+
|
35
|
+
describe "using #stop_and_save" do
|
36
|
+
it "stops video recording and saves file" do
|
37
|
+
Headless::CliUtil.should_receive(:kill_process).with("/tmp/pid", :wait => true)
|
38
|
+
FileUtils.should_receive(:mv).with("/tmp/ci.mov", "/tmp/test.mov")
|
39
|
+
|
40
|
+
subject.stop_and_save("/tmp/test.mov")
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
describe "using #stop_and_discard" do
|
45
|
+
it "stops video recording and deletes temporary file" do
|
46
|
+
Headless::CliUtil.should_receive(:kill_process).with("/tmp/pid", :wait => true)
|
47
|
+
FileUtils.should_receive(:rm).with("/tmp/ci.mov")
|
48
|
+
|
49
|
+
subject.stop_and_discard
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def stub_environment
|
57
|
+
Headless::CliUtil.stub!(:application_exists?).and_return(true)
|
58
|
+
Headless::CliUtil.stub!(:fork_process).and_return(true)
|
59
|
+
end
|
60
|
+
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: headless
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
5
|
-
prerelease:
|
4
|
+
hash: 21
|
5
|
+
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
+
- 2
|
8
9
|
- 1
|
9
|
-
|
10
|
-
version: 0.1.0
|
10
|
+
version: 0.2.1
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Leonid Shevtsov
|
@@ -15,10 +15,24 @@ autorequire:
|
|
15
15
|
bindir: bin
|
16
16
|
cert_chain: []
|
17
17
|
|
18
|
-
date:
|
18
|
+
date: 2011-08-26 00:00:00 +03:00
|
19
19
|
default_executable:
|
20
|
-
dependencies:
|
21
|
-
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rspec
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 15
|
30
|
+
segments:
|
31
|
+
- 2
|
32
|
+
- 6
|
33
|
+
version: "2.6"
|
34
|
+
type: :development
|
35
|
+
version_requirements: *id001
|
22
36
|
description: " Headless is a Ruby interface for Xvfb. It allows you to create a headless display straight from Ruby code, hiding some low-level action.\n"
|
23
37
|
email: leonid@shevtsov.me
|
24
38
|
executables: []
|
@@ -28,12 +42,20 @@ extensions: []
|
|
28
42
|
extra_rdoc_files: []
|
29
43
|
|
30
44
|
files:
|
31
|
-
-
|
45
|
+
- .gitignore
|
32
46
|
- CHANGELOG
|
33
|
-
-
|
47
|
+
- Gemfile
|
34
48
|
- LICENSE
|
49
|
+
- README.md
|
50
|
+
- Rakefile
|
51
|
+
- headless.gemspec
|
52
|
+
- lib/headless.rb
|
53
|
+
- lib/headless/cli_util.rb
|
54
|
+
- lib/headless/video/video_recorder.rb
|
55
|
+
- spec/headless_spec.rb
|
56
|
+
- spec/video_recorder_spec.rb
|
35
57
|
has_rdoc: true
|
36
|
-
homepage: http://
|
58
|
+
homepage: http://leonid.shevtsov.me/en/headless
|
37
59
|
licenses: []
|
38
60
|
|
39
61
|
post_install_message:
|
@@ -62,7 +84,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
62
84
|
requirements:
|
63
85
|
- Xvfb
|
64
86
|
rubyforge_project:
|
65
|
-
rubygems_version: 1.
|
87
|
+
rubygems_version: 1.6.2
|
66
88
|
signing_key:
|
67
89
|
specification_version: 3
|
68
90
|
summary: Ruby headless display interface
|