I am manually constructing path strings in Elisp by concatenating partial paths and directory names. Unfortunately sometimes the paths end with slash, sometimes not. Theref
If you deal with file manipulation, joining and splitting filepaths, checking empty directories and such, I strongly recommend installing f.el, modern file manipulation library. You will have a huge set of file and filepath manipulation functions under one namespace and will never reinvent the wheel again.
The function you need is f-join, it concatenates parts of a path, adding slash only where needed.
Unless you really care about keeping relative file names as relative, then it's always much better to avoid concat
and use expand-file-name
instead.
Something like this should work as a starting point, although you'd want to flesh it out a bit to make it platform independent, etc.
(defun append-path-component (path new-part)
(if (string-match ".*/$" path)
(concat path new-part)
(concat path "/" new-part)))
As per usual, there's probably some bit of elisp that already does this that I'm just not aware of.
The easiest way to assemble file names from parts of questionable content is with expand-file-name. For example:
(expand-file-name "foo.txt")
this common form will give you a full file name based on default-directory:
/home/me/foo.txt
but if you have a variable 'dir' whose content is "/home/them/subdir" and want to use that, do this:
(expand-file-name "foo.txt" dir)
it doesn't matter if dir ends in / or not. If you are on some other platform, and contains the other slash, it will do the right thing then too. Do you have a mix? Just stack them:
(expand-file-name "foo.txt" (expand-file-name "somesubdir" dir))
(defun* tofilename (directorylist &optional (filename nil))
"concatenate directory names into a path, with an optional file name as last part"
(concat
(mapconcat 'directory-file-name directorylist "/")
"/"
filename))
(tofilename '("~/" "Temp/") "temp.txt")
;; => "~/Temp/temp.txt"
(tofilename '("~/" "Temp/"))
;; => "~/Temp/"
(tofilename '("~/" "Temp/" "test"))
;; => "~/Temp/temp/"
(file-name-as-directory dir)
will return directory path dir
with a trailing slash, adding one if necessary, and not otherwise.
If you had your sequence of partial paths in a list, you could do something like:
(let ((directory-list '("/foo" "bar/" "p/q/" "x/y"))
(file-name "some_file.el"))
(concat
(mapconcat 'file-name-as-directory directory-list "")
file-name))
"/foo/bar/p/q/x/y/some_file.el"
or as an alternative, if you wanted to include the file name in the list, you could utilise directory-file-name
which does the opposite of file-name-as-directory
:
(let ((path-list '("/foo" "bar/" "p/q/" "x/y/some_file.el")))
(mapconcat 'directory-file-name path-list "/"))
"/foo/bar/p/q/x/y/some_file.el"
(Someone please correct me if using directory-file-name
on a non-directory is not portable?)