From 25441f47d31880e4be29418da040adc7bfa28fe2 Mon Sep 17 00:00:00 2001 From: Jim Myhrberg Date: Mon, 18 May 2020 19:56:29 +0100 Subject: [PATCH] feat(lang): Use rubocop-daemon to format ruby files on save On my 2016 MacBook Pro correction times are around 200ms with the daemon, compared to around 3-4 seconds without. There might be some issues if bundler depends heavily on gems included in the project, but we'll see how that goes. --- Makefile | 8 +- bin/rubocop-daemon-wrapper | 132 ++++++++++++++++++++++++++++++++ core/siren-core-env.el | 3 + modules/languages/siren-ruby.el | 4 +- 4 files changed, 144 insertions(+), 3 deletions(-) create mode 100755 bin/rubocop-daemon-wrapper diff --git a/Makefile b/Makefile index 14ebb58..e14331c 100644 --- a/Makefile +++ b/Makefile @@ -20,11 +20,14 @@ new-version: # define vendored -VENDORED = $(VENDORED) $(1) +VENDORED += $(1) .SILENT: $(1) $(1): - echo "fetching $(1)..." && curl -s -L -o "$(1)" "$(2)" + echo "fetching $(1)..." && \ + mkdir -p "$(dir $(1))" && \ + curl -s -L -o "$(1)" "$(2)" && \ + ([[ "$(1)" == "bin/"* ]] && chmod +x "$(1)") || exit 0 .PHONY: remove_$(1) .SILENT: remove_$(1) @@ -41,6 +44,7 @@ endef # Defined vendored dependencies. # +$(eval $(call vendored,bin/rubocop-daemon-wrapper,https://github.com/fohte/rubocop-daemon/raw/master/bin/rubocop-daemon-wrapper)) # # Main targets. diff --git a/bin/rubocop-daemon-wrapper b/bin/rubocop-daemon-wrapper new file mode 100755 index 0000000..5c4012d --- /dev/null +++ b/bin/rubocop-daemon-wrapper @@ -0,0 +1,132 @@ +#!/bin/bash + +set -e + +COMMAND_PREFIX="" + +if [ -n "$RUBOCOP_DAEMON_USE_BUNDLER" ]; then + COMMAND_PREFIX="bundle exec" +fi + +if ! command -v rubocop-daemon > /dev/null; then + $COMMAND_PREFIX rubocop $@ + exit $? +fi + +if [[ "$OSTYPE" == "linux-gnu" ]]; then + if [ -f "/etc/fedora-release" ]; then + NETCAT_CMD="nc" + else + NETCAT_CMD="nc -N" + fi +elif [[ "$OSTYPE" == "darwin"* ]]; then + NETCAT_CMD="nc" +elif [[ "$OSTYPE" == "freebsd"* ]]; then + # https://www.freebsd.org/cgi/man.cgi?query=netcat&manpath=SuSE+Linux/i386+11.3 + NETCAT_CMD="nc" +else + echo "Sorry, we're not sure if the rubocop-daemon-wrapper script will work" \ + "on your OS: \"$OSTYPE\"" >&2 + echo "Try to comment out this message in the script, and use one of the following:" >&2 + echo >&2 + echo "NETCAT_CMD=\"nc\"" >&2 + echo "# Or" >&2 + echo "NETCAT_CMD=\"nc -N\"" >&2 + echo >&2 + echo "Then please leave a comment on this GitHub issue and" \ + "let us know which one worked:" >&2 + echo >&2 + echo "* https://github.com/fohte/rubocop-daemon/issues/4" >&2 + echo >&2 + exit 1 +fi + +find_project_root() { + path=$(pwd -P) + while [[ "$path" != "" && ! -f "$path/Gemfile" && ! -f "$path/gems.rb" ]]; do + path=${path%/*} + done + echo "$path" +} + +PROJECT_ROOT="$(find_project_root)" +if [ -z "$PROJECT_ROOT" ]; then + # If we can't find a Gemfile, just use the current directory + PROJECT_ROOT="$(pwd -P)" +fi + +CACHE_DIR="$HOME/.cache/rubocop-daemon" +PROJECT_CACHE_KEY="$(echo ${PROJECT_ROOT:1} | tr '/' '+')" +PROJECT_CACHE_DIR="$CACHE_DIR/$PROJECT_CACHE_KEY" +TOKEN_PATH="$PROJECT_CACHE_DIR/token" +PORT_PATH="$PROJECT_CACHE_DIR/port" +STDIN_PATH="$PROJECT_CACHE_DIR/stdin" +STATUS_PATH="$PROJECT_CACHE_DIR/status" +LOCK_PATH="$CACHE_DIR/running.lock" +RUBOCOP_DAEMON="$COMMAND_PREFIX rubocop-daemon" + +# If a lock file exist, wait up to 5 seconds. +i=0 +while [ -d "$LOCK_PATH" ]; do + # rubocop-daemon is already processing a request. Pause before trying again... + sleep 1 + i=$((i + 1)) + if [ $i -ge 5 ]; then + echo "rubocop-daemon-wrapper: Waited more than 5 seconds; ignoring the lock and proceeding." >&2 + break + fi +done + +unlock() { + rm -r "$LOCK_PATH" 2> /dev/null +} + +trap unlock EXIT + +# Acquire a file lock before proceeding. +# Macs don't support the `lockfile` command, so just use mkdir. +mkdir -p "$LOCK_PATH" + +# If -s or --stdin args are present, read stdin with `cat` +for ARG in $@; do + if [ -z "$STDIN_CONTENT" ] && [ "$ARG" == "--stdin" ] || [ "$ARG" == "-s" ]; then + # Preserve final new lines when ingesting from STDIN + STDIN_CONTENT="$(cat; printf x)" + STDIN_CONTENT=${STDIN_CONTENT%x} + fi +done + +if [ ! -f "$TOKEN_PATH" ]; then + $RUBOCOP_DAEMON start +fi + +run_rubocop_command() { + TOKEN="$(cat "$TOKEN_PATH")" + PORT="$(cat "$PORT_PATH")" + COMMAND="$TOKEN $PROJECT_ROOT exec $@" + rm -f "$STATUS_PATH" # Clear the previous status + if printf '%s\n%s' "$COMMAND" "$STDIN_CONTENT" | $NETCAT_CMD localhost "$PORT"; then + if [ -f "$STATUS_PATH" ]; then + exit "$(cat $STATUS_PATH)" + else + echo "rubocop-daemon-wrapper: server did not write status to $STATUS_PATH!" >&2 + exit 1 + fi + fi + return 1 +} + +if ! run_rubocop_command $@; then + echo "rubocop-daemon-wrapper: Error sending command to localhost:$PORT ($COMMAND)" >&2 + echo "Killing all rubocop-daemon processes and removing cache directory..." >&2 + rm -rf "$CACHE_DIR" + pkill -f "rubocop-daemon (re)?start" + echo "Starting new rubocop-daemon server..." >&2 + $RUBOCOP_DAEMON start + if ! run_rubocop_command $@; then + echo "Sorry, something went wrong with rubocop-daemon!" >&2 + echo "Please try updating the gem or re-installing the rubocop-daemon-wrapper script." + echo "If that doesn't work, please open an issue on GitHub:" \ + "https://github.com/fohte/rubocop-daemon/issues/new" >&2 + fi +fi diff --git a/core/siren-core-env.el b/core/siren-core-env.el index aad4333..a66f72b 100644 --- a/core/siren-core-env.el +++ b/core/siren-core-env.el @@ -27,5 +27,8 @@ (when (and tmpdir (not (string-blank-p tmpdir))) (setq temporary-file-directory tmpdir))) +;; Add bin directory within emacs configuration dir to `exec-path'. +(add-to-list 'exec-path (expand-file-name "bin" siren-dir)) + (provide 'siren-core-env) ;;; siren-core-env.el ends here diff --git a/modules/languages/siren-ruby.el b/modules/languages/siren-ruby.el index f1ef22a..9712505 100644 --- a/modules/languages/siren-ruby.el +++ b/modules/languages/siren-ruby.el @@ -149,7 +149,9 @@ (ruby-mode . rubocopfmt-mode) :custom - (rubocopfmt-show-errors 'echo)) + (rubocopfmt-show-errors 'echo) + (rubocopfmt-rubocop-command + (expand-file-name "bin/rubocop-daemon-wrapper" siren-dir))) (use-package ruby-compilation :defer t)