forward 0.3.3 → 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile +0 -2
- data/README.md +24 -0
- data/Rakefile +3 -1
- data/bin/forward +1 -1
- data/forward.gemspec +17 -11
- data/lib/forward/api/resource.rb +51 -83
- data/lib/forward/api/tunnel.rb +41 -68
- data/lib/forward/api/user.rb +14 -11
- data/lib/forward/api.rb +7 -26
- data/lib/forward/cli.rb +55 -253
- data/lib/forward/command/account.rb +69 -0
- data/lib/forward/command/base.rb +62 -0
- data/lib/forward/command/config.rb +64 -0
- data/lib/forward/command/tunnel.rb +178 -0
- data/lib/forward/common.rb +44 -0
- data/lib/forward/config.rb +75 -118
- data/lib/forward/request.rb +72 -0
- data/lib/forward/socket.rb +125 -0
- data/lib/forward/static/app.rb +157 -0
- data/lib/forward/static/directory.erb +142 -0
- data/lib/forward/tunnel.rb +102 -40
- data/lib/forward/version.rb +1 -1
- data/lib/forward.rb +80 -63
- data/test/api/resource_test.rb +70 -54
- data/test/api/tunnel_test.rb +50 -51
- data/test/api/user_test.rb +33 -20
- data/test/cli_test.rb +0 -126
- data/test/command/account_test.rb +26 -0
- data/test/command/tunnel_test.rb +133 -0
- data/test/config_test.rb +103 -54
- data/test/forward_test.rb +47 -0
- data/test/test_helper.rb +35 -26
- data/test/tunnel_test.rb +50 -22
- metadata +210 -169
- data/forwardhq.crt +0 -112
- data/lib/forward/api/client_log.rb +0 -20
- data/lib/forward/api/tunnel_key.rb +0 -18
- data/lib/forward/client.rb +0 -110
- data/lib/forward/error.rb +0 -12
- data/test/api/tunnel_key_test.rb +0 -28
- data/test/api_test.rb +0 -0
- data/test/client_test.rb +0 -8
data/lib/forward/cli.rb
CHANGED
@@ -1,280 +1,82 @@
|
|
1
|
+
require 'forward/command/base'
|
2
|
+
require 'forward/command/account'
|
3
|
+
require 'forward/command/tunnel'
|
4
|
+
require 'forward/command/config'
|
5
|
+
|
1
6
|
module Forward
|
2
7
|
class CLI
|
3
|
-
|
4
|
-
CNAME_REGEX = /\A[a-z0-9]+(?:[\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/i
|
5
|
-
SUBDOMAIN_PREFIX_REGEX = /\A[a-z0-9]{1}[a-z0-9\-]+\z/i
|
6
|
-
USERNAME_REGEX = PASSWORD_REGEX = /\A[^\s]+\z/i
|
7
|
-
|
8
|
-
BANNER = <<-BANNER
|
9
|
-
Usage: forward <port> [options]
|
10
|
-
forward <host> [options]
|
11
|
-
forward <host:port> [options]
|
12
|
-
|
13
|
-
Description:
|
14
|
-
|
15
|
-
Share a server running on localhost:port over the web by tunneling
|
16
|
-
through Forward. A URL is created for each tunnel.
|
17
|
-
|
18
|
-
Simple example:
|
8
|
+
class ValidationError < StandardError; end
|
19
9
|
|
20
|
-
|
10
|
+
extend Forward::Common
|
11
|
+
BANNER = <<-BANNER.gsub /^ {4}/, ''
|
12
|
+
Usage: forward <port|host|host:port|path> [options]
|
21
13
|
|
22
|
-
|
23
|
-
|
24
|
-
|
14
|
+
Examples:
|
15
|
+
> forward 3000 # forward server running on port 3000
|
16
|
+
> forward mysite.dev # forward virtual host mysite.dev
|
17
|
+
> forward ~/Dev/mysite # forward a path or directory
|
25
18
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
Forward created at https://myapp-mycompany.fwd.wf
|
31
|
-
|
32
|
-
Virtual Host example:
|
33
|
-
|
34
|
-
# You are already running something on port 80 that uses
|
35
|
-
# virtual host names.
|
36
|
-
|
37
|
-
> forward mysite.dev
|
38
|
-
Forward created at https://mycompany.fwd.wf
|
19
|
+
Common Options:
|
20
|
+
> forward 3000 -a foo:bar # password protect a tunnel
|
21
|
+
> forward 3000 myapp # assign a static subdomain prefix
|
22
|
+
> forward 3000 myapp -c my.domain.com # use a custom CNAME
|
39
23
|
|
24
|
+
Options:
|
40
25
|
BANNER
|
41
26
|
|
42
|
-
#
|
43
|
-
#
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
27
|
+
# Parses various options and arguments, validates everything to ensure
|
28
|
+
# we're safe to proceed, and finally passes options to the Client.
|
29
|
+
def self.start
|
30
|
+
HighLine.use_color = false if windows?
|
31
|
+
if ARGV.include?('--debug')
|
32
|
+
Forward.debug!
|
33
|
+
ARGV.delete('--debug')
|
34
|
+
end
|
35
|
+
Forward.logger.debug("Starting forward v#{Forward::VERSION}")
|
51
36
|
|
52
|
-
|
53
|
-
|
37
|
+
Slop.parse(banner: BANNER, help: true) do
|
38
|
+
on '-a=', '--auth=', "Protect this tunnel with HTTP Basic Auth."
|
39
|
+
on '-A', '--no-auth', "Disable authentication on this tunnel (if a default is set in your preferences)"
|
40
|
+
on '-c=', '--cname=', "Allow access to this tunnel as CNAME (requires CNAME setup on your DNS server)"
|
54
41
|
|
55
|
-
|
56
|
-
|
57
|
-
username, password = credentials.split(':')
|
58
|
-
options[:username] = username
|
59
|
-
options[:password] = password
|
42
|
+
on '-q', '--quiet', "Don't display requests" do
|
43
|
+
Forward.quiet!
|
60
44
|
end
|
61
45
|
|
62
|
-
|
63
|
-
|
46
|
+
on '-v', '--version', 'Display version number' do
|
47
|
+
puts "forward #{VERSION}"
|
48
|
+
exit
|
64
49
|
end
|
65
50
|
|
66
|
-
|
67
|
-
|
51
|
+
# Start / Open a Tunnel
|
52
|
+
run do |opts, args|
|
53
|
+
Command::Tunnel.run(:start, opts, args)
|
68
54
|
end
|
69
55
|
|
70
|
-
|
71
|
-
|
56
|
+
# Account Commands
|
57
|
+
command 'account:login', banner: "Usage: forward account:login", help: true do
|
58
|
+
description "Logs into a new account and makes it the default"
|
59
|
+
run { |opts, args| Command::Account.run(:login, opts, args) }
|
72
60
|
end
|
73
61
|
|
74
|
-
|
75
|
-
|
76
|
-
|
62
|
+
command 'account:default', banner: "Usage: forward account:default SUBDOMAIN", help: true do
|
63
|
+
description "Sets an account to the default account"
|
64
|
+
run { |opts, args| Command::Account.run(:default, opts, args) }
|
77
65
|
end
|
78
66
|
|
79
|
-
|
80
|
-
|
81
|
-
|
67
|
+
command 'account:logout', banner: "Usage: forward account:logout SUBDOMAIN", help: true do
|
68
|
+
description "Logs out of an account"
|
69
|
+
run { |opts, args| Command::Account.run(:logout, opts, args) }
|
82
70
|
end
|
83
|
-
end
|
84
|
-
|
85
|
-
@opts.parse!(args)
|
86
|
-
|
87
|
-
options
|
88
|
-
end
|
89
|
-
|
90
|
-
# Returns a String file path for PWD/Forwardfile
|
91
|
-
def self.forwardfile_path
|
92
|
-
File.join(Dir.pwd, 'Forwardfile')
|
93
|
-
end
|
94
|
-
|
95
|
-
# Parse arguments from CLI and options from Forwardfile
|
96
|
-
#
|
97
|
-
# args - An Array of command line arguments
|
98
|
-
#
|
99
|
-
# Returns a Hash of options.
|
100
|
-
def self.parse_args_and_options(args)
|
101
|
-
options = {
|
102
|
-
:host => '127.0.0.1',
|
103
|
-
:port => 80
|
104
|
-
}
|
105
|
-
|
106
|
-
Forward.log.debug("Default options: `#{options.inspect}'")
|
107
|
-
|
108
|
-
if File.exist? forwardfile_path
|
109
|
-
options.merge!(parse_forwardfile)
|
110
|
-
Forward.log.debug("Forwardfile options: `#{options.inspect}'")
|
111
|
-
end
|
112
|
-
|
113
|
-
options.merge!(parse_cli_options(args))
|
114
|
-
|
115
|
-
forwarded, prefix = args[0..1]
|
116
|
-
options[:subdomain_prefix] = prefix unless prefix.nil?
|
117
|
-
options.merge!(parse_forwarded(forwarded))
|
118
|
-
Forward.log.debug("CLI options: `#{options.inspect}'")
|
119
|
-
|
120
|
-
options
|
121
|
-
end
|
122
|
-
|
123
|
-
# Parse a local Forwardfile (in the PWD) and return it as a Hash.
|
124
|
-
# Raise an error and exit if unable to parse or result isn't a Hash.
|
125
|
-
#
|
126
|
-
# Returns a Hash of the options found in the Forwardfile
|
127
|
-
def self.parse_forwardfile
|
128
|
-
options = YAML.load_file(forwardfile_path)
|
129
|
-
raise CLIError unless options.kind_of?(Hash)
|
130
|
-
|
131
|
-
options.symbolize_keys
|
132
|
-
rescue ArgumentError, SyntaxError, CLIError
|
133
|
-
exit_with_error("Unable to parse #{forwardfile_path}")
|
134
|
-
end
|
135
|
-
|
136
|
-
# Parses the arguments to determine if we're forwarding a port or host
|
137
|
-
# and validates the port or host and updates @options if valid.
|
138
|
-
#
|
139
|
-
# arg - A String representing the port or host.
|
140
|
-
#
|
141
|
-
# Returns a Hash containing the forwarded host or port
|
142
|
-
def self.parse_forwarded(arg)
|
143
|
-
Forward.log.debug("Forwarded: `#{arg}'")
|
144
|
-
forwarded = {}
|
145
71
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
elsif arg =~ /\A[-a-z0-9\.\-]+\z/i
|
151
|
-
forwarded[:host] = arg
|
152
|
-
elsif arg =~ /\A[-a-z0-9\.\-]+:\d{1,5}\z/i
|
153
|
-
host, port = arg.split(':')
|
154
|
-
port = port.to_i
|
155
|
-
|
156
|
-
forwarded[:host] = host
|
157
|
-
forwarded[:port] = port
|
158
|
-
end
|
159
|
-
|
160
|
-
forwarded
|
161
|
-
end
|
162
|
-
|
163
|
-
# Checks to make sure the port being set is a number between 1 and 65535
|
164
|
-
# and exits with an error message if it's not.
|
165
|
-
#
|
166
|
-
# port - port number Integer
|
167
|
-
def self.validate_port(port)
|
168
|
-
Forward.log.debug("Validating Port: `#{port}'")
|
169
|
-
unless port.between?(1, 65535)
|
170
|
-
exit_with_error "Invalid Port: #{port} is an invalid port number"
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
# Checks to make sure the username is a valid format
|
175
|
-
# and exits with an error message if not.
|
176
|
-
#
|
177
|
-
# username - username String
|
178
|
-
def self.validate_username(username)
|
179
|
-
Forward.log.debug("Validating Username: `#{username}'")
|
180
|
-
exit_with_error("`#{username}' is an invalid username format") unless username =~ USERNAME_REGEX
|
181
|
-
end
|
182
|
-
|
183
|
-
# Checks to make sure the password is a valid format
|
184
|
-
# and exits with an error message if not.
|
185
|
-
#
|
186
|
-
# password - password String
|
187
|
-
def self.validate_password(password)
|
188
|
-
Forward.log.debug("Validating Password: `#{password}'")
|
189
|
-
exit_with_error("`#{password}' is an invalid password format") unless password =~ PASSWORD_REGEX
|
190
|
-
end
|
191
|
-
|
192
|
-
# Checks to make sure the cname is in the correct format and exits with an
|
193
|
-
# error message if it isn't.
|
194
|
-
#
|
195
|
-
# cname - cname String
|
196
|
-
def self.validate_cname(cname)
|
197
|
-
Forward.log.debug("Validating CNAME: `#{cname}'")
|
198
|
-
exit_with_error("`#{cname}' is an invalid domain format") unless cname =~ CNAME_REGEX
|
199
|
-
end
|
200
|
-
|
201
|
-
# Checks to make sure the subdomain prefix is in the correct format
|
202
|
-
# and exits with an error message if it isn't.
|
203
|
-
#
|
204
|
-
# prefix - subdomain prefix String
|
205
|
-
def self.validate_subdomain_prefix(prefix)
|
206
|
-
Forward.log.debug("Validating Subdomain Prefix: `#{prefix}'")
|
207
|
-
exit_with_error("`#{prefix}' is an invalid subdomain prefix format") unless prefix =~ SUBDOMAIN_PREFIX_REGEX
|
208
|
-
end
|
209
|
-
|
210
|
-
# Validate all options in options Hash.
|
211
|
-
#
|
212
|
-
# options - the options Hash
|
213
|
-
def self.validate_options(options)
|
214
|
-
Forward.log.debug("Validating options: `#{options.inspect}'")
|
215
|
-
options.each do |key, value|
|
216
|
-
next if value.nil?
|
217
|
-
validate_method = :"validate_#{key}"
|
218
|
-
send(validate_method, value) if respond_to?(validate_method)
|
219
|
-
end
|
220
|
-
end
|
221
|
-
|
222
|
-
# Asks for the user's email and password and puts them in a Hash.
|
223
|
-
#
|
224
|
-
# Returns a Hash with the email and password
|
225
|
-
def self.authenticate
|
226
|
-
puts 'Enter your email and password'
|
227
|
-
email = ask('email: ').chomp
|
228
|
-
password = ask('password: ') { |q| q.echo = false }.chomp
|
229
|
-
Forward.log.debug("Authenticating User: `#{email}:#{password.gsub(/./, 'x')}'")
|
230
|
-
|
231
|
-
{ :email => email, :password => password }
|
232
|
-
end
|
233
|
-
|
234
|
-
# Remove .forward file and SSH key (log a user out)
|
235
|
-
def self.logout
|
236
|
-
FileUtils.rm_f(Config.config_path)
|
237
|
-
FileUtils.rm_f(Config.key_path)
|
238
|
-
|
239
|
-
puts "You've been logged out. You'll be asked to log back in when you create a new tunnel."
|
240
|
-
exit
|
241
|
-
end
|
242
|
-
|
243
|
-
# Parses various options and arguments, validates everything to ensure
|
244
|
-
# we're safe to proceed, and finally passes options to the Client.
|
245
|
-
def self.run(args)
|
246
|
-
::HighLine.use_color = false if Forward.windows?
|
247
|
-
if ARGV.include?('--debug')
|
248
|
-
Forward.debug!
|
249
|
-
ARGV.delete('--debug')
|
250
|
-
elsif ARGV.include?('--rdebug')
|
251
|
-
Forward.debug_remotely!
|
252
|
-
ARGV.delete('--rdebug')
|
72
|
+
command 'account:list', banner: "Usage: forward account:list", help: true do
|
73
|
+
description "Lists active accounts"
|
74
|
+
run { |opts, args| Command::Account.run(:list, opts, args) }
|
75
|
+
end
|
253
76
|
end
|
254
77
|
|
255
|
-
|
256
|
-
|
257
|
-
options = parse_args_and_options(args)
|
258
|
-
|
259
|
-
validate_options(options)
|
260
|
-
print_usage_and_exit if args.empty? && !File.exist?(forwardfile_path)
|
261
|
-
|
262
|
-
Client.start(options)
|
263
|
-
end
|
264
|
-
|
265
|
-
# Colors an error message red and displays it.
|
266
|
-
#
|
267
|
-
# message - error message String
|
268
|
-
def self.exit_with_error(message)
|
269
|
-
Forward.log.fatal(message)
|
270
|
-
puts HighLine.color(message, :red)
|
271
|
-
exit 1
|
272
|
-
end
|
273
|
-
|
274
|
-
# Print the usage banner and Exit Code 0.
|
275
|
-
def self.print_usage_and_exit
|
276
|
-
puts @opts
|
277
|
-
exit
|
78
|
+
rescue CLIError => e
|
79
|
+
exit_with_error(e.message)
|
278
80
|
end
|
279
81
|
|
280
82
|
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module Forward
|
2
|
+
module Command
|
3
|
+
class Account < Base
|
4
|
+
def login
|
5
|
+
config.create_or_load
|
6
|
+
email = @args.first
|
7
|
+
|
8
|
+
email, password = ask_for_credentials(email)
|
9
|
+
logger.debug("[API] logging in user: `#{email}:#{password.gsub(/./, 'x')}'")
|
10
|
+
|
11
|
+
client do
|
12
|
+
API::User.authenticate(email, password) do |subdomain, token|
|
13
|
+
config.add_account(subdomain, token)
|
14
|
+
exit_with_message "`#{subdomain}' is now logged in and set to the default account"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def logout
|
20
|
+
config.create_or_load
|
21
|
+
subdomain = @args.first
|
22
|
+
|
23
|
+
if config.accounts.empty?
|
24
|
+
exit_with_message "You aren't logged into any accounts"
|
25
|
+
elsif subdomain
|
26
|
+
config.remove_account!(subdomain)
|
27
|
+
exit_with_message "You are now logged out of the '#{subdomain}' account"
|
28
|
+
else
|
29
|
+
message = "You must provide a subdomain to logout of an account, you're currently logged into:\n"
|
30
|
+
message << config.accounts.map { |s, _| " - #{s}" }.join("\n")
|
31
|
+
|
32
|
+
exit_with_message message
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def list
|
37
|
+
config.create_or_load
|
38
|
+
|
39
|
+
if config.accounts.empty?
|
40
|
+
exit_with_message("You're not logged into any accounts. You can login with: `forward account:login'")
|
41
|
+
else
|
42
|
+
puts "Currently logged into:"
|
43
|
+
config.accounts.keys.sort.each do |subdomain|
|
44
|
+
default = config.default_account.to_sym == subdomain.to_sym
|
45
|
+
puts default ? HighLine.color(" - #{subdomain} (default)", :green) : " - #{subdomain}"
|
46
|
+
end
|
47
|
+
exit_with_message
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def default
|
52
|
+
config.create_or_load
|
53
|
+
subdomain = @args.first
|
54
|
+
|
55
|
+
if config.accounts.empty?
|
56
|
+
exit_with_message "You aren't logged into any accounts"
|
57
|
+
elsif subdomain && config.accounts.has_key?(subdomain.to_sym)
|
58
|
+
config.create_or_load
|
59
|
+
config.set_default_account!(subdomain.to_sym)
|
60
|
+
exit_with_message "#{subdomain} is now your default account"
|
61
|
+
else
|
62
|
+
exit_with_message "You're not logged into that account. You can login with: `forward account:login'"
|
63
|
+
end
|
64
|
+
rescue ConfigError => e
|
65
|
+
exit_with_error(e.message)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
module Forward
|
2
|
+
module Command
|
3
|
+
class Base
|
4
|
+
include Forward::Common
|
5
|
+
|
6
|
+
attr_accessor :options
|
7
|
+
attr_accessor :args
|
8
|
+
|
9
|
+
def self.run(command, options = {}, args = [])
|
10
|
+
Forward.logger.debug "[CLI] running `#{command}'"
|
11
|
+
new(options, args).send(command)
|
12
|
+
end
|
13
|
+
|
14
|
+
def initialize(options = {}, args = [])
|
15
|
+
@opts = options
|
16
|
+
@options = options.to_hash
|
17
|
+
@args = args
|
18
|
+
logger.debug "[CLI] options: #{@options.inspect}"
|
19
|
+
logger.debug "[CLI] args: #{@args.inspect}"
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def client(&block)
|
25
|
+
EM.run {
|
26
|
+
yield
|
27
|
+
Signal.trap('INT') { EM.stop; exit }
|
28
|
+
Signal.trap('TERM') { EM.stop; exit }
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def ask_for_credentials(email = nil)
|
33
|
+
if email.nil? || email !~ EMAIL_REGEX
|
34
|
+
puts "Forward requires an account on #{HighLine.color('forwardhq.com', :underline)}"
|
35
|
+
puts "Enter your email and password"
|
36
|
+
email = ask('email: ').chomp
|
37
|
+
end
|
38
|
+
|
39
|
+
password = ask('password: ') { |q| q.echo = false }.chomp
|
40
|
+
|
41
|
+
[email, password]
|
42
|
+
end
|
43
|
+
|
44
|
+
def print_help_and_exit!
|
45
|
+
puts @opts
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
def validate(*validators)
|
50
|
+
validators.each do |validator|
|
51
|
+
validator = "validate_#{validator}"
|
52
|
+
|
53
|
+
if respond_to?(validator, true)
|
54
|
+
send(validator)
|
55
|
+
else
|
56
|
+
raise UnknownValidator, "Unable to find validator `#{validator}' in #{self.class.name}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Forward
|
2
|
+
module Command
|
3
|
+
class Config < Base
|
4
|
+
CONFIGURABLE_SETTINGS = %w[auto_copy auto_open].freeze
|
5
|
+
TRUTHY_REGEX = /\A(?:true|t|yes|y|1)\z/i.freeze
|
6
|
+
FALESY_REGEX = /\A(?:false|f|no|n|0)\z/i.freeze
|
7
|
+
|
8
|
+
def set(*args)
|
9
|
+
@setting = args[0]
|
10
|
+
@value = args[1]
|
11
|
+
|
12
|
+
validate :setting, :value
|
13
|
+
config.load
|
14
|
+
config.send("#{@setting}=", @value)
|
15
|
+
config.write
|
16
|
+
exit_with_message "#{@setting} is now #{config.send(@setting)}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def unset(*args)
|
20
|
+
@setting = args.first
|
21
|
+
|
22
|
+
validate :setting
|
23
|
+
config.load
|
24
|
+
default_value = config.set_default!(@setting)
|
25
|
+
config.write
|
26
|
+
exit_with_message "#{@setting} is now set to the default `#{default_value}'"
|
27
|
+
end
|
28
|
+
|
29
|
+
def get(*args)
|
30
|
+
@setting = args.first
|
31
|
+
|
32
|
+
validate :setting
|
33
|
+
config.load
|
34
|
+
exit_with_message "#{@setting} is currently #{config.send(@setting)}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def validate_value
|
40
|
+
@value = @value.dup.strip
|
41
|
+
|
42
|
+
case @setting
|
43
|
+
when 'auto_copy'
|
44
|
+
booleanize_value
|
45
|
+
when 'auto_open'
|
46
|
+
booleanize_value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def booleanize_value
|
51
|
+
return @value = true if @value =~ TRUTHY_REGEX
|
52
|
+
return @value = false if @value =~ FALESY_REGEX
|
53
|
+
|
54
|
+
raise ValidationError, "#{@setting} cannot be set to `#{@value}'"
|
55
|
+
end
|
56
|
+
|
57
|
+
def validate_setting
|
58
|
+
unless CONFIGURABLE_SETTINGS.include?(@setting)
|
59
|
+
raise ValidationError, "`#{@setting}' is an unknown setting"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|