rubocop-daemon 0.2.0 → 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5b7fc85bc8f0d4eec8c3e059c0dfd63131165881e99b9df8f675569cc9f482d5
4
- data.tar.gz: 8ac1b60ad89d68fd52b9bae1ce5d5f9f2579307e4f68934726077efb4669fa1f
3
+ metadata.gz: d60258fcc9e9afc6e69f83a509ce6af552e8cf2c66ef6fd0553de6e88532b27a
4
+ data.tar.gz: be0da846dbb327c3b61b9cdd0a2ce160a327963f270a758ee2f6912f1cc1b32d
5
5
  SHA512:
6
- metadata.gz: 4ee5f72d514f94b91b5ddddee5a68db28627367e09639e017b80a73500b0343c78f6d4ebee344ed17f88ce3a99558b17b232f75cf8d45541bd733b46db9dc0d8
7
- data.tar.gz: cabc9bca1782ec0b20425b76ca4494e7b887135b0b8f50729044055d7795ba4698a3f355c9ca89fb62b33e931b01c07187198d0dabbf74c310741603a9113df1
6
+ metadata.gz: 1fbb5f36a050acaa7848ccd2f3307696d0cef5fc7b940df7d2088057a7128fbbab28270d555ee4998c9fcab7934c149f47c1122886650cbde54d790a1d764ce1
7
+ data.tar.gz: eb852d20656a2f41a2de055c6a540d00c96a733ea01a514762e4b7eb4c4d60aba33330bfc1dae06ee494cd5d8e152dfd04ef9d6692e803f3a7a84890e26c20a8
data/.gitignore CHANGED
@@ -1,6 +1,7 @@
1
1
  *.gem
2
2
  *.rbc
3
3
  /.config
4
+ .vscode/
4
5
  /coverage/
5
6
  /InstalledFiles
6
7
  /pkg/
data/README.md CHANGED
@@ -48,20 +48,51 @@ rubocop-daemon <command>
48
48
 
49
49
  Available commands:
50
50
 
51
- * `start`: start the server
52
- * `stop`: stop the server
53
- * `status`: print out whether the server is currently running
54
- * `restart`: restart the server
55
- * `exec [file1, file2, ...] [-- [rubocop-options]]`: invoke `rubocop` with the given `rubocop-options`
51
+ - `start`: start the server
52
+ - `stop`: stop the server
53
+ - `status`: print out whether the server is currently running
54
+ - `restart`: restart the server
55
+ - `exec [file1, file2, ...] [-- [rubocop-options]]`: invoke `rubocop` with the given `rubocop-options`
56
56
 
57
57
  ## More speed
58
58
 
59
- If you're really into performance and want the lowest possible latency, talk to the `rubocop-daemon` server with netcat:
59
+ `rubocop-daemon-wrapper` is a bash script that talks to the `rubocop-daemon` server via `netcat`. This provides much lower latency than the `rubocop-daemon` Ruby script.
60
60
 
61
- ```sh
62
- echo "$(cat ~/.cache/rubocop-daemon/token) $PWD exec [rubocop-options]" | nc localhost $(cat ~/.cache/rubocop-daemon/port)
61
+ For now, you have to manually download and install the bash script:
62
+
63
+ > (`rubygems` will wrap any executable files with a Ruby script, and [you can't disable this behavior](https://github.com/rubygems/rubygems/issues/88).)
64
+
65
+ ```
66
+ curl https://raw.githubusercontent.com/fohte/rubocop-daemon/master/bin/rubocop-daemon-wrapper -o /tmp/rubocop-daemon-wrapper
67
+ sudo mv /tmp/rubocop-daemon-wrapper /usr/local/bin/rubocop-daemon-wrapper
68
+ sudo chmod +x /usr/local/bin/rubocop-daemon-wrapper
69
+ ```
70
+
71
+ You can then replace any calls to `rubocop` with `rubocop-daemon-wrapper`.
72
+
73
+ ```
74
+ rubocop-daemon-wrapper foo.rb bar.rb
63
75
  ```
64
76
 
77
+ `rubocop-daemon-wrapper` will automatically start the daemon server if it is not already running. So the first call will be about the same as `rubocop`, but the second call will be much faster.
78
+
79
+ ## Use with VS Code
80
+
81
+ Unfortunately, the [vscode-ruby extension doesn't really allow you to customize the `rubocop` path or binary](https://github.com/rubyide/vscode-ruby/issues/413). (You can change the linter path, but not the formatter.)
82
+
83
+ In the meantime, you could just override the `rubocop` binary with a symlink to `rubocop-daemon-wrapper`:
84
+
85
+ ```bash
86
+ # Find your rubocop path
87
+ $ which rubocop
88
+ # => /Users/username/.rvm/gems/ruby-2.5.3/bin/rubocop
89
+
90
+ # Override rubocop with a symlink to rubocop-daemon-wrapper
91
+ $ ln -fs /usr/local/bin/rubocop-daemon-wrapper /Users/username/.rvm/gems/ruby-2.5.3/bin/rubocop
92
+ ```
93
+
94
+ Now VS Code will use the `rubocop-daemon-wrapper` script, and `formatOnSave` should be much faster (~150ms instead of 3-5 seconds).
95
+
65
96
  ## Contributing
66
97
 
67
98
  Bug reports and pull requests are welcome on GitHub at https://github.com/fohte/rubocop-daemon.
@@ -0,0 +1,66 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ find_project_root() {
5
+ path=$(pwd -P)
6
+ while [[ "$path" != "" && ! -f "$path/Gemfile" && ! -f "$path/gems.rb" ]]; do
7
+ path=${path%/*}
8
+ done
9
+ echo "$path"
10
+ }
11
+
12
+ PROJECT_ROOT="$(find_project_root)"
13
+ if [ -z "$PROJECT_ROOT" ]; then
14
+ # If we can't find a Gemfile, just use the current directory
15
+ PROJECT_ROOT="$(pwd -P)"
16
+ fi
17
+
18
+ CACHE_DIR="$HOME/.cache/rubocop-daemon"
19
+ PROJECT_CACHE_KEY="$(echo ${PROJECT_ROOT:1} | tr '/' '+')"
20
+ PROJECT_CACHE_DIR="$CACHE_DIR/$PROJECT_CACHE_KEY"
21
+ TOKEN_PATH="$PROJECT_CACHE_DIR/token"
22
+ PORT_PATH="$PROJECT_CACHE_DIR/port"
23
+ STDIN_PATH="$PROJECT_CACHE_DIR/stdin"
24
+ STATUS_PATH="$PROJECT_CACHE_DIR/status"
25
+
26
+ # If -s or --stdin args are present, read stdin with `cat`
27
+ for ARG in $@; do
28
+ if [ -z "$STDIN_CONTENT" ] && [ "$ARG" == "--stdin" ] || [ "$ARG" == "-s" ]; then
29
+ STDIN_CONTENT="\n$(cat)"
30
+ fi
31
+ done
32
+
33
+ if [ ! -f "$TOKEN_PATH" ]; then
34
+ rubocop-daemon start
35
+ fi
36
+
37
+ run_rubocop_command() {
38
+ TOKEN="$(cat "$TOKEN_PATH")"
39
+ PORT="$(cat "$PORT_PATH")"
40
+ COMMAND="$TOKEN $PROJECT_ROOT exec $@"
41
+ rm -f "$STATUS_PATH" # Clear the previous status
42
+ if echo -e "$COMMAND${STDIN_CONTENT}" | nc localhost "$PORT"; then
43
+ if [ -f "$STATUS_PATH" ]; then
44
+ exit "$(cat $STATUS_PATH)"
45
+ else
46
+ echo "rubocop-daemon-wrapper: server did not write status to $STATUS_PATH!" >&2
47
+ exit 1
48
+ fi
49
+ fi
50
+ return 1
51
+ }
52
+
53
+ if ! run_rubocop_command $@; then
54
+ echo "rubocop-daemon-wrapper: Error sending command to localhost:$PORT ($COMMAND)" >&2
55
+ echo "Killing all rubocop-daemon processes and removing cache directory..." >&2
56
+ rm -rf "$CACHE_DIR"
57
+ pkill -f "rubocop-daemon (re)?start"
58
+ echo "Starting new rubocop-daemon server..." >&2
59
+ rubocop-daemon start
60
+ if ! run_rubocop_command $@; then
61
+ echo "Sorry, something went wrong with rubocop-daemon!" >&2
62
+ echo "Please try updating the gem or re-installing the rubocop-daemon-wrapper script."
63
+ echo "If that doesn't work, please open an issue on GitHub:" \
64
+ "https://github.com/fohte/rubocop-daemon/issues/new" >&2
65
+ fi
66
+ fi
@@ -2,6 +2,8 @@
2
2
 
3
3
  module RuboCop
4
4
  module Daemon
5
+ TIMEOUT = 20
6
+
5
7
  autoload :VERSION, 'rubocop/daemon/version'
6
8
 
7
9
  autoload :CLI, 'rubocop/daemon/cli'
@@ -15,6 +17,17 @@ module RuboCop
15
17
  def self.running?
16
18
  Cache.dir.exist? && Cache.pid_path.file? && Cache.pid_running?
17
19
  end
20
+
21
+ def self.wait_for_running_status!(expected)
22
+ start_time = Time.now
23
+ while Daemon.running? != expected
24
+ sleep 0.1
25
+ next unless Time.now - start_time > TIMEOUT
26
+
27
+ warn "running? was not #{expected} after #{TIMEOUT} seconds!"
28
+ exit 1
29
+ end
30
+ end
18
31
  end
19
32
  end
20
33
 
@@ -5,37 +5,82 @@ require 'pathname'
5
5
  module RuboCop
6
6
  module Daemon
7
7
  class Cache
8
- def self.dir
9
- Pathname.new(File.join(File.expand_path('~/.cache/rubocop-daemon'), Dir.pwd[1..-1].tr('/', '+'))).tap do |d|
10
- d.mkpath unless d.exist?
8
+ class << self
9
+ # Searches for Gemfile or gems.rb in the current dir or any parent dirs
10
+ def project_dir
11
+ current_dir = Dir.pwd
12
+ while current_dir != '/'
13
+ return current_dir if %w[Gemfile gems.rb].any? do |gemfile|
14
+ File.exist?(File.join(current_dir, gemfile))
15
+ end
16
+
17
+ current_dir = File.expand_path('..', current_dir)
18
+ end
19
+ # If we can't find a Gemfile, just use the current directory
20
+ Dir.pwd
11
21
  end
12
- end
13
22
 
14
- def self.port_path
15
- dir.join('port')
16
- end
23
+ def project_dir_cache_key
24
+ @project_dir_cache_key ||= project_dir[1..-1].tr('/', '+')
25
+ end
17
26
 
18
- def self.token_path
19
- dir.join('token')
20
- end
27
+ def dir
28
+ cache_path = File.expand_path('~/.cache/rubocop-daemon')
29
+ Pathname.new(File.join(cache_path, project_dir_cache_key)).tap do |d|
30
+ d.mkpath unless d.exist?
31
+ end
32
+ end
21
33
 
22
- def self.pid_path
23
- dir.join('pid')
24
- end
34
+ def port_path
35
+ dir.join('port')
36
+ end
25
37
 
26
- def self.pid_running?
27
- Process.kill 0, pid_path.read.to_i
28
- rescue Errno::ESRCH
29
- false
30
- end
38
+ def token_path
39
+ dir.join('token')
40
+ end
31
41
 
32
- def self.make_server_file(port:, token:)
33
- port_path.write(port)
34
- token_path.write(token)
35
- pid_path.write(Process.pid)
36
- yield
37
- ensure
38
- dir.rmtree
42
+ def pid_path
43
+ dir.join('pid')
44
+ end
45
+
46
+ def lock_path
47
+ dir.join('lock')
48
+ end
49
+
50
+ def status_path
51
+ dir.join('status')
52
+ end
53
+
54
+ def pid_running?
55
+ Process.kill(0, pid_path.read.to_i) == 1
56
+ rescue Errno::ESRCH
57
+ false
58
+ end
59
+
60
+ def acquire_lock
61
+ lock_file = File.open(lock_path, File::CREAT)
62
+ flock_result = lock_file.flock(File::LOCK_EX | File::LOCK_NB)
63
+ yield flock_result.zero?
64
+ ensure
65
+ lock_file.flock(File::LOCK_UN)
66
+ lock_file.close
67
+ end
68
+
69
+ def write_port_and_token_files(port:, token:)
70
+ port_path.write(port)
71
+ token_path.write(token)
72
+ end
73
+
74
+ def write_pid_file
75
+ pid_path.write(Process.pid)
76
+ yield
77
+ ensure
78
+ dir.rmtree
79
+ end
80
+
81
+ def write_status_file(status)
82
+ status_path.write(status)
83
+ end
39
84
  end
40
85
  end
41
86
  end
@@ -23,9 +23,6 @@ module RuboCop
23
23
  rescue UnknownClientCommandError => e
24
24
  warn "rubocop-daemon: #{e.message}. See 'rubocop-daemon --help'."
25
25
  exit 1
26
- rescue ServerIsNotRunningError
27
- warn 'rubocop-daemon: server is not running.'
28
- exit 1
29
26
  end
30
27
 
31
28
  def parser
@@ -25,8 +25,16 @@ module RuboCop
25
25
  end
26
26
  end
27
27
 
28
- def check_running_server!
29
- raise ServerIsNotRunningError unless Daemon.running?
28
+ def check_running_server
29
+ Daemon.running?.tap do |running|
30
+ warn 'rubocop-daemon: server is not running.' unless running
31
+ end
32
+ end
33
+
34
+ def ensure_server!
35
+ return if check_running_server
36
+
37
+ ClientCommand::Start.new([]).run
30
38
  end
31
39
  end
32
40
  end
@@ -6,12 +6,14 @@ module RuboCop
6
6
  class Exec < Base
7
7
  def run
8
8
  args = parser.parse(@argv)
9
- check_running_server!
9
+ ensure_server!
10
+ Cache.status_path.delete if Cache.status_path.file?
10
11
  send_request(
11
12
  command: 'exec',
12
13
  args: args,
13
14
  body: $stdin.tty? ? '' : $stdin.read,
14
15
  )
16
+ exit_with_status!
15
17
  end
16
18
 
17
19
  private
@@ -21,6 +23,15 @@ module RuboCop
21
23
  p.banner = 'usage: rubocop-daemon exec [options] [files...] [-- [rubocop-options]]'
22
24
  end
23
25
  end
26
+
27
+ def exit_with_status!
28
+ raise "rubocop-daemon: Could not find status file at: #{Cache.status_path}" unless Cache.status_path.file?
29
+
30
+ status = Cache.status_path.read
31
+ raise "rubocop-daemon: '#{status}' is not a valid status!" unless status.match?(/^\d+$/)
32
+
33
+ exit status.to_i
34
+ end
24
35
  end
25
36
  end
26
37
  end
@@ -6,9 +6,9 @@ module RuboCop
6
6
  class Restart < Base
7
7
  def run
8
8
  parser.parse(@argv)
9
- check_running_server!
10
- send_request(command: 'stop')
11
- Server.new.start(@options.fetch(:port, 0))
9
+
10
+ ClientCommand::Stop.new([]).run
11
+ ClientCommand::Start.new(@argv).run
12
12
  end
13
13
 
14
14
  private
@@ -5,8 +5,22 @@ module RuboCop
5
5
  module ClientCommand
6
6
  class Start < Base
7
7
  def run
8
- parser.parse(@argv)
9
- Server.new(@options.fetch(:no_daemon, false)).start(@options.fetch(:port, 0))
8
+ if Daemon.running?
9
+ warn 'rubocop-daemon: server is already running.'
10
+ return
11
+ end
12
+
13
+ Cache.acquire_lock do |locked|
14
+ unless locked
15
+ # Another process is already starting the daemon,
16
+ # so wait for it to be ready.
17
+ Daemon.wait_for_running_status!(true)
18
+ exit 0
19
+ end
20
+
21
+ parser.parse(@argv)
22
+ Server.new(@options.fetch(:no_daemon, false)).start(@options.fetch(:port, 0))
23
+ end
10
24
  end
11
25
 
12
26
  private
@@ -5,9 +5,11 @@ module RuboCop
5
5
  module ClientCommand
6
6
  class Stop < Base
7
7
  def run
8
+ return unless check_running_server
9
+
8
10
  parser.parse(@argv)
9
- check_running_server!
10
11
  send_request(command: 'stop')
12
+ Daemon.wait_for_running_status!(false)
11
13
  end
12
14
 
13
15
  private
@@ -2,8 +2,8 @@
2
2
 
3
3
  module RuboCop
4
4
  module Daemon
5
+ class GemfileNotFound < StandardError; end
5
6
  class InvalidTokenError < StandardError; end
6
- class ServerIsNotRunningError < StandardError; end
7
7
  class ServerStopRequest < StandardError; end
8
8
  class UnknownClientCommandError < StandardError; end
9
9
  class UnknownServerCommandError < StandardError; end
@@ -24,8 +24,9 @@ module RuboCop
24
24
  def start(port)
25
25
  require 'rubocop'
26
26
  start_server(port)
27
+ Cache.write_port_and_token_files(port: @server.addr[1], token: token)
27
28
  Process.daemon(true) unless verbose
28
- Cache.make_server_file(port: @server.addr[1], token: token) do
29
+ Cache.write_pid_file do
29
30
  read_socket(@server.accept) until @server.closed?
30
31
  end
31
32
  end
@@ -5,8 +5,17 @@ module RuboCop
5
5
  module ServerCommand
6
6
  class Exec < Base
7
7
  def run
8
- RuboCop::CLI.new.run(@args)
9
- rescue SystemExit # rubocop:disable Lint/HandleExceptions
8
+ Cache.status_path.delete if Cache.status_path.file?
9
+ # RuboCop output is colorized by default where there is a TTY.
10
+ # We must pass the --color option to preserve this behavior.
11
+ @args.unshift('--color') unless %w[--color --no-color].any? { |f| @args.include?(f) }
12
+ status = RuboCop::CLI.new.run(@args)
13
+ # This status file is read by `rubocop-daemon exec` and `rubocop-daemon-wrapper`,
14
+ # so that they use the correct exit code.
15
+ # Status is 1 when there are any issues, and 0 otherwise.
16
+ Cache.write_status_file(status)
17
+ rescue SystemExit
18
+ Cache.write_status_file(1)
10
19
  end
11
20
  end
12
21
  end
@@ -26,8 +26,12 @@ module RuboCop
26
26
  private
27
27
 
28
28
  def parse_request(content)
29
- puts content if @verbose
30
29
  raw_header, *body = content.lines
30
+ if @verbose
31
+ puts raw_header.to_s
32
+ puts "STDIN: #{body.size} lines" if body.any?
33
+ end
34
+
31
35
  Request.new(parse_header(raw_header), body.join)
32
36
  end
33
37
 
@@ -2,6 +2,6 @@
2
2
 
3
3
  module RuboCop
4
4
  module Daemon
5
- VERSION = '0.2.0'.freeze
5
+ VERSION = '0.3.0'.freeze
6
6
  end
7
7
  end
@@ -25,6 +25,7 @@ Gem::Specification.new do |spec|
25
25
  spec.add_dependency 'rubocop'
26
26
 
27
27
  spec.add_development_dependency 'bundler', '~> 1.16'
28
+ spec.add_development_dependency 'pry-byebug', '~> 3.6.0'
28
29
  spec.add_development_dependency 'rake', '~> 10.0'
29
30
  spec.add_development_dependency 'rspec', '~> 3.0'
30
31
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubocop-daemon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hayato Kawai
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2018-12-14 00:00:00.000000000 Z
11
+ date: 2018-12-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rubocop
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: pry-byebug
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 3.6.0
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 3.6.0
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: rake
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -83,6 +97,7 @@ files:
83
97
  - README.md
84
98
  - Rakefile
85
99
  - bin/console
100
+ - bin/rubocop-daemon-wrapper
86
101
  - bin/setup
87
102
  - exe/rubocop-daemon
88
103
  - lib/rubocop/daemon.rb