]> git.eshelyaron.com Git - emacs.git/commitdiff
Add new function `directory-append'
authorLars Ingebrigtsen <larsi@gnus.org>
Sat, 24 Jul 2021 11:30:58 +0000 (13:30 +0200)
committerLars Ingebrigtsen <larsi@gnus.org>
Sat, 24 Jul 2021 11:30:58 +0000 (13:30 +0200)
* 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.

doc/lispref/files.texi
etc/NEWS
lisp/emacs-lisp/shortdoc.el
src/fileio.c
test/src/fileio-tests.el

index c7e5537c10ceb7fbcdfddf91064436d1f64977a9..ac49c5aa745e6983509f90bf042cc1b7fcd1c1e9 100644 (file)
@@ -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:
index e4b0809c5f5f0426b1b8e21b3fd7825ad6f15363..a10c58003742fc9c41114b67b4017ae59ee80c7a 100644 (file)
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -3120,6 +3120,10 @@ The former is now declared obsolete.
 \f
 * 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,
index 22439f4c36cb5c5bae75e3a2bd2628e4f761256e..7506d756d198512e7881ef3223396f6548c72531 100644 (file)
@@ -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"
index 04c9d7d4af32b8e8341d332f3e3997a484553cf6..277da48315e12d204ebe3f32ff962396fec44064 100644 (file)
@@ -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);
index b989c97fe6bed553e58c49accba11f4b3d2748b1..80afeae41ba71f9ea3b853ea86f6566c4e3c5038 100644 (file)
@@ -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