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
|
;;; system-lib.el --- System utility library functions -*- lexical-binding: t; -*-
;;
;;; Commentary:
;; This module provides low-level system utility functions for checking
;; the availability of external programs and system capabilities.
;;
;; Functions include:
;; - Checking if external programs are available in PATH
;; - Silent logging to *Messages* buffer
;;
;;; Code:
(defun cj/executable-exists-p (program)
"Return non-nil if PROGRAM is available in PATH.
PROGRAM should be a string naming an executable program."
(and (stringp program)
(not (string-empty-p program))
(executable-find program)))
(defun cj/executable-find-or-warn (program feature &optional group)
"Return PROGRAM's executable path, or warn that FEATURE is unavailable.
When PROGRAM is in PATH, return its absolute path. When it isn't,
emit a `display-warning' naming PROGRAM and FEATURE so the user gets
a clear hint about what won't work, and return nil.
GROUP is the symbol passed to `display-warning' for filtering and
defaults to `cj/system-lib'. Callers should pass their own module
symbol (for example `mail-config') so per-feature warning filters
keep working."
(or (executable-find program)
(progn
(display-warning
(or group 'cj/system-lib)
(format "%s not found; %s unavailable" program feature)
:warning)
nil)))
(defconst cj/shell-safe-argument-regexp "\\`[[:alnum:]_./=+@%:,^-]+\\'"
"Regexp matching shell arguments safe to interpolate unchanged.
Members of this character set survive shell parsing without quoting,
so a command line containing only these characters in each argument
remains both safe and readable.")
(defun cj/shell-quote-argument-readable (argument)
"Quote ARGUMENT for shell command interpolation when needed.
When ARGUMENT consists only of characters in `cj/shell-safe-argument-regexp'
it is returned unchanged so the surrounding command stays human-readable
(useful for compile/test command lines you'll inspect in *compilation*).
Otherwise falls back to `shell-quote-argument' so the result is safe to
interpolate."
(if (string-match-p cj/shell-safe-argument-regexp argument)
argument
(shell-quote-argument argument)))
(defun cj/process-output-or-error (program &rest args)
"Run PROGRAM with ARGS via `process-file' and return stdout, or signal error.
On zero exit, returns the program's stdout as a string (including any
trailing newline -- callers that need a trimmed value should call
`string-trim' themselves). On non-zero exit, signals `user-error' with
a message naming the program, the exit status, and the (trimmed) output
so a user inspecting *Messages* can see what went wrong."
(with-temp-buffer
(let ((status (apply #'process-file program nil (current-buffer) nil args))
(output (buffer-string)))
(unless (zerop status)
(user-error "%s %s failed with status %s: %s"
program
(string-join args " ")
status
(string-trim output)))
output)))
(defun cj/git-output-or-error (&rest args)
"Run git with ARGS and return stdout, or signal `user-error' on failure.
Thin wrapper around `cj/process-output-or-error' with `git' as the
program."
(apply #'cj/process-output-or-error "git" args))
(defun cj/file-from-context (&optional explicit-filename)
"Return a file path from the current context, or nil.
Resolves in priority order:
1. EXPLICIT-FILENAME, if non-nil.
2. `buffer-file-name' of the current buffer.
3. The file at point if the current buffer is in `dired-mode'.
Returns nil when none of these yield a file. Useful for any command
that operates on \"the current file\" -- buffer commands, dired
commands, and external-open dispatchers all want this resolution."
(or explicit-filename
buffer-file-name
(and (derived-mode-p 'dired-mode)
(dired-file-name-at-point))))
(defun cj/log-silently (format-string &rest args)
"Append formatted message (FORMAT-STRING with ARGS) to *Messages* buffer.
This does so without echoing in the minibuffer."
(let ((inhibit-read-only t))
(with-current-buffer (get-buffer-create "*Messages*")
(goto-char (point-max))
(unless (bolp) (insert "\n"))
(insert (apply #'format format-string args))
(unless (bolp) (insert "\n")))))
;; ------------------------------ Auth Source ----------------------------------
(declare-function auth-source-search "auth-source")
(defun cj/auth-source-secret-value (host &optional user)
"Return the auth-source secret for HOST, or nil when none is found.
With USER, also match on the login. Resolves a function-valued secret
\(the netrc backend returns the secret as a function\) by calling it.
Callers that must have a secret layer their own error on top."
(let* ((spec (append (list :host host :require '(:secret) :max 1)
(when user (list :user user))))
(secret (plist-get (car (apply #'auth-source-search spec)) :secret)))
(if (functionp secret) (funcall secret) secret)))
(provide 'system-lib)
;;; system-lib.el ends here
|