sanford 0.4.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|