rubocop-service 0.1.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/.rubocop.yml +18 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +10 -0
- data/Gemfile.lock +53 -0
- data/LICENSE.txt +21 -0
- data/README.md +35 -0
- data/Rakefile +8 -0
- data/exe/rubocop-service +6 -0
- data/lib/rubocop/service/cli.rb +66 -0
- data/lib/rubocop/service/installer.rb +48 -0
- data/lib/rubocop/service/patch/server/cache.rb +41 -0
- data/lib/rubocop/service/patch/server/client_command/start.rb +54 -0
- data/lib/rubocop/service/patch/server/client_command/stop.rb +18 -0
- data/lib/rubocop/service/patch/server/core.rb +19 -0
- data/lib/rubocop/service/patch/server.rb +12 -0
- data/lib/rubocop/service/server.rb +310 -0
- data/lib/rubocop/service/uninstaller.rb +51 -0
- data/lib/rubocop/service/utils.rb +5 -0
- data/lib/rubocop/service/version.rb +7 -0
- data/lib/rubocop/service.rb +16 -0
- data/rubocop-service.gemspec +49 -0
- data/sig/rubocop/service.rbs +6 -0
- metadata +102 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8df8f58e911c4b4d4c9d7273bbc25eb9a31f4d58b09020b06a7e646680d8e5e9
|
4
|
+
data.tar.gz: 5bdad78bbc9c8567b77b81c938f063f28b2137e4203ed08fbc6065104be65fa7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ccd98a43198966eec68b084255ec0836974d40d2caa95ea9c1f7506b2d13ae517e42fdf8076b55d9ca45bbbfaa77b2b6a3e08054a9d94cc9e2f6ed34baea68f5
|
7
|
+
data.tar.gz: 03e05d0a626b84392339e96c48b32a70d74326d8bc12f6a6f97cc88f3a8555c4f25819919279e7a90abf3a82aae3269d5c4e905097caaeeae74f334f33e0550c
|
data/.rubocop.yml
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
inherit_gem:
|
2
|
+
syntax_tree: config/rubocop.yml
|
3
|
+
|
4
|
+
AllCops:
|
5
|
+
NewCops: enable
|
6
|
+
TargetRubyVersion: 3.0
|
7
|
+
|
8
|
+
Style/Documentation:
|
9
|
+
Enabled: false
|
10
|
+
|
11
|
+
Lint/ScriptPermission:
|
12
|
+
Enabled: false
|
13
|
+
|
14
|
+
Metrics:
|
15
|
+
Enabled: false
|
16
|
+
|
17
|
+
Style/IfUnlessModifier:
|
18
|
+
Enabled: false
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
rubocop-service (0.1.0)
|
5
|
+
rubocop (~> 1.36.0)
|
6
|
+
win32-service (~> 2.3.2)
|
7
|
+
|
8
|
+
GEM
|
9
|
+
remote: https://rubygems.org/
|
10
|
+
specs:
|
11
|
+
ast (2.4.2)
|
12
|
+
ffi (1.15.5-x86-mingw32)
|
13
|
+
ffi-win32-extensions (1.0.4)
|
14
|
+
ffi
|
15
|
+
json (2.6.2)
|
16
|
+
parallel (1.22.1)
|
17
|
+
parser (3.1.2.1)
|
18
|
+
ast (~> 2.4.1)
|
19
|
+
prettier_print (0.1.0)
|
20
|
+
rainbow (3.1.1)
|
21
|
+
rake (13.0.6)
|
22
|
+
regexp_parser (2.5.0)
|
23
|
+
rexml (3.2.5)
|
24
|
+
rubocop (1.36.0)
|
25
|
+
json (~> 2.3)
|
26
|
+
parallel (~> 1.10)
|
27
|
+
parser (>= 3.1.2.1)
|
28
|
+
rainbow (>= 2.2.2, < 4.0)
|
29
|
+
regexp_parser (>= 1.8, < 3.0)
|
30
|
+
rexml (>= 3.2.5, < 4.0)
|
31
|
+
rubocop-ast (>= 1.20.1, < 2.0)
|
32
|
+
ruby-progressbar (~> 1.7)
|
33
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
34
|
+
rubocop-ast (1.21.0)
|
35
|
+
parser (>= 3.1.1.0)
|
36
|
+
ruby-progressbar (1.11.0)
|
37
|
+
syntax_tree (3.5.0)
|
38
|
+
prettier_print
|
39
|
+
unicode-display_width (2.3.0)
|
40
|
+
win32-service (2.3.2)
|
41
|
+
ffi
|
42
|
+
ffi-win32-extensions
|
43
|
+
|
44
|
+
PLATFORMS
|
45
|
+
x86-mingw32
|
46
|
+
|
47
|
+
DEPENDENCIES
|
48
|
+
rake (~> 13.0)
|
49
|
+
rubocop-service!
|
50
|
+
syntax_tree (~> 3.5)
|
51
|
+
|
52
|
+
BUNDLED WITH
|
53
|
+
2.3.17
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2022 Nanashi. <@sevenc-nanashi>
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
# rubocop-service / Unofficial support of rubocop server, for Windows!
|
2
|
+
|
3
|
+
> **Warning**
|
4
|
+
> This project is beta!
|
5
|
+
|
6
|
+
This gem provides support of rubocop server for Windows.
|
7
|
+
This gem patches rubocop.
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
You can install this gem using the following command:
|
12
|
+
|
13
|
+
$ gem install rubocop-service
|
14
|
+
|
15
|
+
This gem is not for bundler.
|
16
|
+
|
17
|
+
## Usage
|
18
|
+
|
19
|
+
1. Run `rubocop-service start` with administrator privileges to start the manager server.
|
20
|
+
2. Run `rubocop-service install` to patch rubocop.
|
21
|
+
3. Enjoy faster rubocop!
|
22
|
+
|
23
|
+
## Development
|
24
|
+
|
25
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
26
|
+
|
27
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
28
|
+
|
29
|
+
## Contributing
|
30
|
+
|
31
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/sevenc-nanashi/rubocop-service.
|
32
|
+
|
33
|
+
## License
|
34
|
+
|
35
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/exe/rubocop-service
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Service
|
7
|
+
class CLI
|
8
|
+
def initialize
|
9
|
+
@options = { verbose: false }
|
10
|
+
@commands = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(argv)
|
14
|
+
parser = OptionParser.new
|
15
|
+
|
16
|
+
parser.program_name = "rubocop-service"
|
17
|
+
parser.version = VERSION
|
18
|
+
|
19
|
+
command "install", "Patch the rubocop." do
|
20
|
+
RuboCop::Service::Installer.new.run
|
21
|
+
end
|
22
|
+
command "uninstall", "Unpatch the rubocop." do
|
23
|
+
RuboCop::Service::Uninstaller.new.run
|
24
|
+
end
|
25
|
+
command "start", "Start the manager server." do
|
26
|
+
RuboCop::Service::Server.start
|
27
|
+
end
|
28
|
+
command "stop", "Stop the manager server." do
|
29
|
+
RuboCop::Service::Server.stop
|
30
|
+
end
|
31
|
+
command "status", "Show status of the manager server." do
|
32
|
+
RuboCop::Service::Server.status
|
33
|
+
end
|
34
|
+
command nil do
|
35
|
+
puts parser.help
|
36
|
+
end
|
37
|
+
|
38
|
+
parser.banner = +<<~BANNER
|
39
|
+
Usage: rubocop-service [options] [command]
|
40
|
+
|
41
|
+
Commands:
|
42
|
+
BANNER
|
43
|
+
@commands.each do |command, (desc, _block)|
|
44
|
+
next unless desc
|
45
|
+
parser.banner << " #{command} - #{desc}\n"
|
46
|
+
end
|
47
|
+
|
48
|
+
parser.separator ""
|
49
|
+
parser.separator "Options:"
|
50
|
+
parser.on("-v", "--verbose", "Verbose output") do |verb|
|
51
|
+
@options[:verbose] = verb
|
52
|
+
end
|
53
|
+
|
54
|
+
parser.parse! argv
|
55
|
+
|
56
|
+
ENV["RUBOCOP_SERVICE_VERBOSE"] = "true" if @options[:verbose]
|
57
|
+
|
58
|
+
@commands[argv.first] ? @commands[argv.first].last.call : @commands[nil].last.call
|
59
|
+
end
|
60
|
+
|
61
|
+
def command(name, description = nil, &block)
|
62
|
+
@commands[name] = [description, block]
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Service
|
7
|
+
class Installer
|
8
|
+
def run
|
9
|
+
puts "Finding RuboCop installation..."
|
10
|
+
rubocop_path = find_rubocop_path
|
11
|
+
unless rubocop_path
|
12
|
+
warn "RuboCop not found! Please install it first."
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
dputs "rubocop.rb path:", rubocop_path
|
16
|
+
rubocop_dir = Pathname.new File.dirname(rubocop_path)
|
17
|
+
dputs "rubocop dir:", rubocop_dir
|
18
|
+
if (rubocop_dir / ".rubocop-service_patched").exist?
|
19
|
+
warn "Already patched! Use `rubocop-service uninstall` to restore."
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
puts "RuboCop found, patching it..."
|
23
|
+
patch_libs = Dir.glob("#{__dir__}/patch/**/*.rb")
|
24
|
+
patch_libs.each do |libpath|
|
25
|
+
path =
|
26
|
+
(Pathname.new libpath).relative_path_from(__dir__).sub("patch/", "")
|
27
|
+
rubocop_file = rubocop_dir / "rubocop" / path
|
28
|
+
rubocop_file.open("a") do |file|
|
29
|
+
file.puts "# !!! Patched by rubocop-service !!!"
|
30
|
+
file.puts "require 'rubocop/service/patch/#{path.sub(".rb", "")}' if RuboCop::Platform.windows?"
|
31
|
+
file.puts "# !!! End of patch !!!"
|
32
|
+
end
|
33
|
+
dputs "Patched:", rubocop_file
|
34
|
+
end
|
35
|
+
(rubocop_dir / ".rubocop-service_patched").open("w").close
|
36
|
+
puts "Patched #{patch_libs.size} files! Run `rubocop-service uninstall` to restore."
|
37
|
+
end
|
38
|
+
|
39
|
+
def find_rubocop_path
|
40
|
+
feature_path = $LOAD_PATH.resolve_feature_path("rubocop")
|
41
|
+
feature_path.nil? ? nil : feature_path[1]
|
42
|
+
rescue LoadError => e
|
43
|
+
dputs "$LOAD_PATH.resolve_feature_path raised error:", e.message
|
44
|
+
nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Server
|
5
|
+
class Cache
|
6
|
+
class << self
|
7
|
+
def project_dir_cache_key
|
8
|
+
@project_dir_cache_key ||= project_dir.tr("/:", "++")
|
9
|
+
end
|
10
|
+
|
11
|
+
def write_pid_file
|
12
|
+
pid_path.write(Process.pid)
|
13
|
+
yield
|
14
|
+
ensure
|
15
|
+
$unlocker&.call # rubocop:disable Style/GlobalVars
|
16
|
+
dir.rmtree
|
17
|
+
end
|
18
|
+
|
19
|
+
def acquire_lock
|
20
|
+
lock_file = File.open(lock_path, File::CREAT)
|
21
|
+
# flock returns 0 if successful, and false if not.
|
22
|
+
flock_result = lock_file.flock(File::LOCK_EX | File::LOCK_NB)
|
23
|
+
# rubocop:disable Style/GlobalVars
|
24
|
+
$unlocker = -> do
|
25
|
+
next if lock_file.closed?
|
26
|
+
lock_file.flock(File::LOCK_UN)
|
27
|
+
lock_file.close
|
28
|
+
end
|
29
|
+
# rubocop:enable Style/GlobalVars
|
30
|
+
yield flock_result != false
|
31
|
+
end
|
32
|
+
|
33
|
+
def pid_running?
|
34
|
+
Process.kill(0, pid_path.read.to_i) == 1
|
35
|
+
rescue Errno::ESRCH, Errno::ENOENT
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
require "json"
|
5
|
+
require "rubocop/service/server"
|
6
|
+
|
7
|
+
module RuboCop
|
8
|
+
module Server
|
9
|
+
module ClientCommand
|
10
|
+
class Start < Base
|
11
|
+
def run
|
12
|
+
if Server.running?
|
13
|
+
warn "RuboCop server (#{Cache.pid_path.read}) is already running."
|
14
|
+
return
|
15
|
+
end
|
16
|
+
|
17
|
+
if ENV["RUBOCOP_SERVICE_SERVER_PROCESS"] == "true"
|
18
|
+
Cache.acquire_lock do |locked|
|
19
|
+
unless locked
|
20
|
+
# Another process is already starting server,
|
21
|
+
# so wait for it to be ready.
|
22
|
+
Server.wait_for_running_status!(true)
|
23
|
+
exit 0
|
24
|
+
end
|
25
|
+
|
26
|
+
Cache.write_version_file(RuboCop::Version::STRING)
|
27
|
+
|
28
|
+
host = ENV.fetch("RUBOCOP_SERVER_HOST", "127.0.0.1")
|
29
|
+
port = ENV.fetch("RUBOCOP_SERVER_PORT", 0)
|
30
|
+
|
31
|
+
Server::Core.new.start(host, port)
|
32
|
+
end
|
33
|
+
else
|
34
|
+
exit_code =
|
35
|
+
RuboCop::Service::Server.connect do |connection|
|
36
|
+
connection.puts JSON.generate(
|
37
|
+
{ type: :spawn, directory: Dir.pwd }
|
38
|
+
)
|
39
|
+
end
|
40
|
+
if exit_code.nil?
|
41
|
+
warn(
|
42
|
+
"\nConnection closed without exit code. " \
|
43
|
+
"Please check the server log: #{File.expand_path("~/.rubocop-service.log")}"
|
44
|
+
)
|
45
|
+
exit 1
|
46
|
+
else
|
47
|
+
exit exit_code.to_i
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Server
|
5
|
+
module ClientCommand
|
6
|
+
# This class is a client command to stop server process.
|
7
|
+
# @api private
|
8
|
+
class Stop < Base
|
9
|
+
def run
|
10
|
+
return unless check_running_server
|
11
|
+
|
12
|
+
send_request(command: "stop")
|
13
|
+
Server.wait_for_running_status!(false)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RuboCop
|
4
|
+
module Server
|
5
|
+
class Core
|
6
|
+
private
|
7
|
+
|
8
|
+
def demonize
|
9
|
+
Cache.write_port_and_token_files(port: @server.addr[1], token: token)
|
10
|
+
|
11
|
+
Cache.write_pid_file do
|
12
|
+
print "rubocop-service-nonce:#{ENV.fetch("RUBOCOP_SERVICE_STARTING_NONCE", "")}"
|
13
|
+
$stdout.flush
|
14
|
+
read_socket(@server.accept) until @server.closed?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,310 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# -- NOTE --
|
4
|
+
# This script may be run separately from the library.
|
5
|
+
|
6
|
+
require "socket"
|
7
|
+
require "json"
|
8
|
+
require "win32/service"
|
9
|
+
require "rbconfig"
|
10
|
+
require_relative "version"
|
11
|
+
require "open3"
|
12
|
+
require "securerandom"
|
13
|
+
require "English"
|
14
|
+
|
15
|
+
module RuboCop
|
16
|
+
module Service
|
17
|
+
class Server
|
18
|
+
DEFAULT_SERVER_CONFIG = {
|
19
|
+
pid: -1,
|
20
|
+
port: -1,
|
21
|
+
host: -1,
|
22
|
+
version: "0.0.0"
|
23
|
+
}.freeze
|
24
|
+
SERVICE_NAME = "rubocop_service"
|
25
|
+
|
26
|
+
def initialize
|
27
|
+
host = ENV.fetch("RUBOCOP_SERVICE_SERVER_HOST", "127.0.0.1")
|
28
|
+
port = ENV.fetch("RUBOCOP_SERVICE_SERVER_PORT", 0)
|
29
|
+
@server = TCPServer.open(host, port)
|
30
|
+
@threads = []
|
31
|
+
@processes = []
|
32
|
+
end
|
33
|
+
|
34
|
+
def start
|
35
|
+
File.write(Server.server_config_path, server_config.to_json)
|
36
|
+
puts "Server ready! pid: #{Process.pid}, port: #{port}, host: #{host}"
|
37
|
+
|
38
|
+
@main_thread =
|
39
|
+
Thread.new do
|
40
|
+
loop do
|
41
|
+
# process(@server.accept)
|
42
|
+
# client.close
|
43
|
+
@threads << Thread.start(@server.accept) do |client|
|
44
|
+
puts "#{Thread.current.inspect}: Connected #{client.inspect}"
|
45
|
+
process(client)
|
46
|
+
puts "#{Thread.current.inspect}: Processed #{client.inspect}"
|
47
|
+
client.close
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
@main_thread.join
|
52
|
+
rescue Interrupt
|
53
|
+
puts "Terminating..."
|
54
|
+
stop
|
55
|
+
end
|
56
|
+
|
57
|
+
def stop
|
58
|
+
@processes.each(&:kill)
|
59
|
+
@threads.each(&:kill)
|
60
|
+
@main_thread.kill
|
61
|
+
end
|
62
|
+
|
63
|
+
def process(connection)
|
64
|
+
r = connection.gets
|
65
|
+
message = JSON.parse(r, symbolize_names: true)
|
66
|
+
case message[:type]
|
67
|
+
when "spawn"
|
68
|
+
spawn_server(connection, message[:directory])
|
69
|
+
else
|
70
|
+
connection.puts(
|
71
|
+
JSON.generate(
|
72
|
+
{
|
73
|
+
type: "stderr",
|
74
|
+
message:
|
75
|
+
"Unknown message type: #{message[:type]}. This is bug, please report it to https://github.com/sevenc-nanashi/rubocop-service/issues"
|
76
|
+
}
|
77
|
+
)
|
78
|
+
)
|
79
|
+
connection.puts(JSON.generate({ type: "exitcode", message: 1 }))
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def spawn_server(connection, directory)
|
84
|
+
nonce = SecureRandom.hex(8)
|
85
|
+
puts "#{Thread.current.inspect}: Starting server..."
|
86
|
+
Open3.popen3(
|
87
|
+
{
|
88
|
+
"RUBOCOP_SERVICE_SERVER_PROCESS" => "true",
|
89
|
+
"RUBOCOP_SERVICE_STARTING_NONCE" => nonce
|
90
|
+
},
|
91
|
+
"rubocop --start-server",
|
92
|
+
chdir: directory
|
93
|
+
) do |i, o, e, t|
|
94
|
+
i.close
|
95
|
+
queue = Queue.new
|
96
|
+
started = false
|
97
|
+
@processes << t
|
98
|
+
Thread.start do
|
99
|
+
while (od = o.readpartial(4096))
|
100
|
+
$stdout.write od
|
101
|
+
$stdout.flush
|
102
|
+
if od.include?("rubocop-service-nonce:#{nonce}")
|
103
|
+
queue << -1
|
104
|
+
od.gsub!("rubocop-service-nonce:#{nonce}", "")
|
105
|
+
end
|
106
|
+
unless started
|
107
|
+
connection.puts("#{{ type: "stdout", message: od }.to_json}\n")
|
108
|
+
end
|
109
|
+
end
|
110
|
+
rescue IOError
|
111
|
+
# ignore
|
112
|
+
end
|
113
|
+
Thread.start do
|
114
|
+
while (ed = e.readpartial(4096))
|
115
|
+
$stderr.write ed
|
116
|
+
$stderr.flush
|
117
|
+
unless started
|
118
|
+
connection.puts("#{{ type: "stderr", message: ed }.to_json}\n")
|
119
|
+
end
|
120
|
+
end
|
121
|
+
rescue IOError
|
122
|
+
# ignore
|
123
|
+
end
|
124
|
+
Thread.start { queue << t.value }
|
125
|
+
exit_status = queue.pop
|
126
|
+
|
127
|
+
connection.puts(
|
128
|
+
JSON.generate(
|
129
|
+
{ type: "exitcode", message: exit_status == -1 ? 0 : exit_status }
|
130
|
+
)
|
131
|
+
)
|
132
|
+
case exit_status
|
133
|
+
when -1
|
134
|
+
puts "#{Thread.current.inspect}: Server started."
|
135
|
+
else
|
136
|
+
puts "#{Thread.current.inspect}: Server failed to start."
|
137
|
+
end
|
138
|
+
started = true
|
139
|
+
t.join
|
140
|
+
exit_status = queue.pop
|
141
|
+
case exit_status
|
142
|
+
when 0
|
143
|
+
puts "#{Thread.current.inspect}: Server exited normally."
|
144
|
+
else
|
145
|
+
puts "#{Thread.current.inspect}: Server exited with error, exit status: #{exit_status}"
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
def server_config
|
151
|
+
{
|
152
|
+
pid: Process.pid,
|
153
|
+
port: port,
|
154
|
+
host: host,
|
155
|
+
version: RuboCop::Service::VERSION
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
159
|
+
def host
|
160
|
+
@server.addr[3]
|
161
|
+
end
|
162
|
+
|
163
|
+
def port
|
164
|
+
@server.addr[1]
|
165
|
+
end
|
166
|
+
|
167
|
+
class << self
|
168
|
+
def connect
|
169
|
+
assert_running
|
170
|
+
connection =
|
171
|
+
TCPSocket.open(server_config[:host], server_config[:port])
|
172
|
+
exitcode = nil
|
173
|
+
connection_thread =
|
174
|
+
Thread.new do
|
175
|
+
catch :exit do
|
176
|
+
while (messages = connection.gets)
|
177
|
+
messages
|
178
|
+
.split("\n")
|
179
|
+
.each do |message|
|
180
|
+
next if message.strip.empty?
|
181
|
+
data = JSON.parse(message, symbolize_names: true)
|
182
|
+
case data[:type]
|
183
|
+
when "stdout"
|
184
|
+
$stdout.write(data[:message])
|
185
|
+
when "stderr"
|
186
|
+
$stderr.write(data[:message])
|
187
|
+
when "exitcode"
|
188
|
+
exitcode = data[:message]
|
189
|
+
throw :exit
|
190
|
+
end
|
191
|
+
rescue JSON::ParserError
|
192
|
+
warn "Invalid message received: #{message}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
rescue IOError
|
197
|
+
# ignore
|
198
|
+
end
|
199
|
+
return connection unless block_given?
|
200
|
+
yield connection
|
201
|
+
connection_thread.join
|
202
|
+
connection.close
|
203
|
+
exitcode
|
204
|
+
end
|
205
|
+
|
206
|
+
def assert_running
|
207
|
+
return if running?
|
208
|
+
|
209
|
+
warn "Service is not running! Please run `rubocop-service start` with administrator privileges."
|
210
|
+
exit 1
|
211
|
+
end
|
212
|
+
|
213
|
+
def start
|
214
|
+
begin
|
215
|
+
register unless Win32::Service.exists?(SERVICE_NAME)
|
216
|
+
Win32::Service.start SERVICE_NAME
|
217
|
+
rescue Errno::EIO
|
218
|
+
puts "Could not start service! Missing administrator privileges?"
|
219
|
+
exit 1
|
220
|
+
end
|
221
|
+
|
222
|
+
puts "Service started successfully!"
|
223
|
+
end
|
224
|
+
|
225
|
+
def stop
|
226
|
+
unless Win32::Service.exists?(SERVICE_NAME)
|
227
|
+
puts "Service not found."
|
228
|
+
exit 1
|
229
|
+
end
|
230
|
+
|
231
|
+
if running?
|
232
|
+
begin
|
233
|
+
Win32::Service.stop SERVICE_NAME
|
234
|
+
rescue Errno::EIO
|
235
|
+
puts "Could not stop service! Missing administrator privileges?"
|
236
|
+
exit 1
|
237
|
+
end
|
238
|
+
puts "Service stopped."
|
239
|
+
else
|
240
|
+
puts "Service is not running."
|
241
|
+
exit 1
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def status
|
246
|
+
if running?
|
247
|
+
puts "Service is running."
|
248
|
+
else
|
249
|
+
puts "Service is not running."
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def running?
|
254
|
+
return false if server_config[:pid] == -1
|
255
|
+
begin
|
256
|
+
Process.kill(0, server_config[:pid])
|
257
|
+
true
|
258
|
+
rescue Errno::ESRCH
|
259
|
+
false
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def server_config_path
|
264
|
+
File.expand_path("~/.rubocop-service")
|
265
|
+
end
|
266
|
+
|
267
|
+
def server_config
|
268
|
+
if File.exist?(server_config_path)
|
269
|
+
JSON.load_file(server_config_path, symbolize_names: true)
|
270
|
+
else
|
271
|
+
DEFAULT_SERVER_CONFIG.dup
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def register
|
276
|
+
Win32::Service.create(
|
277
|
+
service_name: SERVICE_NAME,
|
278
|
+
service_type: Win32::Service::WIN32_OWN_PROCESS,
|
279
|
+
description:
|
280
|
+
"RuboCop Server for windows, provided by rubocop-service gem",
|
281
|
+
start_type: Win32::Service::AUTO_START,
|
282
|
+
error_control: Win32::Service::ERROR_NORMAL,
|
283
|
+
binary_path_name: "#{RbConfig.ruby} #{File.expand_path(__FILE__)}",
|
284
|
+
load_order_group: "Network",
|
285
|
+
dependencies: %w[W32Time Schedule]
|
286
|
+
)
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
290
|
+
end
|
291
|
+
end
|
292
|
+
|
293
|
+
if __FILE__ == $PROGRAM_NAME
|
294
|
+
require "win32/daemon"
|
295
|
+
|
296
|
+
class ServiceDaemon < Win32::Daemon
|
297
|
+
def service_main
|
298
|
+
server = RuboCop::Service::Server.new
|
299
|
+
Thread.start { server.start }
|
300
|
+
sleep 0.1 while running?
|
301
|
+
server.stop
|
302
|
+
end
|
303
|
+
|
304
|
+
def service_stop
|
305
|
+
exit!
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
ServiceDaemon.mainloop
|
310
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "pathname"
|
4
|
+
|
5
|
+
module RuboCop
|
6
|
+
module Service
|
7
|
+
class Uninstaller
|
8
|
+
def run
|
9
|
+
puts "Finding RuboCop installation..."
|
10
|
+
rubocop_path = find_rubocop_path
|
11
|
+
unless rubocop_path
|
12
|
+
warn "RuboCop not found! Please install it first."
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
dputs "rubocop.rb path:", rubocop_path
|
16
|
+
rubocop_dir = Pathname.new File.dirname(rubocop_path)
|
17
|
+
dputs "rubocop dir:", rubocop_dir
|
18
|
+
unless (rubocop_dir / ".rubocop-service_patched").exist?
|
19
|
+
warn "Not patched! Use `rubocop-service install` to install."
|
20
|
+
exit 1
|
21
|
+
end
|
22
|
+
puts "RuboCop found, unpatching it..."
|
23
|
+
rubocop_files = rubocop_dir.glob("rubocop/**/*.rb")
|
24
|
+
rubocop_files.filter! do |path|
|
25
|
+
dputs "Checking:", path
|
26
|
+
content = path.read
|
27
|
+
if content.gsub!(
|
28
|
+
/# !!! Patched by rubocop-service !!!.*# !!! End of patch !!!\n/m,
|
29
|
+
""
|
30
|
+
)
|
31
|
+
dputs "Unpatched."
|
32
|
+
path.write content
|
33
|
+
else
|
34
|
+
dputs "Not patched."
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
(rubocop_dir / ".rubocop-service_patched").delete
|
39
|
+
puts "Unpatched #{rubocop_files.size} files! Run `rubocop-service install` to install again."
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_rubocop_path
|
43
|
+
feature_path = $LOAD_PATH.resolve_feature_path("rubocop")
|
44
|
+
feature_path.nil? ? nil : feature_path[1]
|
45
|
+
rescue LoadError => e
|
46
|
+
dputs "$LOAD_PATH.resolve_feature_path raised error:", e.message
|
47
|
+
nil
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "service/version"
|
4
|
+
require_relative "service/utils"
|
5
|
+
|
6
|
+
module RuboCop
|
7
|
+
module Service
|
8
|
+
class Error < StandardError
|
9
|
+
end
|
10
|
+
|
11
|
+
autoload :CLI, "rubocop/service/cli"
|
12
|
+
autoload :Installer, "rubocop/service/installer"
|
13
|
+
autoload :Uninstaller, "rubocop/service/uninstaller"
|
14
|
+
autoload :Server, "rubocop/service/server"
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/rubocop/service/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "rubocop-service"
|
7
|
+
spec.version = RuboCop::Service::VERSION
|
8
|
+
spec.authors = ["sevenc-nanashi"]
|
9
|
+
spec.email = ["sevenc7c@sevenc7c.com"]
|
10
|
+
|
11
|
+
spec.summary = "Provides support of rubocop server, for Windows!"
|
12
|
+
spec.homepage = "https://github.com/sevenc-nanashi/rubocop-service"
|
13
|
+
spec.license = "MIT"
|
14
|
+
spec.required_ruby_version = ">= 3.0.0"
|
15
|
+
|
16
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
17
|
+
spec.metadata[
|
18
|
+
"source_code_uri"
|
19
|
+
] = "https://github.com/sevenc-nanashi/rubocop-service"
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/sevenc-nanashi/rubocop-service/blob/main/CHANGELOG.md"
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
spec.files =
|
25
|
+
Dir.chdir(__dir__) do
|
26
|
+
`git ls-files -z`.split("\x0")
|
27
|
+
.reject do |f|
|
28
|
+
(f == __FILE__) ||
|
29
|
+
f.match(
|
30
|
+
%r{\A(?:(?:bin|test|spec|features)/|\.(?:git|travis|circleci)|appveyor)}
|
31
|
+
)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
spec.bindir = "exe"
|
35
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
36
|
+
spec.require_paths = ["lib"]
|
37
|
+
|
38
|
+
# Uncomment to register a new dependency of your gem
|
39
|
+
spec.add_dependency "rubocop", "~> 1.36.0"
|
40
|
+
spec.add_dependency "win32-service", "~> 2.3.2"
|
41
|
+
|
42
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
43
|
+
spec.post_install_message = <<~MESSAGE
|
44
|
+
Thanks for installing rubocop-service!
|
45
|
+
You need to run `rubocop-service install` to patch rubocop,
|
46
|
+
and `rubocop-service start` with administrator privileges
|
47
|
+
to start the manager server.
|
48
|
+
MESSAGE
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubocop-service
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- sevenc-nanashi
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-09-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rubocop
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 1.36.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 1.36.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: win32-service
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.3.2
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 2.3.2
|
41
|
+
description:
|
42
|
+
email:
|
43
|
+
- sevenc7c@sevenc7c.com
|
44
|
+
executables:
|
45
|
+
- rubocop-service
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- ".rubocop.yml"
|
50
|
+
- CHANGELOG.md
|
51
|
+
- Gemfile
|
52
|
+
- Gemfile.lock
|
53
|
+
- LICENSE.txt
|
54
|
+
- README.md
|
55
|
+
- Rakefile
|
56
|
+
- exe/rubocop-service
|
57
|
+
- lib/rubocop/service.rb
|
58
|
+
- lib/rubocop/service/cli.rb
|
59
|
+
- lib/rubocop/service/installer.rb
|
60
|
+
- lib/rubocop/service/patch/server.rb
|
61
|
+
- lib/rubocop/service/patch/server/cache.rb
|
62
|
+
- lib/rubocop/service/patch/server/client_command/start.rb
|
63
|
+
- lib/rubocop/service/patch/server/client_command/stop.rb
|
64
|
+
- lib/rubocop/service/patch/server/core.rb
|
65
|
+
- lib/rubocop/service/server.rb
|
66
|
+
- lib/rubocop/service/uninstaller.rb
|
67
|
+
- lib/rubocop/service/utils.rb
|
68
|
+
- lib/rubocop/service/version.rb
|
69
|
+
- rubocop-service.gemspec
|
70
|
+
- sig/rubocop/service.rbs
|
71
|
+
homepage: https://github.com/sevenc-nanashi/rubocop-service
|
72
|
+
licenses:
|
73
|
+
- MIT
|
74
|
+
metadata:
|
75
|
+
homepage_uri: https://github.com/sevenc-nanashi/rubocop-service
|
76
|
+
source_code_uri: https://github.com/sevenc-nanashi/rubocop-service
|
77
|
+
changelog_uri: https://github.com/sevenc-nanashi/rubocop-service/blob/main/CHANGELOG.md
|
78
|
+
rubygems_mfa_required: 'true'
|
79
|
+
post_install_message: |
|
80
|
+
Thanks for installing rubocop-service!
|
81
|
+
You need to run `rubocop-service install` to patch rubocop,
|
82
|
+
and `rubocop-service start` with administrator privileges
|
83
|
+
to start the manager server.
|
84
|
+
rdoc_options: []
|
85
|
+
require_paths:
|
86
|
+
- lib
|
87
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
88
|
+
requirements:
|
89
|
+
- - ">="
|
90
|
+
- !ruby/object:Gem::Version
|
91
|
+
version: 3.0.0
|
92
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
requirements: []
|
98
|
+
rubygems_version: 3.3.17
|
99
|
+
signing_key:
|
100
|
+
specification_version: 4
|
101
|
+
summary: Provides support of rubocop server, for Windows!
|
102
|
+
test_files: []
|