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