;; -*- lexical-binding: t; -*- ;; A handy package for running external commands (use-package run-command :straight (run-command :host github :repo "bard/emacs-run-command" :fork (:host github :repo "jdormit/emacs-run-command" :branch "master")) :init (defvar-local run-command-local-commands nil) (put 'run-command-local-commands 'safe-local-variable (lambda (_) t)) (defvar-local run-command-project-local-commands nil) (put 'run-command-project-local-commands 'safe-local-variable (lambda (_) t)) :config (general-def run-command-term-minor-mode-map [remap recompile] #'run-command-term-recompile "g" nil) (general-def 'normal run-command-term-minor-mode-map "gr" #'run-command-term-recompile) (defun run-command-recipe-terraform () (when (directory-files default-directory nil (rx (one-or-more any) ".tf" eol)) (list (list :command-name "init" :command-line "terraform init") (list :command-name "plan" :command-line "terraform plan") (list :command-name "apply" :command-line "terraform apply") (list :command-name "destroy" :command-line "terraform destroy")))) (defun run-command-recipe-local () (when run-command-local-commands (mapcar (cl-function (lambda ((name . spec)) (let ((name (if (symbolp name) (symbol-name name) name))) (list :command-name name :command-line spec)))) run-command-local-commands))) (defun run-command-recipe-package-json--get-scripts (package-json-file) "Extract NPM scripts from `package-json-file'." (with-temp-buffer (insert-file-contents package-json-file) (let* ((json-data (json-parse-buffer)) (script-hash (gethash "scripts" json-data)) (scripts '())) (when script-hash (maphash (lambda (key _value) (push key scripts)) script-hash)) scripts))) (defun run-command-recipe-package-json () (when-let* ((project-dir (locate-dominating-file default-directory "package.json")) (scripts (run-command-recipe-package-json--get-scripts (concat project-dir "package.json"))) (script-runner (if (file-exists-p (concat project-dir "yarn.lock")) "yarn" "npm"))) (mapcar (lambda (script) (list :command-name script :command-line (concat script-runner " run " script) :display script :working-dir project-dir)) scripts))) (defun makefile-target-list-default (makefile) "Return the target list for MAKEFILE by parsing it." (let (targets) (with-temp-buffer (insert-file-contents makefile) (goto-char (point-min)) (while (re-search-forward "^\\([^: \n]+\\) *:\\(?: \\|$\\)" nil t) (let ((str (match-string 1))) (unless (string-match "^\\." str) (push str targets))))) (nreverse targets))) (defun run-command-recipe-makefile () (when-let* ((project-dir (locate-dominating-file default-directory "Makefile")) (makefile (concat project-dir "Makefile")) (targets (makefile-target-list-default makefile))) (mapcar (lambda (target) (list :command-name target :command-line (concat "make " target) :display target :working-dir project-dir)) targets))) (defun run-command-recipe-project () (when (projectile-project-root) (mapcar (lambda (cmd) (when-let ((cmd-val (symbol-value (intern (format "projectile-project-%s-cmd" cmd))))) (list :command-name cmd :command-line cmd-val :working-dir (projectile-compilation-dir)))) '("test" "run" "compilation" "configure" "install" "package")))) (defun run-command-recipe-project-local () (when (and run-command-project-local-commands (projectile-project-root)) (mapcar (cl-function (lambda ((name . spec)) (let ((name (if (symbolp name) (symbol-name name) name))) (list :command-name name :command-line spec :working-dir (projectile-compilation-dir))))) run-command-project-local-commands))) (defun run-command-recipe-executables () (let* ((buffer-file (buffer-file-name)) (executable-p (and buffer-file (file-executable-p buffer-file)))) (list (when executable-p (list :command-name "run-buffer-file" :command-line buffer-file :display "Run this buffer's file")) (when (and executable-p (executable-find "entr")) (list :command-name "run-buffer-file-watch" :command-line (format "echo %s | entr -c /_" buffer-file) :display "Run this buffer's file (re-run on each save)"))))) (defun run-command-recipe-obelix () (when-let* ((config (or (locate-dominating-file default-directory "obelix.json") (locate-dominating-file default-directory "obelix.edn"))) (dir (file-name-directory config))) (list (list :command-name "build" :command-line "obelix build" :working-dir dir) (list :command-name "serve" :command-line "obelix serve" :working-dir dir)))) (defun run-command-recipe-kustomize () (when-let* ((kustomization (locate-dominating-file default-directory "kustomization.yaml")) (dir (file-name-directory kustomization))) (list (list :command-name "build" :command-line "kustomize build --enable_alpha_plugins" :working-dir dir)))) (defun run-command-recipe-sops () (when (save-excursion (goto-char (point-min)) (search-forward-regexp "sops:" nil t)) (list (list :command-name "edit" :command-line (format "sops %s" (buffer-file-name)))))) (defun run-command-recipe-pytest () (when (and (derived-mode-p 'python-mode) (= 0 (call-process "python" nil nil nil "-c" "import pytest"))) (let ((test-file-p (string-match-p "test" (or (buffer-file-name) "")))) (list (when (and (projectile-project-root) (file-exists-p (concat (file-name-as-directory (projectile-project-root)) "tests"))) (list :command-name "test all" :command-line "pytest tests" :working-dir (projectile-project-root))) (when test-file-p (list :command-name "test this file" :command-line (format "pytest %s" (buffer-file-name)))) (when (and test-file-p (python-info-current-defun)) (list :command-name "test this function" :command-line (format "pytest %s::%s" (buffer-file-name) (replace-regexp-in-string "\\." "::" (python-info-current-defun))))))))) (defun run-command-recipe-nosetests() (when (and (derived-mode-p 'python-mode) (= 0 (call-process "python" nil nil nil "-c" "import nose"))) (let ((test-file-p (string-match-p "test" (or (buffer-file-name) "")))) (list (when (and (projectile-project-root) (file-exists-p (concat (file-name-as-directory (projectile-project-root)) "tests"))) (list :command-name "test all" :command-line "nosetests -w tests" :working-dir (projectile-project-root))) (when test-file-p (list :command-name "test this file" :command-line (format "nosetests %s" (buffer-file-name)))) (when (and test-file-p (python-info-current-defun)) (list :command-name "test this function" :command-line (format "nosetests %s:%s" (buffer-file-name) (python-info-current-defun)))))))) (defun run-command-recipe-web-ext () (when-let* ((_ (executable-find "web-ext")) (manifest (locate-dominating-file default-directory "manifest.json")) (dir (file-name-directory manifest))) (list (list :command-name "run" :command-line "web-ext run" :working-dir dir) (list :command-name "build" :command-line "web-ext build" :working-dir dir)))) (defun run-command-recipe-pip () (when (and (buffer-file-name) (string-match-p ".*requirements.*txt$" (buffer-file-name))) (list (list :command-name "install" :command-line (format "pip install -r %s" (buffer-file-name)))))) (defun run-command-recipe-maven () (when-let* ((root-dir (or (projectile-project-root) default-directory)) (project-dir (locate-dominating-file root-dir "pom.xml"))) (list (list :command-name "validate" :command-line "mvn validate" :working-dir project-dir) (list :command-name "compile" :command-line "mvn compile" :working-dir project-dir) (list :command-name "clean compile" :command-line "mvn clean compile" :working-dir project-dir) (list :command-name "test" :command-line "mvn test -DfailIfNoTests=false" :working-dir project-dir) (list :command-name "package" :command-line "mvn package" :working-dir project-dir) (list :command-name "verify" :command-line "mvn verify" :working-dir project-dir) (list :command-name "install" :command-line "mvn install" :working-dir project-dir) (list :command-name "deploy" :command-line "mvn deploy" :working-dir project-dir) (list :command-name "clean" :command-line "mvn clean" :working-dir project-dir) (list :command-name "exec:java" :command-line "mvn compile exec:java" :working-dir project-dir) (when-let ((test-class (and (buffer-file-name) (let ((case-fold-search nil)) (string-match-p ".*\\(Test\\|IT\\).*\\.java$" (buffer-file-name))) (fboundp 'dap-java-test-class) (dap-java-test-class)))) (list :command-name "test this class" :command-line (format "mvn test -DfailIfNoTests=false -Dtest=%s" test-class) :working-dir project-dir)) (when-let ((test-method (and (buffer-file-name) (let ((case-fold-search nil)) (string-match-p ".*\\(Test\\|IT\\).*\\.java$" (buffer-file-name))) (fboundp 'dap-java-test-method-at-point) (dap-java-test-method-at-point t)))) (list :command-name "test this method" :command-line (format "mvn test -DfailIfNoTests=false -Dtest=%s" test-method) :working-dir project-dir))))) (defun get-cargo-commands (dir) (when (executable-find "cargo") (with-temp-buffer (cd dir) (-as-> (shell-command-to-string "cargo --list") v (split-string v "\n") (cdr v) (-filter (-not (-partial 's-contains-p "alias")) v) (-map 's-trim v) (-map (-partial 's-split " ") v) (-map 'car v) (-filter (-not 's-blank?) v))))) (defun run-command-recipe-cargo () (when-let* ((project-dir (locate-dominating-file default-directory "Cargo.toml")) (cargo-cmds (get-cargo-commands project-dir))) (-concat (-map (lambda (cmd) (list :command-name cmd :command-line (format "cargo %s" cmd) :working-dir project-dir)) cargo-cmds) (list (list :command-name "doc --open" :command-line "cargo doc --open" :working-dir project-dir) (list :command-name "build --release" :command-line "cargo build --release" :working-dir project-dir))))) :general (leader-map "\"" #'run-command) :custom (run-command-run-method 'async-shell) (run-command-recipes '(run-command-recipe-terraform run-command-recipe-local run-command-recipe-package-json run-command-recipe-makefile run-command-recipe-project run-command-recipe-project-local run-command-recipe-executables run-command-recipe-obelix run-command-recipe-kustomize run-command-recipe-sops run-command-recipe-pytest run-command-recipe-nosetests run-command-recipe-web-ext run-command-recipe-pip run-command-recipe-maven run-command-recipe-cargo))) (provide 'init-run-command)