870 lines
40 KiB
EmacsLisp
870 lines
40 KiB
EmacsLisp
|
;;; trusted-dirs.el --- Simplistic security for Eglot and auto-complete. -*- lexical-binding: t -*-
|
||
|
;;; Commentary:
|
||
|
;;; Code:
|
||
|
(require 'cl-lib)
|
||
|
(eval-and-compile
|
||
|
(defconst trusted-dirs-generated-function-name-prefix "trusted-dirs--"
|
||
|
"Prefix to append to generated functions.
|
||
|
This is used by `trusted-dirs-add-hook-if-safe' and
|
||
|
`trusted-dirs-mark-function-unsafe'. Note that these two functions are actually
|
||
|
macros, so if you change this (which you probably shouldn't do), code that uses
|
||
|
these will need to be recompiled.")
|
||
|
|
||
|
(defconst trusted-dirs-hook-function-name-suffix "@trusted-dirs-hook-if-safe"
|
||
|
"Suffix to append to function names in `trusted-dirs-add-hook-if-safe'.
|
||
|
Note that `trusted-dirs-add-hook-if-safe' is a macro, so if this value is
|
||
|
changed (which you probably shouldn't do), code that calls
|
||
|
`trusted-dirs-add-hook-if-safe' will need to be recompiled.")
|
||
|
|
||
|
(defconst trusted-dirs-advice-function-name-suffix
|
||
|
"@trusted-dirs-advice-if-safe"
|
||
|
"Suffix to append to function names in `trusted-dirs-mark-function-unsafe'.
|
||
|
Note that `trusted-dirs-mark-function-unsafe' is a macro, so if this value is
|
||
|
changed (which you probably shouldn't do), code that calls
|
||
|
`trusted-dirs-mark-function-unsafe' will need to be recompiled. "))
|
||
|
|
||
|
(defgroup trusted-dirs nil
|
||
|
"Simplistic security for Eglot, auto-complete, etc."
|
||
|
:group 'files
|
||
|
:prefix "trusted-dirs-")
|
||
|
|
||
|
(defun trusted-dirs--validate-only-allow-absolute-paths (widget)
|
||
|
"Custom validation function to only allow WIDGET to contain absolute paths."
|
||
|
(let ((path (widget-value widget)))
|
||
|
(unless (and (stringp path) (file-name-absolute-p path))
|
||
|
(widget-put widget :error "Path must be absolute")
|
||
|
widget)))
|
||
|
|
||
|
(defcustom trusted-dirs-list ()
|
||
|
"List of directories that should be considered safe."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Trusted Directories"
|
||
|
:type '(repeat
|
||
|
(cons :tag "Entry"
|
||
|
(directory
|
||
|
:tag "Directory"
|
||
|
:validate trusted-dirs--validate-only-allow-absolute-paths)
|
||
|
(boolean :tag "Also trust subdirectories" :value t)))
|
||
|
:risky t)
|
||
|
|
||
|
(defcustom trusted-dirs-truename-trusted-directories t
|
||
|
"If non-nil, use the `file-truename' of for entries in `trusted-dirs-list'.
|
||
|
Note that this does not affect `default-directory', see
|
||
|
`trusted-dirs-truename-current-directory' for that."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Resolve Symbolic Links for Trusted Directories"
|
||
|
:type 'boolean
|
||
|
:risky t)
|
||
|
|
||
|
(defcustom trusted-dirs-truename-current-directory t
|
||
|
"If non-nil, use the `file-truename' of `default-directory' when checking safety.
|
||
|
If this is nil, each link to a directory must individually be in
|
||
|
`trusted-dirs-list' to be considered safe. Note that this does _NOT_ effect the
|
||
|
entries in `trusted-dirs-list', only `default-directory'."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Resolve Symbolic Links for the Current Directory"
|
||
|
:type 'boolean
|
||
|
:risky t)
|
||
|
|
||
|
(defcustom trusted-dirs-show-in-modeline 'dynamic-temporary-untrusted
|
||
|
"How to show the current buffer's trusted status in the mode line.
|
||
|
There are three possible values:
|
||
|
- t: always show the status
|
||
|
- \\='untrusted: show the status if `default-directory' is untrusted
|
||
|
- \\='dynamic: as above, but only if a protected function tried to run
|
||
|
- \\='dynamic-untrusted: as above, but only if the function failed
|
||
|
- \\='dynamic-temporary: save as \\='dynamic, but also show when the buffer is
|
||
|
temporarily trusted
|
||
|
- \\='dynamic-temporary-untrusted: as above, but only if a function failed
|
||
|
|
||
|
To completely disabled display of the trusted status, disable
|
||
|
`trusted-dirs-modeline-mode'."
|
||
|
:group 'tusted-dirs
|
||
|
:tag "Show Trusted State in Modeline"
|
||
|
:type '(choice (const :tag "Always" t)
|
||
|
(const :tag "If Untrusted" untrusted)
|
||
|
(const :tag "Dynamic" dynamic)
|
||
|
(const :tag "Dynamic if Untrusted" dynamic-untrusted)
|
||
|
(const :tag "Dynamic (or Temporary)" dynamic-temporary)
|
||
|
(const :tag "Dynamic if Untrusted (or Temporary)"
|
||
|
dynamic-temporary-untrusted))
|
||
|
:set (lambda (sym val)
|
||
|
(set-default-toplevel-value sym val)
|
||
|
(force-mode-line-update t)))
|
||
|
|
||
|
(defface trusted-dirs-trusted-modeline-face
|
||
|
'((t))
|
||
|
"Face for the trusted notification string in the mode line."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Mode Line Trusted Notification Face")
|
||
|
|
||
|
(defface trusted-dirs-temporary-modeline-face
|
||
|
'((t . (:inherit warning)))
|
||
|
"Face for the temporarily trusted notification string in the mode line."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Mode Line Temporarily Trusted Notification Face")
|
||
|
|
||
|
(defface trusted-dirs-untrusted-modeline-face
|
||
|
'((t . (:inherit error)))
|
||
|
"Face for the untrusted notification string in the mode line."
|
||
|
:group 'trusted-dirs
|
||
|
:tag "Mode Line Untrusted Notification Face")
|
||
|
|
||
|
(defvar-local trusted-dirs--did-protected-function-run nil
|
||
|
"Non-nil if a protected function tried to run in the current buffer.")
|
||
|
;;;###autoload (put 'trusted-dirs--did-protected-function-run 'risky-local-variable t)
|
||
|
|
||
|
(defvar-local trusted-dirs--did-protected-function-fail nil
|
||
|
"Non-nil if a protected function failed to run in the current buffer.")
|
||
|
;;;###autoload (put 'trusted-dirs--did-protected-function-fail 'risky-local-variable t)
|
||
|
|
||
|
(defvar trusted-dirs--temporarily-trusted-cache (make-hash-table :test 'equal)
|
||
|
"Hash table of temporarily trusted directories and buffers.
|
||
|
Each key is a directory or buffer. In the case of a buffer, any non-nil values
|
||
|
means that the buffer is trusted. In the case of a directory, the key is one of
|
||
|
the following:
|
||
|
- t: this directory is trusted
|
||
|
- \\='subdir: this directory and its subdirectories are trusted
|
||
|
|
||
|
Entries are removed from this list by
|
||
|
`trusted-dirs--cleanup-temporary-trusted-cache', which is called from
|
||
|
`kill-buffer-hook'.")
|
||
|
;;;###autoload (put 'trusted-dirs--temporarily-trusted-cache 'risky-local-variable t)
|
||
|
|
||
|
(defun trusted-dirs--modeline-string ()
|
||
|
"Return the trusted-dirs mode line string for the current buffer.
|
||
|
To change when this is shown, customize `trusted-dirs-show-in-modeline'."
|
||
|
(let* ((safe (car (trusted-dirs-safe-p nil t)))
|
||
|
(temporary (car (memq safe
|
||
|
'(temp-buffer temp-dir temp-subdir)))))
|
||
|
(and (or (eq trusted-dirs-show-in-modeline t)
|
||
|
(and temporary
|
||
|
(memq trusted-dirs-show-in-modeline
|
||
|
'(dynamic-temporary dynamic-temporary-untrusted)))
|
||
|
(and (not safe) (eq trusted-dirs-show-in-modeline 'untrusted))
|
||
|
(and trusted-dirs--did-protected-function-run
|
||
|
(memq trusted-dirs-show-in-modeline
|
||
|
'(dynamic dynamic-temporary)))
|
||
|
(and trusted-dirs--did-protected-function-fail
|
||
|
(memq trusted-dirs-show-in-modeline
|
||
|
'(dynamic-untrusted dynamic-temporary-untrusted))))
|
||
|
(list
|
||
|
(cond
|
||
|
(temporary
|
||
|
`(:propertize ,(format "Temp. Trusted %s"
|
||
|
(cl-case temporary
|
||
|
(temp-buffer "(B)")
|
||
|
(temp-dir "(D)")
|
||
|
(temp-subdir "(S)")))
|
||
|
face trusted-dirs-temporary-modeline-face
|
||
|
mouse-face mode-line-highlight
|
||
|
help-echo
|
||
|
,(cl-case temporary
|
||
|
(temp-buffer
|
||
|
"This buffer is temp. trusted. Click to untrust.")
|
||
|
(temp-dir
|
||
|
"This directory is temp. trusted. Click to untrust it.")
|
||
|
(temp-subdir
|
||
|
"A parent directory is temp. trusted. Click to untrust it."))
|
||
|
keymap
|
||
|
(mode-line keymap
|
||
|
(mouse-1 . trusted-dirs-remove-temporary-current-buffer))))
|
||
|
(safe '(:propertize "Trusted"
|
||
|
face trusted-dirs-trusted-modeline-face
|
||
|
help-echo
|
||
|
(cl-case safe
|
||
|
(dir "This buffer's directory (not a parent) is trusted.")
|
||
|
(subdir "A parent directory of this buffer is trusted."))))
|
||
|
(t '(:propertize "Untrusted"
|
||
|
face trusted-dirs-untrusted-modeline-face
|
||
|
help-echo "This buffer is untrusted.")))
|
||
|
" "))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(define-minor-mode trusted-dirs-modeline-mode
|
||
|
"Minor mode for showing current buffer's trusted status in the mode line."
|
||
|
:group 'trusted-dirs
|
||
|
:global t
|
||
|
:lighter nil
|
||
|
(let ((item '(:eval (trusted-dirs--modeline-string))))
|
||
|
(if trusted-dirs-modeline-mode
|
||
|
(add-to-list 'global-mode-string item)
|
||
|
(setq global-mode-string (remove item global-mode-string))))
|
||
|
(force-mode-line-update))
|
||
|
|
||
|
(defun trusted-dirs--remove-extra-path-parts (path)
|
||
|
"Remove extra path parts from PATH.
|
||
|
This removes \".\" and \"..\" components. This always returns a path without a
|
||
|
trailing slash. The difference between this and `expand-file-name' is that this
|
||
|
will not return things like \"/..\"."
|
||
|
(let ((expanded (expand-file-name path)))
|
||
|
(while (string-prefix-p "/.." expanded)
|
||
|
(setq expanded (substring expanded 3))
|
||
|
(unless (string-prefix-p "/" expanded)
|
||
|
(setq expanded (concat "/" expanded))))
|
||
|
(directory-file-name expanded)))
|
||
|
|
||
|
(defun trusted-dirs--resolve-trusted-directory (path)
|
||
|
"Resolve PATH, which is resolved according to user settings.
|
||
|
If `trusted-dirs-truename-trusted-directories' is set, return the
|
||
|
`file-truename' of PATH. In any case, remove \".\" and \"..\" components from
|
||
|
PATH. Also make PATH absolute and remove any trailing slash."
|
||
|
(if trusted-dirs-truename-trusted-directories
|
||
|
(directory-file-name (file-truename path))
|
||
|
(trusted-dirs--remove-extra-path-parts path)))
|
||
|
|
||
|
(defun trusted-dirs-resolved-list ()
|
||
|
"Return `trusted-dirs-list' as a hash table, removing invalid entries.
|
||
|
Also, take the `file-truename' of each entry if
|
||
|
`trusted-dirs-truename-trusted-directories' is set."
|
||
|
(let ((ht (make-hash-table :test 'equal)))
|
||
|
(dolist (entry trusted-dirs-list ht)
|
||
|
(when (file-name-absolute-p (car entry))
|
||
|
(let* ((resolved (trusted-dirs--resolve-trusted-directory (car entry)))
|
||
|
(cur-val (gethash resolved ht)))
|
||
|
;; Prioritize the most specific rule. If the output is already set to
|
||
|
;; t (more specific than 'subdir), don't touch it. Otherwise, set the
|
||
|
;; output to whatever the current entry is
|
||
|
(unless (eq cur-val t)
|
||
|
(puthash resolved (cdr entry) ht)))))))
|
||
|
|
||
|
(defsubst trusted-dirs--resolve-current-directory (path)
|
||
|
"Resolve PATH, which is resolved according to user settings.
|
||
|
If `trusted-dirs-truename-current-directory' is set, return the `file-truename'
|
||
|
of PATH. In any case, remove \".\" and \"..\" components from PATH. Also make
|
||
|
PATH absolute and remove any trailing slash."
|
||
|
(if trusted-dirs-truename-current-directory
|
||
|
(directory-file-name (file-truename path))
|
||
|
(trusted-dirs--remove-extra-path-parts path)))
|
||
|
|
||
|
(defun trusted-dirs--path-equal-p (path1 path2)
|
||
|
"Return non-nil if PATH1 and PATH2 are the same path.
|
||
|
This is the same as `equal' except that it calls `directory-file-name' on PATH1
|
||
|
and PATH2 before comparing them."
|
||
|
(and (equal (directory-file-name path1)
|
||
|
(directory-file-name path2))))
|
||
|
|
||
|
(defun trusted-dirs--normal-buffer-p (&optional buffer)
|
||
|
"Return non-nil if BUFFER (or the current buffer) is a normal buffer.
|
||
|
A buffer is normal if it visits a file, is not hidden, and it's name does not
|
||
|
start and end with asterisks."
|
||
|
(unless buffer (setq buffer (current-buffer)))
|
||
|
(and (buffer-file-name buffer)
|
||
|
(not (string-prefix-p " " (buffer-name buffer)))
|
||
|
(not (string-match-p "\\`\\*.*\\*\\'" (buffer-name buffer)))))
|
||
|
|
||
|
(defun trusted-dirs--subdirectory-p (parent child &optional resolved)
|
||
|
"Return non-nil if CHILD is a subdirectory of PARENT.
|
||
|
This will resolve both PARENT and CHILD with
|
||
|
`trusted-dirs--resolve-current-directory', unless RESOLVED is non-nil."
|
||
|
(unless resolved
|
||
|
(setq parent (trusted-dirs--resolve-current-directory parent)
|
||
|
child (trusted-dirs--resolve-current-directory child)))
|
||
|
(or (equal parent "/")
|
||
|
(string-match-p (concat "\\`" (regexp-quote parent) "\\(\\'\\|/\\)") child)))
|
||
|
|
||
|
(defun trusted-dirs--find-buffers-in-dir
|
||
|
(dir &optional subdir-too resolved special-too)
|
||
|
"Return a list of buffers that have a `default-directory' of DIR.
|
||
|
If SUBDIR-TOO is set, also search for subdirectories of DIR. If SPECIAL-TOO is
|
||
|
set, also consider buffers that are special. Otherwise, only consider regular,
|
||
|
visible, file-visiting buffers.
|
||
|
|
||
|
Unless RESOLVED is set, resolve DIR with
|
||
|
`trusted-dirs--resolve-current-directory'."
|
||
|
(unless resolved (setq dir (trusted-dirs--resolve-current-directory dir)))
|
||
|
(let (out)
|
||
|
(dolist (buffer (buffer-list) out)
|
||
|
(when (or special-too (trusted-dirs--normal-buffer-p buffer))
|
||
|
(let ((target-dir (trusted-dirs--resolve-current-directory
|
||
|
(buffer-local-value 'default-directory
|
||
|
buffer))))
|
||
|
(when (or (and subdir-too (trusted-dirs--subdirectory-p
|
||
|
dir target-dir))
|
||
|
(equal dir target-dir))
|
||
|
(push buffer out)))))))
|
||
|
|
||
|
(defun trusted-dirs--cleanup-temporary-trusted-cache ()
|
||
|
"Cleanup `trusted-dirs--temporarily-trusted-cache'."
|
||
|
(remhash (current-buffer) trusted-dirs--temporarily-trusted-cache)
|
||
|
(cl-loop with resolved = (trusted-dirs--resolve-current-directory
|
||
|
default-directory)
|
||
|
for prev = nil then cur
|
||
|
for cur = resolved then (directory-file-name
|
||
|
(file-name-directory cur))
|
||
|
until (equal prev cur)
|
||
|
for rule = (gethash cur trusted-dirs--temporarily-trusted-cache)
|
||
|
when (and rule (null (delq (current-buffer)
|
||
|
(trusted-dirs--find-buffers-in-dir
|
||
|
cur (eq rule 'subdir)))))
|
||
|
collect cur into steps
|
||
|
and do (remhash cur trusted-dirs--temporarily-trusted-cache)
|
||
|
finally do
|
||
|
(message "Untrusted %s" (trusted-dirs--pprint-list steps))))
|
||
|
|
||
|
(add-hook 'kill-buffer-hook #'trusted-dirs--cleanup-temporary-trusted-cache)
|
||
|
|
||
|
(defun trusted-dirs--buffer-temporarily-trusted-p (buffer)
|
||
|
"Return non-nil if BUFFER is temprarily trusted.
|
||
|
This checks both BUFFER and BUFFER's `default-directory'.
|
||
|
|
||
|
Return a cons. For the car if the BUFFER is trusted, return \\='temp-buffer.
|
||
|
If `default-directory' is exactly trusted, return \\='temp-dir. If a parent
|
||
|
directory of it is trusted, return \\='temp-subdir. For the cdr, return the
|
||
|
directory that matched, or the BUFFER it itself matched."
|
||
|
(or
|
||
|
(and (gethash buffer trusted-dirs--temporarily-trusted-cache)
|
||
|
(cons 'temp-buffer buffer))
|
||
|
(cl-loop with dir = (trusted-dirs--resolve-current-directory
|
||
|
(buffer-local-value 'default-directory buffer))
|
||
|
for prev = nil then cur
|
||
|
for cur = dir then (directory-file-name
|
||
|
(file-name-directory cur))
|
||
|
until (equal prev cur)
|
||
|
for result = (gethash cur trusted-dirs--temporarily-trusted-cache)
|
||
|
;; if we find an exact match, return t
|
||
|
when (and result (equal dir cur)) return (cons 'temp-dir cur)
|
||
|
;; otherwise, return t if the entry allows subdirectories
|
||
|
when (eq result 'subdir) return (cons 'temp-subdir cur))))
|
||
|
|
||
|
(defun trusted-dirs--permanently-trusted-p (path &optional resolved)
|
||
|
"Return non-nil if PATH is in `trusted-dirs-list'.
|
||
|
This will resolve PATH with `trusted-dirs--resolve-trusted-directory' unless
|
||
|
RESOLVED is non-nil.
|
||
|
|
||
|
Return a cons. For the car if PATH (not a parent directory) is trusted, return
|
||
|
\\='dir. If a parent directory of PATH is trusted, return \\='subdir. For the
|
||
|
cdr, return the directory that matched."
|
||
|
(unless resolved (setq path (trusted-dirs--resolve-current-directory path)))
|
||
|
(cl-loop with trusted-list = (trusted-dirs-resolved-list)
|
||
|
for prev = nil then cur
|
||
|
for cur = path then (directory-file-name
|
||
|
(file-name-directory cur))
|
||
|
until (equal prev cur)
|
||
|
for result = (gethash cur trusted-list)
|
||
|
;; if we find an exact match, return t
|
||
|
when (and result (equal path cur)) return (cons 'dir cur)
|
||
|
;; otherwise, return t if the entry allows subdirectories
|
||
|
when (eq result 'subdir) return (cons 'subdir cur)))
|
||
|
|
||
|
(defun trusted-dirs-safe-p (&optional dir no-modify)
|
||
|
"Return non-nil if DIR is considered safe.
|
||
|
DIR defaults to `default-directory' if it is nil. Also, if DIR is unsafe, set
|
||
|
`trusted-dirs--did-protected-function-fail' to t unless NO-MODIFY is non-nil.
|
||
|
In any case, set `trusted-dirs--did-protected-function-run' to t unless
|
||
|
NO-MODIFY is non-nil.
|
||
|
|
||
|
This can return a few different things depending on how DIR is trusted. See
|
||
|
`trusted-dirs--permanently-trusted-p' and
|
||
|
`trusted-dirs--buffer-temporarily-trusted-p' for a list of possible return
|
||
|
values."
|
||
|
(unless dir (setq dir default-directory))
|
||
|
(setq dir (trusted-dirs--resolve-current-directory dir))
|
||
|
(unless no-modify
|
||
|
(setq trusted-dirs--did-protected-function-run t))
|
||
|
(let ((result (or (trusted-dirs--permanently-trusted-p dir)
|
||
|
(trusted-dirs--buffer-temporarily-trusted-p
|
||
|
(current-buffer)))))
|
||
|
(unless (or no-modify result)
|
||
|
(setq trusted-dirs--did-protected-function-fail t))
|
||
|
(unless no-modify
|
||
|
(force-mode-line-update))
|
||
|
result))
|
||
|
|
||
|
(defun trusted-dirs--visible-buffer-list ()
|
||
|
"Return a list of all visible buffers.
|
||
|
A buffer is coincided visible if it's name does not start with a space."
|
||
|
(cl-delete-if (lambda (buf)
|
||
|
(string-prefix-p " " (buffer-name buf)))
|
||
|
(buffer-list)))
|
||
|
|
||
|
(defun trusted-dirs--pprint-buffer-name (buffer)
|
||
|
"Return a string which can represent BUFFER when prompting the user."
|
||
|
(if-let ((path (buffer-file-name buffer))
|
||
|
(file (file-name-nondirectory path)))
|
||
|
(if (equal file (buffer-name buffer))
|
||
|
file
|
||
|
(format "%s (buffer %s)" file (buffer-name buffer)))
|
||
|
(buffer-name buffer)))
|
||
|
|
||
|
(defun trusted-dirs-outdated-trust-information-p (&optional buffer)
|
||
|
"Return non-nil if BUFFER has outdated trust information.
|
||
|
See `trusted-dirs-reload-newly-trusted-buffers' for an explanation of when a
|
||
|
buffer might have outdated trust information.
|
||
|
|
||
|
If BUFFER is nil, default to the current buffer;"
|
||
|
(with-current-buffer (or buffer (current-buffer))
|
||
|
(let ((safe (trusted-dirs-safe-p nil t)))
|
||
|
(or (and safe trusted-dirs--did-protected-function-fail)
|
||
|
(and (not safe) trusted-dirs--did-protected-function-run
|
||
|
(not trusted-dirs--did-protected-function-fail))))))
|
||
|
|
||
|
(cl-defun trusted-dirs--outdated-buffer-list (&optional (buffers (buffer-list)))
|
||
|
"Return a list of buffers that have outdated trust information.
|
||
|
See `trusted-dirs-reload-newly-trusted-buffers' for an explanation of when a
|
||
|
buffer might have outdated trust information.
|
||
|
|
||
|
If BUFFERS is passed, only consider buffers in that list. Otherwise, consider
|
||
|
all live buffers (even special and hidden ones)."
|
||
|
(cl-remove-if-not #'trusted-dirs-outdated-trust-information-p buffers))
|
||
|
|
||
|
(defun trusted-dirs--princ-to-string (object)
|
||
|
"Return the output resulting from calling `princ' on OBJECT."
|
||
|
(with-output-to-string
|
||
|
(princ object standard-output)))
|
||
|
|
||
|
(defun trusted-dirs--pprint-list (items &optional formatter no-oxford-comma)
|
||
|
"Pretty print ITEMS, a list of things.
|
||
|
Each item will be converted to a string, using FORMATTER, before being printed.
|
||
|
If FORMATTER is nil, use `trusted-dirs--princ-to-string'. The FORMATTER must
|
||
|
take a single argument, the item to format, and return a string.
|
||
|
|
||
|
With NO-OXFORD-COMMA, don't insert an Oxford comma."
|
||
|
(unless formatter (setq formatter #'trusted-dirs--princ-to-string))
|
||
|
(let ((len (length items)))
|
||
|
(cl-case len
|
||
|
(0 "")
|
||
|
(1 (funcall formatter (car items)))
|
||
|
(2 (concat (funcall formatter (cl-first items))
|
||
|
" and "
|
||
|
(funcall formatter (cl-second items))))
|
||
|
(t (cl-loop for i upfrom 1
|
||
|
for item in items
|
||
|
when (/= len i)
|
||
|
concat (funcall formatter item)
|
||
|
and concat (if (and no-oxford-comma
|
||
|
(= i (1- len)))
|
||
|
" "
|
||
|
", ")
|
||
|
else
|
||
|
concat "and "
|
||
|
and concat (funcall formatter item))))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(cl-defun trusted-dirs-revert-newly-trusted-buffers
|
||
|
(&optional force silent (buffers (trusted-dirs--visible-buffer-list)))
|
||
|
"Revert all buffers that have outdated trust information.
|
||
|
A buffer is considered to have outdated trust information if:
|
||
|
- it is marked as having a had a function fail, even though it is trusted
|
||
|
- it is marked as having had no function fail, even though it is untrusted
|
||
|
|
||
|
By default this prompts the user to save any buffers before reverting them. If
|
||
|
the user says no to saving a buffer, skip it. With FORCE, don't ask the user
|
||
|
anything and (possibly destructively) revert all buffers.
|
||
|
|
||
|
Unless SILENT is non-nil, `message' the user with a list of each revered buffer.
|
||
|
|
||
|
By default, revert all live buffers. To only check some buffers, pass a list of
|
||
|
buffers in BUFFERS."
|
||
|
(interactive)
|
||
|
(let (reverted)
|
||
|
(dolist (buffer (trusted-dirs--outdated-buffer-list buffers))
|
||
|
(with-current-buffer buffer
|
||
|
(when (and (not force)
|
||
|
(buffer-modified-p)
|
||
|
(y-or-n-p (format "Save and revert %s?"
|
||
|
(trusted-dirs--pprint-buffer-name buffer))))
|
||
|
(save-buffer))
|
||
|
(when (or force (not (buffer-modified-p)))
|
||
|
(revert-buffer nil t)
|
||
|
(push buffer reverted))))
|
||
|
(when (and (not silent) reverted)
|
||
|
(message
|
||
|
"Reverted buffer%s %s"
|
||
|
(if (length= reverted 1) "" "s")
|
||
|
(trusted-dirs--pprint-list reverted
|
||
|
#'trusted-dirs--pprint-buffer-name)))))
|
||
|
|
||
|
(cl-defun trusted-dirs--maybe-prompt-revert-newly-trusted-buffers
|
||
|
(&optional (buffers (trusted-dirs--outdated-buffer-list
|
||
|
(trusted-dirs--visible-buffer-list))))
|
||
|
"If there are buffers with outdated trust, prompt the user to revert them.
|
||
|
For a definition of what qualifies as a buffer with outdated trust, see
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'.
|
||
|
|
||
|
With BUFFERS, only consider those buffers."
|
||
|
(and buffers (y-or-n-p "Buffers with outdated trust detected! Revert?")
|
||
|
(trusted-dirs-revert-newly-trusted-buffers nil nil buffers)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-add (path &optional no-recursive no-revert)
|
||
|
"Mark PATH as a trusted directory.
|
||
|
If NO-RECURSIVE is non-nil, don't trust any subdirectories of PATH.
|
||
|
Interactively, prompt for PATH. With a prefix argument, set NO-RECURSIVE.
|
||
|
|
||
|
By default, this calls asks the user if they want to run
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'. If NO-REVERT is set, don't ask or
|
||
|
call it.
|
||
|
|
||
|
PATH is processed according to `trusted-dirs-truename-trusted-directories'."
|
||
|
(interactive "DTrust Directory: \nP")
|
||
|
(cl-loop with resolved = (trusted-dirs--resolve-trusted-directory path)
|
||
|
with set-val = (if no-recursive t 'subdir)
|
||
|
with did-set = nil
|
||
|
for entry in trusted-dirs-list
|
||
|
for entry-dir = (trusted-dirs--resolve-trusted-directory
|
||
|
(car entry))
|
||
|
when (trusted-dirs--path-equal-p entry-dir resolved) do
|
||
|
(setcdr entry set-val)
|
||
|
(setq did-set t)
|
||
|
finally
|
||
|
(unless did-set
|
||
|
(push (cons resolved set-val) trusted-dirs-list))
|
||
|
;; Now that resolved is permanently trusted, we can remove it from
|
||
|
;; the temporary cache
|
||
|
(remhash resolved trusted-dirs--temporarily-trusted-cache)
|
||
|
(unless no-revert
|
||
|
(message "Added %s to the list of trusted directories"
|
||
|
resolved)
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-add-current (&optional no-recursive no-revert)
|
||
|
"Mark `default-directory' as a trusted directory.
|
||
|
NO-RECURSIVE and NO-REVERT are the same as for `trusted-dirs-add' (which see).
|
||
|
|
||
|
`default-directory' is processed according to
|
||
|
`trusted-dirs-truename-trusted-directories'."
|
||
|
(interactive "P")
|
||
|
(trusted-dirs-add default-directory no-recursive no-revert))
|
||
|
|
||
|
(defun trusted-dirs--read-trusted-dir (&optional prompt)
|
||
|
"Read a trusted directory from the minibuffer with completion.
|
||
|
PROMPT is the prompt to use, defaulting to \"Trusted Directory: \"."
|
||
|
(let ((ht (trusted-dirs-resolved-list))
|
||
|
dirs)
|
||
|
(maphash (lambda (dir _v) (push dir dirs)) ht)
|
||
|
(completing-read (or prompt "Trusted Directory") dirs nil t)))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-remove (path &optional no-revert)
|
||
|
"Mark PATH as an untrusted directory.
|
||
|
Interactively, prompt for PATH. Return the number of entries removed, or nil if
|
||
|
none where removed.
|
||
|
|
||
|
By default, this asks the user if they want to run
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'. If NO-REVERT is set, don't ask or
|
||
|
call it.
|
||
|
|
||
|
PATH is processed according to `trusted-dirs-truename-trusted-directories'."
|
||
|
(interactive (list (trusted-dirs--read-trusted-dir "Untrust Directory: ")))
|
||
|
(cl-loop with resolved = (trusted-dirs--resolve-trusted-directory path)
|
||
|
for entry in trusted-dirs-list
|
||
|
for entry-dir = (trusted-dirs--resolve-trusted-directory
|
||
|
(car entry))
|
||
|
when (trusted-dirs--path-equal-p entry-dir resolved)
|
||
|
count t into removed-count
|
||
|
else
|
||
|
collect entry into new-list
|
||
|
finally
|
||
|
(setq trusted-dirs-list new-list)
|
||
|
(unless no-revert
|
||
|
(if (zerop removed-count)
|
||
|
(message "%s is not trusted" resolved)
|
||
|
(message "Removed %s from the list of trusted directories"
|
||
|
resolved))
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers))
|
||
|
finally return (unless (zerop removed-count)
|
||
|
removed-count)))
|
||
|
|
||
|
(defun trusted-dirs-remove-current (&optional no-revert)
|
||
|
"Mark `default-directory' as an untrusted directory.
|
||
|
NO-REVERT is the same as for `trusted-dirs-remove' (which see). The return
|
||
|
value is also the same.
|
||
|
|
||
|
`default-directory' is processed according to
|
||
|
`trusted-dirs-truename-trusted-directories'."
|
||
|
(interactive)
|
||
|
(trusted-dirs-remove default-directory no-revert))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-add-temporary-directory
|
||
|
(path &optional no-recursive no-revert)
|
||
|
"Temporarily trust PATH.
|
||
|
PATH will be trusted until _ALL_ buffers that have it as their
|
||
|
`default-directory' are closed. Unless NO-RECURSIVE is set, also trust
|
||
|
subdirectories of `default-directory'. In this case buffers in all
|
||
|
subdirectories of `default-directory' will also be trusted, and PATH will not be
|
||
|
untrusted until _ALL_ of these buffers are closed as well.
|
||
|
|
||
|
Unless NO-REVERT is set, prompt the user to call
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'.
|
||
|
|
||
|
Note that only non-special, visible, file-visiting buffers are considered."
|
||
|
(interactive "DTemporarily Trust: \nP")
|
||
|
(let ((resolved (trusted-dirs--resolve-trusted-directory path)))
|
||
|
(when (trusted-dirs--permanently-trusted-p resolved t)
|
||
|
(user-error "%s is already permanently trusted" resolved))
|
||
|
(unless (trusted-dirs--find-buffers-in-dir resolved (not no-recursive) t)
|
||
|
(user-error "There are no buffers in %s" resolved))
|
||
|
(puthash resolved (if no-recursive t 'subdir)
|
||
|
trusted-dirs--temporarily-trusted-cache)
|
||
|
(unless no-revert
|
||
|
(message "Temporarily trusted %s" resolved)
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-add-temporary-buffer (&optional buffer-or-name no-revert)
|
||
|
"Temporarily trust BUFFER-OR-NAME, defaulting to the current buffer.
|
||
|
The buffer will be trusted until it is closed. If a new buffer visiting the
|
||
|
same file were to be created at a later time, that buffer would not be trusted.
|
||
|
Interactively, prompt for the buffer.
|
||
|
|
||
|
Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
|
||
|
have outdated trust information. For an explanation of what this means, see
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'."
|
||
|
(interactive "bTemporarily Trust:")
|
||
|
(unless buffer-or-name (setq buffer-or-name (current-buffer)))
|
||
|
(unless (bufferp buffer-or-name)
|
||
|
(setq buffer-or-name (get-buffer buffer-or-name)))
|
||
|
(puthash buffer-or-name t trusted-dirs--temporarily-trusted-cache)
|
||
|
(unless no-revert
|
||
|
(message "Temporarily trusted %s"
|
||
|
(trusted-dirs--pprint-buffer-name buffer-or-name))
|
||
|
(when (trusted-dirs-outdated-trust-information-p buffer-or-name)
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers
|
||
|
(list buffer-or-name)))))
|
||
|
|
||
|
(defun trusted-dirs--filter-temporary-cache (predicate)
|
||
|
"Return anything in the temporary trust cache that matches PREDICATE.
|
||
|
PREDICATE should be a function of one argument. If will be passed each key in
|
||
|
`trusted-dirs--temporarily-trusted-cache'. It should return non-nil if that
|
||
|
item should be included in the returned set."
|
||
|
(cl-delete-if-not predicate
|
||
|
(hash-table-keys trusted-dirs--temporarily-trusted-cache)))
|
||
|
|
||
|
(defun trusted-dirs--read-temporary-directory (&optional prompt)
|
||
|
"Prompt for and return the path of a temporarily trusted directory.
|
||
|
PROMPT defaults to \"Temporarily Trusted Directory: \"."
|
||
|
(completing-read (or prompt "Temporarily Trusted Directory: ")
|
||
|
(trusted-dirs--filter-temporary-cache 'stringp)
|
||
|
nil t))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-remove-temporary-directory (path &optional no-revert)
|
||
|
"Untrust the temporarily trusted directory PATH.
|
||
|
|
||
|
Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
|
||
|
have outdated trust information. For an explanation of what this means, see
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'."
|
||
|
(interactive (list (trusted-dirs--read-temporary-directory
|
||
|
"Untrust Directory: ")))
|
||
|
(let ((resolved (trusted-dirs--resolve-trusted-directory path)))
|
||
|
(remhash resolved trusted-dirs--temporarily-trusted-cache)
|
||
|
(unless no-revert
|
||
|
(message "Untrusted %s" resolved)
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers))))
|
||
|
|
||
|
(defun trusted-dirs--read-temporary-buffer (&optional prompt)
|
||
|
"Prompt the user for a temporarily trusted buffer and it (not its name).
|
||
|
PROMPT defaults to \"Temporarily Trusted Buffer: \"."
|
||
|
(let ((names (mapcar 'buffer-name
|
||
|
(trusted-dirs--filter-temporary-cache 'bufferp))))
|
||
|
(get-buffer (read-buffer (or prompt "Temporarily Trusted Buffer: ")
|
||
|
nil t (lambda (buf-name)
|
||
|
(unless (stringp buf-name)
|
||
|
(setq buf-name (car buf-name)))
|
||
|
(member buf-name names))))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-remove-temporary-buffer (&optional buffer-or-name no-revert)
|
||
|
"Untust BUFFER-OR-NAME if it is a temporarily trusted buffer.
|
||
|
If it was trusted, return non-nil, otherwise, return nil. Note that this only
|
||
|
untrusts BUFFER-OR-NAME, and not its `default-directory'. For that, see
|
||
|
`trusted-dirs-remove-temporary-directory'.
|
||
|
|
||
|
Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
|
||
|
have outdated trust information. For an explanation of what this means, see
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'."
|
||
|
(interactive (list (trusted-dirs--read-temporary-buffer "Untrust Buffer: ")))
|
||
|
(unless buffer-or-name (setq buffer-or-name (current-buffer)))
|
||
|
(unless (bufferp buffer-or-name)
|
||
|
(setq buffer-or-name (get-buffer buffer-or-name)))
|
||
|
(remhash buffer-or-name trusted-dirs--temporarily-trusted-cache)
|
||
|
(unless no-revert
|
||
|
(message "Untrusted %s"
|
||
|
(trusted-dirs--pprint-buffer-name buffer-or-name))
|
||
|
(when (trusted-dirs-outdated-trust-information-p buffer-or-name)
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers
|
||
|
(list buffer-or-name)))))
|
||
|
|
||
|
;;;###autoload
|
||
|
(defun trusted-dirs-remove-temporary-current-buffer (&optional no-revert)
|
||
|
"Untrust the current buffer, however it's temporarily trusted.
|
||
|
This will either untrust the current buffer directly, untrust its
|
||
|
`default-directory', or untrust a parent of its `default-directory'. If need
|
||
|
be, it may do multiple of these.
|
||
|
|
||
|
Unless NO-REVERT is set, prompt the user to revert the buffer if it is deemed to
|
||
|
have outdated trust information. For an explanation of what this means, see
|
||
|
`trusted-dirs-revert-newly-trusted-buffers'."
|
||
|
(interactive)
|
||
|
(let (steps)
|
||
|
(while-let ((how (cdr (trusted-dirs--buffer-temporarily-trusted-p
|
||
|
(current-buffer)))))
|
||
|
(push how steps)
|
||
|
(if (stringp how)
|
||
|
(trusted-dirs-remove-temporary-directory how t)
|
||
|
(trusted-dirs-remove-temporary-buffer how t)))
|
||
|
(unless no-revert
|
||
|
(message "Untrusted %s"
|
||
|
(trusted-dirs--pprint-list
|
||
|
(nreverse steps)
|
||
|
(lambda (elt)
|
||
|
(if (stringp elt)
|
||
|
elt
|
||
|
(concat "buffer " (buffer-name elt))))))
|
||
|
(trusted-dirs--maybe-prompt-revert-newly-trusted-buffers))))
|
||
|
|
||
|
(eval-and-compile
|
||
|
(defun trusted-dirs--quoted-symbol-p (form)
|
||
|
"Return non-nil if FORM is a quoted symbol.
|
||
|
This returns non-nil if FORM is a proper list of two elements, the first being
|
||
|
the symbol \\='quote or \\='function and the second being a symbol."
|
||
|
(and (memq (car-safe form) '(function quote))
|
||
|
(consp (cdr form))
|
||
|
(symbolp (cadr form))
|
||
|
(null (cddr form)))))
|
||
|
|
||
|
(defmacro trusted-dirs-only-if-safe (function &optional replacement prefix suffix)
|
||
|
"Return a function that will call FUNCTION if `default-directory' is safe.
|
||
|
If REPLACEMENT is non-nil, call it instead of FUNCTION if `default-directory' is
|
||
|
unsafe. REPLACEMENT is called with the same arguments that FUNCTION would have
|
||
|
been called with.
|
||
|
|
||
|
If either PREFIX or SUFFIX is a string and FUNCTION is a symbol (this is a
|
||
|
macro, so these must be true at compile time), define a new function named by
|
||
|
concatenating PREFIX, the name of FUNCTION, and SUFFIX."
|
||
|
(let* ((args (make-symbol "args"))
|
||
|
(evaled-prefix (eval prefix t))
|
||
|
(evaled-suffix (eval suffix t))
|
||
|
(do-defun (and (or (stringp evaled-prefix) (stringp evaled-suffix))
|
||
|
(trusted-dirs--quoted-symbol-p function))))
|
||
|
`(,@(if do-defun
|
||
|
(list 'defun (intern (concat (when (stringp evaled-prefix)
|
||
|
evaled-prefix)
|
||
|
(symbol-name (cl-second function))
|
||
|
(when (stringp evaled-suffix)
|
||
|
evaled-suffix))))
|
||
|
'(lambda))
|
||
|
(&rest ,args)
|
||
|
,@(when do-defun
|
||
|
(list (format "Execute `%s' when `default-directory' is safe.
|
||
|
The safety check is done with `trusted-dirs-safe-p'."
|
||
|
(cl-second function))))
|
||
|
(require 'trusted-dirs)
|
||
|
(if (trusted-dirs-safe-p)
|
||
|
(apply ,function ,args)
|
||
|
,@(when replacement
|
||
|
(list `(apply ,replacement ,args)))))))
|
||
|
|
||
|
(cl-defmacro trusted-dirs-add-hook-if-safe
|
||
|
(hook function &optional (depth nil depthp) (local nil localp))
|
||
|
"Like `add-hook', but only when `default-directory' is trusted.
|
||
|
This will add FUNCTION to HOOK, initializing it if necessary. DEPTH and LOCAL
|
||
|
are the same as `add-hook' (which see). If FUNCTION is a symbol, it is wrapped
|
||
|
in a new function who's name is formed by concatenating the name of FUNCTION and
|
||
|
`trusted-dirs-hook-function-name-suffix'."
|
||
|
`(add-hook ,hook (trusted-dirs-only-if-safe
|
||
|
,function nil ,trusted-dirs-generated-function-name-prefix
|
||
|
,trusted-dirs-hook-function-name-suffix)
|
||
|
,@(when depthp
|
||
|
(list depth))
|
||
|
,@(when localp
|
||
|
(list local))))
|
||
|
|
||
|
(defun trusted-dirs-remove-hook (hook function &optional local)
|
||
|
"Remove FUNCTION from HOOK if it was added by trusted-dirs.
|
||
|
This undoes `trusted-dirs-add-hook-if-safe'. LOCAL is the same for this as for
|
||
|
`add-hook'. This only works if FUNCTION is a symbol."
|
||
|
(cl-check-type function symbol)
|
||
|
(when-let ((wrapped (intern-soft
|
||
|
(format "%s%s%s"
|
||
|
trusted-dirs-generated-function-name-prefix
|
||
|
(symbol-name function)
|
||
|
trusted-dirs-hook-function-name-suffix))))
|
||
|
(remove-hook hook wrapped local)))
|
||
|
|
||
|
(eval-and-compile
|
||
|
(defun trusted-dirs--format-doc-string (format &rest args)
|
||
|
"Call `format' fill the output as a documentation string.
|
||
|
This will call `format' using FORMAT and ARGS. Every paragraph in the output
|
||
|
except the first line will then be filled to a `fill-column' of 80 using
|
||
|
`fill-region'."
|
||
|
(let ((raw-string (apply 'format format args)))
|
||
|
(with-temp-buffer
|
||
|
(insert raw-string)
|
||
|
(goto-char (point-min))
|
||
|
(forward-line)
|
||
|
(fill-individual-paragraphs (point) (point-max))
|
||
|
(buffer-string))))
|
||
|
|
||
|
(defun trusted-dirs--make-advice-function (target replacement)
|
||
|
"Make `:around' advice for TARGET to only call it in safe directories.
|
||
|
If REPLACEMENT is non-nil, it will be called instead in unsafe directories."
|
||
|
(let ((oldfun (make-symbol "oldfun"))
|
||
|
(args (make-symbol "args"))
|
||
|
(do-defun (trusted-dirs--quoted-symbol-p target)))
|
||
|
`(,@(if do-defun
|
||
|
`(defun ,(intern
|
||
|
(concat trusted-dirs-generated-function-name-prefix
|
||
|
(symbol-name (cl-second target))
|
||
|
trusted-dirs-advice-function-name-suffix)))
|
||
|
'(lambda))
|
||
|
(,oldfun &rest ,args)
|
||
|
,@(when do-defun
|
||
|
(list
|
||
|
(trusted-dirs--format-doc-string
|
||
|
"Advice `:around' `%s' to only call it in safe directories.
|
||
|
The safety check is done with `trusted-dirs-safe-p'. If this check fails, %s."
|
||
|
(symbol-name (cl-second target))
|
||
|
(cond
|
||
|
((trusted-dirs--quoted-symbol-p replacement)
|
||
|
(concat (symbol-name (cl-second replacement))
|
||
|
" is called instead"))
|
||
|
(replacement
|
||
|
"an anonymous function is called instead.")
|
||
|
(t "nil is returned instead.")))))
|
||
|
(require 'trusted-dirs)
|
||
|
(if (trusted-dirs-safe-p)
|
||
|
(apply ,oldfun ,args)
|
||
|
,@(when replacement
|
||
|
(list `(apply ,replacement ,args))))))))
|
||
|
|
||
|
(defmacro trusted-dirs-mark-function-unsafe (function &optional replacement)
|
||
|
"Mark FUNCTION as only being runnable in safe directories.
|
||
|
This will add advice to FUNCTION such that it will simply return nil unless the
|
||
|
current directory is safe. If REPLACEMENT is non-nil, it will be run instead of
|
||
|
FUNCTION in unsafe directories. If FUNCTION is a symbol, it is wrapped
|
||
|
in a new function who's name is formed by concatenating the name of FUNCTION and
|
||
|
`trusted-dirs-advice-function-name-suffix'.
|
||
|
|
||
|
This will attempt to make the advice run before any other advice by giving it a
|
||
|
depth of -100 (see `add-function' for what this means), however, there is
|
||
|
nothing stopping other functions from doing this as well, so care must be taken
|
||
|
that these other pieces of advice do not call potentially unsafe functions."
|
||
|
(let ((advice (trusted-dirs--make-advice-function function replacement)))
|
||
|
(if (trusted-dirs--quoted-symbol-p function)
|
||
|
`(advice-add ,function :around ,advice '(:depth -100))
|
||
|
`(add-function :around ,function ,advice '(:depth -100)))))
|
||
|
|
||
|
(defun trusted-dirs-unmark-function (function)
|
||
|
"Mark FUNCTION as safe for execution in unsafe directories.
|
||
|
This undoes the effects of `trusted-dirs-mark-function-unsafe'. This only works
|
||
|
if FUNCTION is a symbol.
|
||
|
|
||
|
Note that this is a function and that is a macro. Thus, this will only work if
|
||
|
the values of `trusted-dirs-generated-function-name-prefix' and
|
||
|
`trusted-dirs-advice-function-name-suffix' are the same as when
|
||
|
`trusted-dirs-mark-function-unsafe' was compiled."
|
||
|
(cl-check-type function symbol)
|
||
|
(when-let ((advice (intern-soft
|
||
|
(format "%s%s%s"
|
||
|
trusted-dirs-generated-function-name-prefix
|
||
|
(symbol-name function)
|
||
|
trusted-dirs-advice-function-name-suffix))))
|
||
|
(advice-remove function advice)))
|
||
|
|
||
|
(provide 'trusted-dirs)
|
||
|
;;; trusted-dirs.el ends here
|