How to move a file in Lisp using rename-file - lisp

What's the best way to move a file in Lisp in an implementation-independent way? For example I have an image file:
(setq oldpath #P"SERVER:PICTURES;TEMP;PHOTO.PNG")
and I want to move it out of the TEMP directory into the PICTURES directory. This seems to work:
(setq newpath
(make-pathname
:host (pathname-host oldpath)
:directory (butlast (pathname-directory oldpath))
:name (pathname-name oldpath)
:type (pathname-type oldpath)))
(rename-file oldpath newpath)
but is there a more elegant way?
Thanks, David

I'm usually using:
(make-pathname :defaults old-path
:directory (butlast (pathname-directory oldpath)))
The :defaults argument makes sure that all relevant parts of the old pathname are being copied over.

Related

Getting "root" folder of orgmode package installation in emacs via elisp

How can I get the folder in which org-mode is installed in emacs? Depending on the way it was installed, it will be different. Is there a variable which holds this value?
I would need it to access a file which is part of the org-mode installation.
I am not looking for a particular library, but an .R file, which is an R file which I want to load programmatically into R (from elisp code).
So using
(locate-library "ob-R")
"/Users/rainerkrug/.emacs.d/org-mode/lisp/ob-R.elc"
I would then have to use the following:
(concat (locate-library "ob-R") "/../etc/")
"/Users/rainerkrug/.emacs.d/org-mode/lisp/ob-R.elc/../etc/"
And I still have to get rid of the ob-R.elc
This works, but I am looking for a function which gives me the path
(IS-THERE-SOMETHING-LIKE-THIS "org")
"/Users/rainerkrug/.emacs.d/org-mode/"
Thanks
Emacs provides a rich set of file name manipulation functions which easily solve your problem:
(expand-file-name "../etc/R" (file-name-directory (locate-library "ob-R")))
M-x locate-library RET org RET
or, if you would like to open:
M-x find-library RET org RET
You can use this:
(org-find-library-dir "org")
Or, in your case:
(concat (org-find-library-dir "org") "etc/R")
If you don't need to do this programmatically, you could use M-x describe-mode and in the description one of first lines is Org mode defined in org.el The link to org.el is clickable, and leads to org-mode's directory.
I found a solution which is efectively using locate-library and to truncate the not-needed elements (file name and last dir) and assembles them again as a path:
(locate-library "org")
"/Users/rainerkrug/.emacs.d/org-mode/lisp/org.elc"
(split-string (locate-library "org") "/")
("" "Users" "rainerkrug" ".emacs.d" "org-mode" "lisp" "org.elc")
(butlast (split-string (locate-library "org") "/") 2)
("" "Users" "rainerkrug" ".emacs.d" "org-mode")
(append (butlast (split-string (locate-library "org") "/") 2) '("etc" "R"))
("" "Users" "rainerkrug" ".emacs.d" "org-mode" "etc" "R")
(mapconcat 'identity
(append (butlast (split-string (locate-library "org") "/") 2) '("etc" "R"))
"/")
"/Users/rainerkrug/.emacs.d/org-mode/etc/R"
As pointed out in the comments, the usage of split-string is suboptimal. Please see accepted answer for the best approach.

How do I get the path from which init.el was loaded?

I am looking to create a custom config for emacs to use for Erlang work and I want to refer to my custom EDTS repo as being under the directory from which init.el was loaded. Right now I have this:
(add-to-list 'load-path "~/.emacs-edts/edts/")
But I would rather not hardcode it and refer to it by variable.
Suggestions?
Strictly speaking the answer is (file-name-directory user-init-file), but instead see C-hv user-emacs-directory
I have the following snippet in my init.el:
(setq my-init-dir
(file-name-directory
(or load-file-name (buffer-file-name))))
This has the advantage of working whether init.el is in your emacs.d directory or not.
I have the following in my init file:
(defun my-file-name-basename (s)
"The directory name, without the final part.
For example:
(my-file-name-basename \"alpha/beta/gamma\") => \"alpha/beta\""
(substring (file-name-directory s) 0 -1))
;; Note: Normally, it's not possible to find out the file a specific
;; function is defined in. However, it's possible to save the file
;; name at the time this file was loaded.
(defvar my-load-file-name load-file-name
"The file name of this file.")
(defun my-start-directory (&optional path)
"The root directory that contains this module.
When PATH is specified, return the start directory concatenated with PATH.
Otherwise return the directory with a trailing slash."
;; Note: Try to figure out where we are, so that we can add the
;; subdirectories. `load-file-name' only works when the file is
;; loaded. Picking up the file from the symbol works when this is
;; evaluated later.
(let ((file-name (or my-load-file-name
(symbol-file 'my-start-directory)
;; Default value. (This is used, for example,
;; when using `eval-buffer' or `eval-region'.)
"~/emacs")))
(let ((start (concat (my-file-name-basename
(my-file-name-basename file-name))
"/")))
(if path
(concat start path)
start))))
In addition to finding out where the file containing the above above code is located (which does not have to be the init file), it provides a convenient way to create paths based on it. For example:
(setq custom-file (my-start-directory "init/custom.el"))

Recursively adding .org files in a top-level directory for org-agenda-files takes a long time

I'm trying to find a way to quickly recurse through every subdirectory searching for org files. I've found several solutions (Elisp Cookbook, and several solutions on github), but they don't handle my real world usage (hundreds of directories (and subdirectories) and hundreds of org files). They seem to run forever on my system (Windows 7, with max-lisp-eval-depth = 10000). My work around is to add each directory manually to my org-agenda-list, but it's annoying and I know I've probably forgotten some. Any suggestions would be appreciated.
Haven't seen other people post this, so I will do.
Have you tried load "find-list" library and use its "find-lisp-find-files" function?
I added these lines in my org config and it works, but it may not fit your performance requirement:
(load-library "find-lisp")
(setq org-agenda-files
(find-lisp-find-files "FOLDERNAME" "\.org$"))
source: http://emacs-orgmode.gnu.narkive.com/n5bQRs5t/o-multiple-recursive-directories-with-org-agenda-files
The following code works well in emacs 24.3+:
;; Collect all .org from my Org directory and subdirs
(setq org-agenda-file-regexp "\\`[^.].*\\.org\\'") ; default value
(defun load-org-agenda-files-recursively (dir) "Find all directories in DIR."
(unless (file-directory-p dir) (error "Not a directory `%s'" dir))
(unless (equal (directory-files dir nil org-agenda-file-regexp t) nil)
(add-to-list 'org-agenda-files dir)
)
(dolist (file (directory-files dir nil nil t))
(unless (member file '("." ".."))
(let ((file (concat dir file "/")))
(when (file-directory-p file)
(load-org-agenda-files-recursively file)
)
)
)
)
)
(load-org-agenda-files-recursively "/path/to/your/org/dir/" ) ; trailing slash required
It does not require intermediate files creation and you can put it on a shortcut as well.
To be able to refile to any file found add this:
(setq org-refile-targets
'((nil :maxlevel . 3)
(org-agenda-files :maxlevel . 1)))
How about storing the list of org-agenda directories in a file that you (automatically) update every once in a while, when you know the direcory structure has changed and you have some time.
You could for example use something like this:
;; From http://www.emacswiki.org/emacs/ElispCookbook#toc58
(defun directory-dirs (dir)
"Find all directories in DIR."
(unless (file-directory-p dir)
(error "Not a directory `%s'" dir))
(let ((dir (directory-file-name dir))
(dirs '())
(files (directory-files dir nil nil t)))
(dolist (file files)
(unless (member file '("." ".."))
(let ((file (concat dir "/" file)))
(when (file-directory-p file)
(setq dirs (append (cons file
(directory-dirs file))
dirs))))))
dirs))
(setq my-org-agenda-root "~/org")
(setq my-org-agenda-files-list "~/.emacs.d/org-agenda-list.el")
(defun my-update-org-agenda-files ()
"Create or update the `my-org-agenda-files-list' file.
This file contains elisp code to set `org-agenda-files' to a
recursive list of all children under `my-org-agenda-root'. "
(interactive)
(with-temp-buffer
(insert
";; Warning: this file has been automatically generated\n"
";; by `my-update-org-agenda-files'\n")
(let ((dir-list (directory-dirs my-org-agenda-root))
(print-level nil)
(print-length nil))
(cl-prettyprint `(setq org-agenda-files (quote ,dir-list))))
(write-file my-org-agenda-files-list)))
(load my-org-agenda-files-list)
Every once in a while, run M-xmy-update-org-agenda-files to update the list.
As of Emacs 25, you can use directory-files-recursively which returns all files matching a regex from a root directory.
(defun org-get-agenda-files-recursively (dir)
"Get org agenda files from root DIR."
(directory-files-recursively dir "\.org$"))
(defun org-set-agenda-files-recursively (dir)
"Set org-agenda files from root DIR."
(setq org-agenda-files
(org-get-agenda-files-recursively dir)))
(defun org-add-agenda-files-recursively (dir)
"Add org-agenda files from root DIR."
(nconc org-agenda-files
(org-get-agenda-files-recursively dir)))
(setq org-agenda-files nil) ; zero out for testing
(org-set-agenda-files-recursively "~/Github") ; test set
(org-add-agenda-files-recursively "~/Dropbox") ; test add
You can view the contents of org-agenda-files by typing C-hv org-agenda-files.
Little late to the party, and probably not the answer you were hoping for, but the only way I found to speed up my agenda load times was by not including some directories which had thousands of org files.
(setq org-directory "~/org/"
my-agenda-dirs '("personal" "projects" "todos" "work")
org-agenda-files (mapcan (lambda (x) (directory-files-recursively
(expand-file-name x org-directory)
"\.org$"))
my-agenda-dirs))
Essentially rather than recursing over my entire org dir I only recurse through a select few of it's subdirs.
I also tried "recursive directory listing" at Emacs startup. It simply is way to loooong to be usable. So, try to stick with a limited number of "root" directories where you put your agenda files.
Even better is sticking with "~/org", and possibly a few subdirs like "work" and "personal", and that's it. You can put there your agenda files, and have links Inside them to your real project root dirs.
I know, this is not optimal, but I don't have anything better right now.

How to create an empty file by elisp?

I set an explicit file to customization created via the UI. It's named custom.el. Currently, I use the followed snippets to create this file if not exist.
(defconst custom-file (expand-file-name "custom.el" user-emacs-directory))
(unless (file-exists-p custom-file)
(shell-command (concat "touch " custom-file)))
There is an ugly shell-command touch in, any other elisp functions can do this?
You can use (write-region "" nil custom-file) not sure that is the ideal solution.
Perhaps a simpler solution to the underlying problem would be:
(defconst custom-file (expand-file-name "custom.el" user-emacs-directory))
;; NOERROR to ignore nonexistent file - Emacs will create it
(load custom-file t)
In other words, instead of manually creating custom.el, simply don't error when you try to load it (optional arg NOERROR is only for file existence error). Emacs will create the file the first time it writes out custom variables.
This is what I'm currently using in my init.el
I write this function below based on Joao Tavara anwser in this post: How do I create an empty file in emacs?
(defun fzl-create-empty-file-if-no-exists(filePath)
"Create a file with FILEPATH parameter."
(if (file-exists-p filePath)
(message (concat "File " (concat filePath " already exists")))
(with-temp-buffer (write-file filePath))))
(make-empty-file custom-file)
Check help to know more about it: C-h f make-empty-file

problem in `delete-directory` with enabled `delete-by-removing-to-trash`

There is a strange behavior of delete-directory function with enabled flag delete-by-removing-to-trash. It deletes files one by one instead of applying move-file-to-trash to the directory. As a result emacs deletes big directories slowly and there
are many files in the trash after deleting, so it is impossible to restore the directory.
Example:
Directory structure:
ddd/
ccc/
1.txt
There are three files in the trash after deleting ddd:
trash/
ddd/
ccc/
1.txt
instead of one:
trash/
ddd/
It is very slow, because emacs traverse directory recursively.
I can't restore deleted directory.
What i need is exactly the same behavior as of move-file-to-trash. But it should be transparent (i.e. 'D x' in dired mode). How to solve the problem? As a temporary solution i see the making advice function for `delete-directory'.
As a temporary workaround i use:
(setq delete-by-moving-to-trash t)
(defadvice dired-delete-file (around
activate-move-to-trash
activate
compile)
(if delete-by-moving-to-trash
(move-file-to-trash (ad-get-arg 0))
ad-do-it))
Here's my whole system. If you look at the bottom you'll see a nearly identical defadvice to Andreo's.
;;; trash-settings.el - Intelligent integration with system trash
;; Use the system trash, except for temp files and stuff
(require 'cl)
(defcustom system-trash-exclude-names
nil
"List of file names to exclude from system trash.
The names in this variable are matched only against the basename
of the file to be deleted."
:type '(repeat string)
:group 'trash)
(defcustom system-trash-exclude-paths
nil
"List of absolute paths to exclude from system trash.
If a path to a directory is excluded, then all the contents of that directory are also excluded."
:type '(repeat string)
:group 'trash)
(defcustom system-trash-exclude-matches
nil
"List of regexps or functions matching file names to exclude from system trash.
The matches are only applied against the file name, not the path."
:type '(repeat (choice regexp function))
:group 'trash)
(defcustom system-trash-exclude-path-matches
nil
"List of regexps or functions matching paths to exclude from system trash.
The matches are applied against the full path."
:type '(repeat (choice regexp function))
:group 'trash)
(defun call-process-discard-output (program &rest args)
"Execute program with args without saving any output.
In particular, no temp files are created."
(eval (append `(call-process ,program nil nil nil) args)))
(defun string-begins-with-p (string beginning)
"Return t if and only if string begins with beginning"
(string-match-p (concat "^" (regexp-quote beginning)) string))
(defun file-excluded-from-system-trash-p (path)
"Returns non-nil if file name is excluded from trash."
(let ((basename (file-name-nondirectory path)))
(or
(some (apply-partially 'string= basename)
system-trash-exclude-names)
(some (apply-partially 'string-begins-with-p path)
system-trash-exclude-paths)
(some (lambda (match)
(funcall
(cond ((stringp match) 'string-match-p)
((functionp protected-match) 'funcall)
(t 'ignore))
match
basename))
system-trash-exclude-matches)
(some (lambda (match)
(funcall
(cond ((stringp match) 'string-match-p)
((functionp protected-match) 'funcall)
(t 'ignore))
match
path))
system-trash-exclude-path-matches))))
(defun trash-or-rm (filename)
"Attempt to move a file to the trash. If this fails, simply delete it.
This guarantees that any deletable file will either be trashed or deleted.
If the file is excluded from the trash, it is simply deleted."
(unless (file-excluded-from-system-trash-p filename)
(ignore-errors
(call-process-discard-output "gvfs-trash" filename)))
(when (file-exists-p filename)
(call-process-discard-output "rm" "-rf" filename)))
(defalias 'system-move-file-to-trash 'trash-or-rm)
(defadvice delete-directory (around no-recursive-trash activate)
"When trashing a directory, there's no need to trash its contents first."
(if delete-by-moving-to-trash
(move-file-to-trash directory)
ad-do-it))
(defadvice dired-delete-file (around no-recursive-trash activate)
"When trashing a directory, there's no need to trash its contents first.
There's also no need to ask, because it's undoable."
(if delete-by-moving-to-trash
(move-file-to-trash file)
ad-do-it))
Edit: I should mention that my solution above requires the gvfs-trash command-line program, which is associated with GNOME. You can replace this with trash from the trash-cli package if you don't use GNOME.
My "answer" would be: M-x report-emacs-bug. Bug reports help everyone. Emacs Dev will determine whether there is actually a bug, and everyone involved will learn from the report.