summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/lispref/files.texi50
-rw-r--r--etc/NEWS4
-rw-r--r--lisp/emacs-lisp/shortdoc.el9
-rw-r--r--src/fileio.c46
-rw-r--r--test/src/fileio-tests.el8
5 files changed, 77 insertions, 40 deletions
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
@@ -3121,6 +3121,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,
respecting quoting with single ('like this') and double ("like this")
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