In Elisp, how to get path string with slash properly inserted? - emacs

I am manually constructing path strings in Elisp by concatenating partial paths and directory names. Unfortunately sometimes the paths end with slash, sometimes not. Therefore, I need to insert slash before concatenating a directory name when necessary but not otherwise. What's a good way to do this?

(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?)

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))

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.

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.

(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/"

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.

Related

get the absolute path of a file without extension

To get the absolute file path without extension in a buffer, e.g. /home/alice/hello.cpp -> /home/alice/hello, the following code works
(concat (file-name-directory (buffer-file-name)) (file-name-base (buffer-file-name)))
But it looks too verbose. Is there a much elegant way or a direct function for this?
(file-name-sans-extension (buffer-file-name))
Are you using auto-complete? It completes elisp names so I found the function in a second.
If you manipulate files very often in Elisp, i recommend installing f.el file and directory API, which adds a big amount of utility functions. For example, you can use f-no-ext to drop extension from the path.

Setting byte-compile-dest-file-function

I want to set the destination directory for emacs lisp byte compilation using relative path such as ../foo. I figured out I should use byte-compile-dest-file-function, but do not know how to set it. How can I set it?
To set the byte-compile-dest-function variable, you can use either customize-variable interactively, or setq in your init file. Since you'll have to write a function doing the job either way, I would recommand the latter, so that everything is in the same place in your init file.
For example:
(defun my-dest-function (filename)
(concat (file-name-directory filename)
"../"
(file-name-sans-extension (file-name-nondirectory filename))
".elc"))
(setq byte-compile-dest-file-function 'my-dest-function)
You can find it using C-h v followed by that variable name.
(defcustom byte-compile-dest-file-function nil
"Function for the function `byte-compile-dest-file' to call.
It should take one argument, the name of an Emacs Lisp source
file name, and return the name of the compiled file."
:group 'bytecomp
:type '(choice (const nil) function)
:version "23.2")
You can see that it is a customizable variable, so you can change it's value to "function".
EDIT: I am not so sure this is the variable you want to change. In fact, you can see that it deals with the variable directories often, I don't see how to set a certain directory where all the .elc's should go.

How to configure cleverly org-archive-location in org-mode

BACKGROUND: In org-mode, the variable org-archive-location is set to "%s_archive::" by default, so that a file "toto.org" archives into a file "toto.org_archive". I would like it to archive to "toto.ref" instead. I am using org-mode version 7.4 (out of the git server).
I would have thought it to be as simple as
(setq org-archive-location
`(replace-regexp-in-string ".org" ".ref" %s)
)
But I was pointed out that this was not proper in LISP (plus, it did not work). My final solution is as follow, you should be able to adapt to most clever configurations of org-archive-location:
(setq org-archive-location "%s::* ARCHIVES")
(defadvice org-extract-archive-file (after org-to-ref activate)
(setq ad-return-value
(replace-regexp-in-string "\\.org" ".ref" ad-return-value)
)
)
Note that:
1) I voluntarily did not add a $ at the end of ".org" so that it would properly alter "test.org.gpg" into "test.ref.gpg".
2) It seems that one should use the regular expression "\.org" (rather than, say, ".org") (longer explanation below in the answers).
You can't define a variable in Emacs such that its value is obtained by running code; variables have simple, static values.
You can achieve the effect you described by advising the function org-extract-archive-file, which is the one that generates an archive location from org-archive-location:
(defadvice org-extract-archive-file (after org-to-ref activate)
(setq ad-return-value
(replace-regexp-in-string "\\.org" ".ref" ad-return-value)))
This works for me now, but of course the internals of org-mode are subject to change and this solution may not work forever.
You should not quote an expression that you want to evaluate. Note also that in a regular expression, . matches any character.
Here is an example of how to set the file, the location (e.g., main heading) in the file, and whether or not to include additional archive information:
(let* (
(org-archive-location "~/todo.org::* TASKS")
(org-archive-save-context-info nil))
...)
You can try this: #+ARCHIVE: %s.ref:: at the beginning of your org file.
Read more about it here.
Also, other interesting option is to set inside your headtree the following, for instance:
* Main Header of tree
:PROPERTIES:
:ARCHIVE: toto.ref:: * Main Header of tree in archive file
:END:
** sub tree of main header and so on
The latter I took from this video.

elisp newbie question: Can't find 'filename' function definition in org.el?

I really love org-mode in emacs and want to customize a few things. While reading thru org.el, I'm finding several references to filename but can't find filename using describe-function?
I'm sure there's a simple answer, but I'm just learning elisp and it's not obvious. Any insight into where filename is defined? And/or if it's not a function, what is it?
For example, filename on line 25502:
(filename (if to-buffer
(expand-file-name
(concat
(file-name-sans-extension
(or (and subtree-p
(org-entry-get (region-beginning)
"EXPORT_FILE_NAME" t))
(file-name-nondirectory buffer-file-name)))
"." html-extension)
(file-name-as-directory
(or pub-dir (org-export-directory :html opt-plist))))))
That's not a function, that's a local variable created by the let special form.
Note: This is, of course, a wild guess since I cannot find the code you posted in the versions of org.el I can find, and none of them are even close to 25502 lines long.

How can I check if a file exists using Emacs Lisp?

I would like emacs to mark files that are generated as read-only when they're opened. The part of the puzzle that I'm missing is how to check if a file "exists". I currently have the following:
;;
;; get file extension
;;
(defun get-ext (file-name)
(car (cdr (split-string file-name "\\."))))
;;
;; get the base name of the file
;;
(defun base-name (file-name)
(car (split-string file-name "\\.")))
;;
;; if an 'lzz' file exists for this header, mark it as read only
;;
(defun mark-read-only ()
(if (string= (get-ext (cur-file)) "h")
(if ( ??file-exists??? (concat (base-name (cur-file)) ".lzz") )
(toggle-read-only))))
What can I use for "???file-exists???"?
Once I find this, I'll add "mark-read-only" to the appropriate hook (which I think is the find-file-hook).
BACKGROUND
We use lzz as a code generator to simplify our C/C++ development process. Briefly, lzz takes a single input file (which looks very like C/C++) and generates header and source files as appropriate.
By default, lzz includes #line directives so that the debugger points to the original source and not the generated source, however, to reduce compilation dependencies we normally disable these directives in header files. The result is that when debugging templates or inline functions, the debugger normally points to the generated header file and not the original source file.
This is not a big deal, however, recently I've found that when debugging I'll make a quick modification to the displayed file and then I'll rebuild. Of course this normally means that the change I made disappears because the file I edited is generated and so the changes are "blown away" during the library rebuild.
SOLUTION
Thanks to everyone for their help and comments. A special thanks to cobbal for pointing out the correct function to use.
Here's the resulting code (with updates based on the other comments here too):
(defun cur-file ()
"Return the filename (without directory) of the current buffer"
(file-name-nondirectory (buffer-file-name (current-buffer)))
)
(defun mark-generated-as-read-only ()
"Mark generated source files as read only.
Mark generated files (lzz or gz) read only to avoid accidental updates."
(if
(or (string= (file-name-extension (cur-file)) "h")
(string= (file-name-extension (cur-file)) "cpp"))
(cond
(
(file-exists-p (concat (file-name-sans-extension (cur-file)) ".lzz"))
(toggle-read-only))
(
(file-exists-p (concat (file-name-sans-extension (cur-file)) ".gz") )
(toggle-read-only))
)
)
)
try file-exists-p
"Return t if file filename exists (whether or not you can read it.)".
Note that it's not spesific to files and works for directories too.
Depending on what you need, you might want file-readable-p instead of file-exists-p.
Apropos will only get you so far. Icicles provides apropos completion and progressive completion which let you find help easily for command, function, variable, etc. names that match subparts in an arbitrary order (is it file-exists-p or exists-file-p?).
Use f.el, modern library for file and directory manipulation. You can use f-exists?, f-file?, f-directory? and many other predicates. The library is better than standard functions, because it's every file related function you'll ever need under one namespace.