My solution comes with a bonus: if you kill the buffer without saving it, Emacs will offer to delete those empty directories that were created (but only if they didn't exist before you invoked
;; Automatically create any nonexistent parent directories when
;; finding a file. If the buffer for the new file is killed without
;; being saved, then offer to delete the created directory or
(original-function filename &rest args)
"Automatically create and delete parent directories of files.
This is an `:override' advice for `find-file' and friends. It
automatically creates the parent directory (or directories) of
the file being visited, if necessary. It also sets a buffer-local
variable so that the user will be prompted to delete the newly
created directories if they kill the buffer without saving it."
;; The variable `dirs-to-delete' is a list of the directories that
;; will be automatically created by `make-directory'. We will want
;; to offer to delete these directories if the user kills the buffer
;; without saving it.
(let ((dirs-to-delete ()))
;; If the file already exists, we don't need to worry about
;; creating any directories.
(unless (file-exists-p filename)
;; It's easy to figure out how to invoke `make-directory',
;; because it will automatically create all parent directories.
;; We just need to ask for the directory immediately containing
;; the file to be created.
(let* ((dir-to-create (file-name-directory filename))
;; However, to find the exact set of directories that
;; might need to be deleted afterward, we need to iterate
;; upward through the directory tree until we find a
;; directory that already exists, starting at the
;; directory containing the new file.
;; If the directory containing the new file already exists,
;; nothing needs to be created, and therefore nothing needs to
;; be destroyed, either.
(while (not (file-exists-p current-dir))
;; Otherwise, we'll add that directory onto the list of
;; directories that are going to be created.
(push current-dir dirs-to-delete)
;; Now we iterate upwards one directory. The
;; `directory-file-name' function removes the trailing slash
;; of the current directory, so that it is viewed as a file,
;; and then the `file-name-directory' function returns the
;; directory component in that path (which means the parent
(setq current-dir (file-name-directory
;; Only bother trying to create a directory if one does not
;; already exist.
(unless (file-exists-p dir-to-create)
;; Make the necessary directory and its parents.
(make-directory dir-to-create 'parents))))
;; Call the original `find-file', now that the directory
;; containing the file to found exists. We make sure to preserve
;; the return value, so as not to mess up any commands relying on
(prog1 (apply original-function filename args)
;; If there are directories we want to offer to delete later, we
;; have more to do.
;; Since we already called `find-file', we're now in the buffer
;; for the new file. That means we can transfer the list of
;; directories to possibly delete later into a buffer-local
;; variable. But we pushed new entries onto the beginning of
;; `dirs-to-delete', so now we have to reverse it (in order to
;; later offer to delete directories from innermost to
(setq-local radian--dirs-to-delete (reverse dirs-to-delete))
;; Now we add a buffer-local hook to offer to delete those
;; directories when the buffer is killed, but only if it's
;; appropriate to do so (for instance, only if the directories
;; still exist and the file still doesn't exist).
;; The above hook removes itself when it is run, but that will
;; only happen when the buffer is killed (which might never
;; happen). Just for cleanliness, we automatically remove it
;; when the buffer is saved. This hook also removes itself when
;; run, in addition to removing the above hook.
;; Add the advice that we just defined.
(advice-add #'find-file :around
;; Also enable it for `find-alternate-file' (C-x C-v).
(advice-add #'find-alternate-file :around
;; Also enable it for `write-file' (C-x C-w).
(advice-add #'write-file :around
(defun radian--kill-buffer-delete-directory-if-appropriate ()
"Delete parent directories if appropriate.
This is a function for `kill-buffer-hook'. If
the directory containing the file for the current buffer
automatically, then offer to delete it. Otherwise, do nothing.
Also clean up related hooks."
;; Stop if there aren't any directories to delete (shouldn't
;; Stop if `radian--dirs-to-delete' somehow got set to
;; something other than a list (shouldn't happen).
;; Stop if the current buffer doesn't represent a
;; file (shouldn't happen).
;; Stop if the buffer has been saved, so that the file
;; actually exists now. This might happen if the buffer were
;; saved without `after-save-hook' running, or if the
;; `find-file'-like function called was `write-file'.
(not (file-exists-p buffer-file-name)))
(cl-dolist (dir-to-delete radian--dirs-to-delete)
;; Ignore any directories that no longer exist or are malformed.
;; We don't return immediately if there's a nonexistent
;; directory, because it might still be useful to offer to
;; delete other (parent) directories that should be deleted. But
;; this is an edge case.
(when (and (stringp dir-to-delete)
;; Only delete a directory if the user is OK with it.
(if (y-or-n-p (format "Also delete directory `%s'? "
;; The `directory-file-name' function
;; removes the trailing slash.
;; If the user doesn't want to delete a directory, then they
;; obviously don't want to delete any of its parent
;; directories, either.
;; It shouldn't be necessary to remove this hook, since the buffer
;; is getting killed anyway, but just in case...
(defun radian--remove-kill-buffer-delete-directory-hook ()
"Clean up directory-deletion hooks, if necessary.
This is a function for `after-save-hook'. Remove
`kill-buffer-hook', and also remove this function from
Canonical version here.