|
|
- ;;; rvm.el --- Emacs integration for rvm
-
- ;; Copyright (C) 2010-2011 Yves Senn
-
- ;; Author: Yves Senn <yves.senn@gmx.ch>
- ;; URL: http://www.emacswiki.org/emacs/RvmEl
- ;; Package-Version: 20150402.1442
- ;; Version: 1.4.0
- ;; Created: 5 April 2010
- ;; Keywords: ruby rvm
- ;; EmacsWiki: RvmEl
-
- ;; This file is NOT part of GNU Emacs.
-
- ;;; License:
-
- ;; This program is free software; you can redistribute it and/or modify
- ;; it under the terms of the GNU General Public License as published by
- ;; the Free Software Foundation; either version 3, or (at your option)
- ;; any later version.
- ;;
- ;; This program is distributed in the hope that it will be useful,
- ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
- ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- ;; GNU General Public License for more details.
- ;;
- ;; You should have received a copy of the GNU General Public License
- ;; along with GNU Emacs; see the file COPYING. If not, write to the
- ;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
- ;; Boston, MA 02110-1301, USA.
-
- ;;; Commentary:
-
- ;; M-x rvm-use-default prepares the current Emacs session to use
- ;; the default ruby configured with rvm.
-
- ;; M-x rvm-use allows you to switch the current session to the ruby
- ;; implementation of your choice. You can also change the active gemset.
-
- ;;; Compiler support:
-
- (eval-when-compile (require 'cl))
- (defvar eshell-path-env)
- (defvar persp-mode)
- (defvar perspectives-hash)
- (declare-function persp-switch "perspective" (name))
-
- ;;; Code:
-
- (defcustom rvm-executable
- (or (executable-find "rvm")
- (and (file-readable-p "~/.rvm/bin/rvm") "~/.rvm/bin/rvm")
- (and (file-readable-p "/usr/local/rvm/bin/rvm") "/usr/local/rvm/bin/rvm"))
- "Location of RVM executable."
- :group 'rvm
- :type 'file)
-
- (defcustom rvm-configuration-file-name
- ".rvmrc"
- "RVM configuration file name"
- :group 'rvm
- :type 'string)
-
- (defcustom rvm-verbose t
- "If true, RVM will print messages for various tasks."
- :group 'rvm
- :type 'boolean)
-
- (defvar rvm-configuration-ruby-version-file-name
- ".ruby-version"
- "Ruby version configuration file name")
-
- (defvar rvm-configuration-ruby-gemset-file-name
- ".ruby-gemset"
- "Ruby version configuration file name")
-
- (defvar rvm-configuration-gemfile-file-name
- "Gemfile"
- "Gemfile file name")
-
- (defcustom rvm-interactive-completion-function
- (if ido-mode 'ido-completing-read 'completing-read)
- "The function which is used by rvm.el to interactivly complete user input"
- :group 'rvm
- :type 'function)
-
- (defcustom rvm-interactive-find-file-function
- (if ido-mode 'ido-find-file 'find-file)
- "The function which is used by rvm.el to interactivly open files"
- :group 'rvm
- :type 'function)
-
- (defvar rvm--current-ruby nil
- "Current active Ruby version.")
-
- (defvar rvm--current-gemset nil
- "Current active gemset.")
-
- (defvar rvm--gemset-default "global"
- "the default gemset per ruby interpreter")
-
- (defvar rvm--gemset-separator "@"
- "character that separates the ruby version from the gemset.")
-
- (defvar rvm--current-ruby-binary-path nil
- "reflects the path to the current 'ruby' executable.
- This path gets added to the PATH variable and the exec-path list.")
-
- (defvar rvm--current-gem-binary-path nil
- "reflects the path to the current 'rubygems' executables.
- This path gets added to the PATH variable and the exec-path list.")
-
- (defvar rvm--info-option-regexp "\s+\\(.+?\\):\s+\"\\(.+?\\)\""
- "regular expression to parse the options from rvm info")
-
- (defvar rvm--list-ruby-regexp "\s*\\(=?[>\*]\\)?\s*\\(.+?\\)\s*\\[\\(.+\\)\\]\s*$"
- "regular expression to parse the ruby version from the 'rvm list' output")
-
- (defvar rvm--gemset-list-filter-regexp "^\\(gemsets for\\|Gemset '\\)"
- "regular expression to filter the output of rvm gemset list")
-
- (defvar rvm--gemset-list-regexp "\s*\\(=>\\)?\s*\\(.+?\\)\s*$"
- "regular expression to parse the gemset from the 'rvm gemset list' output")
-
- (defvar rvm--gemfile-parse-ruby-regexp-as-comment "\\#ruby=\\(.+\\)"
- "regular expression to parse the ruby version from the Gemfile as comment")
-
- (defvar rvm--gemfile-parse-ruby-regexp-as-directive "ruby [\'\"]\\(.+\\)[\'\"]"
- "regular expression to parse the ruby version from the Gemfile as directive")
-
- (defvar rvm--gemfile-parse-gemset-regexp "#ruby-gemset=\\(.+\\)"
- "regular expression to parse the ruby gemset from the Gemfile")
-
- (defvar rvm--rvmrc-parse-regexp (concat "\\(?:^rvm\s+\\(?:use\s+\\|\\)\\|environment_id=\"\\)\s*"
- "\\(?:--.+\s\\)*" ;; Flags
- "\\([^"
- rvm--gemset-separator
- "\n]+\\)\\(?:"
- rvm--gemset-separator
- "\\([^\"\s\n]+\\)\\)?\\(?:\"\\|\\)")
- "regular expression to parse the .rvmrc files inside project directories.
- the first group matches the ruby-version and the second group is the gemset.
- when no gemset is set, the second group is nil")
-
- ;; Support Code
-
- ;; Put with other utils
- ;; From http://www.emacswiki.org/emacs/ElispCookbook
- (defun rvm--chomp (str)
- "Chomp leading and tailing whitespace from STR."
- (let ((s (if (symbolp str) (symbol-name str) str)))
- (replace-regexp-in-string "\\(^[[:space:]\n]*\\|[[:space:]\n]*$\\)" "" s)))
-
- (defun rvm--message (format-string &rest objects)
- "Like `message', but will only print if `rvm-verbose' is true."
- (when rvm-verbose
- (apply 'message (cons format-string objects))))
-
- ;; Application Code
-
- ;;;###autoload
- (defun rvm-use-default ()
- "use the rvm-default ruby as the current ruby version"
- (interactive)
- (when (rvm-working-p)
- (rvm-use (rvm--ruby-default) rvm--gemset-default)))
-
- ;;;###autoload
- (defun rvm-activate-corresponding-ruby ()
- "activate the corresponding ruby version for the file in the current buffer.
- This function searches for an .rvmrc file and activates the configured ruby.
- If no .rvmrc file is found, the default ruby is used insted."
- (interactive)
-
- (when (rvm-working-p)
- (let ((config-file-path nil)
- (config-gemset-file-path nil)
- (rvmrc-info (or (rvm--load-info-rvmrc) (rvm--load-info-ruby-version) (rvm--load-info-gemfile))))
- (if rvmrc-info (rvm-use (first rvmrc-info) (second rvmrc-info))
- (rvm-use-default)))))
-
- (defun rvm--load-info-rvmrc (&optional path)
- (let ((config-file-path (rvm--locate-file rvm-configuration-file-name path)))
- (if config-file-path
- (rvm--rvmrc-read-version config-file-path)
- nil)))
-
- (defun rvm--load-info-ruby-version (&optional path)
- (let ((config-file-path (rvm--locate-file rvm-configuration-ruby-version-file-name path))
- (gemset-file-path (rvm--locate-file rvm-configuration-ruby-gemset-file-name path)))
- (if config-file-path
- (list (rvm--chomp (rvm--get-string-from-file config-file-path))
- (if gemset-file-path
- (rvm--chomp (rvm--get-string-from-file gemset-file-path))
- rvm--gemset-default))
- nil)))
-
- (defun rvm--load-info-gemfile (&optional path)
- (let ((config-file-path (rvm--locate-file rvm-configuration-gemfile-file-name path)))
- (if config-file-path
- (rvm--gemfile-read-version config-file-path)
- nil)))
-
- ;;;###autoload
- (defun rvm-use (new-ruby new-gemset)
- "switch the current ruby version to any ruby, which is installed with rvm"
- (interactive
- (let* ((picked-ruby (rvm--completing-read "Ruby Version: "
- (rvm/list)))
- (picked-gemset (rvm--completing-read "Gemset: "
- (rvm/gemset-list picked-ruby))))
- (list picked-ruby picked-gemset)))
- (when (rvm-working-p)
- (let* ((new-ruby-with-gemset (rvm--ruby-gemset-string new-ruby new-gemset))
- (ruby-info (rvm/info new-ruby-with-gemset))
- (new-ruby-binary (cdr (assoc "ruby" ruby-info)))
- (new-ruby-gemhome (cdr (assoc "GEM_HOME" ruby-info)))
- (new-ruby-gempath (cdr (assoc "GEM_PATH" ruby-info))))
- (setq rvm--current-ruby new-ruby)
- (setq rvm--current-gemset new-gemset)
- (rvm--set-ruby (file-name-directory new-ruby-binary))
- (rvm--set-gemhome new-ruby-gemhome new-ruby-gempath new-gemset))
- (rvm--message (concat "Ruby: " new-ruby " Gemset: " new-gemset))))
-
- ;;;###autoload
- (defun rvm-open-gem (gemhome)
- (interactive (list (rvm--emacs-gemhome)))
- (when (rvm-working-p)
- (let* ((gems-dir (concat gemhome "/gems/"))
- (gem-name (rvm--completing-read "Gem: "
- (directory-files gems-dir nil "^[^.]")))
- (gem-dir (concat gems-dir gem-name)))
- (when (and (featurep 'perspective) persp-mode)
- (let ((initialize (not (gethash gem-name perspectives-hash))))
- (persp-switch gem-name)))
- (rvm--find-file gem-dir))))
-
- (defun rvm-activate-ruby-for (path &optional callback)
- "Activate Ruby for PATH.
-
- If CALLBACK is specified, active Ruby for PATH only in that
- function."
- (let* ((path (directory-file-name path))
- (prev-ruby rvm--current-ruby)
- (prev-gemset rvm--current-gemset)
- (rvmrc-info
- (or
- (rvm--load-info-rvmrc path)
- (rvm--load-info-ruby-version path)
- (rvm--load-info-gemfile path))))
- (apply 'rvm-use rvmrc-info)
- (when callback
- (unwind-protect
- (funcall callback)
- (rvm-use prev-ruby prev-gemset)))))
-
- ;;;; TODO: take buffer switching into account
- (defun rvm-autodetect-ruby ()
- (interactive)
- (when (rvm-working-p)
- (add-hook 'ruby-mode-hook 'rvm-activate-corresponding-ruby)
- (rvm--message "rvm.el is now autodetecting the ruby version")))
-
- (defun rvm-autodetect-ruby-stop ()
- (interactive)
- (when (rvm-working-p)
- (remove-hook 'ruby-mode-hook 'rvm-activate-corresponding-ruby)
- (rvm--message "stopped rvm.el from autodetecting ruby versions")))
-
- (defun rvm/list (&optional default-ruby)
- (let ((rubies (rvm--call-process "list" (when default-ruby "default")))
- (start 0)
- (parsed-rubies '())
- (current-ruby '()))
- (while (string-match rvm--list-ruby-regexp rubies start)
- (let ((ruby-version (match-string 2 rubies))
- (ruby-platform (match-string 3 rubies))
- (ruby-current-version (match-string 1 rubies)))
- (add-to-list 'current-ruby ruby-current-version)
- (if ruby-current-version (add-to-list 'parsed-rubies ruby-version)
- (add-to-list 'parsed-rubies ruby-version t))
- (setq start (match-end 0))))
- parsed-rubies))
-
- (defun rvm/gemset-list (ruby-version)
- (let* ((gemset-result (rvm--call-process "gemset" "list_all"))
- (gemset-lines (split-string gemset-result "\n"))
- (parsed-gemsets (list))
- (ruby-current-version nil))
- (loop for gemset in gemset-lines do
- (let ((filtered-gemset (string-match rvm--gemset-list-filter-regexp gemset)))
- (if filtered-gemset
- (if (string-match ruby-version gemset)
- (setq ruby-current-version ruby-version)
- (setq ruby-current-version nil)))
- (if (and (> (length gemset) 0)
- ruby-current-version
- (not filtered-gemset)
- (string-match rvm--gemset-list-regexp gemset))
- ;; replace-regexp is to handle default gemset which is returned with
- ;; wrapping parens but is referred to without them in rvm use command
- (add-to-list 'parsed-gemsets (replace-regexp-in-string "^(\\|)$" "" (match-string 2 gemset)) t))))
- parsed-gemsets))
-
- (defun rvm/info (&optional ruby-version)
- (let ((info (rvm--call-process "info" ruby-version))
- (start 0)
- (parsed-info '()))
- (when (not info) (error "The ruby version: %s is not installed" ruby-version))
- (while (string-match rvm--info-option-regexp info start)
- (let ((info-key (match-string 1 info))
- (info-value (match-string 2 info)))
- (add-to-list 'parsed-info (cons info-key info-value))
- (setq start (match-end 0))))
- parsed-info))
-
- (defun rvm--string-trim (string)
- (replace-regexp-in-string "^\\s-*\\|\\s-*$" "" string))
-
- (defun rvm--ruby-gemset-string (ruby-version gemset)
- (if (rvm--default-gemset-p gemset) ruby-version
- (concat ruby-version rvm--gemset-separator gemset)))
-
- (defun rvm--completing-read (prompt options)
- (let ((selected (funcall rvm-interactive-completion-function prompt options)))
- (rvm--string-trim selected)))
-
- (defun rvm--find-file (directory)
- (let ((default-directory directory))
- (call-interactively rvm-interactive-find-file-function)))
-
- (defun rvm--emacs-ruby-binary ()
- rvm--current-ruby-binary-path)
-
- (defun rvm--emacs-gemhome ()
- (getenv "GEM_HOME"))
-
- (defun rvm--emacs-gempath ()
- (getenv "GEM_PATH"))
-
- (defun rvm--change-path (current-binary-var new-binaries)
- (let ((current-binaries-for-path
- (mapconcat 'identity (eval current-binary-var) ":"))
- (new-binaries-for-path (mapconcat 'identity new-binaries ":")))
- (if (and (eval current-binary-var)
- (not (string= (first (eval current-binary-var)) "/bin")))
- (progn
- (setenv "PATH" (replace-regexp-in-string
- (regexp-quote current-binaries-for-path)
- new-binaries-for-path
- (getenv "PATH")))
- (dolist (binary (eval current-binary-var))
- (setq exec-path (remove binary exec-path))))
- (setenv "PATH" (concat new-binaries-for-path ":" (getenv "PATH"))))
- (dolist (binary new-binaries)
- (add-to-list 'exec-path binary))
- (setq eshell-path-env (getenv "PATH"))
- (set current-binary-var new-binaries)))
-
- (defun rvm--set-ruby (ruby-binary)
- (rvm--change-path 'rvm--current-ruby-binary-path (list ruby-binary)))
-
- (defun rvm--locate-file (file-name &optional path)
- "searches the directory tree for an given file. Returns nil if the file was not found."
- (let ((directory (locate-dominating-file (or path (expand-file-name (or buffer-file-name ""))) file-name)))
- (when directory (expand-file-name file-name directory))))
-
- (defun rvm--get-string-from-file (file-path)
- (with-temp-buffer
- (insert-file-contents file-path)
- (buffer-string)))
-
- (defun rvm--rvmrc-read-version (path-to-rvmrc)
- (rvm--rvmrc-parse-version (rvm--get-string-from-file path-to-rvmrc)))
-
- (defun rvm--gemfile-read-version (path-to-gemfile)
- (rvm--gemfile-parse-version (rvm--get-string-from-file path-to-gemfile)))
-
- (defun rvm--rvmrc-parse-version (rvmrc-line)
- (let ((rvmrc-without-comments (replace-regexp-in-string "#.*$" "" rvmrc-line)))
- (when (string-match rvm--rvmrc-parse-regexp rvmrc-without-comments)
- (list (rvm--string-trim (match-string 1 rvmrc-without-comments))
- (rvm--string-trim (or (match-string 2 rvmrc-without-comments) rvm--gemset-default))))))
-
- (defun rvm--gemfile-parse-version (gemfile-line)
- (let ((ruby-version (when (or (string-match rvm--gemfile-parse-ruby-regexp-as-comment gemfile-line)
- (string-match rvm--gemfile-parse-ruby-regexp-as-directive gemfile-line))
- (match-string 1 gemfile-line)))
- (ruby-gemset (when (string-match rvm--gemfile-parse-gemset-regexp gemfile-line)
- (match-string 1 gemfile-line))))
- (if ruby-version
- (list ruby-version (or ruby-gemset rvm--gemset-default))
- nil)))
-
- (defun rvm--gem-binary-path-from-gem-path (gempath)
- (let ((gem-paths (split-string gempath ":")))
- (mapcar (lambda (path) (concat path "/bin")) gem-paths)))
-
- (defun rvm--set-gemhome (gemhome gempath gemset)
- (if (and gemhome gempath gemset)
- (progn
- (setenv "GEM_HOME" gemhome)
- (setenv "GEM_PATH" gempath)
- (setenv "BUNDLE_PATH" gemhome)
- (rvm--change-path 'rvm--current-gem-binary-path (rvm--gem-binary-path-from-gem-path gempath)))
- (setenv "GEM_HOME" "")
- (setenv "GEM_PATH" "")
- (setenv "BUNDLE_PATH" "")))
-
- (defun rvm--ruby-default ()
- (car (rvm/list t)))
-
- (defun rvm-working-p ()
- (and rvm-executable (file-exists-p rvm-executable)))
-
- (defun rvm--default-gemset-p (gemset)
- (string= gemset rvm--gemset-default))
-
- (defun rvm--call-process (&rest args)
- (with-temp-buffer
- (let* ((success (apply 'call-process rvm-executable nil t nil
- (delete nil args)))
- (output (buffer-substring-no-properties
- (point-min) (point-max))))
- (if (= 0 success)
- output
- (rvm--message output)))))
-
- (defun rvm-gem-install (gem)
- "Install GEM into the currently active RVM Gemset."
- (interactive "SGem Install: ")
- (shell-command (format "%s install %s&" ; & executes async
- (concat (first rvm--current-ruby-binary-path) "/gem") gem))
- (pop-to-buffer "*Async Shell Command*"))
-
- (provide 'rvm)
- ;;; rvm.el ends here
|