prettier 1.2.2 → 1.4.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 +4 -4
- data/CHANGELOG.md +64 -1
- data/CONTRIBUTING.md +2 -2
- data/README.md +22 -94
- data/node_modules/prettier/index.js +54 -54
- data/package.json +2 -3
- data/rubocop.yml +26 -0
- data/src/haml/embed.js +87 -0
- data/src/haml/nodes/comment.js +27 -0
- data/src/haml/nodes/doctype.js +34 -0
- data/src/haml/nodes/filter.js +16 -0
- data/src/haml/nodes/hamlComment.js +21 -0
- data/src/haml/nodes/plain.js +6 -0
- data/src/haml/nodes/root.js +8 -0
- data/src/haml/nodes/script.js +33 -0
- data/src/haml/nodes/silentScript.js +59 -0
- data/src/haml/nodes/tag.js +193 -0
- data/src/haml/parser.js +22 -0
- data/src/haml/parser.rb +138 -0
- data/src/haml/printer.js +28 -0
- data/src/parser/getLang.js +32 -0
- data/src/parser/getNetcat.js +50 -0
- data/src/parser/netcat.js +15 -0
- data/src/parser/parseSync.js +33 -0
- data/src/parser/requestParse.js +74 -0
- data/src/parser/server.rb +61 -0
- data/src/{ruby.js → plugin.js} +25 -4
- data/src/prettier.js +1 -0
- data/src/rbs/parser.js +39 -0
- data/src/rbs/parser.rb +88 -0
- data/src/rbs/printer.js +605 -0
- data/src/{embed.js → ruby/embed.js} +6 -2
- data/src/{nodes.js → ruby/nodes.js} +0 -0
- data/src/{nodes → ruby/nodes}/alias.js +1 -1
- data/src/{nodes → ruby/nodes}/aref.js +8 -1
- data/src/{nodes → ruby/nodes}/args.js +2 -2
- data/src/{nodes → ruby/nodes}/arrays.js +2 -3
- data/src/{nodes → ruby/nodes}/assign.js +7 -3
- data/src/ruby/nodes/blocks.js +90 -0
- data/src/{nodes → ruby/nodes}/calls.js +20 -47
- data/src/{nodes → ruby/nodes}/case.js +1 -1
- data/src/{nodes → ruby/nodes}/class.js +1 -1
- data/src/ruby/nodes/commands.js +131 -0
- data/src/{nodes → ruby/nodes}/conditionals.js +3 -3
- data/src/{nodes → ruby/nodes}/constants.js +2 -2
- data/src/{nodes → ruby/nodes}/flow.js +2 -2
- data/src/{nodes → ruby/nodes}/hashes.js +32 -10
- data/src/{nodes → ruby/nodes}/heredocs.js +2 -2
- data/src/ruby/nodes/hooks.js +34 -0
- data/src/{nodes → ruby/nodes}/ints.js +0 -0
- data/src/{nodes → ruby/nodes}/lambdas.js +2 -2
- data/src/{nodes → ruby/nodes}/loops.js +10 -7
- data/src/{nodes → ruby/nodes}/massign.js +8 -1
- data/src/{nodes → ruby/nodes}/methods.js +10 -9
- data/src/{nodes → ruby/nodes}/operators.js +2 -2
- data/src/{nodes → ruby/nodes}/params.js +31 -16
- data/src/{nodes → ruby/nodes}/patterns.js +17 -6
- data/src/{nodes → ruby/nodes}/regexp.js +2 -2
- data/src/{nodes → ruby/nodes}/rescue.js +34 -27
- data/src/{nodes → ruby/nodes}/return.js +21 -10
- data/src/{nodes → ruby/nodes}/statements.js +9 -9
- data/src/{nodes → ruby/nodes}/strings.js +28 -36
- data/src/{nodes → ruby/nodes}/super.js +2 -2
- data/src/{nodes → ruby/nodes}/undef.js +1 -1
- data/src/ruby/parser.js +39 -0
- data/src/{parser.rb → ruby/parser.rb} +498 -529
- data/src/{printer.js → ruby/printer.js} +1 -3
- data/src/{toProc.js → ruby/toProc.js} +4 -8
- data/src/utils.js +10 -93
- data/src/utils/containsAssignment.js +11 -0
- data/src/utils/getTrailingComma.js +5 -0
- data/src/utils/hasAncestor.js +17 -0
- data/src/utils/literal.js +7 -0
- data/src/utils/makeCall.js +14 -0
- data/src/utils/noIndent.js +11 -0
- data/src/utils/skipAssignIndent.js +10 -0
- metadata +71 -41
- data/src/nodes/blocks.js +0 -85
- data/src/nodes/commands.js +0 -91
- data/src/nodes/hooks.js +0 -44
- data/src/parser.js +0 -86
data/src/haml/parser.js
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
const parseSync = require("../parser/parseSync");
|
2
|
+
|
3
|
+
const parse = (text, _parsers, _opts) => {
|
4
|
+
return parseSync("haml", text);
|
5
|
+
};
|
6
|
+
|
7
|
+
const pragmaPattern = /^\s*-#\s*@(prettier|format)/;
|
8
|
+
const hasPragma = (text) => pragmaPattern.test(text);
|
9
|
+
|
10
|
+
// These functions are just placeholders until we can actually perform this
|
11
|
+
// properly. The functions are necessary otherwise the format with cursor
|
12
|
+
// functions break.
|
13
|
+
const locStart = (_node) => 0;
|
14
|
+
const locEnd = (_node) => 0;
|
15
|
+
|
16
|
+
module.exports = {
|
17
|
+
parse,
|
18
|
+
astFormat: "haml",
|
19
|
+
hasPragma,
|
20
|
+
locStart,
|
21
|
+
locEnd
|
22
|
+
};
|
data/src/haml/parser.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ripper'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'haml'
|
7
|
+
rescue LoadError
|
8
|
+
# If we can't load the haml gem, then we're going to provide a shim parser
|
9
|
+
# that will warn and bail out.
|
10
|
+
class Prettier::HAMLParser
|
11
|
+
def self.parse(text)
|
12
|
+
warn(
|
13
|
+
'The `haml` gem could not be loaded. Please ensure you have it ' \
|
14
|
+
'installed and that it is available in the gem path.'
|
15
|
+
)
|
16
|
+
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
return
|
22
|
+
end
|
23
|
+
|
24
|
+
class Haml::Parser::ParseNode
|
25
|
+
class DeepAttributeParser
|
26
|
+
def parse(string)
|
27
|
+
Haml::AttributeParser.available? ? parse_value(string) : string
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def literal(string, level)
|
33
|
+
level == 0 ? string : "&#{string}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def parse_value(string, level = 0)
|
37
|
+
response = Ripper.sexp(string)
|
38
|
+
return literal(string, level) unless response
|
39
|
+
|
40
|
+
case response[1][0][0]
|
41
|
+
when :hash
|
42
|
+
hash = Haml::AttributeParser.parse(string)
|
43
|
+
|
44
|
+
if hash
|
45
|
+
# Explicitly not using Enumerable#to_h here to support Ruby 2.5
|
46
|
+
hash.each_with_object({}) do |(key, value), response|
|
47
|
+
response[key] = parse_value(value, level + 1)
|
48
|
+
end
|
49
|
+
else
|
50
|
+
literal(string, level)
|
51
|
+
end
|
52
|
+
when :string_literal
|
53
|
+
string[1...-1]
|
54
|
+
else
|
55
|
+
literal(string, level)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
ESCAPE = /Haml::Helpers.html_escape\(\((.+)\)\)/.freeze
|
61
|
+
|
62
|
+
# If a node comes in as the plain type but starts with one of the special
|
63
|
+
# characters that haml parses, then we need to escape it with a \ when
|
64
|
+
# printing. So here we make a regexp pattern to check if the node needs to be
|
65
|
+
# escaped.
|
66
|
+
special_chars =
|
67
|
+
Haml::Parser::SPECIAL_CHARACTERS.map { |char| Regexp.escape(char) }
|
68
|
+
|
69
|
+
SPECIAL_START = /\A(?:#{special_chars.join('|')})/
|
70
|
+
|
71
|
+
def as_json
|
72
|
+
case type
|
73
|
+
when :comment, :doctype, :silent_script
|
74
|
+
to_h.tap do |json|
|
75
|
+
json.delete(:parent)
|
76
|
+
json[:children] = children.map(&:as_json)
|
77
|
+
end
|
78
|
+
when :filter, :haml_comment
|
79
|
+
to_h.tap { |json| json.delete(:parent) }
|
80
|
+
when :plain
|
81
|
+
to_h.tap do |json|
|
82
|
+
json.delete(:parent)
|
83
|
+
json[:children] = children.map(&:as_json)
|
84
|
+
|
85
|
+
text = json[:value][:text]
|
86
|
+
json[:value][:text] = "\\#{text}" if text.match?(SPECIAL_START)
|
87
|
+
end
|
88
|
+
when :root
|
89
|
+
to_h.tap { |json| json[:children] = children.map(&:as_json) }
|
90
|
+
when :script
|
91
|
+
to_h.tap do |json|
|
92
|
+
json.delete(:parent)
|
93
|
+
json[:children] = children.map(&:as_json)
|
94
|
+
|
95
|
+
if json[:value][:text].match?(ESCAPE)
|
96
|
+
json[:value][:text].gsub!(ESCAPE) { $1 }
|
97
|
+
json[:value].merge!(escape_html: 'escape_html', interpolate: true)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
when :tag
|
101
|
+
to_h.tap do |json|
|
102
|
+
json.delete(:parent)
|
103
|
+
|
104
|
+
# For some reason this is actually using a symbol to represent a null
|
105
|
+
# object ref instead of nil itself, so just replacing it here for
|
106
|
+
# simplicity in the printer
|
107
|
+
json[:value][:object_ref] = nil if json[:value][:object_ref] == :nil
|
108
|
+
|
109
|
+
# Get a reference to the dynamic attributes hash
|
110
|
+
dynamic_attributes = value[:dynamic_attributes].to_h
|
111
|
+
|
112
|
+
# If we have any in the old style, then we're going to pass it through
|
113
|
+
# the deep attribute parser filter.
|
114
|
+
if dynamic_attributes[:old]
|
115
|
+
dynamic_attributes[:old] =
|
116
|
+
DeepAttributeParser.new.parse(dynamic_attributes[:old])
|
117
|
+
end
|
118
|
+
|
119
|
+
json.merge!(
|
120
|
+
children: children.map(&:as_json),
|
121
|
+
value: value.merge(dynamic_attributes: dynamic_attributes)
|
122
|
+
)
|
123
|
+
end
|
124
|
+
else
|
125
|
+
raise ArgumentError, "Unsupported type: #{type}"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
module Prettier
|
131
|
+
class HAMLParser
|
132
|
+
def self.parse(source)
|
133
|
+
Haml::Parser.new({}).call(source).as_json
|
134
|
+
rescue StandardError
|
135
|
+
false
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/src/haml/printer.js
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
const embed = require("./embed");
|
2
|
+
const nodes = {
|
3
|
+
comment: require("./nodes/comment"),
|
4
|
+
doctype: require("./nodes/doctype"),
|
5
|
+
filter: require("./nodes/filter"),
|
6
|
+
haml_comment: require("./nodes/hamlComment"),
|
7
|
+
plain: require("./nodes/plain"),
|
8
|
+
root: require("./nodes/root"),
|
9
|
+
script: require("./nodes/script"),
|
10
|
+
silent_script: require("./nodes/silentScript"),
|
11
|
+
tag: require("./nodes/tag")
|
12
|
+
};
|
13
|
+
|
14
|
+
const genericPrint = (path, opts, print) => {
|
15
|
+
const { type } = path.getValue();
|
16
|
+
|
17
|
+
/* istanbul ignore next */
|
18
|
+
if (!(type in nodes)) {
|
19
|
+
throw new Error(`Unsupported node encountered: ${type}`);
|
20
|
+
}
|
21
|
+
|
22
|
+
return nodes[type](path, opts, print);
|
23
|
+
};
|
24
|
+
|
25
|
+
module.exports = {
|
26
|
+
embed,
|
27
|
+
print: genericPrint
|
28
|
+
};
|
@@ -0,0 +1,32 @@
|
|
1
|
+
// In order to properly parse ruby code, we need to tell the ruby process to
|
2
|
+
// parse using UTF-8. Unfortunately, the way that you accomplish this looks
|
3
|
+
// differently depending on your platform.
|
4
|
+
/* istanbul ignore next */
|
5
|
+
function getLang() {
|
6
|
+
const { env, platform } = process;
|
7
|
+
const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
|
8
|
+
|
9
|
+
// If an env var is set for the locale that already includes UTF-8 in the
|
10
|
+
// name, then assume we can go with that.
|
11
|
+
if (envValue && envValue.includes("UTF-8")) {
|
12
|
+
return envValue;
|
13
|
+
}
|
14
|
+
|
15
|
+
// Otherwise, we're going to guess which encoding to use based on the system.
|
16
|
+
// This is probably not the best approach in the world, as you could be on
|
17
|
+
// linux and not have C.UTF-8, but in that case you're probably passing an env
|
18
|
+
// var for it. This object below represents all of the possible values of
|
19
|
+
// process.platform per:
|
20
|
+
// https://nodejs.org/api/process.html#process_process_platform
|
21
|
+
return {
|
22
|
+
aix: "C.UTF-8",
|
23
|
+
darwin: "en_US.UTF-8",
|
24
|
+
freebsd: "C.UTF-8",
|
25
|
+
linux: "C.UTF-8",
|
26
|
+
openbsd: "C.UTF-8",
|
27
|
+
sunos: "C.UTF-8",
|
28
|
+
win32: ".UTF-8"
|
29
|
+
}[platform];
|
30
|
+
}
|
31
|
+
|
32
|
+
module.exports = getLang;
|
@@ -0,0 +1,50 @@
|
|
1
|
+
const { spawnSync } = require("child_process");
|
2
|
+
const os = require("os");
|
3
|
+
|
4
|
+
// Checks to see if an executable is available.
|
5
|
+
function hasCommand(name) {
|
6
|
+
let result;
|
7
|
+
|
8
|
+
if (os.type() === "Windows_NT") {
|
9
|
+
result = spawnSync("where", [name]);
|
10
|
+
} else {
|
11
|
+
result = spawnSync("command", ["-v", name]);
|
12
|
+
}
|
13
|
+
|
14
|
+
return result.status === 0;
|
15
|
+
}
|
16
|
+
|
17
|
+
// Finds an netcat-like adapter to use for sending data to a socket. We order
|
18
|
+
// these by likelihood of being found so we can avoid some shell-outs.
|
19
|
+
function getCommandAndArg() {
|
20
|
+
if (hasCommand("nc")) {
|
21
|
+
return ["nc", "-U"];
|
22
|
+
}
|
23
|
+
|
24
|
+
if (hasCommand("telnet")) {
|
25
|
+
return ["telnet", "-u"];
|
26
|
+
}
|
27
|
+
|
28
|
+
if (hasCommand("ncat")) {
|
29
|
+
return ["ncat", "-U"];
|
30
|
+
}
|
31
|
+
|
32
|
+
if (hasCommand("socat")) {
|
33
|
+
return ["socat", "-"];
|
34
|
+
}
|
35
|
+
|
36
|
+
return ["node", require.resolve("./netcat.js")];
|
37
|
+
}
|
38
|
+
|
39
|
+
let command;
|
40
|
+
let arg;
|
41
|
+
|
42
|
+
function getNetcat() {
|
43
|
+
if (!command) {
|
44
|
+
[command, arg] = getCommandAndArg();
|
45
|
+
}
|
46
|
+
|
47
|
+
return { command, arg };
|
48
|
+
}
|
49
|
+
|
50
|
+
module.exports = getNetcat;
|
@@ -0,0 +1,15 @@
|
|
1
|
+
// A simple fallback when no netcat-compatible adapter is found on the system.
|
2
|
+
// On average, this is 2-3x slower than netcat, but still much faster than
|
3
|
+
// spawning a new Ruby process.
|
4
|
+
|
5
|
+
const { createConnection } = require("net");
|
6
|
+
|
7
|
+
const sock = process.argv[process.argv.length - 1];
|
8
|
+
|
9
|
+
const client = createConnection(sock, () => process.stdin.pipe(client));
|
10
|
+
|
11
|
+
client.on("data", (data) => process.stdout.write(data));
|
12
|
+
|
13
|
+
client.on("error", (error) => {
|
14
|
+
console.error(error);
|
15
|
+
});
|
@@ -0,0 +1,33 @@
|
|
1
|
+
const requestParse = require("./requestParse");
|
2
|
+
|
3
|
+
// Formats and sends a request to the parser server. We use netcat (or something
|
4
|
+
// like it) here since Prettier requires the results of `parse` to be
|
5
|
+
// synchronous and Node.js does not offer a mechanism for synchronous socket
|
6
|
+
// requests.
|
7
|
+
function parseSync(parser, source) {
|
8
|
+
const response = requestParse(parser, source);
|
9
|
+
|
10
|
+
if (
|
11
|
+
response.stdout.length === 0 ||
|
12
|
+
(response.status !== null && response.status !== 0)
|
13
|
+
) {
|
14
|
+
console.error("Could not parse response from server");
|
15
|
+
console.error(response);
|
16
|
+
|
17
|
+
throw new Error(response.stderr || "An unknown error occurred");
|
18
|
+
}
|
19
|
+
|
20
|
+
const parsed = JSON.parse(response.stdout);
|
21
|
+
|
22
|
+
if (parsed.error) {
|
23
|
+
throw new Error(
|
24
|
+
`@prettier/plugin-ruby encountered an error when attempting to parse \
|
25
|
+
the ruby source. This usually means there was a syntax error in the \
|
26
|
+
file in question. You can verify by running \`ruby -i [path/to/file]\`.`
|
27
|
+
);
|
28
|
+
}
|
29
|
+
|
30
|
+
return parsed;
|
31
|
+
}
|
32
|
+
|
33
|
+
module.exports = parseSync;
|
@@ -0,0 +1,74 @@
|
|
1
|
+
const { spawn, spawnSync, execSync } = require("child_process");
|
2
|
+
const path = require("path");
|
3
|
+
const { existsSync, mkdtempSync } = require("fs");
|
4
|
+
const process = require("process");
|
5
|
+
const os = require("os");
|
6
|
+
|
7
|
+
const getNetcat = require("./getNetcat");
|
8
|
+
const getLang = require("./getLang");
|
9
|
+
|
10
|
+
let sockfile = process.env.PRETTIER_RUBY_HOST;
|
11
|
+
|
12
|
+
// Spawn the parser.rb subprocess. We do this since booting Ruby is slow, and we
|
13
|
+
// can re-use the parser process multiple times since it is statelesss.
|
14
|
+
function spawnParseServer() {
|
15
|
+
const server = spawn(
|
16
|
+
"ruby",
|
17
|
+
[path.join(__dirname, "./server.rb"), sockfile],
|
18
|
+
{
|
19
|
+
env: Object.assign({}, process.env, { LANG: getLang() }),
|
20
|
+
detached: true,
|
21
|
+
stdio: "inherit"
|
22
|
+
}
|
23
|
+
);
|
24
|
+
|
25
|
+
process.on("exit", () => {
|
26
|
+
try {
|
27
|
+
process.kill(-server.pid);
|
28
|
+
} catch (e) {
|
29
|
+
// ignore
|
30
|
+
}
|
31
|
+
});
|
32
|
+
|
33
|
+
server.unref();
|
34
|
+
const now = new Date();
|
35
|
+
|
36
|
+
// Wait for server to go live.
|
37
|
+
while (!existsSync(sockfile) && new Date() - now < 3000) {
|
38
|
+
execSync("sleep 0.1");
|
39
|
+
}
|
40
|
+
}
|
41
|
+
|
42
|
+
// Ensures that a parser server is currently running by checking against the
|
43
|
+
// sockfile variable. If it is not, create a temporary directory to house the
|
44
|
+
// sockfile and spawn the ruby process.
|
45
|
+
function ensureParseServer() {
|
46
|
+
if (sockfile) {
|
47
|
+
return;
|
48
|
+
}
|
49
|
+
|
50
|
+
const tmpDir = mkdtempSync(path.join(os.tmpdir(), "prettier-ruby"));
|
51
|
+
sockfile = path.join(tmpDir, `${process.pid}.sock`);
|
52
|
+
|
53
|
+
spawnParseServer();
|
54
|
+
}
|
55
|
+
|
56
|
+
// Sends a request to the parse server to parse the given content.
|
57
|
+
function requestParse(parser, source) {
|
58
|
+
ensureParseServer();
|
59
|
+
|
60
|
+
const { command, arg } = getNetcat();
|
61
|
+
const { stdout, stderr, status } = spawnSync(command, [arg, sockfile], {
|
62
|
+
input: `${parser}|${source}`,
|
63
|
+
maxBuffer: 15 * 1024 * 1024
|
64
|
+
});
|
65
|
+
|
66
|
+
return {
|
67
|
+
command,
|
68
|
+
stdout: stdout.toString(),
|
69
|
+
stderr: stderr.toString(),
|
70
|
+
status
|
71
|
+
};
|
72
|
+
}
|
73
|
+
|
74
|
+
module.exports = requestParse;
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup' if ENV['CI']
|
4
|
+
require 'socket'
|
5
|
+
require 'json'
|
6
|
+
|
7
|
+
require_relative '../ruby/parser'
|
8
|
+
require_relative '../rbs/parser'
|
9
|
+
require_relative '../haml/parser'
|
10
|
+
|
11
|
+
# Set the program name so that it's easy to find if we need it
|
12
|
+
$PROGRAM_NAME = 'prettier-ruby-parser'
|
13
|
+
|
14
|
+
# Make sure we trap these signals to be sure we get the quit command coming from
|
15
|
+
# the parent node process
|
16
|
+
quit = false
|
17
|
+
trap(:QUIT) { quit = true }
|
18
|
+
trap(:INT) { quit = true }
|
19
|
+
trap(:TERM) { quit = true }
|
20
|
+
|
21
|
+
sockfile = ARGV.first || "/tmp/#{$PROGRAM_NAME}.sock"
|
22
|
+
server = UNIXServer.new(sockfile)
|
23
|
+
|
24
|
+
at_exit do
|
25
|
+
server.close
|
26
|
+
File.unlink(sockfile)
|
27
|
+
end
|
28
|
+
|
29
|
+
loop do
|
30
|
+
break if quit
|
31
|
+
|
32
|
+
# Start up a new thread that will handle each successive connection.
|
33
|
+
Thread.new(server.accept_nonblock) do |socket|
|
34
|
+
parser, source = socket.read.force_encoding('UTF-8').split('|', 2)
|
35
|
+
|
36
|
+
response =
|
37
|
+
case parser
|
38
|
+
when 'ruby'
|
39
|
+
Prettier::Parser.parse(source)
|
40
|
+
when 'rbs'
|
41
|
+
Prettier::RBSParser.parse(source)
|
42
|
+
when 'haml'
|
43
|
+
Prettier::HAMLParser.parse(source)
|
44
|
+
end
|
45
|
+
|
46
|
+
if response
|
47
|
+
socket.write(JSON.fast_generate(response))
|
48
|
+
else
|
49
|
+
socket.write('{ "error": true }')
|
50
|
+
end
|
51
|
+
ensure
|
52
|
+
socket.close
|
53
|
+
end
|
54
|
+
rescue IO::WaitReadable, Errno::EINTR
|
55
|
+
# Wait for select(2) to give us a connection that has content for 1 second.
|
56
|
+
# Otherwise timeout and continue on (so that we hit our "break if quit"
|
57
|
+
# pretty often).
|
58
|
+
IO.select([server], nil, nil, 1)
|
59
|
+
|
60
|
+
retry unless quit
|
61
|
+
end
|