rfbeam 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.rubocop.yml +11 -0
  4. data/.tool-versions +1 -0
  5. data/CHANGELOG.md +9 -0
  6. data/Gemfile +12 -0
  7. data/Gemfile.lock +63 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +45 -0
  10. data/Rakefile +16 -0
  11. data/lib/rfbeam/kld7/commands.rb +89 -0
  12. data/lib/rfbeam/kld7/detection.rb +32 -0
  13. data/lib/rfbeam/kld7/device_data.rb +33 -0
  14. data/lib/rfbeam/version.rb +5 -0
  15. data/lib/rfbeam.rb +28 -0
  16. data/node_modules/.bin/prettier +1 -0
  17. data/node_modules/.yarn-integrity +19 -0
  18. data/node_modules/@prettier/plugin-ruby/CHANGELOG.md +1351 -0
  19. data/node_modules/@prettier/plugin-ruby/CODE_OF_CONDUCT.md +76 -0
  20. data/node_modules/@prettier/plugin-ruby/CONTRIBUTING.md +185 -0
  21. data/node_modules/@prettier/plugin-ruby/LICENSE +21 -0
  22. data/node_modules/@prettier/plugin-ruby/README.md +226 -0
  23. data/node_modules/@prettier/plugin-ruby/docs/logo.png +0 -0
  24. data/node_modules/@prettier/plugin-ruby/node_modules/.bin/prettier +1 -0
  25. data/node_modules/@prettier/plugin-ruby/package.json +64 -0
  26. data/node_modules/@prettier/plugin-ruby/prettier.gemspec +36 -0
  27. data/node_modules/@prettier/plugin-ruby/rubocop.yml +64 -0
  28. data/node_modules/@prettier/plugin-ruby/src/getInfo.js +23 -0
  29. data/node_modules/@prettier/plugin-ruby/src/netcat.js +13 -0
  30. data/node_modules/@prettier/plugin-ruby/src/parseSync.js +236 -0
  31. data/node_modules/@prettier/plugin-ruby/src/plugin.js +172 -0
  32. data/node_modules/@prettier/plugin-ruby/src/server.rb +199 -0
  33. data/node_modules/prettier/LICENSE +5945 -0
  34. data/node_modules/prettier/README.md +109 -0
  35. data/node_modules/prettier/bin-prettier.js +62 -0
  36. data/node_modules/prettier/cli.js +15136 -0
  37. data/node_modules/prettier/doc.js +1473 -0
  38. data/node_modules/prettier/esm/parser-angular.mjs +2 -0
  39. data/node_modules/prettier/esm/parser-babel.mjs +29 -0
  40. data/node_modules/prettier/esm/parser-espree.mjs +26 -0
  41. data/node_modules/prettier/esm/parser-flow.mjs +35 -0
  42. data/node_modules/prettier/esm/parser-glimmer.mjs +27 -0
  43. data/node_modules/prettier/esm/parser-graphql.mjs +15 -0
  44. data/node_modules/prettier/esm/parser-html.mjs +36 -0
  45. data/node_modules/prettier/esm/parser-markdown.mjs +76 -0
  46. data/node_modules/prettier/esm/parser-meriyah.mjs +19 -0
  47. data/node_modules/prettier/esm/parser-postcss.mjs +76 -0
  48. data/node_modules/prettier/esm/parser-typescript.mjs +257 -0
  49. data/node_modules/prettier/esm/parser-yaml.mjs +150 -0
  50. data/node_modules/prettier/esm/standalone.mjs +116 -0
  51. data/node_modules/prettier/index.js +37885 -0
  52. data/node_modules/prettier/package.json +21 -0
  53. data/node_modules/prettier/parser-angular.js +2 -0
  54. data/node_modules/prettier/parser-babel.js +29 -0
  55. data/node_modules/prettier/parser-espree.js +26 -0
  56. data/node_modules/prettier/parser-flow.js +35 -0
  57. data/node_modules/prettier/parser-glimmer.js +27 -0
  58. data/node_modules/prettier/parser-graphql.js +15 -0
  59. data/node_modules/prettier/parser-html.js +36 -0
  60. data/node_modules/prettier/parser-markdown.js +76 -0
  61. data/node_modules/prettier/parser-meriyah.js +19 -0
  62. data/node_modules/prettier/parser-postcss.js +76 -0
  63. data/node_modules/prettier/parser-typescript.js +257 -0
  64. data/node_modules/prettier/parser-yaml.js +150 -0
  65. data/node_modules/prettier/standalone.js +116 -0
  66. data/node_modules/prettier/third-party.js +8978 -0
  67. data/package.json +6 -0
  68. data/sig/rfbeam.rbs +4 -0
  69. data/yarn.lock +15 -0
  70. metadata +156 -0
@@ -0,0 +1,64 @@
1
+ # Disabling all Layout/* rules, as they're unnecessary when the user is using
2
+ # Syntax Tree to handle all of the formatting.
3
+ Layout:
4
+ Enabled: false
5
+
6
+ # Re-enable Layout/LineLength because certain cops that most projects use
7
+ # (e.g. Style/IfUnlessModifier) require Layout/LineLength to be enabled.
8
+ # By leaving it disabled, those rules will mis-fire.
9
+ #
10
+ # Users can always override these defaults in their own rubocop.yml files.
11
+ # https://github.com/prettier/plugin-ruby/issues/825
12
+ Layout/LineLength:
13
+ Enabled: true
14
+
15
+ Style/MultilineIfModifier:
16
+ Enabled: false
17
+
18
+ # Syntax Tree will expand empty methods to put the end keyword on the subsequent
19
+ # line to reduce git diff noise.
20
+ Style/EmptyMethod:
21
+ EnforcedStyle: expanded
22
+
23
+ # lambdas that are constructed with the lambda method call cannot be safely
24
+ # turned into lambda literals without removing a method call.
25
+ Style/Lambda:
26
+ Enabled: false
27
+
28
+ # When method chains with multiple blocks are chained together, rubocop will let
29
+ # them pass if they're using braces but not if they're using do and end
30
+ # keywords. Because we will break individual blocks down to using keywords if
31
+ # they are multiline, this conflicts with rubocop.
32
+ Style/MultilineBlockChain:
33
+ Enabled: false
34
+
35
+ # Disable the single- vs double-quotes rules as these depend on whether the user
36
+ # has added or not `plugin/single_quotes` for `syntax_tree`
37
+ Style/StringLiterals:
38
+ Enabled: false
39
+
40
+ Style/StringLiteralsInInterpolation:
41
+ Enabled: false
42
+
43
+ Style/QuotedSymbols:
44
+ Enabled: false
45
+
46
+ # We let users have a little more freedom with symbol and words arrays. If the
47
+ # user only has an individual item like ["value"] then we don't bother
48
+ # converting it because it ends up being just noise.
49
+ Style/SymbolArray:
50
+ Enabled: false
51
+
52
+ Style/WordArray:
53
+ Enabled: false
54
+
55
+ # Disable the trailing-comma rules as these depend on whether the user has added
56
+ # or not `plugin/trailing_comma` for `syntax_tree`
57
+ Style/TrailingCommaInArguments:
58
+ Enabled: false
59
+
60
+ Style/TrailingCommaInArrayLiteral:
61
+ Enabled: false
62
+
63
+ Style/TrailingCommaInHashLiteral:
64
+ Enabled: false
@@ -0,0 +1,23 @@
1
+ const { existsSync, readFileSync } = require("fs");
2
+
3
+ // This is how long to wait for the parser to spin up. For the most part, 5
4
+ // seconds is plenty of time. But in some environments, it may be necessary to
5
+ // increase this value.
6
+ const timeoutMs = parseInt(process.env.PRETTIER_RUBY_TIMEOUT_MS || "5000", 10);
7
+
8
+ const filepath = process.argv[process.argv.length - 1];
9
+
10
+ const timeout = setTimeout(() => {
11
+ clearInterval(interval);
12
+ throw new Error(`Failed to get information from parse server in time. If this
13
+ happens repeatedly, try increasing the PRETTIER_RUBY_TIMEOUT_MS environment
14
+ variable beyond 5000.`);
15
+ }, timeoutMs);
16
+
17
+ const interval = setInterval(() => {
18
+ if (existsSync(filepath)) {
19
+ process.stdout.write(readFileSync(filepath).toString("utf8"));
20
+ clearTimeout(timeout);
21
+ clearInterval(interval);
22
+ }
23
+ }, 100);
@@ -0,0 +1,13 @@
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
+ const client = createConnection(sock, () => process.stdin.pipe(client));
9
+
10
+ client.on("data", (data) => process.stdout.write(data));
11
+ client.on("error", (error) => {
12
+ console.error(error);
13
+ });
@@ -0,0 +1,236 @@
1
+ const { spawn, spawnSync } = require("child_process");
2
+ const {
3
+ existsSync,
4
+ unlinkSync,
5
+ mkdtempSync,
6
+ copyFileSync,
7
+ mkdirSync,
8
+ rmdirSync
9
+ } = require("fs");
10
+ const os = require("os");
11
+ const path = require("path");
12
+ const process = require("process");
13
+
14
+ let parserArgs;
15
+
16
+ if (process.env.PRETTIER_RUBY_HOST) {
17
+ const [cmd, ...args] = process.env.PRETTIER_RUBY_HOST.split(" ");
18
+ parserArgs = { cmd, args };
19
+ }
20
+
21
+ // In order to properly parse ruby code, we need to tell the ruby process to
22
+ // parse using UTF-8. Unfortunately, the way that you accomplish this looks
23
+ // differently depending on your platform.
24
+ /* istanbul ignore next */
25
+ function getLang() {
26
+ const { env, platform } = process;
27
+ const envValue = env.LC_ALL || env.LC_CTYPE || env.LANG;
28
+
29
+ // If an env var is set for the locale that already includes UTF-8 in the
30
+ // name, then assume we can go with that.
31
+ if (envValue && envValue.includes("UTF-8")) {
32
+ return envValue;
33
+ }
34
+
35
+ // Otherwise, we're going to guess which encoding to use based on the system.
36
+ // This is probably not the best approach in the world, as you could be on
37
+ // linux and not have C.UTF-8, but in that case you're probably passing an env
38
+ // var for it. This object below represents all of the possible values of
39
+ // process.platform per:
40
+ // https://nodejs.org/api/process.html#process_process_platform
41
+ return {
42
+ aix: "C.UTF-8",
43
+ android: "C.UTF-8",
44
+ cygwin: "C.UTF-8",
45
+ darwin: "en_US.UTF-8",
46
+ freebsd: "C.UTF-8",
47
+ haiku: "C.UTF-8",
48
+ linux: "C.UTF-8",
49
+ netbsd: "C.UTF-8",
50
+ openbsd: "C.UTF-8",
51
+ sunos: "C.UTF-8",
52
+ win32: ".UTF-8"
53
+ }[platform];
54
+ }
55
+
56
+ // Generate the filepath that should be used to communicate the connection
57
+ // information between this process and the parser server.
58
+ function getInfoFilepath() {
59
+ return path.join(os.tmpdir(), `prettier-ruby-parser-${process.pid}.info`);
60
+ }
61
+
62
+ // Return the list of plugins that should be passed to the server process.
63
+ function getPlugins(opts) {
64
+ const plugins = new Set();
65
+
66
+ const rubyPlugins = opts.rubyPlugins.trim();
67
+ if (rubyPlugins.length > 0) {
68
+ rubyPlugins.split(",").forEach((plugin) => plugins.add(plugin.trim()));
69
+ }
70
+
71
+ if (opts.singleQuote) {
72
+ plugins.add("plugin/single_quotes");
73
+ }
74
+
75
+ if (opts.trailingComma !== "none") {
76
+ plugins.add("plugin/trailing_comma");
77
+ }
78
+
79
+ return Array.from(plugins);
80
+ }
81
+
82
+ // Create a file that will act as a communication mechanism, spawn a parser
83
+ // server with that filepath as an argument, then spawn another process that
84
+ // will read that information in order to enable us to connect to it in the
85
+ // spawnSync function.
86
+ function spawnServer(opts) {
87
+ const tempDir = mkdtempSync(path.join(os.tmpdir(), "prettier-plugin-ruby-"));
88
+ const filepath = getInfoFilepath();
89
+
90
+ let serverRbPath = path.join(__dirname, "./server.rb");
91
+ let getInfoJsPath = path.join(__dirname, "./getInfo.js");
92
+ let cleanupTempFiles;
93
+
94
+ if (runningInPnPZip()) {
95
+ // If we're running in a Yarn PnP environment inside a ZIP file, it's not possible to run
96
+ // the Ruby server or the getInfo.js script directly. Instead, we need to copy them and all
97
+ // the files they depend on to a temporary directory.
98
+
99
+ const sourceFiles = ["server.rb", "getInfo.js", "netcat.js"];
100
+ serverRbPath = path.join(tempDir, "server.rb");
101
+ getInfoJsPath = path.join(tempDir, "getInfo.js");
102
+
103
+ sourceFiles.forEach((rubyFile) => {
104
+ const destDir = path.join(tempDir, path.dirname(rubyFile));
105
+ if (!existsSync(destDir)) {
106
+ mkdirSync(destDir);
107
+ }
108
+ copyFileSync(
109
+ path.join(__dirname, "..", "src", rubyFile),
110
+ path.join(tempDir, rubyFile)
111
+ );
112
+ });
113
+
114
+ cleanupTempFiles = () => {
115
+ [
116
+ getInfoJsPath,
117
+ ...sourceFiles.map((rubyFile) => path.join(tempDir, rubyFile))
118
+ ].forEach((tmpFilePath) => {
119
+ if (existsSync(tmpFilePath)) {
120
+ unlinkSync(tmpFilePath);
121
+ }
122
+ });
123
+
124
+ sourceFiles.forEach((rubyFile) => {
125
+ const tempSubdir = path.join(tempDir, path.dirname(rubyFile));
126
+ if (existsSync(tempSubdir)) {
127
+ rmdirSync(tempSubdir);
128
+ }
129
+ });
130
+
131
+ if (existsSync(tempDir)) {
132
+ rmdirSync(tempDir);
133
+ }
134
+ };
135
+ }
136
+
137
+ const server = spawn(
138
+ "ruby",
139
+ [serverRbPath, `--plugins=${getPlugins(opts).join(",")}`, filepath],
140
+ {
141
+ env: Object.assign({}, process.env, { LANG: getLang() }),
142
+ detached: true,
143
+ stdio: "inherit"
144
+ }
145
+ );
146
+
147
+ server.unref();
148
+ process.on("exit", () => {
149
+ if (existsSync(filepath)) {
150
+ unlinkSync(filepath);
151
+ }
152
+
153
+ if (cleanupTempFiles != null) {
154
+ cleanupTempFiles();
155
+ }
156
+
157
+ try {
158
+ if (server.pid) {
159
+ // Kill the server process if it's still running. If we're on windows
160
+ // we're going to use the process ID number. If we're not, we're going
161
+ // to use the negative process ID to indicate the group.
162
+ const pid = process.platform === "win32" ? server.pid : -server.pid;
163
+ process.kill(pid);
164
+ }
165
+ } catch (e) {
166
+ if (process.env.PLUGIN_RUBY_CI) {
167
+ throw new Error(`Failed to kill the parser server: ${e}`);
168
+ }
169
+ }
170
+ });
171
+
172
+ const info = spawnSync("node", [getInfoJsPath, filepath]);
173
+
174
+ if (info.status !== 0) {
175
+ throw new Error(`
176
+ We failed to spawn our parser server. Please report this error on GitHub
177
+ at https://github.com/prettier/plugin-ruby. The error message was:
178
+
179
+ ${info.stderr.toString()}.
180
+ `);
181
+ }
182
+
183
+ const [cmd, ...args] = info.stdout.toString().split(" ");
184
+ return { cmd, args };
185
+ }
186
+
187
+ // If we're in a yarn Plug'n'Play environment, then the relative paths being
188
+ // used by the parser server and the various scripts used to communicate
189
+ // therein are not going to work with its virtual file system.
190
+ function runningInPnPZip() {
191
+ return process.versions.pnp && __dirname.includes(".zip");
192
+ }
193
+
194
+ // Formats and sends a request to the parser server. We use netcat (or something
195
+ // like it) here since Prettier requires the results of `parse` to be
196
+ // synchronous and Node.js does not offer a mechanism for synchronous socket
197
+ // requests.
198
+ function parseSync(parser, source, opts) {
199
+ if (!parserArgs) {
200
+ parserArgs = spawnServer(opts);
201
+ }
202
+
203
+ const response = spawnSync(parserArgs.cmd, parserArgs.args, {
204
+ input: `${parser}|${opts.printWidth}|${opts.tabWidth}|${source}`,
205
+ maxBuffer: 15 * 1024 * 1024
206
+ });
207
+
208
+ const stdout = response.stdout.toString();
209
+ const stderr = response.stderr.toString();
210
+ const { status } = response;
211
+
212
+ // If we didn't receive anything over stdout or we have a bad exit status,
213
+ // then throw whatever we can.
214
+ if (stdout.length === 0 || (status !== null && status !== 0)) {
215
+ throw new Error(stderr || "An unknown error occurred");
216
+ }
217
+
218
+ const parsed = JSON.parse(stdout);
219
+
220
+ if (parsed.error) {
221
+ const error = new Error(parsed.error);
222
+ if (parsed.loc) {
223
+ error.loc = parsed.loc;
224
+ }
225
+
226
+ throw error;
227
+ }
228
+
229
+ return parsed;
230
+ }
231
+
232
+ module.exports = {
233
+ getLang,
234
+ getInfoFilepath,
235
+ parseSync
236
+ };
@@ -0,0 +1,172 @@
1
+ const { parseSync } = require("./parseSync");
2
+
3
+ /*
4
+ * metadata mostly pulled from linguist and rubocop:
5
+ * https://github.com/github/linguist/blob/master/lib/linguist/languages.yml
6
+ * https://github.com/rubocop/rubocop/blob/master/spec/rubocop/target_finder_spec.rb
7
+ */
8
+ const plugin = {
9
+ languages: [
10
+ {
11
+ name: "Ruby",
12
+ parsers: ["ruby"],
13
+ extensions: [
14
+ ".arb",
15
+ ".axlsx",
16
+ ".builder",
17
+ ".eye",
18
+ ".fcgi",
19
+ ".gemfile",
20
+ ".gemspec",
21
+ ".god",
22
+ ".jb",
23
+ ".jbuilder",
24
+ ".mspec",
25
+ ".opal",
26
+ ".pluginspec",
27
+ ".podspec",
28
+ ".rabl",
29
+ ".rake",
30
+ ".rb",
31
+ ".rbi",
32
+ ".rbuild",
33
+ ".rbw",
34
+ ".rbx",
35
+ ".ru",
36
+ ".ruby",
37
+ ".thor",
38
+ ".watchr"
39
+ ],
40
+ filenames: [
41
+ ".irbrc",
42
+ ".pryrc",
43
+ ".simplecov",
44
+ "Appraisals",
45
+ "Berksfile",
46
+ "Brewfile",
47
+ "Buildfile",
48
+ "Capfile",
49
+ "Cheffile",
50
+ "Dangerfile",
51
+ "Deliverfile",
52
+ "Fastfile",
53
+ "Gemfile",
54
+ "Guardfile",
55
+ "Jarfile",
56
+ "Mavenfile",
57
+ "Podfile",
58
+ "Puppetfile",
59
+ "Rakefile",
60
+ "Snapfile",
61
+ "Thorfile",
62
+ "Vagabondfile",
63
+ "Vagrantfile",
64
+ "buildfile"
65
+ ],
66
+ interpreters: ["jruby", "macruby", "rake", "rbx", "ruby"],
67
+ linguistLanguageId: 326,
68
+ vscodeLanguageIds: ["ruby"]
69
+ },
70
+ {
71
+ name: "RBS",
72
+ parsers: ["rbs"],
73
+ extensions: [".rbs"]
74
+ },
75
+ {
76
+ name: "HAML",
77
+ parsers: ["haml"],
78
+ extensions: [".haml"],
79
+ vscodeLanguageIds: ["haml"]
80
+ }
81
+ ],
82
+ parsers: {
83
+ ruby: {
84
+ parse(text, _parsers, opts) {
85
+ return parseSync("ruby", text, opts);
86
+ },
87
+ astFormat: "ruby",
88
+ hasPragma(text) {
89
+ return /^\s*#[^\S\n]*@(?:prettier|format)\s*?(?:\n|$)/m.test(text);
90
+ },
91
+ locStart() {
92
+ return 0;
93
+ },
94
+ locEnd() {
95
+ return 0;
96
+ }
97
+ },
98
+ rbs: {
99
+ parse(text, _parsers, opts) {
100
+ return parseSync("rbs", text, opts);
101
+ },
102
+ astFormat: "rbs",
103
+ hasPragma(text) {
104
+ return /^\s*#[^\S\n]*@(prettier|format)\s*(\n|$)/.test(text);
105
+ },
106
+ locStart() {
107
+ return 0;
108
+ },
109
+ locEnd() {
110
+ return 0;
111
+ }
112
+ },
113
+ haml: {
114
+ parse(text, _parsers, opts) {
115
+ return parseSync("haml", text, opts);
116
+ },
117
+ astFormat: "haml",
118
+ hasPragma(text) {
119
+ return /^\s*-#\s*@(prettier|format)/.test(text);
120
+ },
121
+ locStart() {
122
+ return 0;
123
+ },
124
+ locEnd() {
125
+ return 0;
126
+ }
127
+ }
128
+ },
129
+ printers: {
130
+ ruby: {
131
+ print(path) {
132
+ return path.getValue();
133
+ },
134
+ insertPragma(text) {
135
+ return `# @format${text.startsWith("#") ? "\n" : "\n\n"}${text}`;
136
+ }
137
+ },
138
+ rbs: {
139
+ print(path) {
140
+ return path.getValue();
141
+ },
142
+ insertPragma(text) {
143
+ return `# @format${text.startsWith("#") ? "\n" : "\n\n"}${text}`;
144
+ }
145
+ },
146
+ haml: {
147
+ print(path) {
148
+ return path.getValue();
149
+ },
150
+ insertPragma(text) {
151
+ return `-# @format${text.startsWith("-#") ? "\n" : "\n\n"}${text}`;
152
+ }
153
+ }
154
+ },
155
+ options: {
156
+ rubyPlugins: {
157
+ type: "string",
158
+ category: "Ruby",
159
+ default: "",
160
+ description: "The comma-separated list of plugins to require",
161
+ since: "3.1.0"
162
+ }
163
+ },
164
+ defaultOptions: {
165
+ printWidth: 80,
166
+ tabWidth: 2,
167
+ trailingComma: "none",
168
+ singleQuote: false
169
+ }
170
+ };
171
+
172
+ module.exports = plugin;
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "socket"
5
+ require "json"
6
+ require "fileutils"
7
+ require "open3"
8
+
9
+ require "prettier_print"
10
+ require "syntax_tree"
11
+ require "syntax_tree/haml"
12
+ require "syntax_tree/rbs"
13
+
14
+ # First, require all of the plugins that the user specified.
15
+ ARGV.shift[/^--plugins=(.*)$/, 1]
16
+ .split(",")
17
+ .each { |plugin| require "syntax_tree/#{plugin}" }
18
+
19
+ # Make sure we trap these signals to be sure we get the quit command coming from
20
+ # the parent node process
21
+ quit = false
22
+ trap(:INT) { quit = true }
23
+ trap(:TERM) { quit = true }
24
+
25
+ if Signal.list.key?("QUIT") && RUBY_ENGINE != "jruby"
26
+ trap(:QUIT) { quit = true }
27
+ end
28
+
29
+ # The information variable stores the actual connection information, which will
30
+ # either be an IP address and port or a path to a unix socket file.
31
+ information = ""
32
+
33
+ # The candidates array is a list of potential programs that could be used to
34
+ # connect to our server. We'll run through them after the server starts to find
35
+ # the best one to use.
36
+ candidates = []
37
+
38
+ if Gem.win_platform?
39
+ # If we're on windows, we're going to start up a TCP server. The 0 here means
40
+ # to bind to some available port.
41
+ server = TCPServer.new("127.0.0.1", 0)
42
+ address = server.local_address
43
+
44
+ # Ensure that we close the server when this process exits.
45
+ at_exit { server.close }
46
+
47
+ information = "#{address.ip_address} #{address.ip_port}"
48
+ candidates = %w[nc telnet]
49
+ else
50
+ # If we're not on windows, then we're going to assume we can use unix socket
51
+ # files (since they're faster than a TCP server).
52
+ filepath = "/tmp/prettier-ruby-parser-#{Process.pid}.sock"
53
+ server = UNIXServer.new(filepath)
54
+
55
+ # Ensure that we close the server and delete the socket file when this
56
+ # process exits.
57
+ at_exit do
58
+ server.close
59
+ File.unlink(filepath)
60
+ end
61
+
62
+ information = server.local_address.unix_path
63
+ candidates = ["nc -w 3 -U", "ncat -w 3 -U"]
64
+ end
65
+
66
+ # This is the actual listening thread that will be acting as our server. We have
67
+ # to start it in another thread to begin with so that we can run through our
68
+ # candidate connection programs. Eventually we'll just join into this thread
69
+ # though and it will act as a daemon.
70
+ listener =
71
+ Thread.new do
72
+ loop do
73
+ break if quit
74
+
75
+ # Start up a new thread that will handle each successive connection.
76
+ Thread.new(server.accept_nonblock) do |socket|
77
+ parser, maxwidth_string, tabwidth_string, source =
78
+ socket.read.force_encoding("UTF-8").split("|", 4)
79
+
80
+ source.each_line do |line|
81
+ case line
82
+ when /^\s*#.+?coding/
83
+ # If we've found an encoding comment, then we're going to take that
84
+ # into account and reclassify the encoding for the source.
85
+ encoding = Ripper.new(line).tap(&:parse).encoding
86
+ source = source.force_encoding(encoding)
87
+ break
88
+ when /^\s*#/
89
+ # continue
90
+ else
91
+ break
92
+ end
93
+ end
94
+
95
+ # At the moment, we're not going to support odd tabwidths. It's going to
96
+ # have to be a multiple of 2, because of the way that the prettyprint
97
+ # gem functions. So we're going to just use integer division here.
98
+ scalar = tabwidth_string.to_i / 2
99
+ genspace = ->(n) { " " * n * scalar }
100
+
101
+ maxwidth = maxwidth_string.to_i
102
+ response =
103
+ case parser
104
+ when "ping"
105
+ "pong"
106
+ when "ruby"
107
+ formatter =
108
+ SyntaxTree::Formatter.new(source, [], maxwidth, "\n", &genspace)
109
+ SyntaxTree.parse(source).format(formatter)
110
+ formatter.flush
111
+ formatter.output.join
112
+ when "rbs"
113
+ formatter =
114
+ SyntaxTree::RBS::Formatter.new(
115
+ source,
116
+ [],
117
+ maxwidth,
118
+ "\n",
119
+ &genspace
120
+ )
121
+ SyntaxTree::RBS.parse(source).format(formatter)
122
+ formatter.flush
123
+ formatter.output.join
124
+ when "haml"
125
+ formatter_class =
126
+ if defined?(SyntaxTree::Haml::Format::Formatter)
127
+ SyntaxTree::Haml::Format::Formatter
128
+ else
129
+ PrettierPrint
130
+ end
131
+
132
+ formatter =
133
+ formatter_class.new(source, +"", maxwidth, "\n", &genspace)
134
+
135
+ SyntaxTree::Haml.parse(source).format(formatter)
136
+ formatter.flush
137
+ formatter.output
138
+ end
139
+
140
+ if response
141
+ socket.write(JSON.fast_generate(response.force_encoding("UTF-8")))
142
+ else
143
+ socket.write("{ \"error\": true }")
144
+ end
145
+ rescue SyntaxTree::Parser::ParseError => error
146
+ loc = { start: { line: error.lineno, column: error.column } }
147
+ socket.write(JSON.fast_generate(error: error.message, loc: loc))
148
+ rescue StandardError => error
149
+ begin
150
+ socket.write(JSON.fast_generate(error: error.message))
151
+ rescue Errno::EPIPE
152
+ # Do nothing, the pipe has been closed by the parent process so we don't
153
+ # actually care about writing to it anymore.
154
+ end
155
+ ensure
156
+ socket.close
157
+ end
158
+ rescue IO::WaitReadable, Errno::EINTR
159
+ # Wait for select(2) to give us a connection that has content for 1
160
+ # second. Otherwise timeout and continue on (so that we hit our
161
+ # "break if quit" pretty often).
162
+ IO.select([server], nil, nil, 1)
163
+
164
+ retry unless quit
165
+ end
166
+ end
167
+
168
+ # Map each candidate connection method to a thread that will check if it works.
169
+ candidates.map! do |candidate|
170
+ Thread.new do
171
+ Thread.current.report_on_exception = false
172
+
173
+ # We do not care about stderr here, so throw it away
174
+ stdout, _stderr, status =
175
+ Open3.capture3("#{candidate} #{information}", stdin_data: "ping")
176
+
177
+ candidate if JSON.parse(stdout) == "pong" && status.exitstatus == 0
178
+ rescue StandardError
179
+ # We don't actually care if this fails, because we'll just skip that
180
+ # connection option.
181
+ end
182
+ end
183
+
184
+ # Find the first one prefix that successfully returned the correct value.
185
+ prefix =
186
+ candidates.detect do |candidate|
187
+ value = candidate.value
188
+ break value if value
189
+ end
190
+
191
+ # Default to running the netcat.js script that we ship with the plugin. It's a
192
+ # good fallback as it will always work, but it is slower than the other options.
193
+ prefix ||= "node #{File.expand_path("netcat.js", __dir__)}"
194
+
195
+ # Write out our connection information to the file given as the first argument
196
+ # to this script.
197
+ File.write(ARGV[0], "#{prefix} #{information}")
198
+
199
+ listener.join