dante 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +96 -0
- data/Rakefile +15 -0
- data/dante.gemspec +23 -0
- data/lib/dante.rb +27 -0
- data/lib/dante/runner.rb +153 -0
- data/lib/dante/version.rb +3 -0
- data/test/dante_test.rb +16 -0
- data/test/runner_test.rb +50 -0
- data/test/test_helper.rb +54 -0
- metadata +108 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Miso
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Dante
|
2
|
+
|
3
|
+
Turn any process into a daemon with ease.
|
4
|
+
|
5
|
+
## Why Dante?
|
6
|
+
|
7
|
+
Dante is the simplest possible thing that can work to turn arbitrary ruby code into a 'robust' binary that
|
8
|
+
can be started normally or as a daemon, and will store a pid file automatically. Dante also allows a process
|
9
|
+
to be stopped just as easily using a standardized set of command line options.
|
10
|
+
|
11
|
+
If you need to create a ruby executable and you want standard daemon start/stop with pid files
|
12
|
+
and no hassle, this gem will be a great way to get started.
|
13
|
+
|
14
|
+
## Installation
|
15
|
+
|
16
|
+
Add to your Gemfile:
|
17
|
+
|
18
|
+
```
|
19
|
+
# Gemfile
|
20
|
+
|
21
|
+
gem "dante"
|
22
|
+
```
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
Dante is meant to be used from any "bin" executable. For instance, to create a binary for a web server, create a file in `bin/mysite`:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
#!/usr/bin/env ruby
|
30
|
+
|
31
|
+
require File.expand_path("../../myapp.rb", __FILE__)
|
32
|
+
|
33
|
+
Dante.run('myapp') do
|
34
|
+
Thin::Server.start('0.0.0.0', port) do
|
35
|
+
use Rack::CommonLogger
|
36
|
+
use Rack::ShowExceptions
|
37
|
+
run MyApp
|
38
|
+
end
|
39
|
+
end
|
40
|
+
```
|
41
|
+
|
42
|
+
This gives your binary several useful things for free:
|
43
|
+
|
44
|
+
```
|
45
|
+
./bin/myapp
|
46
|
+
```
|
47
|
+
|
48
|
+
will start the app undaemonized in the terminal, handling trapping and stopping the process.
|
49
|
+
|
50
|
+
```
|
51
|
+
./bin/myapp -d -P /var/run/myapp.pid
|
52
|
+
```
|
53
|
+
|
54
|
+
will daemonize and start the process, storing the pid in the specified pid file.
|
55
|
+
|
56
|
+
```
|
57
|
+
./bin/myapp -k -P /var/run/myapp.pid
|
58
|
+
```
|
59
|
+
|
60
|
+
will stop all daemonized processes for the specified pid file.
|
61
|
+
|
62
|
+
```
|
63
|
+
./bin/myapp --help
|
64
|
+
```
|
65
|
+
|
66
|
+
Will return a useful help banner message explaining the simple usage.
|
67
|
+
|
68
|
+
## God
|
69
|
+
|
70
|
+
Dante can be used well in conjunction with the excellent God process manager. Simply, use Dante to daemonize a process
|
71
|
+
and then you can easily use God to monitor:
|
72
|
+
|
73
|
+
```ruby
|
74
|
+
# /etc/god/myapp.rb
|
75
|
+
|
76
|
+
God.watch do |w|
|
77
|
+
w.name = "myapp"
|
78
|
+
w.interval = 30.seconds
|
79
|
+
w.start = "ruby /path/to/myapp/bin/myapp -d"
|
80
|
+
w.stop = "ruby /path/to/myapp/bin/myapp -k"
|
81
|
+
w.start_grace = 15.seconds
|
82
|
+
w.restart_grace = 15.seconds
|
83
|
+
w.pid_file = "/var/run/myapp.pid"
|
84
|
+
|
85
|
+
w.behavior(:clean_pid_file)
|
86
|
+
|
87
|
+
w.start_if do |start|
|
88
|
+
start.condition(:process_running) do |c|
|
89
|
+
c.interval = 5.seconds
|
90
|
+
c.running = false
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
```
|
95
|
+
|
96
|
+
and that's all. Of course now you can also easily daemonize as well as start/stop the process on the command line as well.
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
# require 'yard'
|
4
|
+
|
5
|
+
task :test do
|
6
|
+
Rake::TestTask.new do |t|
|
7
|
+
t.libs.push "lib"
|
8
|
+
t.test_files = FileList[File.expand_path('../test/**/*_test.rb', __FILE__)]
|
9
|
+
t.verbose = true
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# task :doc do
|
14
|
+
# YARD::CLI::Yardoc.new.run
|
15
|
+
# end
|
data/dante.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "dante/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "dante"
|
7
|
+
s.version = Dante::VERSION
|
8
|
+
s.authors = ["Nathan Esquenazi"]
|
9
|
+
s.email = ["nesquena@gmail.com"]
|
10
|
+
s.homepage = "https://github.com/bazaarlabs/dante"
|
11
|
+
s.summary = %q{Turn any process into a demon}
|
12
|
+
s.description = %q{Turn any process into a demon.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "dante"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency 'rake'
|
22
|
+
s.add_development_dependency 'minitest'
|
23
|
+
end
|
data/lib/dante.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$:.unshift File.expand_path(File.dirname(__FILE__))
|
2
|
+
require "dante/version"
|
3
|
+
require "dante/runner"
|
4
|
+
|
5
|
+
=begin
|
6
|
+
|
7
|
+
Dante.run("process-name") do
|
8
|
+
begin
|
9
|
+
# ...something here
|
10
|
+
rescue Abort
|
11
|
+
# ...shutdown here
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
=end
|
16
|
+
|
17
|
+
module Dante
|
18
|
+
|
19
|
+
# Forks a process and takes some list of params. I don't really know what this does.
|
20
|
+
#
|
21
|
+
# @example
|
22
|
+
# Dante.run("process-name") { Server.run! }
|
23
|
+
#
|
24
|
+
def self.run(name, options={}, &blk)
|
25
|
+
Runner.new(name, options, &blk).execute!
|
26
|
+
end
|
27
|
+
end
|
data/lib/dante/runner.rb
ADDED
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'optparse'
|
3
|
+
require 'yaml'
|
4
|
+
require 'erb'
|
5
|
+
|
6
|
+
=begin
|
7
|
+
|
8
|
+
This is a utility for setting up a binary executable for a service.
|
9
|
+
|
10
|
+
# Dante::Runner.run("buffet", :pid_path => "/var/run/buffet.pid") do
|
11
|
+
# ...startup service here...
|
12
|
+
# end
|
13
|
+
|
14
|
+
=end
|
15
|
+
|
16
|
+
module Dante
|
17
|
+
class Runner
|
18
|
+
# Signal to application that the process is shutting down
|
19
|
+
class Abort < Exception; end
|
20
|
+
|
21
|
+
attr_accessor :options
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def run(*args, &block)
|
25
|
+
self.new(*args, &block)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def initialize(name, defaults={}, &block)
|
30
|
+
@name = name
|
31
|
+
@startup_command = block
|
32
|
+
self.options = {
|
33
|
+
:host => '0.0.0.0',
|
34
|
+
:pid_path => "/var/run/#{@name}.pid"
|
35
|
+
}.merge(defaults)
|
36
|
+
|
37
|
+
parse_options
|
38
|
+
|
39
|
+
if options.include?(:kill)
|
40
|
+
kill_pid(options[:kill] || '*')
|
41
|
+
end
|
42
|
+
|
43
|
+
Process.euid = options[:user] if options[:user]
|
44
|
+
Process.egid = options[:group] if options[:group]
|
45
|
+
end
|
46
|
+
|
47
|
+
# Executes the runner based on options
|
48
|
+
def execute!
|
49
|
+
if !options[:daemonize]
|
50
|
+
start
|
51
|
+
else
|
52
|
+
daemonize
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def start
|
57
|
+
puts "Starting #{@name} service..."
|
58
|
+
|
59
|
+
trap("INT") {
|
60
|
+
stop
|
61
|
+
exit
|
62
|
+
}
|
63
|
+
trap("TERM"){
|
64
|
+
stop
|
65
|
+
exit
|
66
|
+
}
|
67
|
+
|
68
|
+
@startup_command.call
|
69
|
+
end
|
70
|
+
|
71
|
+
def stop
|
72
|
+
raise Abort
|
73
|
+
sleep(1)
|
74
|
+
end
|
75
|
+
|
76
|
+
def parse_options
|
77
|
+
OptionParser.new do |opts|
|
78
|
+
opts.summary_width = 25
|
79
|
+
opts.banner = ["#{@name} (#{VERSION})\n\n",
|
80
|
+
"Usage: #{@name} [-P file] [-d] [-k port]\n",
|
81
|
+
" #{@name} --help\n"].join("")
|
82
|
+
opts.separator ""
|
83
|
+
|
84
|
+
opts.on("-p", "--port PORT", Integer, "Specify port", "(default: #{options[:port]})") do |v|
|
85
|
+
options[:port] = v
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("-P", "--pid FILE", String, "save PID in FILE when using -d option.", "(default: #{options[:pid_path]})") do |v|
|
89
|
+
options[:pid_path] = File.expand_path(v)
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on("-d", "--daemon", "Daemonize mode") do |v|
|
93
|
+
options[:daemonize] = v
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on("-k", "--kill [PORT]", String, "Kill specified running daemons - leave blank to kill all.") do |v|
|
97
|
+
options[:kill] = v
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("-u", "--user USER", String, "User to run as") do |user|
|
101
|
+
options[:user] = user
|
102
|
+
end
|
103
|
+
|
104
|
+
opts.on("-G", "--group GROUP", String, "Group to run as") do |group|
|
105
|
+
options[:group] = group
|
106
|
+
end
|
107
|
+
|
108
|
+
opts.on_tail("-?", "--help", "Display this usage information.") do
|
109
|
+
puts "#{opts}\n"
|
110
|
+
exit
|
111
|
+
end
|
112
|
+
end.parse!
|
113
|
+
options
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
def store_pid(pid)
|
119
|
+
FileUtils.mkdir_p(File.dirname(options[:pid_path]))
|
120
|
+
File.open(options[:pid_path], 'w'){|f| f.write("#{pid}\n")}
|
121
|
+
end
|
122
|
+
|
123
|
+
def kill_pid(k)
|
124
|
+
Dir[options[:pid_path]].each do |f|
|
125
|
+
begin
|
126
|
+
puts f
|
127
|
+
pid = IO.read(f).chomp.to_i
|
128
|
+
FileUtils.rm f
|
129
|
+
Process.kill(9, pid)
|
130
|
+
puts "killed PID: #{pid}"
|
131
|
+
rescue => e
|
132
|
+
puts "Failed to kill! #{k}: #{e}"
|
133
|
+
end
|
134
|
+
end
|
135
|
+
exit
|
136
|
+
end
|
137
|
+
|
138
|
+
def daemonize
|
139
|
+
pid = fork do
|
140
|
+
exit if fork
|
141
|
+
Process.setsid
|
142
|
+
exit if fork
|
143
|
+
store_pid(Process.pid)
|
144
|
+
File.umask 0000
|
145
|
+
STDIN.reopen "/dev/null"
|
146
|
+
STDOUT.reopen "/dev/null", "a"
|
147
|
+
STDERR.reopen STDOUT
|
148
|
+
start
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
end
|
153
|
+
end
|
data/test/dante_test.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "dante module" do
|
4
|
+
before do
|
5
|
+
@process = TestingProcess.new('a')
|
6
|
+
end
|
7
|
+
|
8
|
+
it "can run jobs using #run method" do
|
9
|
+
capture_stdout do
|
10
|
+
Dante.run('test-process') { @process.run_a! }
|
11
|
+
end
|
12
|
+
@output = File.read(@process.tmp_path)
|
13
|
+
assert_match /Started/, @output
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
data/test/runner_test.rb
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
require File.expand_path('../test_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "dante runner" do
|
4
|
+
describe "with no daemonize" do
|
5
|
+
before do
|
6
|
+
@process = TestingProcess.new('a')
|
7
|
+
@runner = Dante::Runner.new('test-process') { @process.run_a! }
|
8
|
+
@stdout = capture_stdout { @runner.execute! }
|
9
|
+
end
|
10
|
+
|
11
|
+
it "prints correct stdout" do
|
12
|
+
assert_match /Starting test-process/, @stdout
|
13
|
+
end
|
14
|
+
|
15
|
+
it "starts successfully when executed" do
|
16
|
+
@output = File.read(@process.tmp_path)
|
17
|
+
assert_match /Started/, @output
|
18
|
+
end
|
19
|
+
end # no daemonize
|
20
|
+
|
21
|
+
describe "with daemonize flag" do
|
22
|
+
before do
|
23
|
+
@process = TestingProcess.new('b')
|
24
|
+
@run_options = { :daemonize => true, :pid_path => "/tmp/dante.pid" }
|
25
|
+
@runner = Dante::Runner.new('test-process-2', @run_options) { @process.run_b! }
|
26
|
+
@stdout = capture_stdout { @runner.execute! }
|
27
|
+
sleep(1)
|
28
|
+
end
|
29
|
+
|
30
|
+
it "can properly handles aborts and starts / stops on INT" do
|
31
|
+
refute_equal 0, @pid = `cat /tmp/dante.pid`.to_i
|
32
|
+
Process.kill "INT", @pid
|
33
|
+
sleep(1) # Wait to complete
|
34
|
+
@output = File.read(@process.tmp_path)
|
35
|
+
assert_match /Started!!/, @output
|
36
|
+
assert_match /Abort!!/, @output
|
37
|
+
assert_match /Closing!!/, @output
|
38
|
+
end
|
39
|
+
|
40
|
+
it "can properly handles aborts and starts / stops on TERM" do
|
41
|
+
refute_equal 0, @pid = `cat /tmp/dante.pid`.to_i
|
42
|
+
Process.kill "TERM", @pid
|
43
|
+
sleep(1) # Wait to complete
|
44
|
+
@output = File.read(@process.tmp_path)
|
45
|
+
assert_match /Started!!/, @output
|
46
|
+
assert_match /Abort!!/, @output
|
47
|
+
assert_match /Closing!!/, @output
|
48
|
+
end
|
49
|
+
end # daemonize
|
50
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'tempfile'
|
3
|
+
require 'minitest/autorun'
|
4
|
+
$:.unshift File.expand_path("../../lib")
|
5
|
+
require 'dante'
|
6
|
+
|
7
|
+
## Kernel Extensions
|
8
|
+
require 'stringio'
|
9
|
+
|
10
|
+
module Kernel
|
11
|
+
# Redirect standard out, standard error and the buffered logger for sprinkle to StringIO
|
12
|
+
# capture_stdout { any_commands; you_want } => "all output from the commands"
|
13
|
+
def capture_stdout
|
14
|
+
return yield if ENV['DEBUG'] # Skip if debug mode
|
15
|
+
|
16
|
+
out = StringIO.new
|
17
|
+
$stdout = out
|
18
|
+
$stderr = out
|
19
|
+
yield
|
20
|
+
return out.string
|
21
|
+
ensure
|
22
|
+
$stdout = STDOUT
|
23
|
+
$stderr = STDERR
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Process fixture
|
28
|
+
class TestingProcess
|
29
|
+
attr_reader :tmp_path
|
30
|
+
|
31
|
+
def initialize(name)
|
32
|
+
@tmp_path = "/tmp/dante-#{name}.log"
|
33
|
+
end # initialize
|
34
|
+
|
35
|
+
def run_a!
|
36
|
+
@tmp = File.new(@tmp_path, 'w')
|
37
|
+
@tmp.print("Started")
|
38
|
+
@tmp.close
|
39
|
+
end # run_a!
|
40
|
+
|
41
|
+
def run_b!
|
42
|
+
begin
|
43
|
+
@tmp = File.new(@tmp_path, 'w')
|
44
|
+
@tmp.print "Started!!"
|
45
|
+
sleep(100)
|
46
|
+
rescue Dante::Runner::Abort
|
47
|
+
@tmp.print "Abort!!"
|
48
|
+
exit
|
49
|
+
ensure
|
50
|
+
@tmp.print "Closing!!"
|
51
|
+
@tmp.close
|
52
|
+
end
|
53
|
+
end # run_b!
|
54
|
+
end # TestingProcess
|
metadata
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: dante
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Nathan Esquenazi
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-21 00:00:00 -08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: rake
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :development
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: minitest
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
44
|
+
segments:
|
45
|
+
- 0
|
46
|
+
version: "0"
|
47
|
+
type: :development
|
48
|
+
version_requirements: *id002
|
49
|
+
description: Turn any process into a demon.
|
50
|
+
email:
|
51
|
+
- nesquena@gmail.com
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files: []
|
57
|
+
|
58
|
+
files:
|
59
|
+
- .gitignore
|
60
|
+
- Gemfile
|
61
|
+
- LICENSE
|
62
|
+
- README.md
|
63
|
+
- Rakefile
|
64
|
+
- dante.gemspec
|
65
|
+
- lib/dante.rb
|
66
|
+
- lib/dante/runner.rb
|
67
|
+
- lib/dante/version.rb
|
68
|
+
- test/dante_test.rb
|
69
|
+
- test/runner_test.rb
|
70
|
+
- test/test_helper.rb
|
71
|
+
has_rdoc: true
|
72
|
+
homepage: https://github.com/bazaarlabs/dante
|
73
|
+
licenses: []
|
74
|
+
|
75
|
+
post_install_message:
|
76
|
+
rdoc_options: []
|
77
|
+
|
78
|
+
require_paths:
|
79
|
+
- lib
|
80
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
requirements: []
|
99
|
+
|
100
|
+
rubyforge_project: dante
|
101
|
+
rubygems_version: 1.6.2
|
102
|
+
signing_key:
|
103
|
+
specification_version: 3
|
104
|
+
summary: Turn any process into a demon
|
105
|
+
test_files:
|
106
|
+
- test/dante_test.rb
|
107
|
+
- test/runner_test.rb
|
108
|
+
- test/test_helper.rb
|