Skip to content

Commit

Permalink
Merge pull request #2 from a13/master
Browse files Browse the repository at this point in the history
  • Loading branch information
licht1stein authored Aug 8, 2023
2 parents 0714564 + d42953d commit 8059e5b
Show file tree
Hide file tree
Showing 2 changed files with 87 additions and 37 deletions.
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,25 +45,36 @@ project's root, add `babashka-project-tasks` to your

![](./videos/4-project.gif)

## Customization
`babashka-command` - The command used to execute Babashka, if [CIDER](https://cider.mx/) is loaded, gets its default value from `cider-babashka-command`, so you don't need to customize both.

`babashka-async-shell-command` - a single-arity Emacs lisp function to call `babashka-command`. The default value is `async-shell-command`, another possible option is `detached-shell-command` from [detached.el](https://sr.ht/~niklaseklund/detached.el/) package.

`babashka-annotation-function` - a function to convert `tasks` hashtable to (task . documentation) alist. The only available option now is `babashka--annotation-function`.

## Installation
Babashka.el is available on [MELPA](https://melpa.org/#/babashka) and [MELPA Stable](https://stable.melpa.org/#/babashka) and can be installed with:

```
M-x package-install RET babashka RET
```

or using `use-package`:
or using `use-package` [ensure feature](https://github.com/jwiegley/use-package#package-installation):

```elisp
(use-package babashka)
```

Don't forget to explicitly ensure the installation:

```elisp
(use-package babashka
:ensure t)
```
if `use-package-always-ensure` is not set.

## Versioning
The project uses [break versioning](https://github.com/ptaoussanis/encore/blob/master/BREAK-VERSIONING.md), meaning that upgrading from 1.0.x to 1.0.y will always be safe, upgrade to 1.y.0 might break something small, and upgrade to y.0.0. will break almost everything. That was a versioning spec in one sentence, by the way.

## Contributing
If you have more ideas about using babashka from Emacs — please submit a PR or a feature request.




103 changes: 71 additions & 32 deletions babashka.el
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
;;
;; Author: Mykhaylo Bilyanskyy <[email protected]>
;; Maintainer: Mykhaylo Bilyanskyy <[email protected]>
;; Version: 1.0.4
;; Version: 1.0.5
;; Package-Requires: ((emacs "27.1") (parseedn "1.1.0"))
;;
;; Created: 11 Jun 2023
Expand Down Expand Up @@ -37,8 +37,34 @@
;;
;;; Code:
(require 'parseedn)
(require 'seq)
(require 'project)

(defgroup babashka nil
"Babashka Tasks Interface"
:group 'external)

(defcustom babashka-async-shell-command #'async-shell-command
"Emacs function to run shell commands."
:group 'babashka
:type 'function
:safe #'functionp)

(defcustom babashka-command
(or (when (featurep 'cider)
cider-babashka-command)
"bb")
"The command used to execute Babashka."
:group 'babashka
:type 'string
:safe #'stringp)

(defcustom babashka-annotation-function nil
"Function to annotate completions, can be `babashka--annotation-function' or a similar one."
:group 'babashka
:type '(choice (const :tag "Don't annotate." nil)
function))

(defmacro babashka--comment (&rest _)
"Ignore body eval to nil."
nil)
Expand All @@ -50,51 +76,66 @@
(insert-file-contents file-path)
(buffer-string))))

(defun babashka--run-shell-command-in-directory (directory command &optional output-buffer)
(defun babashka--run-shell-command-in-directory (directory command)
"Run a shell COMMAND in a DIRECTORY and display output in OUTPUT-BUFFER."
(let ((default-directory directory))
(async-shell-command command output-buffer)))
(funcall babashka-async-shell-command command)))

(defun babashka--locate-bb-edn (&optional dir)
"Recursively search upwards from DIR for bb.edn file."
(if-let ((found (locate-dominating-file (or dir default-directory) "bb.edn")))
(concat found "bb.edn")))
(when-let ((found (locate-dominating-file (or dir default-directory) "bb.edn")))
(concat found "bb.edn")))

(defun babashka--get-tasks-hash-table (file-path)
"List babashka tasks as hash table from edn file unde FILE-PATH."
(thread-last
file-path
(thread-last file-path
babashka--read-edn-file
(gethash :tasks)))


(defun babashka--escape-args (s)
"Shell quote parts of the string S that require it."
(mapconcat #'shell-quote-argument (split-string s) " "))

(defun babashka--annotation-function (s)
"Annotate S using current completiong table."
(when-let ((item (assoc s minibuffer-completion-table)))
(concat " " (cdr item))))

(defun babashka--tasks-to-annotated-names (tasks)
"Convert TASKS to annotated alist."
(let (results)
(maphash (lambda (key value)
(let ((task-name (symbol-name key)))
(unless (string-prefix-p ":" task-name)
(push (cons task-name (gethash :doc value))
results))))
tasks)
results))

(defun babashka--run-task (dir &optional do-not-recurse)
"Select a task to run from bb.edn in DIR or its parents.
If DO-NOT-RECURSE is passed and is not nil, don't search for bb.edn in
DIR's parents."
(if-let*
((bb-edn (if do-not-recurse
(let ((f (concat dir "/bb.edn"))) (and (file-exists-p f) f))
(babashka--locate-bb-edn dir))))
(let* ((bb-edn-dir (file-name-directory bb-edn))
(tasks (babashka--get-tasks-hash-table bb-edn))
(task-names (thread-last tasks hash-table-keys (mapcar #'symbol-name)))
(sorted-task-names (sort task-names #'string<)))
(if tasks
(thread-last
sorted-task-names
(if-let* ((bb-edn (if do-not-recurse
(let ((f (concat dir "/bb.edn")))
(and (file-exists-p f) f))
(babashka--locate-bb-edn dir))))
(if-let* ((bb-edn-dir (file-name-directory bb-edn))
(tasks (babashka--get-tasks-hash-table bb-edn)))
(let ((completion-extra-properties (when babashka-annotation-function
`(:annotation-function ,babashka-annotation-function))))
(thread-last tasks
babashka--tasks-to-annotated-names
(completing-read "Run tasks: ")
babashka--escape-args
(format "bb %s")
(babashka--run-shell-command-in-directory bb-edn-dir))
(message "No tasks found in %s" bb-edn)))
(message (if do-not-recurse
"No bb.edn found in directory."
"No bb.edn found in directory or any of the parents."))))
(format "%s %s" babashka-command)
(babashka--run-shell-command-in-directory bb-edn-dir)))
(message "No tasks found in %s" bb-edn))
(let ((msg-suffix (if do-not-recurse "" " or any of the parents")))
(message (format "No bb.edn found in directory %s%s." dir msg-suffix)))))


;;;###autoload
(defun babashka-tasks (arg)
Expand All @@ -106,12 +147,11 @@ a task.
When called with interactive ARG prompts for directory."
(interactive "P")
(let* ((dir (if arg
(read-file-name "Enter a path to bb.edn: ")
default-directory)))
(if dir
(babashka--run-task dir)
(message "Not in a file buffer. Run babashka-tasks when visiting one of your project's files."))))
(if-let* ((dir (if arg
(read-file-name "Enter a path to bb.edn: ")
default-directory)))
(babashka--run-task dir)
(message "Not in a file buffer. Run babashka-tasks when visiting one of your project's files.")))

;;;###autoload
(defun babashka-project-tasks ()
Expand All @@ -125,8 +165,7 @@ For example by evaling:
\(add-to-list \\='project-switch-commands
\\='(babashka-project-tasks \"Babashka task\" \"t\"))"
(interactive)
(thread-first
(project-current t)
(thread-first (project-current t)
project-root
(babashka--run-task t)))

Expand Down

0 comments on commit 8059e5b

Please sign in to comment.