Skip to content

Commit

Permalink
Import rubocop-daemon
Browse files Browse the repository at this point in the history
Preparing to resolve #9503.

This PR imports https://github.com/fohte/rubocop-daemon into RuboCop core.

The rubocop-daemon's original license (MIT) is written at the beginning
of the imported files.
  • Loading branch information
koic authored and bbatsov committed Jun 10, 2022
1 parent 92764a7 commit 1ce3029
Show file tree
Hide file tree
Showing 18 changed files with 784 additions and 0 deletions.
41 changes: 41 additions & 0 deletions lib/rubocop/daemon.rb
@@ -0,0 +1,41 @@
# frozen_string_literal: true

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
TIMEOUT = 20

autoload :CLI, 'rubocop/daemon/cli'
autoload :Cache, 'rubocop/daemon/cache'
autoload :ClientCommand, 'rubocop/daemon/client_command'
autoload :Helper, 'rubocop/daemon/helper'
autoload :Server, 'rubocop/daemon/server'
autoload :ServerCommand, 'rubocop/daemon/server_command'
autoload :SocketReader, 'rubocop/daemon/socket_reader'

def self.running?
Cache.dir.exist? && Cache.pid_path.file? && Cache.pid_running?
end

def self.wait_for_running_status!(expected)
start_time = Time.now
while Daemon.running? != expected
sleep 0.1
next unless Time.now - start_time > TIMEOUT

warn "running? was not #{expected} after #{TIMEOUT} seconds!"
exit 1
end
end
end
end

require 'rubocop/daemon/errors'
97 changes: 97 additions & 0 deletions lib/rubocop/daemon/cache.rb
@@ -0,0 +1,97 @@
# frozen_string_literal: true

require 'pathname'

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
class Cache
class << self
# Searches for Gemfile or gems.rb in the current dir or any parent dirs
def project_dir
current_dir = Dir.pwd
while current_dir != '/'
return current_dir if %w[Gemfile gems.rb].any? do |gemfile|
File.exist?(File.join(current_dir, gemfile))
end

current_dir = File.expand_path('..', current_dir)
end
# If we can't find a Gemfile, just use the current directory
Dir.pwd
end

def project_dir_cache_key
@project_dir_cache_key ||= project_dir[1..-1].tr('/', '+')
end

def dir
cache_path = File.expand_path('~/.cache/rubocop-daemon')
Pathname.new(File.join(cache_path, project_dir_cache_key)).tap do |d|
d.mkpath unless d.exist?
end
end

def port_path
dir.join('port')
end

def token_path
dir.join('token')
end

def pid_path
dir.join('pid')
end

def lock_path
dir.join('lock')
end

def status_path
dir.join('status')
end

def pid_running?
Process.kill(0, pid_path.read.to_i) == 1
rescue Errno::ESRCH
false
end

def acquire_lock
lock_file = File.open(lock_path, File::CREAT)
# flock returns 0 if successful, and false if not.
flock_result = lock_file.flock(File::LOCK_EX | File::LOCK_NB)
yield flock_result != false
ensure
lock_file.flock(File::LOCK_UN)
lock_file.close
end

def write_port_and_token_files(port:, token:)
port_path.write(port)
token_path.write(token)
end

def write_pid_file
pid_path.write(Process.pid)
yield
ensure
dir.rmtree
end

def write_status_file(status)
status_path.write(status)
end
end
end
end
end
62 changes: 62 additions & 0 deletions lib/rubocop/daemon/cli.rb
@@ -0,0 +1,62 @@
# frozen_string_literal: true

require 'optparse'

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
class CLI
def self.new_parser(&_block)
OptionParser.new do |opts|
yield(opts)
end
end

def run(argv = ARGV)
parser.order!(argv)
return if argv.empty?

create_subcommand_instance(argv)
rescue OptionParser::InvalidOption => e
warn "error: #{e.message}"
exit 1
rescue UnknownClientCommandError => e
warn "rubocop-daemon: #{e.message}. See 'rubocop-daemon --help'."
exit 1
end

def parser
@parser ||= self.class.new_parser do |opts|
opts.banner = 'usage: rubocop-daemon <command> [<args>]'
end
end

private

def create_subcommand_instance(argv)
subcommand, *args = argv
find_subcommand_class(subcommand).new(args).run
end

def find_subcommand_class(subcommand)
case subcommand
when 'exec' then ClientCommand::Exec
when 'restart' then ClientCommand::Restart
when 'start' then ClientCommand::Start
when 'status' then ClientCommand::Status
when 'stop' then ClientCommand::Stop
else
raise UnknownClientCommandError, "#{subcommand.inspect} is not a rubocop-daemon command"
end
end
end
end
end
23 changes: 23 additions & 0 deletions lib/rubocop/daemon/client_command.rb
@@ -0,0 +1,23 @@
# frozen_string_literal: true

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
module ClientCommand
autoload :Base, 'rubocop/daemon/client_command/base'
autoload :Exec, 'rubocop/daemon/client_command/exec'
autoload :Restart, 'rubocop/daemon/client_command/restart'
autoload :Start, 'rubocop/daemon/client_command/start'
autoload :Status, 'rubocop/daemon/client_command/status'
autoload :Stop, 'rubocop/daemon/client_command/stop'
end
end
end
51 changes: 51 additions & 0 deletions lib/rubocop/daemon/client_command/base.rb
@@ -0,0 +1,51 @@
# frozen_string_literal: true

require 'shellwords'
require 'socket'

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
module ClientCommand
class Base
def initialize(argv)
@argv = argv.dup
@options = {}
end

def run; end

private

def send_request(command:, args: [], body: '')
TCPSocket.open('127.0.0.1', Cache.port_path.read) do |socket|
socket.puts [Cache.token_path.read, Dir.pwd, command, *args].shelljoin
socket.write body
socket.close_write
STDOUT.write socket.read(4096) until socket.eof?
end
end

def check_running_server
Daemon.running?.tap do |running|
warn 'rubocop-daemon: server is not running.' unless running
end
end

def ensure_server!
return if check_running_server

ClientCommand::Start.new([]).run
end
end
end
end
end
47 changes: 47 additions & 0 deletions lib/rubocop/daemon/client_command/exec.rb
@@ -0,0 +1,47 @@
# frozen_string_literal: true

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
module ClientCommand
class Exec < Base
def run
args = parser.parse(@argv)
ensure_server!
Cache.status_path.delete if Cache.status_path.file?
send_request(
command: 'exec',
args: args,
body: $stdin.tty? ? '' : $stdin.read,
)
exit_with_status!
end

private

def parser
@parser ||= CLI.new_parser do |p|
p.banner = 'usage: rubocop-daemon exec [options] [files...] [-- [rubocop-options]]'
end
end

def exit_with_status!
raise "rubocop-daemon: Could not find status file at: #{Cache.status_path}" unless Cache.status_path.file?

status = Cache.status_path.read
raise "rubocop-daemon: '#{status}' is not a valid status!" if (status =~ /^\d+$/).nil?

exit status.to_i
end
end
end
end
end
35 changes: 35 additions & 0 deletions lib/rubocop/daemon/client_command/restart.rb
@@ -0,0 +1,35 @@
# frozen_string_literal: true

#
# This code is based on https://github.com/fohte/rubocop-daemon.
#
# Copyright (c) 2018 Hayato Kawai
#
# The MIT License (MIT)
#
# https://github.com/fohte/rubocop-daemon/blob/master/LICENSE.txt
#
module RuboCop
module Daemon
module ClientCommand
class Restart < Base
def run
parser.parse(@argv)

ClientCommand::Stop.new([]).run
ClientCommand::Start.new(@argv).run
end

private

def parser
@parser ||= CLI.new_parser do |p|
p.banner = 'usage: rubocop-daemon restart'

p.on('-p', '--port [PORT]') { |v| @options[:port] = v }
end
end
end
end
end
end

0 comments on commit 1ce3029

Please sign in to comment.