aboutsummaryrefslogtreecommitdiffstats
path: root/elpa/compat-28.1.1.0/compat-28.el
diff options
context:
space:
mode:
Diffstat (limited to 'elpa/compat-28.1.1.0/compat-28.el')
-rw-r--r--elpa/compat-28.1.1.0/compat-28.el835
1 files changed, 835 insertions, 0 deletions
diff --git a/elpa/compat-28.1.1.0/compat-28.el b/elpa/compat-28.1.1.0/compat-28.el
new file mode 100644
index 0000000..862dd08
--- /dev/null
+++ b/elpa/compat-28.1.1.0/compat-28.el
@@ -0,0 +1,835 @@
+;;; compat-28.el --- Compatibility Layer for Emacs 28.1 -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2021, 2022 Free Software Foundation, Inc.
+
+;; Author: Philip Kaludercic <philipk@posteo.net>
+;; Maintainer: Compat Development <~pkal/compat-devel@lists.sr.ht>
+;; URL: https://git.sr.ht/~pkal/compat/
+;; Keywords: lisp
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program. If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Find here the functionality added in Emacs 28.1, needed by older
+;; versions.
+;;
+;; Do NOT load this library manually. Instead require `compat'.
+
+;;; Code:
+
+(eval-when-compile (require 'compat-macs))
+
+;;;; Defined in fns.c
+
+;;* INCOMPLETE FEATURE: Should handle multibyte regular expressions
+(compat-defun string-search (needle haystack &optional start-pos)
+ "Search for the string NEEDLE in the strign HAYSTACK.
+
+The return value is the position of the first occurrence of
+NEEDLE in HAYSTACK, or nil if no match was found.
+
+The optional START-POS argument says where to start searching in
+HAYSTACK and defaults to zero (start at the beginning).
+It must be between zero and the length of HAYSTACK, inclusive.
+
+Case is always significant and text properties are ignored."
+ :note "Prior to Emacs 27 `string-match' has issues handling
+multibyte regular expressions. As the compatibility function
+for `string-search' is implemented via `string-match', these
+issues are inherited."
+ (when (and start-pos (or (< (length haystack) start-pos)
+ (< start-pos 0)))
+ (signal 'args-out-of-range (list start-pos)))
+ (save-match-data
+ (let ((case-fold-search nil))
+ (string-match (regexp-quote needle) haystack start-pos))))
+
+(compat-defun length= (sequence length)
+ "Returns non-nil if SEQUENCE has a length equal to LENGTH."
+ (cond
+ ((null sequence) (zerop length))
+ ((consp sequence)
+ (and (null (nthcdr length sequence))
+ (nthcdr (1- length) sequence)
+ t))
+ ((arrayp sequence)
+ (= (length sequence) length))
+ ((signal 'wrong-type-argument sequence))))
+
+(compat-defun length< (sequence length)
+ "Returns non-nil if SEQUENCE is shorter than LENGTH."
+ (cond
+ ((null sequence) (not (zerop length)))
+ ((listp sequence)
+ (null (nthcdr (1- length) sequence)))
+ ((arrayp sequence)
+ (< (length sequence) length))
+ ((signal 'wrong-type-argument sequence))))
+
+(compat-defun length> (sequence length)
+ "Returns non-nil if SEQUENCE is longer than LENGTH."
+ (cond
+ ((listp sequence)
+ (and (nthcdr length sequence) t))
+ ((arrayp sequence)
+ (> (length sequence) length))
+ ((signal 'wrong-type-argument sequence))))
+
+;;;; Defined in fileio.c
+
+(compat-defun file-name-concat (directory &rest components)
+ "Append COMPONENTS to DIRECTORY and return the resulting string.
+Elements in COMPONENTS must be a string or nil.
+DIRECTORY or the non-final elements in COMPONENTS may or may not end
+with a slash -- if they don’t end with a slash, a slash will be
+inserted before contatenating."
+ (let ((seperator (eval-when-compile
+ (if (memq system-type '(ms-dos windows-nt cygwin))
+ "\\" "/")))
+ (last (if components (car (last components)) directory)))
+ (mapconcat (lambda (part)
+ (if (eq part last) ;the last component is not modified
+ last
+ (replace-regexp-in-string
+ (concat seperator "+\\'") "" part)))
+ (cons directory components)
+ seperator)))
+
+;;;; Defined in alloc.c
+
+;;* UNTESTED (but also not necessary)
+(compat-defun garbage-collect-maybe (_factor)
+ "Call ‘garbage-collect’ if enough allocation happened.
+FACTOR determines what \"enough\" means here: If FACTOR is a
+positive number N, it means to run GC if more than 1/Nth of the
+allocations needed to trigger automatic allocation took place.
+Therefore, as N gets higher, this is more likely to perform a GC.
+Returns non-nil if GC happened, and nil otherwise."
+ :note "For releases of Emacs before version 28, this function will do nothing."
+ ;; Do nothing
+ nil)
+
+;;;; Defined in filelock.c
+
+(compat-defun unlock-buffer ()
+ "Handle `file-error' conditions:
+
+Handles file system errors by calling ‘display-warning’ and
+continuing as if the error did not occur."
+ :prefix t
+ (condition-case error
+ (unlock-buffer)
+ (file-error
+ (display-warning
+ '(unlock-file)
+ (message "%s, ignored" (error-message-string error))
+ :warning))))
+
+;;;; Defined in characters.c
+
+(compat-defun string-width (string &optional from to)
+ "Handle optional arguments FROM and TO:
+
+Optional arguments FROM and TO specify the substring of STRING to
+consider, and are interpreted as in `substring'."
+ :prefix t
+ (string-width (substring string (or from 0) to)))
+
+;;;; Defined in dired.c
+
+;;* UNTESTED
+(compat-defun directory-files (directory &optional full match nosort count)
+ "Handle additional optional argument COUNT:
+
+If COUNT is non-nil and a natural number, the function will
+ return COUNT number of file names (if so many are present)."
+ :prefix t
+ (let ((files (directory-files directory full match nosort)))
+ (when (natnump count)
+ (setf (nthcdr count files) nil))
+ files))
+
+;;;; Defined in json.c
+
+(declare-function json-insert nil (object &rest args))
+(declare-function json-serialize nil (object &rest args))
+(declare-function json-parse-string nil (string &rest args))
+(declare-function json-parse-buffer nil (&rest args))
+
+(compat-defun json-serialize (object &rest args)
+ "Handle top-level JSON values."
+ :prefix t
+ :min-version "27"
+ (if (or (listp object) (vectorp object))
+ (apply #'json-serialize object args)
+ (substring (json-serialize (list object)) 1 -1)))
+
+(compat-defun json-insert (object &rest args)
+ "Handle top-level JSON values."
+ :prefix t
+ :min-version "27"
+ (if (or (listp object) (vectorp object))
+ (apply #'json-insert object args)
+ (insert (apply #'compat-json-serialize object args))))
+
+(compat-defun json-parse-string (string &rest args)
+ "Handle top-level JSON values."
+ :prefix t
+ :min-version "27"
+ (if (string-match-p "\\`[[:space:]]*[[{]" string)
+ (apply #'json-parse-string string args)
+ ;; Wrap the string in an array, and extract the value back using
+ ;; `elt', to ensure that no matter what the value of `:array-type'
+ ;; is we can access the first element.
+ (elt (apply #'json-parse-string (concat "[" string "]") args) 0)))
+
+(compat-defun json-parse-buffer (&rest args)
+ "Handle top-level JSON values."
+ :prefix t
+ :min-version "27"
+ (if (looking-at-p "[[:space:]]*[[{]")
+ (apply #'json-parse-buffer args)
+ (catch 'escape
+ (atomic-change-group
+ (with-syntax-table
+ (let ((st (make-syntax-table)))
+ (modify-syntax-entry ?\" "\"" st)
+ (modify-syntax-entry ?. "_" st)
+ st)
+ (let ((inhibit-read-only t))
+ (save-excursion
+ (insert "[")
+ (forward-sexp 1)
+ (insert "]"))))
+ (throw 'escape (elt (apply #'json-parse-buffer args) 0))))))
+
+;;;; xfaces.c
+
+(compat-defun color-values-from-color-spec (spec)
+ "Parse color SPEC as a numeric color and return (RED GREEN BLUE).
+This function recognises the following formats for SPEC:
+
+ #RGB, where R, G and B are hex numbers of equal length, 1-4 digits each.
+ rgb:R/G/B, where R, G, and B are hex numbers, 1-4 digits each.
+ rgbi:R/G/B, where R, G and B are floating-point numbers in [0,1].
+
+If SPEC is not in one of the above forms, return nil.
+
+Each of the 3 integer members of the resulting list, RED, GREEN,
+and BLUE, is normalized to have its value in [0,65535]."
+ (let ((case-fold-search nil))
+ (save-match-data
+ (cond
+ ((string-match
+ ;; (rx bos "#"
+ ;; (or (: (group-n 1 (= 1 hex)) (group-n 2 (= 1 hex)) (group-n 3 (= 1 hex)))
+ ;; (: (group-n 1 (= 2 hex)) (group-n 2 (= 2 hex)) (group-n 3 (= 2 hex)))
+ ;; (: (group-n 1 (= 3 hex)) (group-n 2 (= 3 hex)) (group-n 3 (= 3 hex)))
+ ;; (: (group-n 1 (= 4 hex)) (group-n 2 (= 4 hex)) (group-n 3 (= 4 hex))))
+ ;; eos)
+ "\\`#\\(?:\\(?1:[[:xdigit:]]\\{1\\}\\)\\(?2:[[:xdigit:]]\\{1\\}\\)\\(?3:[[:xdigit:]]\\{1\\}\\)\\|\\(?1:[[:xdigit:]]\\{2\\}\\)\\(?2:[[:xdigit:]]\\{2\\}\\)\\(?3:[[:xdigit:]]\\{2\\}\\)\\|\\(?1:[[:xdigit:]]\\{3\\}\\)\\(?2:[[:xdigit:]]\\{3\\}\\)\\(?3:[[:xdigit:]]\\{3\\}\\)\\|\\(?1:[[:xdigit:]]\\{4\\}\\)\\(?2:[[:xdigit:]]\\{4\\}\\)\\(?3:[[:xdigit:]]\\{4\\}\\)\\)\\'"
+ spec)
+ (let ((max (1- (ash 1 (* (- (match-end 1) (match-beginning 1)) 4)))))
+ (list (/ (* (string-to-number (match-string 1 spec) 16) 65535) max)
+ (/ (* (string-to-number (match-string 2 spec) 16) 65535) max)
+ (/ (* (string-to-number (match-string 3 spec) 16) 65535) max))))
+ ((string-match
+ ;; (rx bos "rgb:"
+ ;; (group (** 1 4 hex)) "/"
+ ;; (group (** 1 4 hex)) "/"
+ ;; (group (** 1 4 hex))
+ ;; eos)
+ "\\`rgb:\\([[:xdigit:]]\\{1,4\\}\\)/\\([[:xdigit:]]\\{1,4\\}\\)/\\([[:xdigit:]]\\{1,4\\}\\)\\'"
+ spec)
+ (list (/ (* (string-to-number (match-string 1 spec) 16) 65535)
+ (1- (ash 1 (* (- (match-end 1) (match-beginning 1)) 4))))
+ (/ (* (string-to-number (match-string 2 spec) 16) 65535)
+ (1- (ash 1 (* (- (match-end 2) (match-beginning 2)) 4))))
+ (/ (* (string-to-number (match-string 3 spec) 16) 65535)
+ (1- (ash 1 (* (- (match-end 3) (match-beginning 3)) 4))))))
+ ;; The "RGBi" (RGB Intensity) specification is defined by
+ ;; XCMS[0], see [1] for the implementation in Xlib.
+ ;;
+ ;; [0] http://www.nic.funet.fi/pub/X11/X11R4/DOCS/color/Xcms.text
+ ;; [1] https://gitlab.freedesktop.org/xorg/lib/libx11/-/blob/master/src/xcms/LRGB.c#L1392
+ ((string-match
+ (rx bos "rgbi:" (* space)
+ (group (? (or "-" "+"))
+ (or (: (+ digit) (? "." (* digit)))
+ (: "." (+ digit)))
+ (? "e" (? (or "-" "+")) (+ digit)))
+ "/" (* space)
+ (group (? (or "-" "+"))
+ (or (: (+ digit) (? "." (* digit)))
+ (: "." (+ digit)))
+ (? "e" (? (or "-" "+")) (+ digit)))
+ "/" (* space)
+ (group (? (or "-" "+"))
+ (or (: (+ digit) (? "." (* digit)))
+ (: "." (+ digit)))
+ (? "e" (? (or "-" "+")) (+ digit)))
+ eos)
+ spec)
+ (let ((r (round (* (string-to-number (match-string 1 spec)) 65535)))
+ (g (round (* (string-to-number (match-string 2 spec)) 65535)))
+ (b (round (* (string-to-number (match-string 3 spec)) 65535))))
+ (when (and (<= 0 r) (<= r 65535)
+ (<= 0 g) (<= g 65535)
+ (<= 0 b) (<= b 65535))
+ (list r g b))))))))
+
+;;;; Defined in subr.el
+
+;;* INCOMPLETE FEATURE: Should handle multibyte regular expressions
+(compat-defun string-replace (fromstring tostring instring)
+ "Replace FROMSTRING with TOSTRING in INSTRING each time it occurs."
+ (when (equal fromstring "")
+ (signal 'wrong-length-argument '(0)))
+ (let ((case-fold-search nil))
+ (replace-regexp-in-string
+ (regexp-quote fromstring)
+ tostring instring
+ t t)))
+
+(compat-defun always (&rest _arguments)
+ "Do nothing and return t.
+This function accepts any number of ARGUMENTS, but ignores them.
+Also see `ignore'."
+ t)
+
+;;* UNTESTED
+(compat-defun insert-into-buffer (buffer &optional start end)
+ "Insert the contents of the current buffer into BUFFER.
+If START/END, only insert that region from the current buffer.
+Point in BUFFER will be placed after the inserted text."
+ (let ((current (current-buffer)))
+ (with-current-buffer buffer
+ (insert-buffer-substring current start end))))
+
+;;* UNTESTED
+(compat-defun replace-string-in-region (string replacement &optional start end)
+ "Replace STRING with REPLACEMENT in the region from START to END.
+The number of replaced occurrences are returned, or nil if STRING
+doesn't exist in the region.
+
+If START is nil, use the current point. If END is nil, use `point-max'.
+
+Comparisons and replacements are done with fixed case."
+ (if start
+ (when (< start (point-min))
+ (error "Start before start of buffer"))
+ (setq start (point)))
+ (if end
+ (when (> end (point-max))
+ (error "End after end of buffer"))
+ (setq end (point-max)))
+ (save-excursion
+ (let ((matches 0)
+ (case-fold-search nil))
+ (goto-char start)
+ (while (search-forward string end t)
+ (delete-region (match-beginning 0) (match-end 0))
+ (insert replacement)
+ (setq matches (1+ matches)))
+ (and (not (zerop matches))
+ matches))))
+
+;;* UNTESTED
+(compat-defun replace-regexp-in-region (regexp replacement &optional start end)
+ "Replace REGEXP with REPLACEMENT in the region from START to END.
+The number of replaced occurrences are returned, or nil if REGEXP
+doesn't exist in the region.
+
+If START is nil, use the current point. If END is nil, use `point-max'.
+
+Comparisons and replacements are done with fixed case.
+
+REPLACEMENT can use the following special elements:
+
+ `\\&' in NEWTEXT means substitute original matched text.
+ `\\N' means substitute what matched the Nth `\\(...\\)'.
+ If Nth parens didn't match, substitute nothing.
+ `\\\\' means insert one `\\'.
+ `\\?' is treated literally."
+ (if start
+ (when (< start (point-min))
+ (error "Start before start of buffer"))
+ (setq start (point)))
+ (if end
+ (when (> end (point-max))
+ (error "End after end of buffer"))
+ (setq end (point-max)))
+ (save-excursion
+ (let ((matches 0)
+ (case-fold-search nil))
+ (goto-char start)
+ (while (re-search-forward regexp end t)
+ (replace-match replacement t)
+ (setq matches (1+ matches)))
+ (and (not (zerop matches))
+ matches))))
+
+;;* UNTESTED
+(compat-defun buffer-local-boundp (symbol buffer)
+ "Return non-nil if SYMBOL is bound in BUFFER.
+Also see `local-variable-p'."
+ (catch 'fail
+ (condition-case nil
+ (buffer-local-value symbol buffer)
+ (void-variable nil (throw 'fail nil)))
+ t))
+
+;;* UNTESTED
+(compat-defmacro with-existing-directory (&rest body)
+ "Execute BODY with `default-directory' bound to an existing directory.
+If `default-directory' is already an existing directory, it's not changed."
+ (declare (indent 0) (debug t))
+ (let ((quit (make-symbol "with-existing-directory-quit")))
+ `(catch ',quit
+ (dolist (dir (list default-directory
+ (expand-file-name "~/")
+ (getenv "TMPDIR")
+ "/tmp/"
+ ;; XXX: check if "/" works on non-POSIX
+ ;; system.
+ "/"))
+ (when (and dir (file-exists-p dir))
+ (throw ',quit (let ((default-directory dir))
+ ,@body)))))))
+
+;;* UNTESTED
+(compat-defmacro dlet (binders &rest body)
+ "Like `let' but using dynamic scoping."
+ (declare (indent 1) (debug let))
+ `(let (_)
+ ,@(mapcar (lambda (binder)
+ `(defvar ,(if (consp binder) (car binder) binder)))
+ binders)
+ (let ,binders ,@body)))
+
+(compat-defun ensure-list (object)
+ "Return OBJECT as a list.
+If OBJECT is already a list, return OBJECT itself. If it's
+not a list, return a one-element list containing OBJECT."
+ (if (listp object)
+ object
+ (list object)))
+
+;;;; Defined in subr-x.el
+
+(compat-defun string-clean-whitespace (string)
+ "Clean up whitespace in STRING.
+All sequences of whitespaces in STRING are collapsed into a
+single space character, and leading/trailing whitespace is
+removed."
+ :feature 'subr-x
+ (let ((blank "[[:blank:]\r\n]+"))
+ (replace-regexp-in-string
+ "^[[:blank:]\r\n]+\\|[[:blank:]\r\n]+$"
+ ""
+ (replace-regexp-in-string
+ blank " " string))))
+
+(compat-defun string-fill (string length)
+ "Clean up whitespace in STRING.
+All sequences of whitespaces in STRING are collapsed into a
+single space character, and leading/trailing whitespace is
+removed."
+ :feature 'subr-x
+ (with-temp-buffer
+ (insert string)
+ (goto-char (point-min))
+ (let ((fill-column length)
+ (adaptive-fill-mode nil))
+ (fill-region (point-min) (point-max)))
+ (buffer-string)))
+
+(compat-defun string-lines (string &optional omit-nulls)
+ "Split STRING into a list of lines.
+If OMIT-NULLS, empty lines will be removed from the results."
+ :feature 'subr-x
+ (split-string string "\n" omit-nulls))
+
+(compat-defun string-pad (string length &optional padding start)
+ "Pad STRING to LENGTH using PADDING.
+If PADDING is nil, the space character is used. If not nil, it
+should be a character.
+
+If STRING is longer than the absolute value of LENGTH, no padding
+is done.
+
+If START is nil (or not present), the padding is done to the end
+of the string, and if non-nil, padding is done to the start of
+the string."
+ :feature 'subr-x
+ (unless (natnump length)
+ (signal 'wrong-type-argument (list 'natnump length)))
+ (let ((pad-length (- length (length string))))
+ (if (< pad-length 0)
+ string
+ (concat (and start
+ (make-string pad-length (or padding ?\s)))
+ string
+ (and (not start)
+ (make-string pad-length (or padding ?\s)))))))
+
+(compat-defun string-chop-newline (string)
+ "Remove the final newline (if any) from STRING."
+ :feature 'subr-x
+ (if (and (>= (length string) 1) (= (aref string (1- (length string))) ?\n))
+ (substring string 0 -1)
+ string))
+
+(compat-defmacro named-let (name bindings &rest body)
+ "Looping construct taken from Scheme.
+Like `let', bind variables in BINDINGS and then evaluate BODY,
+but with the twist that BODY can evaluate itself recursively by
+calling NAME, where the arguments passed to NAME are used
+as the new values of the bound variables in the recursive invocation."
+ :feature 'subr-x
+ (declare (indent 2) (debug (symbolp (&rest (symbolp form)) body)))
+ (let ((fargs (mapcar (lambda (b)
+ (let ((var (if (consp b) (car b) b)))
+ (make-symbol (symbol-name var))))
+ bindings))
+ (aargs (mapcar (lambda (b) (if (consp b) (cadr b))) bindings))
+ rargs)
+ (dotimes (i (length bindings))
+ (let ((b (nth i bindings)))
+ (push (list (if (consp b) (car b) b) (nth i fargs))
+ rargs)
+ (setf (if (consp b) (car b) b)
+ (nth i fargs))))
+ (letrec
+ ((quit (make-symbol "quit")) (self (make-symbol "self"))
+ (total-tco t)
+ (macro (lambda (&rest args)
+ (setq total-tco nil)
+ `(funcall ,self . ,args)))
+ ;; Based on `cl--self-tco':
+ (tco-progn (lambda (exprs)
+ (append
+ (butlast exprs)
+ (list (funcall tco (car (last exprs)))))))
+ (tco (lambda (expr)
+ (cond
+ ((eq (car-safe expr) 'if)
+ (append (list 'if
+ (cadr expr)
+ (funcall tco (nth 2 expr)))
+ (funcall tco-progn (nthcdr 3 expr))))
+ ((eq (car-safe expr) 'cond)
+ (let ((conds (cdr expr)) body)
+ (while conds
+ (let ((branch (pop conds)))
+ (push (cond
+ ((cdr branch) ;has tail
+ (funcall tco-progn branch))
+ ((null conds) ;last element
+ (list t (funcall tco (car branch))))
+ ((progn
+ branch)))
+ body)))
+ (cons 'cond (nreverse body))))
+ ((eq (car-safe expr) 'or)
+ (if (cddr expr)
+ (let ((var (make-symbol "var")))
+ `(let ((,var ,(cadr expr)))
+ (if ,var ,(funcall tco var)
+ ,(funcall tco (cons 'or (cddr expr))))))
+ (funcall tco (cadr expr))))
+ ((eq (car-safe expr) 'condition-case)
+ (append (list 'condition-case (cadr expr) (nth 2 expr))
+ (mapcar
+ (lambda (handler)
+ (cons (car handler)
+ (funcall tco-progn (cdr handler))))
+ (nthcdr 3 expr))))
+ ((memq (car-safe expr) '(and progn))
+ (cons (car expr) (funcall tco-progn (cdr expr))))
+ ((memq (car-safe expr) '(let let*))
+ (append (list (car expr) (cadr expr))
+ (funcall tco-progn (cddr expr))))
+ ((eq (car-safe expr) name)
+ (let (sets (args (cdr expr)))
+ (dolist (farg fargs)
+ (push (list farg (pop args))
+ sets))
+ (cons 'setq (apply #'nconc (nreverse sets)))))
+ (`(throw ',quit ,expr))))))
+ (let ((tco-body (funcall tco (macroexpand-all (macroexp-progn body)))))
+ (when tco-body
+ (setq body `((catch ',quit
+ (while t (let ,rargs ,@(macroexp-unprogn tco-body))))))))
+ (let ((expand (macroexpand-all (macroexp-progn body) (list (cons name macro)))))
+ (if total-tco
+ `(let ,bindings ,expand)
+ `(funcall
+ (letrec ((,self (lambda ,fargs ,expand))) ,self)
+ ,@aargs))))))
+
+;;;; Defined in files.el
+
+(declare-function compat--string-trim-left "compat-26" (string &optional regexp))
+(declare-function compat--directory-name-p "compat-25" (name))
+(compat-defun file-name-with-extension (filename extension)
+ "Set the EXTENSION of a FILENAME.
+The extension (in a file name) is the part that begins with the last \".\".
+
+Trims a leading dot from the EXTENSION so that either \"foo\" or
+\".foo\" can be given.
+
+Errors if the FILENAME or EXTENSION are empty, or if the given
+FILENAME has the format of a directory.
+
+See also `file-name-sans-extension'."
+ (let ((extn (compat--string-trim-left extension "[.]")))
+ (cond
+ ((string= filename "")
+ (error "Empty filename"))
+ ((string= extn "")
+ (error "Malformed extension: %s" extension))
+ ((compat--directory-name-p filename)
+ (error "Filename is a directory: %s" filename))
+ (t
+ (concat (file-name-sans-extension filename) "." extn)))))
+
+;;* UNTESTED
+(compat-defun directory-empty-p (dir)
+ "Return t if DIR names an existing directory containing no other files.
+Return nil if DIR does not name a directory, or if there was
+trouble determining whether DIR is a directory or empty.
+
+Symbolic links to directories count as directories.
+See `file-symlink-p' to distinguish symlinks."
+ (and (file-directory-p dir)
+ (null (directory-files dir nil directory-files-no-dot-files-regexp t))))
+
+(compat-defun file-modes-number-to-symbolic (mode &optional filetype)
+ "Return a string describing a file's MODE.
+For instance, if MODE is #o700, then it produces `-rwx------'.
+FILETYPE if provided should be a character denoting the type of file,
+such as `?d' for a directory, or `?l' for a symbolic link and will override
+the leading `-' char."
+ (string
+ (or filetype
+ (pcase (lsh mode -12)
+ ;; POSIX specifies that the file type is included in st_mode
+ ;; and provides names for the file types but values only for
+ ;; the permissions (e.g., S_IWOTH=2).
+
+ ;; (#o017 ??) ;; #define S_IFMT 00170000
+ (#o014 ?s) ;; #define S_IFSOCK 0140000
+ (#o012 ?l) ;; #define S_IFLNK 0120000
+ ;; (8 ??) ;; #define S_IFREG 0100000
+ (#o006 ?b) ;; #define S_IFBLK 0060000
+ (#o004 ?d) ;; #define S_IFDIR 0040000
+ (#o002 ?c) ;; #define S_IFCHR 0020000
+ (#o001 ?p) ;; #define S_IFIFO 0010000
+ (_ ?-)))
+ (if (zerop (logand 256 mode)) ?- ?r)
+ (if (zerop (logand 128 mode)) ?- ?w)
+ (if (zerop (logand 64 mode))
+ (if (zerop (logand 2048 mode)) ?- ?S)
+ (if (zerop (logand 2048 mode)) ?x ?s))
+ (if (zerop (logand 32 mode)) ?- ?r)
+ (if (zerop (logand 16 mode)) ?- ?w)
+ (if (zerop (logand 8 mode))
+ (if (zerop (logand 1024 mode)) ?- ?S)
+ (if (zerop (logand 1024 mode)) ?x ?s))
+ (if (zerop (logand 4 mode)) ?- ?r)
+ (if (zerop (logand 2 mode)) ?- ?w)
+ (if (zerop (logand 512 mode))
+ (if (zerop (logand 1 mode)) ?- ?x)
+ (if (zerop (logand 1 mode)) ?T ?t))))
+
+;;* UNTESTED
+(compat-defun file-backup-file-names (filename)
+ "Return a list of backup files for FILENAME.
+The list will be sorted by modification time so that the most
+recent files are first."
+ ;; `make-backup-file-name' will get us the right directory for
+ ;; ordinary or numeric backups. It might create a directory for
+ ;; backups as a side-effect, according to `backup-directory-alist'.
+ (let* ((filename (file-name-sans-versions
+ (make-backup-file-name (expand-file-name filename))))
+ (dir (file-name-directory filename))
+ files)
+ (dolist (file (file-name-all-completions
+ (file-name-nondirectory filename) dir))
+ (let ((candidate (concat dir file)))
+ (when (and (backup-file-name-p candidate)
+ (string= (file-name-sans-versions candidate) filename))
+ (push candidate files))))
+ (sort files #'file-newer-than-file-p)))
+
+(compat-defun make-lock-file-name (filename)
+ "Make a lock file name for FILENAME.
+This prepends \".#\" to the non-directory part of FILENAME, and
+doesn't respect `lock-file-name-transforms', as Emacs 28.1 and
+onwards does."
+ (expand-file-name
+ (concat
+ ".#" (file-name-nondirectory filename))
+ (file-name-directory filename)))
+
+;;;; Defined in files-x.el
+
+(declare-function tramp-tramp-file-p "tramp" (name))
+
+;;* UNTESTED
+(compat-defun null-device ()
+ "Return the best guess for the null device."
+ (require 'tramp)
+ (if (tramp-tramp-file-p default-directory)
+ "/dev/null"
+ null-device))
+
+;;;; Defined in minibuffer.el
+
+(compat-defun format-prompt (prompt default &rest format-args)
+ "Format PROMPT with DEFAULT.
+If FORMAT-ARGS is nil, PROMPT is used as a plain string. If
+FORMAT-ARGS is non-nil, PROMPT is used as a format control
+string, and FORMAT-ARGS are the arguments to be substituted into
+it. See `format' for details.
+
+If DEFAULT is a list, the first element is used as the default.
+If not, the element is used as is.
+
+If DEFAULT is nil or an empty string, no \"default value\" string
+is included in the return value."
+ (concat
+ (if (null format-args)
+ prompt
+ (apply #'format prompt format-args))
+ (and default
+ (or (not (stringp default))
+ (not (null default)))
+ (format " (default %s)"
+ (if (consp default)
+ (car default)
+ default)))
+ ": "))
+
+;;;; Defined in windows.el
+
+;;* UNTESTED
+(compat-defun count-windows (&optional minibuf all-frames)
+ "Handle optional argument ALL-FRAMES:
+
+If ALL-FRAMES is non-nil, count the windows in all frames instead
+just the selected frame."
+ :prefix t
+ (if all-frames
+ (let ((sum 0))
+ (dolist (frame (frame-list))
+ (with-selected-frame frame
+ (setq sum (+ (count-windows minibuf) sum))))
+ sum)
+ (count-windows minibuf)))
+
+;;;; Defined in thingatpt.el
+
+(declare-function mouse-set-point "mouse" (event &optional promote-to-region))
+
+;;* UNTESTED
+(compat-defun thing-at-mouse (event thing &optional no-properties)
+ "Return the THING at mouse click.
+Like `thing-at-point', but tries to use the event
+where the mouse button is clicked to find a thing nearby."
+ :feature 'thingatpt
+ (save-excursion
+ (mouse-set-point event)
+ (thing-at-point thing no-properties)))
+
+;;;; Defined in macroexp.el
+
+;;* UNTESTED
+(compat-defun macroexp-file-name ()
+ "Return the name of the file from which the code comes.
+Returns nil when we do not know.
+A non-nil result is expected to be reliable when called from a macro in order
+to find the file in which the macro's call was found, and it should be
+reliable as well when used at the top-level of a file.
+Other uses risk returning non-nil value that point to the wrong file."
+ :feature 'macroexp
+ (let ((file (car (last current-load-list))))
+ (or (if (stringp file) file)
+ (bound-and-true-p byte-compile-current-file))))
+
+;;;; Defined in env.el
+
+;;* UNTESTED
+(compat-defmacro with-environment-variables (variables &rest body)
+ "Set VARIABLES in the environent and execute BODY.
+VARIABLES is a list of variable settings of the form (VAR VALUE),
+where VAR is the name of the variable (a string) and VALUE
+is its value (also a string).
+
+The previous values will be be restored upon exit."
+ (declare (indent 1) (debug (sexp body)))
+ (unless (consp variables)
+ (error "Invalid VARIABLES: %s" variables))
+ `(let ((process-environment (copy-sequence process-environment)))
+ ,@(mapcar (lambda (elem)
+ `(setenv ,(car elem) ,(cadr elem)))
+ variables)
+ ,@body))
+
+;;;; Defined in button.el
+
+;;* UNTESTED
+(compat-defun button-buttonize (string callback &optional data)
+ "Make STRING into a button and return it.
+When clicked, CALLBACK will be called with the DATA as the
+function argument. If DATA isn't present (or is nil), the button
+itself will be used instead as the function argument."
+ :feature 'button
+ (propertize string
+ 'face 'button
+ 'button t
+ 'follow-link t
+ 'category t
+ 'button-data data
+ 'keymap button-map
+ 'action callback))
+
+;;;; Defined in autoload.el
+
+(defvar generated-autoload-file)
+
+;;* UNTESTED
+(compat-defun make-directory-autoloads (dir output-file)
+ "Update autoload definitions for Lisp files in the directories DIRS.
+DIR can be either a single directory or a list of
+directories. (The latter usage is discouraged.)
+
+The autoloads will be written to OUTPUT-FILE. If any Lisp file
+binds `generated-autoload-file' as a file-local variable, write
+its autoloads into the specified file instead.
+
+The function does NOT recursively descend into subdirectories of the
+directory or directories specified."
+ (let ((generated-autoload-file output-file))
+ ;; We intentionally don't sharp-quote
+ ;; `update-directory-autoloads', because it was deprecated in
+ ;; Emacs 28 and we don't want to trigger the byte compiler for
+ ;; newer versions.
+ (apply 'update-directory-autoloads
+ (if (listp dir) dir (list dir)))))
+
+(provide 'compat-28)
+;;; compat-28.el ends here