-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfriendly-shell-command.el
185 lines (149 loc) · 6.64 KB
/
friendly-shell-command.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
;;; friendly-shell-command.el --- Better shell-command API -*- lexical-binding: t; -*-
;; Copyright (C) 2019-2020 Jordan Besly
;;
;; Version: 0.2.5
;; Keywords: processes, terminals
;; URL: https://github.com/p3r7/friendly-shell
;; Package-Requires: ((emacs "24.1")(cl-lib "0.6.1")(dash "2.17.0")(with-shell-interpreter "0.2.5"))
;;
;; SPDX-License-Identifier: MIT
;;; Commentary:
;;
;; Smarter and more user-friendly shell command executions.
;;
;; Provides wrappers around shell command functions from simple.el with
;; saner defaults and additional keyword arguments:
;; - `friendly-shell-command-to-string' for `shell-command-to-string'
;; - `friendly-shell-command' for `shell-command'
;; - `friendly-shell-command-async' for `async-shell-command'
;;
;; `friendly-shell-command' and `friendly-shell-command-async' can be used
;; as functions or commands (i.e. called interactively).
;;
;; For detailed instructions, please look at each function documentation
;; or the README.md at
;; https://github.com/p3r7/friendly-shell/blob/master/README.md
;;; Code:
;; REQUIRES
(require 'cl-lib)
(require 'dash)
(require 'tramp)
(require 'tramp-sh)
(require 'with-shell-interpreter)
;; (NON-INTERACTIVE) SHELL COMMANDS
(cl-defun friendly-shell-command-to-string (command &key path interpreter command-switch)
"Call COMMAND w/ `shell-command-to-string' with shell
interpreter :interpreter and at location :path. If :path is
remote, the command will be executed with the remote host
interpreter.
For more details about all the keyword arguments, see
`with-shell-interpreter'"
(with-shell-interpreter
:form (shell-command-to-string command)
:path path
:interpreter interpreter
:command-switch command-switch))
(cl-defun friendly-shell-command-async (command &key output-buffer error-buffer
path interpreter command-switch
callback
kill-buffer
sentinel)
"Call COMMAND w/ `async-shell-command' with shell interpreter
:interpreter and at location :path. If :path is remote, the
command will be executed with the remote host interpreter.
Usage:
(friendly-shell-command-async command
[:keyword [option]]...
)
:output-buffer Buffer to output to.
Can be a buffer object or a string (buffer name).
Defaults to *Async Shell Command*.
:error-buffer Buffer to output stderr to.
If not specified, errors will appear in :output-buffer.
:kill-buffer If t, the output buffer will get killed automatically
at the end of command execution.
:callback Function to run at the end of the command execution.
Should take no argument.
:sentinel Process sentinel function to associate to the command
process.
Takes 2 arguments: process and output.
See `set-process-sentinel' for more information.
For more details about the remaining keyword arguments, see `with-shell-interpreter'"
(with-shell-interpreter
:form
(let* ((win (async-shell-command command output-buffer error-buffer))
(buffer-name (or output-buffer "*Async Shell Command*"))
(process (get-buffer-process buffer-name)))
;; REVIEW: do not kill in case of error ?
;; not sure we can grab the errno for async commands
;; but can still see if :error-buffer is not empty I guess...
(when (or sentinel callback kill-buffer)
(let ((combined-sentinel
(friendly-shell-command--build-process-sentinel
process
:sentinel sentinel
:callback callback
:kill-buffer kill-buffer)))
;; REVIEW: why not use `set-process-sentinel'
(setf (process-sentinel process) combined-sentinel)))
win)
:path path
:interpreter interpreter
:command-switch command-switch))
(cl-defun friendly-shell-command (command &key output-buffer error-buffer
callback kill-buffer
path interpreter command-switch)
"Call COMMAND w/ `shell-command' with shell interpreter
:interpreter and at location :path. If :path is remote, the
command will be executed with the remote host interpreter.
Usage:
(friendly-shell-command command
[:keyword [option]]...
)
:output-buffer Buffer to output to.
Can be a buffer object or a string (buffer name).
Defaults to *Shell Command Output*.
:error-buffer Buffer to output stderr to.
If not specified, errors will appear in :output-buffer.
:kill-buffer If t, the output buffer will get killed automatically
at the end of command execution.
:callback Function to run at the end of the command execution.
Should take no argument.
For more details about the remaining keyword arguments, see `with-shell-interpreter'"
(with-shell-interpreter
:form
(let ((errno (shell-command command output-buffer error-buffer))
(buffer-name (or output-buffer "*Shell Command Output*")))
(when callback
(funcall callback))
(when kill-buffer
(kill-buffer buffer-name))
errno)
:path path
:interpreter interpreter
:command-switch command-switch))
;; HELPERS: PROCESS SENTINELS
(defun friendly-shell-command--kill-buffer-sentinel (process _output)
"Process sentinel to auto kill associated buffer once PROCESS dies."
(unless (process-live-p process)
(kill-buffer (process-buffer process))))
(cl-defun friendly-shell-command--build-process-sentinel (process &key sentinel callback kill-buffer)
"Build a process sentinel for PROCESS.
The output process sentinel is the merge of:
- :sentinel, if set
- :callback, if set
- `friendly-shell-command--kill-buffer-sentinel' if :kill-buffer is t"
(let (callback-sentinel kill-buffer-sentinel sentinel-list)
(when kill-buffer
(setq kill-buffer-sentinel #'friendly-shell-command--kill-buffer-sentinel))
(when callback
(setq callback-sentinel (lambda (_process _output)
(unless (process-live-p process)
(funcall callback)))))
(setq sentinel-list (-remove #'null
(list sentinel callback-sentinel kill-buffer-sentinel)))
(lambda (process line)
(--each sentinel-list
(funcall it process line)))))
(provide 'friendly-shell-command)
;;; friendly-shell-command.el ends here