rubocop-daemon 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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