From: Lars Ingebrigtsen Date: Sat, 24 Jul 2021 11:30:58 +0000 (+0200) Subject: Add new function `directory-append' X-Git-Tag: emacs-28.0.90~1726 X-Git-Url: http://git.eshelyaron.com/gitweb/?a=commitdiff_plain;h=5431a58e86d3f2579c1edf1dc8d7074de73ac694;p=emacs.git Add new function `directory-append' * doc/lispref/files.texi (Directory Names): Document it, and remove the concat-based file concatenation description. * lisp/emacs-lisp/shortdoc.el (file-name): Add. And add more expand-file-name examples. * src/fileio.c (Fdirectory_append): New function. --- diff --git a/doc/lispref/files.texi b/doc/lispref/files.texi index c7e5537c10c..ac49c5aa745 100644 --- a/doc/lispref/files.texi +++ b/doc/lispref/files.texi @@ -2343,49 +2343,21 @@ entirely of directory separators. @end example @end defun - Given a directory name, you can combine it with a relative file name -using @code{concat}: +@defun directory-append filename directory +Combine @var{filename} with @var{directory} by optionally putting a +slash in the middle. @example -(concat @var{dirname} @var{relfile}) -@end example - -@noindent -Be sure to verify that the file name is relative before doing that. -If you use an absolute file name, the results could be syntactically -invalid or refer to the wrong file. - - If you want to use a directory file name in making such a -combination, you must first convert it to a directory name using -@code{file-name-as-directory}: - -@example -(concat (file-name-as-directory @var{dirfile}) @var{relfile}) -@end example - -@noindent -Don't try concatenating a slash by hand, as in - -@example -;;; @r{Wrong!} -(concat @var{dirfile} "/" @var{relfile}) -@end example - -@noindent -because this is not portable. Always use -@code{file-name-as-directory}. - - To avoid the issues mentioned above, or if the @var{dirname} value -might be @code{nil} (for example, from an element of @code{load-path}), -use: - -@example -(expand-file-name @var{relfile} @var{dirname}) +@group +(directory-append "/tmp" "foo") + @result{} "/tmp/foo" +@end group @end example -However, @code{expand-file-name} expands leading @samp{~} in -@var{relfile}, which may not be what you want. @xref{File Name -Expansion}. +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. +@end defun To convert a directory name to its abbreviation, use this function: diff --git a/etc/NEWS b/etc/NEWS index e4b0809c5f5..a10c5800374 100644 --- a/etc/NEWS +++ b/etc/NEWS @@ -3120,6 +3120,10 @@ The former is now declared obsolete. * Lisp Changes in Emacs 28.1 ++++ +*** New function 'directory-append'. +This appends a file name to a directory name and returns the result. + +++ *** New function 'split-string-shell-command'. This splits a shell command string into separate components, diff --git a/lisp/emacs-lisp/shortdoc.el b/lisp/emacs-lisp/shortdoc.el index 22439f4c36c..7506d756d19 100644 --- a/lisp/emacs-lisp/shortdoc.el +++ b/lisp/emacs-lisp/shortdoc.el @@ -273,8 +273,15 @@ There can be any number of :example/:result elements." :eval (file-relative-name "/tmp/foo" "/tmp")) (make-temp-name :eval (make-temp-name "/tmp/foo-")) + (directory-append + :eval (directory-append "/tmp/" "foo") + :eval (directory-append "/tmp" "foo") + :eval (directory-append "/tmp" "~")) (expand-file-name - :eval (expand-file-name "foo" "/tmp/")) + :eval (expand-file-name "foo" "/tmp/") + :eval (expand-file-name "foo" "/tmp///") + :eval (expand-file-name "foo" "/tmp/foo/.././") + :eval (expand-file-name "~" "/tmp/")) (substitute-in-file-name :eval (substitute-in-file-name "$HOME/foo")) "Directory Functions" diff --git a/src/fileio.c b/src/fileio.c index 04c9d7d4af3..277da48315e 100644 --- a/src/fileio.c +++ b/src/fileio.c @@ -749,6 +749,51 @@ 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) +{ + USE_SAFE_ALLOCA; + char *p; + + CHECK_STRING (file); + CHECK_STRING (directory); + + if (SCHARS (file) == 0) + xsignal1 (Qfile_error, build_string ("Empty file name")); + + if (SCHARS (directory) == 0) + return file; + + /* Make the strings the same multibytedness. */ + if (STRING_MULTIBYTE (file) != STRING_MULTIBYTE (directory)) + { + 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 (); + return result; +} + /* NAME must be a string. */ static bool file_name_absolute_no_tilde_p (Lisp_Object name) @@ -6488,6 +6533,7 @@ This includes interactive calls to `delete-file' and defsubr (&Sdirectory_file_name); defsubr (&Smake_temp_file_internal); defsubr (&Smake_temp_name); + defsubr (&Sdirectory_append); defsubr (&Sexpand_file_name); defsubr (&Ssubstitute_in_file_name); defsubr (&Scopy_file); diff --git a/test/src/fileio-tests.el b/test/src/fileio-tests.el index b989c97fe6b..80afeae41ba 100644 --- a/test/src/fileio-tests.el +++ b/test/src/fileio-tests.el @@ -160,4 +160,12 @@ Also check that an encoding error can appear in a symlink." (should-error (file-exists-p "/foo\0bar") :type 'wrong-type-argument)) +(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") "foo//bar")) + (should-error (directory-append "foo" "")) + (should (equal (directory-append "" "bar") "bar")) + (should-error (directory-append "" ""))) + ;;; fileio-tests.el ends here