From: Lars Ingebrigtsen Date: Sat, 24 Jul 2021 15:22:43 +0000 (+0200) Subject: Extend directory-append to take an arbitrary number of components X-Git-Tag: emacs-28.0.90~1719 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=b4543dfa9e72deeee607ffa9396a680c51a00968;p=emacs.git Extend directory-append to take an arbitrary number of components * doc/lispref/files.texi (Directory Names): Document it. * lisp/emacs-lisp/shortdoc.el (file-name): Add new example. * src/fileio.c (Fdirectory_append): Change the function to take an arbitrary number of components. --- diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index ec8a2525ed9..a624c2eb937 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@ -2343,9 +2343,10 @@ entirely of directory separators. @end example @end defun -@defun directory-append directory filename -Combine @var{filename} with @var{directory} by optionally putting a -slash in the middle. +@defun directory-append directory &rest components +Concatenate @var{components} to @var{directory}, inserting a slash +before the components if @var{directory} or the preceding component +didn't end with a slash. @example @group @@ -2354,9 +2355,9 @@ slash in the middle. @end group @end example -This is almost the same as using @code{concat}, but @var{dirname} may -or may not end with a slash character, and this function will not -double that character. +This is almost the same as using @code{concat}, but @var{dirname} (and +the non-final components) may or may not end with slash characters, +and this function will not double those characters. @end defun To convert a directory name to its abbreviation, use this diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 7506d756d19..c507ad7c812 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -276,6 +276,7 @@ There can be any number of :example/:result elements." (directory-append :eval (directory-append "/tmp/" "foo") :eval (directory-append "/tmp" "foo") + :eval (directory-append "/tmp" "foo" "bar/" "zot") :eval (directory-append "/tmp" "~")) (expand-file-name :eval (expand-file-name "foo" "/tmp/") diff --git a/src/fileio.c b/src/fileio.c index 277da48315e..a4f08383776 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -749,48 +749,90 @@ For that reason, you should normally use `make-temp-file' instead. */) empty_unibyte_string, Qnil); } -DEFUN ("directory-append", Fdirectory_append, Sdirectory_append, 2, 2, 0, - doc: /* Return FILE (a string) appended to DIRECTORY (a string). -DIRECTORY may or may not end with a slash -- the return value from -this function will be the same. */) - (Lisp_Object directory, Lisp_Object file) +DEFUN ("directory-append", Fdirectory_append, Sdirectory_append, 1, MANY, 0, + doc: /* Append COMPONENTS to DIRECTORY and return the resulting string. +COMPONENTS must be a list of strings. 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. +usage: (record DIRECTORY &rest COMPONENTS) */) + (ptrdiff_t nargs, Lisp_Object *args) { - USE_SAFE_ALLOCA; - char *p; - - CHECK_STRING (file); - CHECK_STRING (directory); + ptrdiff_t chars = 0, bytes = 0, multibytes = 0; + Lisp_Object *elements = args; + Lisp_Object result; + ptrdiff_t i; + + /* First go through the list to check the types and see whether + they're all of the same multibytedness. */ + for (i = 0; i < nargs; i++) + { + Lisp_Object arg = args[i]; + CHECK_STRING (arg); + if (SCHARS (arg) == 0) + xsignal1 (Qfile_error, build_string ("Empty file name")); + /* Multibyte and non-ASCII. */ + if (STRING_MULTIBYTE (arg) && SCHARS (arg) != SBYTES (arg)) + multibytes++; + /* We're not adding a slash to the final part. */ + if (i == nargs - 1 + || IS_DIRECTORY_SEP (*(SSDATA (arg) + SBYTES (arg) - 1))) + { + bytes += SBYTES (arg); + chars += SCHARS (arg); + } + else + { + bytes += SBYTES (arg) + 1; + chars += SCHARS (arg) + 1; + } + } - if (SCHARS (file) == 0) - xsignal1 (Qfile_error, build_string ("Empty file name")); + /* Convert if needed. */ + if (multibytes != 0 && multibytes != nargs) + { + elements = xmalloc (nargs * sizeof *elements); + bytes = 0; + for (i = 0; i < nargs; i++) + { + Lisp_Object arg = args[i]; + if (STRING_MULTIBYTE (arg)) + elements[i] = arg; + else + elements[i] = make_multibyte_string (SSDATA (arg), SCHARS (arg), + SCHARS (arg)); + arg = elements[i]; + /* We have to recompute the number of bytes. */ + if (i == nargs - 1 + || IS_DIRECTORY_SEP (*(SSDATA (arg) + SBYTES (arg) - 1))) + bytes += SBYTES (arg); + else + bytes += SBYTES (arg) + 1; + } + } - if (SCHARS (directory) == 0) - return file; + /* Allocate an empty string. */ + if (multibytes == 0) + result = make_uninit_string (chars); + else + result = make_uninit_multibyte_string (chars, bytes); + /* Null-terminate the string. */ + *(SSDATA (result) + SBYTES (result)) = 0; - /* Make the strings the same multibytedness. */ - if (STRING_MULTIBYTE (file) != STRING_MULTIBYTE (directory)) + /* Copy over the data. */ + char *p = SSDATA (result); + for (i = 0; i < nargs; i++) { - if (STRING_MULTIBYTE (file)) - directory = make_multibyte_string (SSDATA (directory), - SCHARS (directory), - SCHARS (directory)); - else - file = make_multibyte_string (SSDATA (file), - SCHARS (file), - SCHARS (file)); - } - - /* Allocate enough extra space in case we need to put a slash in - there. */ - p = SAFE_ALLOCA (SBYTES (file) + SBYTES (directory) + 2); - ptrdiff_t offset = SBYTES (directory); - memcpy (p, SSDATA (directory), offset); - if (! IS_DIRECTORY_SEP (p[offset - 1])) - p[offset++] = DIRECTORY_SEP; - memcpy (p + offset, SSDATA (file), SBYTES (file)); - p[offset + SBYTES (file)] = 0; - Lisp_Object result = build_string (p); - SAFE_FREE (); + Lisp_Object arg = elements[i]; + memcpy (p, SSDATA (arg), SBYTES (arg)); + p += SBYTES (arg); + /* The last element shouldn't have a slash added at the end. */ + if (i < nargs -1 && !IS_DIRECTORY_SEP (*(p - 1))) + *p++ = DIRECTORY_SEP; + } + + if (multibytes != 0 && multibytes != nargs) + xfree (elements); + return result; } diff --git a/test/src/fileio-tests.el b/test/src/fileio-tests.el index 80afeae41ba..702659fa395 100644 --- a/test/src/fileio-tests.el +++ b/test/src/fileio-tests.el @@ -162,10 +162,16 @@ Also check that an encoding error can appear in a symlink." (ert-deftest fileio-tests/directory-append () (should (equal (directory-append "foo" "bar") "foo/bar")) + (should (equal (directory-append "foo" "bar") "foo/bar")) + (should (equal (directory-append "foo" "bar" "zot") "foo/bar/zot")) (should (equal (directory-append "foo/" "bar") "foo/bar")) (should (equal (directory-append "foo//" "bar") "foo//bar")) + (should (equal (directory-append "foo/" "bar/" "zot") "foo/bar/zot")) + (should (equal (directory-append "fóo" "bar") "fóo/bar")) + (should (equal (directory-append "foo" "bár") "foo/bár")) + (should (equal (directory-append "fóo" "bár") "fóo/bár")) (should-error (directory-append "foo" "")) - (should (equal (directory-append "" "bar") "bar")) + (should-error (directory-append "" "bar")) (should-error (directory-append "" ""))) ;;; fileio-tests.el ends here