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
@@ -0,0 +1,157 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module Forward
|
4
|
+
module Static
|
5
|
+
|
6
|
+
class TemplateContext
|
7
|
+
FILESIZE_FORMAT = [
|
8
|
+
['%.1fT', 1 << 40],
|
9
|
+
['%.1fG', 1 << 30],
|
10
|
+
['%.1fM', 1 << 20],
|
11
|
+
['%.1fK', 1 << 10],
|
12
|
+
]
|
13
|
+
|
14
|
+
|
15
|
+
def initialize(root, path_info)
|
16
|
+
@root = root
|
17
|
+
@project_name = File.basename(root)
|
18
|
+
@path_info = path_info
|
19
|
+
@path = File.join(root, path_info)
|
20
|
+
end
|
21
|
+
|
22
|
+
def path_components
|
23
|
+
@path_info.split('/')[1..-1]
|
24
|
+
end
|
25
|
+
|
26
|
+
def files
|
27
|
+
_files = []
|
28
|
+
|
29
|
+
Dir["#{@path}*"].sort.each do |path|
|
30
|
+
_stat = stat(path)
|
31
|
+
next if _stat.nil?
|
32
|
+
|
33
|
+
basename = File.basename(path)
|
34
|
+
url = path.sub(@root, '')
|
35
|
+
ext = File.extname(path)
|
36
|
+
size = _stat.size
|
37
|
+
type = _stat.directory? ? 'directory' : Rack::Mime.mime_type(ext)
|
38
|
+
size = _stat.directory? ? '-' : filesize_format(size)
|
39
|
+
|
40
|
+
if _stat.directory?
|
41
|
+
url << '/'
|
42
|
+
basename << '/'
|
43
|
+
end
|
44
|
+
|
45
|
+
_files << {
|
46
|
+
url: url,
|
47
|
+
basename: basename,
|
48
|
+
size: size,
|
49
|
+
type: type,
|
50
|
+
}
|
51
|
+
end
|
52
|
+
|
53
|
+
_files
|
54
|
+
end
|
55
|
+
|
56
|
+
def get_binding
|
57
|
+
binding
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def stat(path)
|
63
|
+
File.stat(path)
|
64
|
+
rescue Errno::ENOENT, Errno::ELOOP
|
65
|
+
nil
|
66
|
+
end
|
67
|
+
|
68
|
+
def filesize_format(int)
|
69
|
+
FILESIZE_FORMAT.each do |format, size|
|
70
|
+
return format % (int.to_f / size) if int >= size
|
71
|
+
end
|
72
|
+
|
73
|
+
int.to_s + 'B'
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class App
|
78
|
+
attr_reader :files
|
79
|
+
attr_accessor :root
|
80
|
+
attr_accessor :path
|
81
|
+
|
82
|
+
def initialize(root, app = nil)
|
83
|
+
@root = File.expand_path(root)
|
84
|
+
@app = app || Rack::File.new(@root)
|
85
|
+
@listing_view = File.read(File.expand_path('../directory.erb', __FILE__))
|
86
|
+
end
|
87
|
+
|
88
|
+
def call(env)
|
89
|
+
dup._call(env)
|
90
|
+
end
|
91
|
+
|
92
|
+
def _call(env)
|
93
|
+
@env = env
|
94
|
+
@path_info = Rack::Utils.unescape(env['PATH_INFO'])
|
95
|
+
|
96
|
+
if forbidden?
|
97
|
+
render_404
|
98
|
+
else
|
99
|
+
@path = File.join(@root, @path_info)
|
100
|
+
process
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
private
|
105
|
+
|
106
|
+
def forbidden?
|
107
|
+
@path_info =~ /\.\./
|
108
|
+
end
|
109
|
+
|
110
|
+
def render_directory_listing
|
111
|
+
context = TemplateContext.new(@root, @path_info)
|
112
|
+
body = ERB.new(@listing_view).result(context.get_binding)
|
113
|
+
|
114
|
+
[ 200, {'Content-Type' => 'text/html; charset=utf-8'}, [body] ]
|
115
|
+
end
|
116
|
+
|
117
|
+
def has_index?
|
118
|
+
File.exist?(File.join(@path, 'index.html'))
|
119
|
+
end
|
120
|
+
|
121
|
+
def process
|
122
|
+
return render_404 unless File.exist?(@path)
|
123
|
+
|
124
|
+
@stat = File.stat(@path)
|
125
|
+
|
126
|
+
raise Errno::ENOENT, 'No such file or directory' unless @stat.readable?
|
127
|
+
|
128
|
+
if @stat.directory?
|
129
|
+
return render_404 unless @path.end_with?('/')
|
130
|
+
return render_directory_listing unless has_index?
|
131
|
+
|
132
|
+
@env['PATH_INFO'] << 'index.html'
|
133
|
+
end
|
134
|
+
|
135
|
+
Rack::File.new(@root).call(@env)
|
136
|
+
|
137
|
+
rescue Errno::ENOENT, Errno::ELOOP => e
|
138
|
+
Forward.logger.debug e
|
139
|
+
Forward.logger.debug e.message
|
140
|
+
render_404
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def render_404
|
145
|
+
body = "Not Found: #{@path_info}\n"
|
146
|
+
headers = {
|
147
|
+
"Content-Type" => "text/plain",
|
148
|
+
"Content-Length" => Rack::Utils.bytesize(body).to_s,
|
149
|
+
"X-Cascade" => "pass"
|
150
|
+
}
|
151
|
+
|
152
|
+
[404, headers, [body]]
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
@@ -0,0 +1,142 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Directory Listing: <%= @project_name%><%= @path_info %></title>
|
5
|
+
<meta http-equiv="content-kind" content="text/html; charset=utf-8">
|
6
|
+
<link rel="stylesheet" href="https://assets.50east.co/v1.0/css/mark.css">
|
7
|
+
<link rel="stylesheet" href="https://assets.50east.co/v1.0/css/picons.css">
|
8
|
+
<link rel="stylesheet" href="https://assets.50east.co/v1.0/css/core.css">
|
9
|
+
<style kind="text/css">
|
10
|
+
h1 {
|
11
|
+
padding-left: 3rem;
|
12
|
+
text-indent: -1rem;
|
13
|
+
font-size: 1rem;
|
14
|
+
color: #999;
|
15
|
+
cursor: default;
|
16
|
+
}
|
17
|
+
|
18
|
+
h1 .root {
|
19
|
+
color: #222;
|
20
|
+
font-weight: 600;
|
21
|
+
}
|
22
|
+
|
23
|
+
h1 a {
|
24
|
+
color: inherit;
|
25
|
+
white-space: nowrap;
|
26
|
+
}
|
27
|
+
|
28
|
+
a {
|
29
|
+
color: #222;
|
30
|
+
}
|
31
|
+
|
32
|
+
a:hover {
|
33
|
+
color: #27c166 !important;
|
34
|
+
}
|
35
|
+
|
36
|
+
table {
|
37
|
+
max-width: 35rem;
|
38
|
+
width: 100%;
|
39
|
+
}
|
40
|
+
|
41
|
+
td,
|
42
|
+
th {
|
43
|
+
padding: 0.25rem 0 0.5rem;
|
44
|
+
}
|
45
|
+
|
46
|
+
th {
|
47
|
+
text-align: left;
|
48
|
+
padding-bottom: 1rem;
|
49
|
+
color: #ccc;
|
50
|
+
}
|
51
|
+
|
52
|
+
td.kind,
|
53
|
+
td.size {
|
54
|
+
color: #555;
|
55
|
+
}
|
56
|
+
|
57
|
+
.size {
|
58
|
+
text-align: right;
|
59
|
+
padding-right: 1rem;
|
60
|
+
}
|
61
|
+
|
62
|
+
td.name {
|
63
|
+
position: relative;
|
64
|
+
font-weight: 500;
|
65
|
+
}
|
66
|
+
|
67
|
+
.file-icon {
|
68
|
+
position: absolute;
|
69
|
+
top: 8px; left: -33px;
|
70
|
+
width: 20px;
|
71
|
+
height: 20px;
|
72
|
+
}
|
73
|
+
|
74
|
+
*[data-type="file"] .file-icon {
|
75
|
+
background-position: 0% -0.5%;
|
76
|
+
}
|
77
|
+
|
78
|
+
*[data-type="directory"] .file-icon {
|
79
|
+
top: 10px;
|
80
|
+
left: -34px;
|
81
|
+
}
|
82
|
+
|
83
|
+
footer p {
|
84
|
+
text-align: left !important;
|
85
|
+
}
|
86
|
+
|
87
|
+
footer a {
|
88
|
+
color: #27c166 !important;
|
89
|
+
}
|
90
|
+
|
91
|
+
footer a:hover {
|
92
|
+
border-bottom: 1px solid #ccc;
|
93
|
+
}
|
94
|
+
</style>
|
95
|
+
</head>
|
96
|
+
|
97
|
+
<body>
|
98
|
+
<div class="page">
|
99
|
+
<h1>
|
100
|
+
<a href="/" class="root"><%= @project_name %></a>
|
101
|
+
<% if path_components%>
|
102
|
+
<% path_components.each_with_index do |part, i| %>
|
103
|
+
/ <a href="/<%= path_components[0..i].join('/') %>/"><%= part %></a>
|
104
|
+
<% end %>
|
105
|
+
<% end %>
|
106
|
+
</h1>
|
107
|
+
|
108
|
+
<section>
|
109
|
+
<table>
|
110
|
+
<thead>
|
111
|
+
<tr>
|
112
|
+
<th class="name">Name</th>
|
113
|
+
<th class="size">Size</th>
|
114
|
+
<th class="kind">Kind</th>
|
115
|
+
</tr>
|
116
|
+
</thead>
|
117
|
+
<tbody>
|
118
|
+
<% files.each do |file| %>
|
119
|
+
<tr>
|
120
|
+
<td class="name"><div class="file-icon"></div><a href="<%= file[:url] %>"><%= file[:basename] %></a></td><td class="size"><%= file[:size] %></td><td class="kind"><%= file[:type] %></td>
|
121
|
+
</tr>
|
122
|
+
<% end %>
|
123
|
+
</tbody>
|
124
|
+
</table>
|
125
|
+
</section>
|
126
|
+
<footer>
|
127
|
+
<p>
|
128
|
+
Brought to you by <a href="https://forwardhq.com/">Forward</a>
|
129
|
+
</p>
|
130
|
+
</footer>
|
131
|
+
</div>
|
132
|
+
<script src="https://assets.50east.co/v1.0/js/core.js"></script>
|
133
|
+
<script>
|
134
|
+
$('td.name').each(function(){
|
135
|
+
var filename = $.trim($(this).text());
|
136
|
+
var row = $(this).closest('tr');
|
137
|
+
|
138
|
+
row.attr('data-type', FileTypes.getTypeForFilename(filename));
|
139
|
+
});
|
140
|
+
</script>
|
141
|
+
</body>
|
142
|
+
</html>
|
data/lib/forward/tunnel.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module Forward
|
2
2
|
class Tunnel
|
3
|
-
|
3
|
+
include Common
|
4
4
|
|
5
5
|
# The Tunnel resource ID
|
6
6
|
attr_reader :id
|
@@ -8,60 +8,122 @@ module Forward
|
|
8
8
|
attr_reader :subdomain
|
9
9
|
# The CNAME for the Tunnel
|
10
10
|
attr_reader :cname
|
11
|
-
# The host
|
11
|
+
# The host to forward requests to
|
12
12
|
attr_reader :host
|
13
|
-
# The
|
14
|
-
attr_reader :vhost
|
15
|
-
# The hostport (local port)
|
16
|
-
attr_reader :hostport
|
17
|
-
# The remote port
|
13
|
+
# The port to forward requests to
|
18
14
|
attr_reader :port
|
15
|
+
# Authentication for tunnel
|
16
|
+
attr_reader :username
|
17
|
+
attr_reader :password
|
18
|
+
attr_reader :no_auth
|
19
|
+
# The public hostname/subdomain url for the tunnel
|
20
|
+
attr_reader :url
|
19
21
|
# The tunneler host
|
20
22
|
attr_reader :tunneler
|
21
23
|
# The timeout
|
22
24
|
attr_reader :timeout
|
23
|
-
|
24
|
-
|
25
|
+
|
26
|
+
attr_reader :socket
|
27
|
+
attr_reader :socket_url
|
28
|
+
attr_reader :requests
|
29
|
+
|
30
|
+
attr_accessor :last_active_at
|
25
31
|
|
26
32
|
# Initializes a Tunnel instance for the Client and requests a tunnel from
|
27
33
|
# API.
|
28
34
|
#
|
29
35
|
# client - The Client instance.
|
30
|
-
def initialize(
|
31
|
-
|
32
|
-
@
|
33
|
-
@id
|
34
|
-
@
|
35
|
-
@
|
36
|
-
@
|
37
|
-
@
|
38
|
-
@
|
39
|
-
@
|
40
|
-
@
|
41
|
-
@
|
36
|
+
def initialize(attributes = {})
|
37
|
+
logger.debug "[tunnel] initializing with: #{attributes.inspect}"
|
38
|
+
@attributes = attributes
|
39
|
+
@id = attributes[:id]
|
40
|
+
@host = attributes[:vhost]
|
41
|
+
@port = attributes[:hostport]
|
42
|
+
@subdomain_prefix = attributes[:subdomain_prefix]
|
43
|
+
@username = attributes[:username]
|
44
|
+
@password = attributes[:password]
|
45
|
+
@no_auth = attributes[:no_auth]
|
46
|
+
@static_path = attributes[:static_path]
|
47
|
+
@cname = attributes[:cname]
|
48
|
+
@timeout = attributes[:timeout]
|
49
|
+
@tunneler = attributes[:tunneler]
|
50
|
+
@url = attributes[:url]
|
51
|
+
@requests = {}
|
52
|
+
@open = false
|
53
|
+
@socket = Socket.new(self)
|
54
|
+
end
|
55
|
+
|
56
|
+
def ready!
|
57
|
+
logger.debug '[tunnel] opened successfully'
|
58
|
+
@open = true
|
59
|
+
|
60
|
+
copy_url_to_clipboard
|
61
|
+
open_url_in_browser
|
62
|
+
display_ready_message
|
63
|
+
end
|
64
|
+
|
65
|
+
def open?
|
66
|
+
@open
|
42
67
|
end
|
43
68
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
69
|
+
def destroy(&block)
|
70
|
+
API::Tunnel.destroy(id, &block)
|
71
|
+
end
|
72
|
+
|
73
|
+
def authority
|
74
|
+
@authority ||= begin
|
75
|
+
_authority = "#{host}"
|
76
|
+
_authority << ":#{port}" unless port == 80
|
77
|
+
|
78
|
+
_authority
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def track_activity!
|
83
|
+
self.last_active_at = Time.now.to_i
|
84
|
+
end
|
85
|
+
|
86
|
+
private
|
87
|
+
|
88
|
+
def display_ready_message
|
89
|
+
source = static? ? "Directory `#{File.expand_path(@static_path).split('/').last}'" : authority
|
90
|
+
message = "#{source} is now available at: #{HighLine.color(url, :underline)}"
|
91
|
+
|
92
|
+
message << " and #{HighLine.color("http://#{cname}", :underline)}" if cname
|
93
|
+
|
94
|
+
puts "#{message}\n\nCtrl-C to stop forwarding"
|
95
|
+
end
|
96
|
+
|
97
|
+
def copy_url_to_clipboard
|
98
|
+
return unless config.auto_copy?
|
99
|
+
|
100
|
+
if windows?
|
101
|
+
begin
|
102
|
+
require 'ffi'
|
103
|
+
rescue LoadError
|
104
|
+
puts "The FFI gem is required to copy your url to the clipboard, you can install it with `gem install ffi'"
|
105
|
+
return
|
59
106
|
end
|
60
|
-
|
107
|
+
end
|
108
|
+
|
109
|
+
Clipboard.copy(url)
|
110
|
+
end
|
111
|
+
|
112
|
+
def open_url_in_browser
|
113
|
+
return unless config.auto_open?
|
114
|
+
|
115
|
+
case os
|
116
|
+
when :windows
|
117
|
+
%x[start #{url}]
|
118
|
+
when :osx
|
119
|
+
%x[open #{url}]
|
120
|
+
when :unix
|
121
|
+
%x[xdg-open #{url}]
|
122
|
+
end
|
61
123
|
end
|
62
|
-
|
63
|
-
def
|
64
|
-
|
124
|
+
|
125
|
+
def static?
|
126
|
+
!@static_path.nil?
|
65
127
|
end
|
66
128
|
|
67
129
|
end
|
data/lib/forward/version.rb
CHANGED
data/lib/forward.rb
CHANGED
@@ -1,108 +1,125 @@
|
|
1
1
|
require 'base64'
|
2
2
|
require 'json'
|
3
3
|
require 'logger'
|
4
|
-
require 'openssl'
|
5
|
-
require 'optparse'
|
6
4
|
require 'rbconfig'
|
7
|
-
require '
|
8
|
-
require '
|
5
|
+
require 'fileutils'
|
6
|
+
require 'yaml'
|
9
7
|
|
8
|
+
require 'slop'
|
10
9
|
require 'highline/import'
|
11
|
-
require '
|
10
|
+
require 'eventmachine'
|
11
|
+
require 'em-http-request'
|
12
|
+
require 'faye/websocket'
|
13
|
+
require 'clipboard'
|
12
14
|
|
13
15
|
require 'forward/core_extensions'
|
14
16
|
|
15
|
-
require 'forward/
|
17
|
+
require 'forward/common'
|
16
18
|
require 'forward/api'
|
17
19
|
require 'forward/config'
|
20
|
+
require 'forward/request'
|
21
|
+
require 'forward/socket'
|
18
22
|
require 'forward/tunnel'
|
19
|
-
require 'forward/client'
|
20
23
|
require 'forward/cli'
|
21
24
|
require 'forward/version'
|
22
25
|
|
23
26
|
module Forward
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
class Error < StandardError; end
|
28
|
+
# An error occurred with the CLI
|
29
|
+
class CLIError < Error; end
|
30
|
+
# An error occurred with the Client
|
31
|
+
class ClientError < Error; end
|
32
|
+
# An error occurred with the Config
|
33
|
+
class ConfigError < Error; end
|
34
|
+
# An error occurred with the Tunnel
|
35
|
+
class TunnelError < Error; end
|
36
|
+
|
37
|
+
SUPPORT_EMAIL = 'support@forwardhq.com'.freeze
|
38
|
+
|
39
|
+
# Helper for determining the host OS
|
29
40
|
#
|
30
|
-
# Returns
|
31
|
-
def self.
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
41
|
+
# Returns simplified and symbolized host os name
|
42
|
+
def self.os
|
43
|
+
@os ||= begin
|
44
|
+
case RbConfig::CONFIG['host_os']
|
45
|
+
when /mswin|mingw|cygwin/
|
46
|
+
:windows
|
47
|
+
when /darwin/
|
48
|
+
:osx
|
49
|
+
when /linux|bsd/
|
50
|
+
:unix
|
51
|
+
else
|
52
|
+
:unknown
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Helper to determine if host OS is windows
|
36
58
|
#
|
37
|
-
# Returns
|
38
|
-
def self.ssh_port
|
39
|
-
ENV['FORWARD_SSH_PORT'] || DEFAULT_SSH_PORT
|
40
|
-
end
|
41
|
-
|
59
|
+
# Returns Boolean
|
42
60
|
def self.windows?
|
43
|
-
|
61
|
+
@windows ||= os == :windows
|
44
62
|
end
|
45
63
|
|
46
|
-
|
47
|
-
|
64
|
+
# Setter for the current Client
|
65
|
+
#
|
66
|
+
# Returns the new Forawrd::Client instance
|
67
|
+
def self.tunnel=(tunnel)
|
68
|
+
@tunnel = tunnel
|
48
69
|
end
|
49
70
|
|
50
|
-
|
51
|
-
|
71
|
+
# Getter for the current Client
|
72
|
+
#
|
73
|
+
# Returns a Forward::Client instance
|
74
|
+
def self.tunnel
|
75
|
+
@tunnel
|
52
76
|
end
|
53
77
|
|
54
|
-
|
55
|
-
|
78
|
+
# Helper method for making forward quiet (silence most output)
|
79
|
+
def self.quiet!
|
80
|
+
@quiet = true
|
56
81
|
end
|
57
82
|
|
58
|
-
|
59
|
-
|
83
|
+
# Helper method for making forward quiet (silence most output)
|
84
|
+
def self.quiet?
|
85
|
+
@quiet == true
|
60
86
|
end
|
61
87
|
|
88
|
+
# Helper method for setting the log level to debug
|
62
89
|
def self.debug!
|
63
|
-
|
64
|
-
@debug = true
|
65
|
-
log.level = Logger::DEBUG
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.debug?
|
69
|
-
@debug
|
90
|
+
logger.level = Logger::DEBUG
|
70
91
|
end
|
71
92
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
@logdev = stringio_log
|
78
|
-
@debug = true
|
79
|
-
@debug_remotely = true
|
80
|
-
log.level = Logger::DEBUG
|
81
|
-
end
|
82
|
-
|
83
|
-
def self.debug_remotely?
|
84
|
-
@debug_remotely ||= false
|
85
|
-
end
|
86
|
-
|
87
|
-
def self.logdev
|
88
|
-
@logdev ||= (windows? ? 'NUL:' : '/dev/null')
|
93
|
+
# Setter for the logger
|
94
|
+
#
|
95
|
+
# Returns the new Logger instance
|
96
|
+
def self.logger=(logger)
|
97
|
+
@logger ||= logger
|
89
98
|
end
|
90
99
|
|
100
|
+
# Getter for the logger
|
101
|
+
#
|
102
|
+
# Returns the new or cached Logger instance
|
91
103
|
def self.logger
|
92
|
-
@
|
93
|
-
|
104
|
+
@logger ||= begin
|
105
|
+
_logger = Logger.new(STDOUT)
|
106
|
+
_logger.level = Logger::WARN
|
107
|
+
_logger.formatter = proc do |severity, datetime, progname, msg|
|
108
|
+
"#{severity} - [#{datetime.strftime('%H:%M:%S')}] #{msg}\n"
|
109
|
+
end
|
94
110
|
|
95
|
-
|
96
|
-
|
111
|
+
_logger
|
112
|
+
end
|
97
113
|
end
|
98
114
|
|
99
|
-
# Returns a string representing a detailed client version
|
115
|
+
# Returns a string representing a detailed client version
|
100
116
|
#
|
101
|
-
# Returns a String representing the client
|
117
|
+
# Returns a String representing the client
|
102
118
|
def self.client_string
|
103
119
|
os = RbConfig::CONFIG['host_os']
|
104
120
|
engine = defined?(RUBY_ENGINE) ? RUBY_ENGINE : 'ruby'
|
105
121
|
|
106
|
-
|
122
|
+
# TODO: make os version more friendly
|
123
|
+
"(#{engine}-#{RUBY_VERSION})(#{os})(v#{Forward::VERSION})"
|
107
124
|
end
|
108
125
|
end
|