sanford 0.4.0 → 0.6.0
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/README.md +16 -20
- data/Rakefile +0 -2
- data/bench/report.txt +30 -32
- data/bench/runner.rb +6 -5
- data/bench/services.rb +2 -2
- data/bench/tasks.rb +23 -0
- data/bin/sanford +7 -0
- data/lib/sanford.rb +2 -1
- data/lib/sanford/cli.rb +364 -0
- data/lib/sanford/error_handler.rb +1 -0
- data/lib/sanford/host.rb +18 -13
- data/lib/sanford/host_data.rb +21 -17
- data/lib/sanford/runner.rb +6 -3
- data/lib/sanford/server.rb +64 -12
- data/lib/sanford/service_handler.rb +19 -0
- data/lib/sanford/test_runner.rb +3 -4
- data/lib/sanford/version.rb +1 -1
- data/lib/sanford/worker.rb +4 -6
- data/sanford.gemspec +1 -2
- data/test/support/fake_connection.rb +12 -3
- data/test/support/helpers.rb +11 -34
- data/test/support/service_handlers.rb +9 -0
- data/test/support/services.rb +9 -8
- data/test/support/simple_client.rb +6 -0
- data/test/system/managing_test.rb +55 -66
- data/test/system/request_handling_test.rb +248 -36
- data/test/unit/config_test.rb +1 -1
- data/test/unit/host_configuration_test.rb +2 -6
- data/test/unit/host_data_test.rb +13 -30
- data/test/unit/host_test.rb +3 -3
- data/test/unit/manager_pid_file_test.rb +45 -0
- data/test/unit/manager_test.rb +133 -8
- data/test/unit/runner_test.rb +10 -0
- data/test/unit/server_test.rb +24 -5
- data/test/unit/service_handler_test.rb +19 -0
- data/test/unit/worker_test.rb +3 -192
- metadata +22 -36
- data/lib/sanford/exceptions.rb +0 -37
- data/lib/sanford/manager.rb +0 -49
- data/lib/sanford/rake.rb +0 -42
data/README.md
CHANGED
@@ -12,7 +12,7 @@ class MyHost
|
|
12
12
|
include Sanford::Host
|
13
13
|
|
14
14
|
port 8000
|
15
|
-
|
15
|
+
pid_file '/path/to/host.pid'
|
16
16
|
|
17
17
|
# define some services
|
18
18
|
version 'v1' do
|
@@ -39,7 +39,7 @@ To define a Sanford host, include the mixin `Sanford::Host` on a class and use t
|
|
39
39
|
|
40
40
|
* `ip` - (string) A hostname or IP address for the server to bind to; default: `'0.0.0.0'`.
|
41
41
|
* `port` - (integer) The port number for the server to bind to.
|
42
|
-
* `
|
42
|
+
* `pid_file` - (string) Path to where you want the pid file to be written.
|
43
43
|
* `logger`- (logger) A logger for Sanford to use when handling requests; default: `Logger.new`.
|
44
44
|
|
45
45
|
Any values specified using the DSL act as defaults for instances of the host. You can overwritten when creating new instances:
|
@@ -118,39 +118,35 @@ end
|
|
118
118
|
|
119
119
|
## Running Host Daemons
|
120
120
|
|
121
|
-
Sanford comes with
|
121
|
+
Sanford comes with a CLI for running hosts:
|
122
122
|
|
123
|
-
* `
|
124
|
-
* `
|
125
|
-
* `
|
126
|
-
* `
|
123
|
+
* `sanford start` - spin up a background process running the host daemon.
|
124
|
+
* `sanford stop` - shutdown the background process running the host gracefully.
|
125
|
+
* `sanford restart` - "hot restart" the process running the host.
|
126
|
+
* `sanford run` - starts the server, but doesn't daemonize it (runs in the current ruby process). Convenient when using the server in a development environment.
|
127
127
|
|
128
|
-
|
129
|
-
|
130
|
-
```ruby
|
131
|
-
require 'sanford/rake'
|
132
|
-
```
|
133
|
-
|
134
|
-
The basic rake tasks are useful if your application only has one host defined and if you only want to run the host on a single port. In the case you have multiple hosts defined or you want to run a single host on multiple ports, use environment variables to set custom configurations.
|
128
|
+
The basic commands are useful if your application only has one host defined and if you only want to run the host on a single port. In the case you have multiple hosts defined or you want to run a single host on multiple ports, use environment variables to set custom configurations.
|
135
129
|
|
136
130
|
```bash
|
137
|
-
|
138
|
-
SANFORD_HOST=AnotherHost SANFORD_PORT=13001
|
131
|
+
sanford start # starts the first defined host
|
132
|
+
SANFORD_HOST=AnotherHost SANFORD_PORT=13001 sanford start # choose a specific host and port to run on with ENV vars
|
139
133
|
```
|
140
134
|
|
141
|
-
The
|
135
|
+
The CLI allow using environment variables for specifying which host to run the command against and for overriding the host's configuration. They recognize the a number of environment variables, but the main ones are: `SANFORD_HOST`, `SANFORD_IP`, and `SANFORD_PORT`.
|
136
|
+
|
137
|
+
Define a `name` on a Host to set a string name for your host that can be used to reference a host when using the CLI. If no name is set, Sanford will use the host's class name.
|
142
138
|
|
143
|
-
|
139
|
+
Alternatively, the CLI supports passing switches to override the host's configuration as well. Use `sanford --help` to see the options that are available.
|
144
140
|
|
145
141
|
### Loading An Application
|
146
142
|
|
147
|
-
Typically, a Sanford host is part of a larger application and parts of the application need to be
|
143
|
+
Typically, a Sanford host is part of a larger application and parts of the application need to be initialized or loaded when you start your Sanford server. To support this, Sanford provides an `init` hook for hosts. The proc that is defined will be called before the Sanford server is started, properly running the server in your application's environment:
|
148
144
|
|
149
145
|
```ruby
|
150
146
|
class MyHost
|
151
147
|
include Sanford::Host
|
152
148
|
|
153
|
-
|
149
|
+
init do
|
154
150
|
require File.expand_path("../config/environment", __FILE__)
|
155
151
|
end
|
156
152
|
|
data/Rakefile
CHANGED
data/bench/report.txt
CHANGED
@@ -2,39 +2,37 @@ Running benchmark report...
|
|
2
2
|
|
3
3
|
Hitting "simple" service with {}, 10000 times
|
4
4
|
....................................................................................................
|
5
|
-
Total Time:
|
6
|
-
Average Time:
|
7
|
-
Min Time:
|
8
|
-
Max Time:
|
5
|
+
Total Time: 8960.5727ms
|
6
|
+
Average Time: 0.8960ms
|
7
|
+
Min Time: 0.5099ms
|
8
|
+
Max Time: 87.5380ms
|
9
9
|
|
10
10
|
Distribution (number of requests):
|
11
|
-
0ms:
|
12
|
-
0.
|
13
|
-
0.
|
14
|
-
0.
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
1.
|
19
|
-
1.
|
20
|
-
1.
|
21
|
-
1.
|
22
|
-
1.
|
23
|
-
1.
|
24
|
-
1.
|
25
|
-
1.
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
94ms: 2
|
38
|
-
114ms: 1
|
11
|
+
0ms: 9775
|
12
|
+
0.5ms: 5723
|
13
|
+
0.6ms: 2810
|
14
|
+
0.7ms: 733
|
15
|
+
0.8ms: 364
|
16
|
+
0.9ms: 145
|
17
|
+
1ms: 153
|
18
|
+
1.0ms: 57
|
19
|
+
1.1ms: 18
|
20
|
+
1.2ms: 42
|
21
|
+
1.3ms: 13
|
22
|
+
1.4ms: 8
|
23
|
+
1.5ms: 7
|
24
|
+
1.6ms: 2
|
25
|
+
1.7ms: 3
|
26
|
+
1.8ms: 2
|
27
|
+
1.9ms: 1
|
28
|
+
2ms: 16
|
29
|
+
3ms: 1
|
30
|
+
22ms: 1
|
31
|
+
44ms: 10
|
32
|
+
45ms: 25
|
33
|
+
46ms: 9
|
34
|
+
47ms: 4
|
35
|
+
85ms: 3
|
36
|
+
87ms: 3
|
39
37
|
|
40
38
|
Done running benchmark report
|
data/bench/runner.rb
CHANGED
@@ -7,10 +7,10 @@ module Bench
|
|
7
7
|
|
8
8
|
class Runner
|
9
9
|
# this should match up with bench/services host and port
|
10
|
-
HOST_AND_PORT = [ '127.0.0.1',
|
10
|
+
HOST_AND_PORT = [ '127.0.0.1', 59284 ]
|
11
11
|
|
12
12
|
REQUESTS = [
|
13
|
-
[ 'v1', 'simple',
|
13
|
+
[ 'v1', 'simple', {}, 10000 ]
|
14
14
|
]
|
15
15
|
|
16
16
|
TIME_MODIFIER = 10 ** 4 # 4 decimal places
|
@@ -36,7 +36,7 @@ module Bench
|
|
36
36
|
|
37
37
|
output "\nHitting #{name.inspect} service with #{params.inspect}, #{times} times"
|
38
38
|
[*(1..times.to_i)].each do |index|
|
39
|
-
benchmark = self.hit_service(
|
39
|
+
benchmark = self.hit_service(version, name, params.merge({ :request_number => index }), show_result)
|
40
40
|
benchmarks << self.round_time(benchmark.real * 1000.to_f)
|
41
41
|
output('.', false) if ((index - 1) % 100 == 0) && !show_result
|
42
42
|
end
|
@@ -72,13 +72,12 @@ module Bench
|
|
72
72
|
output "\n"
|
73
73
|
end
|
74
74
|
|
75
|
-
protected
|
76
75
|
|
77
76
|
def hit_service(version, name, params, show_result)
|
78
77
|
Benchmark.measure do
|
79
78
|
begin
|
80
79
|
client = Bench::Client.new(*HOST_AND_PORT)
|
81
|
-
response = client.call(
|
80
|
+
response = client.call(version, name, params)
|
82
81
|
if show_result
|
83
82
|
output "Got a response:"
|
84
83
|
output " #{response.status}"
|
@@ -91,6 +90,8 @@ module Bench
|
|
91
90
|
end
|
92
91
|
end
|
93
92
|
|
93
|
+
protected
|
94
|
+
|
94
95
|
def output(message, puts = true)
|
95
96
|
method = puts ? :puts : :print
|
96
97
|
self.send(method, message)
|
data/bench/services.rb
CHANGED
data/bench/tasks.rb
CHANGED
@@ -4,6 +4,29 @@ namespace :bench do
|
|
4
4
|
require 'bench/runner'
|
5
5
|
end
|
6
6
|
|
7
|
+
namespace :server do
|
8
|
+
|
9
|
+
task :load do
|
10
|
+
ENV['SANFORD_SERVICES_FILE'] = 'bench/services'
|
11
|
+
end
|
12
|
+
|
13
|
+
desc "Run the bench server"
|
14
|
+
task :run => :load do
|
15
|
+
Kernel.exec("bundle exec sanford run")
|
16
|
+
end
|
17
|
+
|
18
|
+
desc "Start a daemonized bench server"
|
19
|
+
task :start => :load do
|
20
|
+
Kernel.system("bundle exec sanford start")
|
21
|
+
end
|
22
|
+
|
23
|
+
desc "Stop the bench server"
|
24
|
+
task :stop => :load do
|
25
|
+
Kernel.system("bundle exec sanford stop")
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
7
30
|
desc "Run a Benchmark report against the Benchmark server"
|
8
31
|
task :report => :load do
|
9
32
|
Bench::Runner.new.build_report
|
data/bin/sanford
ADDED
data/lib/sanford.rb
CHANGED
@@ -5,6 +5,7 @@ require 'pathname'
|
|
5
5
|
require 'set'
|
6
6
|
|
7
7
|
require 'sanford/host'
|
8
|
+
require 'sanford/logger'
|
8
9
|
require 'sanford/server'
|
9
10
|
require 'sanford/service_handler'
|
10
11
|
require 'sanford/version'
|
@@ -38,7 +39,7 @@ module Sanford
|
|
38
39
|
module Config
|
39
40
|
include NsOptions::Proxy
|
40
41
|
option :services_file, Pathname, :default => ENV['SANFORD_SERVICES_FILE']
|
41
|
-
|
42
|
+
option :logger, :default => Sanford::NullLogger.new
|
42
43
|
end
|
43
44
|
|
44
45
|
class Hosts
|
data/lib/sanford/cli.rb
ADDED
@@ -0,0 +1,364 @@
|
|
1
|
+
require 'sanford'
|
2
|
+
require 'sanford/host_data'
|
3
|
+
require 'sanford/server'
|
4
|
+
require 'sanford/version'
|
5
|
+
|
6
|
+
module Sanford
|
7
|
+
|
8
|
+
class CLI
|
9
|
+
|
10
|
+
def self.run(*args)
|
11
|
+
self.new.run(*args)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize
|
15
|
+
@cli = CLIRB.new do
|
16
|
+
option :host, "Name of the Host configuration", :value => String
|
17
|
+
option :ip, "IP address to bind to", :value => String
|
18
|
+
option :port, "Port number to bind to", :value => Integer
|
19
|
+
option :config, "File defining the configured Hosts", :value => String
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def run(*args)
|
24
|
+
begin
|
25
|
+
@cli.parse!(*args)
|
26
|
+
@command = @cli.args.first || 'run'
|
27
|
+
Sanford.config.services_file = @cli.opts['config'] if @cli.opts['config']
|
28
|
+
Sanford.init
|
29
|
+
Sanford::Manager.call(@command, @cli.opts)
|
30
|
+
rescue CLIRB::HelpExit
|
31
|
+
puts help
|
32
|
+
rescue CLIRB::VersionExit
|
33
|
+
puts Sanford::VERSION
|
34
|
+
rescue CLIRB::Error => exception
|
35
|
+
puts "#{exception.message}\n\n"
|
36
|
+
puts help
|
37
|
+
exit(1)
|
38
|
+
rescue SystemExit
|
39
|
+
rescue Exception => exception
|
40
|
+
puts "#{exception.class}: #{exception.message}"
|
41
|
+
puts exception.backtrace.join("\n") if ENV['DEBUG']
|
42
|
+
exit(1)
|
43
|
+
end
|
44
|
+
exit(0)
|
45
|
+
end
|
46
|
+
|
47
|
+
def help
|
48
|
+
"Usage: sanford <command> <options> \n" \
|
49
|
+
"Commands: run, start, stop, restart \n" \
|
50
|
+
"#{@cli}"
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
module Manager
|
56
|
+
|
57
|
+
def self.call(action, options = nil)
|
58
|
+
get_handler_class(action).new(options).tap{ |manager| manager.send(action) }
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.get_handler_class(action)
|
62
|
+
case action.to_sym
|
63
|
+
when :start, :run
|
64
|
+
ServerHandler
|
65
|
+
when :stop, :restart
|
66
|
+
SignalHandler
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ServerHandler
|
71
|
+
|
72
|
+
def initialize(options = nil)
|
73
|
+
@config = Config.new(options)
|
74
|
+
raise Sanford::NoHostError.new(@config.host_name) if !@config.found_host?
|
75
|
+
raise Sanford::InvalidHostError.new(@config.host) if !@config.has_listen_args?
|
76
|
+
@host = @config.host
|
77
|
+
@logger = @host.logger
|
78
|
+
|
79
|
+
@server_options = {}
|
80
|
+
# FUTURE allow passing through dat-tcp options (min/max workers)
|
81
|
+
# FUTURE merge in host options for verbose / keep_alive
|
82
|
+
|
83
|
+
@restart_cmd = RestartCmd.new(@config)
|
84
|
+
end
|
85
|
+
|
86
|
+
def run
|
87
|
+
self.run!
|
88
|
+
end
|
89
|
+
|
90
|
+
def start
|
91
|
+
self.run! true
|
92
|
+
end
|
93
|
+
|
94
|
+
protected
|
95
|
+
|
96
|
+
def run!(daemonize = false)
|
97
|
+
daemonize!(true) if daemonize
|
98
|
+
Sanford::Server.new(@host, @server_options).tap do |server|
|
99
|
+
log "Starting server for #{@host.name}"
|
100
|
+
|
101
|
+
server.listen(*@config.listen_args)
|
102
|
+
log "Listening on #{server.ip}:#{server.port}"
|
103
|
+
log "PID: #{Process.pid}"
|
104
|
+
|
105
|
+
$0 = ProcessName.new(@host.name, server.ip, server.port)
|
106
|
+
@config.pid_file.write
|
107
|
+
|
108
|
+
Signal.trap("TERM"){ self.stop!(server) }
|
109
|
+
Signal.trap("INT"){ self.halt!(server) }
|
110
|
+
Signal.trap("USR2"){ self.restart!(server) }
|
111
|
+
|
112
|
+
server.run(@config.client_file_descriptors).join
|
113
|
+
end
|
114
|
+
ensure
|
115
|
+
@config.pid_file.remove
|
116
|
+
end
|
117
|
+
|
118
|
+
def restart!(server)
|
119
|
+
log "Restarting the server..."
|
120
|
+
server.pause
|
121
|
+
log "server paused"
|
122
|
+
|
123
|
+
ENV['SANFORD_HOST'] = @host.name
|
124
|
+
ENV['SANFORD_SERVER_FD'] = server.file_descriptor.to_s
|
125
|
+
ENV['SANFORD_CLIENT_FDS'] = server.client_file_descriptors.join(',')
|
126
|
+
|
127
|
+
@logger.info "calling exec ..."
|
128
|
+
Dir.chdir @restart_cmd.dir
|
129
|
+
Kernel.exec(*@restart_cmd.argv)
|
130
|
+
end
|
131
|
+
|
132
|
+
def stop!(server)
|
133
|
+
log "Stopping the server..."
|
134
|
+
server.stop
|
135
|
+
log "Done"
|
136
|
+
end
|
137
|
+
|
138
|
+
def halt!(server)
|
139
|
+
log "Halting the server..."
|
140
|
+
server.halt false
|
141
|
+
log "Done"
|
142
|
+
end
|
143
|
+
|
144
|
+
# Full explanation: http://www.steve.org.uk/Reference/Unix/faq_2.html#SEC16
|
145
|
+
def daemonize!(no_chdir = false, no_close = false)
|
146
|
+
exit if fork
|
147
|
+
Process.setsid
|
148
|
+
exit if fork
|
149
|
+
Dir.chdir "/" unless no_chdir
|
150
|
+
if !no_close
|
151
|
+
null = File.open "/dev/null", 'w'
|
152
|
+
STDIN.reopen null
|
153
|
+
STDOUT.reopen null
|
154
|
+
STDERR.reopen null
|
155
|
+
end
|
156
|
+
return 0
|
157
|
+
end
|
158
|
+
|
159
|
+
def log(message)
|
160
|
+
@logger.info "[Sanford] #{message}"
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
class SignalHandler
|
166
|
+
|
167
|
+
def initialize(options = nil)
|
168
|
+
@config = Config.new(options)
|
169
|
+
raise Sanford::NoPIDError.new if !@config.pid
|
170
|
+
end
|
171
|
+
|
172
|
+
def stop
|
173
|
+
Process.kill("TERM", @config.pid)
|
174
|
+
end
|
175
|
+
|
176
|
+
def restart
|
177
|
+
Process.kill("USR2", @config.pid)
|
178
|
+
end
|
179
|
+
|
180
|
+
end
|
181
|
+
|
182
|
+
class Config
|
183
|
+
attr_reader :host_name, :host, :ip, :port, :file_descriptor
|
184
|
+
attr_reader :client_file_descriptors, :pid_file, :pid, :restart_dir
|
185
|
+
|
186
|
+
def initialize(opts = nil)
|
187
|
+
options = OpenStruct.new(opts || {})
|
188
|
+
@host_name = ENV['SANFORD_HOST'] || options.host
|
189
|
+
|
190
|
+
@host = @host_name ? Sanford.hosts.find(@host_name) : Sanford.hosts.first
|
191
|
+
@host ||= NullHost.new
|
192
|
+
|
193
|
+
@file_descriptor = ENV['SANFORD_SERVER_FD'] || options.file_descriptor
|
194
|
+
@file_descriptor = @file_descriptor.to_i if @file_descriptor
|
195
|
+
@ip = ENV['SANFORD_IP'] || options.ip || @host.ip
|
196
|
+
@port = ENV['SANFORD_PORT'] || options.port || @host.port
|
197
|
+
@port = @port.to_i if @port
|
198
|
+
|
199
|
+
client_fds_str = ENV['SANFORD_CLIENT_FDS'] || options.client_fds || ""
|
200
|
+
@client_file_descriptors = client_fds_str.split(',').map(&:to_i)
|
201
|
+
|
202
|
+
@pid_file = PIDFile.new(ENV['SANFORD_PID_FILE'] || options.pid_file || @host.pid_file)
|
203
|
+
@pid = options.pid || @pid_file.pid
|
204
|
+
|
205
|
+
@restart_dir = ENV['SANFORD_RESTART_DIR'] || options.restart_dir
|
206
|
+
end
|
207
|
+
|
208
|
+
def listen_args
|
209
|
+
@file_descriptor ? [ @file_descriptor ] : [ @ip, @port ]
|
210
|
+
end
|
211
|
+
|
212
|
+
def has_listen_args?
|
213
|
+
!!@file_descriptor || !!(@ip && @port)
|
214
|
+
end
|
215
|
+
|
216
|
+
def found_host?
|
217
|
+
!@host.kind_of?(NullHost)
|
218
|
+
end
|
219
|
+
|
220
|
+
end
|
221
|
+
|
222
|
+
class NullHost
|
223
|
+
[ :ip, :port, :pid_file ].each do |method_name|
|
224
|
+
define_method(method_name){ }
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
class ProcessName < String
|
229
|
+
def initialize(name, ip, port)
|
230
|
+
super "#{[ name, ip, port ].join('_')}"
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
class PIDFile
|
235
|
+
def initialize(path)
|
236
|
+
@path = (path || '/dev/null').to_s
|
237
|
+
end
|
238
|
+
|
239
|
+
def pid
|
240
|
+
pid = File.read(@path).strip if File.exists?(@path)
|
241
|
+
pid.to_i if pid && !pid.empty?
|
242
|
+
end
|
243
|
+
|
244
|
+
def write
|
245
|
+
File.open(@path, 'w'){|f| f.puts Process.pid }
|
246
|
+
end
|
247
|
+
|
248
|
+
def remove
|
249
|
+
FileUtils.rm_f(@path)
|
250
|
+
end
|
251
|
+
|
252
|
+
def to_s
|
253
|
+
@path
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
class RestartCmd
|
258
|
+
attr_reader :argv, :dir
|
259
|
+
|
260
|
+
def initialize(config = nil)
|
261
|
+
require 'rubygems'
|
262
|
+
config ||= OpenStruct.new
|
263
|
+
@dir = config.restart_dir || get_pwd
|
264
|
+
@argv = [ Gem.ruby, $0, ARGV.dup ].flatten
|
265
|
+
end
|
266
|
+
|
267
|
+
protected
|
268
|
+
|
269
|
+
# Trick from puma/unicorn. Favor PWD because it contains an unresolved
|
270
|
+
# symlink. This is useful when restarting after deploying; the original
|
271
|
+
# directory may be removed, but the symlink is pointing to a new
|
272
|
+
# directory.
|
273
|
+
def get_pwd
|
274
|
+
env_stat = File.stat(ENV['PWD'])
|
275
|
+
pwd_stat = File.stat(Dir.pwd)
|
276
|
+
if env_stat.ino == pwd_stat.ino && env_stat.dev == pwd_stat.dev
|
277
|
+
ENV['PWD']
|
278
|
+
else
|
279
|
+
Dir.pwd
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
class CLIRB # Version 1.0.0, https://github.com/redding/cli.rb
|
287
|
+
Error = Class.new(RuntimeError);
|
288
|
+
HelpExit = Class.new(RuntimeError); VersionExit = Class.new(RuntimeError)
|
289
|
+
attr_reader :argv, :args, :opts, :data
|
290
|
+
|
291
|
+
def initialize(&block)
|
292
|
+
@options = []; instance_eval(&block) if block
|
293
|
+
require 'optparse'
|
294
|
+
@data, @args, @opts = [], [], {}; @parser = OptionParser.new do |p|
|
295
|
+
p.banner = ''; @options.each do |o|
|
296
|
+
@opts[o.name] = o.value; p.on(*o.parser_args){ |v| @opts[o.name] = v }
|
297
|
+
end
|
298
|
+
p.on_tail('--version', ''){ |v| raise VersionExit, v.to_s }
|
299
|
+
p.on_tail('--help', ''){ |v| raise HelpExit, v.to_s }
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
def option(*args); @options << Option.new(*args); end
|
304
|
+
def parse!(argv)
|
305
|
+
@args = (argv || []).dup.tap do |args_list|
|
306
|
+
begin; @parser.parse!(args_list)
|
307
|
+
rescue OptionParser::ParseError => err; raise Error, err.message; end
|
308
|
+
end; @data = @args + [@opts]
|
309
|
+
end
|
310
|
+
def to_s; @parser.to_s; end
|
311
|
+
def inspect
|
312
|
+
"#<#{self.class}:#{'0x0%x' % (object_id << 1)} @data=#{@data.inspect}>"
|
313
|
+
end
|
314
|
+
|
315
|
+
class Option
|
316
|
+
attr_reader :name, :opt_name, :desc, :abbrev, :value, :klass, :parser_args
|
317
|
+
|
318
|
+
def initialize(name, *args)
|
319
|
+
settings, @desc = args.last.kind_of?(::Hash) ? args.pop : {}, args.pop || ''
|
320
|
+
@name, @opt_name, @abbrev = parse_name_values(name, settings[:abbrev])
|
321
|
+
@value, @klass = gvalinfo(settings[:value])
|
322
|
+
@parser_args = if [TrueClass, FalseClass, NilClass].include?(@klass)
|
323
|
+
["-#{@abbrev}", "--[no-]#{@opt_name}", @desc]
|
324
|
+
else
|
325
|
+
["-#{@abbrev}", "--#{@opt_name} #{@opt_name.upcase}", @klass, @desc]
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
private
|
330
|
+
|
331
|
+
def parse_name_values(name, custom_abbrev)
|
332
|
+
[ (processed_name = name.to_s.strip.downcase), processed_name.gsub('_', '-'),
|
333
|
+
custom_abbrev || processed_name.gsub(/[^a-z]/, '').chars.first || 'a'
|
334
|
+
]
|
335
|
+
end
|
336
|
+
def gvalinfo(v); v.kind_of?(Class) ? [nil,gklass(v)] : [v,gklass(v.class)]; end
|
337
|
+
def gklass(k); k == Fixnum ? Integer : k; end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
class NoHostError < CLIRB::Error
|
342
|
+
def initialize(host_name)
|
343
|
+
message = if Sanford.hosts.empty?
|
344
|
+
"No hosts have been defined. Please define a host before trying to run Sanford."
|
345
|
+
else
|
346
|
+
"A host couldn't be found with the name #{host_name.inspect}. "
|
347
|
+
end
|
348
|
+
super message
|
349
|
+
end
|
350
|
+
end
|
351
|
+
|
352
|
+
class InvalidHostError < CLIRB::Error
|
353
|
+
def initialize(host)
|
354
|
+
super "A port must be configured or provided to run a server for '#{host}'"
|
355
|
+
end
|
356
|
+
end
|
357
|
+
|
358
|
+
class NoPIDError < CLIRB::Error
|
359
|
+
def initialize
|
360
|
+
super "A PID or PID file is required"
|
361
|
+
end
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|