Much inspired by @magnars's excellent s.el and dash.el, f.el is a modern API for working with files and directories in Emacs.
It's available on Melpa:
M-x package-install f
Or you can just dump f.el
in your load path somewhere.
- f-join
(&rest args)
- f-split
(path)
- f-expand
(path &optional dir)
- f-filename
(path)
- f-dirname
(path)
- f-common-parent
(paths)
- f-ext
(path)
- f-no-ext
(path)
- f-swap-ext
(path ext)
- f-base
(path)
- f-relative
(path &optional dir)
- f-short
(path)
- f-long
(path)
- f-canonical
(path)
- f-slash
(path)
- f-full
(path)
- f-uniquify
(paths)
- f-uniquify-alist
(paths)
- f-read-bytes
(path)
- f-write-bytes
(data path)
- f-read-text
(path &optional coding)
- f-write-text
(text coding path)
- f-append-text
(text coding path)
- f-append-bytes
(text coding path)
- f-mkdir
(&rest dirs)
- f-delete
(path &optional force)
- f-symlink
(source path)
- f-move
(from to)
- f-copy
(from to)
- f-copy-contenst
(from to)
- f-touch
(path)
- f-exists?
(path)
- f-directory?
(path)
- f-file?
(path)
- f-symlink?
(path)
- f-readable?
(path)
- f-writable?
(path)
- f-executable?
(path)
- f-absolute?
(path)
- f-relative?
(path)
- f-root?
(path)
- f-ext?
(path ext)
- f-same?
(path-a path-b)
- f-parent-of?
(path-a path-b)
- f-child-of?
(path-a path-b)
- f-ancestor-of?
(path-a path-b)
- f-descendant-of?
(path-a path-b)
- f-hidden?
(path)
- f-this-file
()
- f-path-separator
()
- f-glob
(pattern &optional path)
- f-entries
(path &optional fn recursive)
- f-directories
(path &optional fn recursive)
- f-files
(path &optional fn recursive)
- f-root
()
- f-traverse-upwards
(fn &optional path)
- f-with-sandbox
(path-or-paths &rest body)
Join ARGS to a single path.
(f-join "path") ;; => "path"
(f-join "path" "to") ;; => "path/to"
(f-join "/" "path" "to" "heaven") ;; => "/path/to/heaven"
Split PATH and return list containing parts.
(f-split "path") ;; => '("path")
(f-split "path/to") ;; => '("path" "to")
(f-split "/path/to/heaven") ;; => '("/" "path" "to" "heaven")
Expand PATH relative to DIR (or ‘default-directory’).
(f-expand "name") ;; => "/default/directory/name"
(f-expand "name" "other/directory") ;; => "other/directory/name"
Return the name of PATH.
(f-filename "path/to/file.ext") ;; => "file.ext"
(f-filename "path/to/directory") ;; => "directory"
Return the parent directory to PATH.
Alias: f-parent
(f-dirname "path/to/file.ext") ;; => "path/to"
(f-dirname "path/to/directory") ;; => "path/to"
(f-dirname "/") ;; => nil
Return the deepest common parent directory of PATHS.
(f-common-parent '("foo/bar/baz" "foo/bar/qux" "foo/bar/mux")) ;; => "foo/bar/"
(f-common-parent '("/foo/bar/baz" "/foo/bar/qux" "/foo/bax/mux")) ;; => "/foo/"
(f-common-parent '("foo/bar/baz" "quack/bar/qux" "lack/bar/mux")) ;; => ""
Return the file extension of PATH.
The extension, in a file name, is the part that follows the last ’.’, excluding version numbers and backup suffixes.
(f-ext "path/to/file.ext") ;; => "ext"
(f-ext "path/to/directory") ;; => nil
Return everything but the file extension of PATH.
(f-no-ext "path/to/file.ext") ;; => "path/to/file"
(f-no-ext "path/to/directory") ;; => "path/to/directory"
Return PATH but with EXT as the new extension. EXT must not be nil or empty.
(f-swap-ext "path/to/file.ext" "org") ;; => "path/to/file.org"
(f-swap-ext "path/to/file.ext" "") ;; => error
Return the name of PATH, excluding the extension of file.
(f-base "path/to/file.ext") ;; => "file"
(f-base "path/to/directory") ;; => nil
Return PATH relative to DIR.
(f-relative "/some/path/relative/to/my/file.txt" "/some/path/") ;; => relative/to/my/file.txt
(f-relative "/default/directory/my/file.txt") ;; => my/file.txt
Return abbrev of PATH. See ‘abbreviate-file-name’.
Alias: f-abbrev
(f-short "/Users/foo/Code/bar") ;; => ~/Code/bar
(f-short "/path/to/Code/bar") ;; => /path/to/Code/bar
Return long version of PATH.
(f-long "~/Code/bar") ;; => /Users/foo/Code/bar
(f-long "/path/to/Code/bar") ;; => /path/to/Code/bar
Return the canonical name of PATH.
(f-canonical "/path/to/real/file") ;; => /path/to/real/file
(f-canonical "/link/to/file") ;; => /path/to/real/file
Append slash to PATH unless one already.
Some functions, such as ‘call-process’ requires there to be an ending slash.
(f-slash "/path/to/file") ;; => /path/to/file
(f-slash "/path/to/dir") ;; => /path/to/dir/
(f-slash "/path/to/dir/") ;; => /path/to/dir/
Return absolute path to PATH, with ending slash.
(f-full "~/path/to/file") ;; => /home/path/to/file
(f-full "~/path/to/dir") ;; => /home/path/to/dir/
(f-full "~/path/to/dir/") ;; => /home/path/to/dir/
Return unique suffixes of FILES.
This function expects no duplicate paths.
(f-uniquify '("/foo/bar" "/foo/baz" "/foo/quux")) ;; => '("bar" "baz" "quux")
(f-uniquify '("/foo/bar" "/www/bar" "/foo/quux")) ;; => '("foo/bar" "www/bar" "quux")
(f-uniquify '("/foo/bar" "/www/bar" "/www/bar/quux")) ;; => '("foo/bar" "www/bar" "quux")
(f-uniquify '("/foo/bar" "/foo/baz" "/home/www/bar" "/home/www/baz" "/var/foo" "/opt/foo/www/baz")) ;; => '("foo/bar" "www/bar" "foo/baz" "home/www/baz" "foo/www/baz" "foo")
Return alist mapping FILES to unique suffixes of FILES.
This function expects no duplicate paths.
(f-uniquify-alist '("/foo/bar" "/foo/baz" "/foo/quux")) ;; => '(("/foo/bar" . "bar") ("/foo/baz" . "baz") ("/foo/quux" . "quux"))
(f-uniquify-alist '("/foo/bar" "/www/bar" "/foo/quux")) ;; => '(("/foo/bar" . "foo/bar") ("/www/bar" . "www/bar") ("/foo/quux" . "quux"))
(f-uniquify-alist '("/foo/bar" "/www/bar" "/www/bar/quux")) ;; => '(("/foo/bar" . "foo/bar") ("/www/bar" . "www/bar") ("/www/bar/quux" . "quux"))
(f-uniquify-alist '("/foo/bar" "/foo/baz" "/home/www/bar" "/home/www/baz" "/var/foo" "/opt/foo/www/baz")) ;; => '(("/foo/bar" . "foo/bar") ("/home/www/bar" . "www/bar") ("/foo/baz" . "foo/baz") ("/home/www/baz" . "home/www/baz") ("/opt/foo/www/baz" . "foo/www/baz") ("/var/foo" . "foo"))
Read binary data from PATH.
Return the binary data as unibyte string.
(f-read-bytes "path/to/binary/data")
Write binary DATA to PATH.
DATA is a unibyte string. PATH is a file name to write to.
(f-write-bytes (unibyte-string 72 101 108 108 111 32 119 111 114 108 100) "path/to/binary/data")
Read text with PATH, using CODING.
CODING defaults to ‘utf-8’.
Return the decoded text as multibyte string.
Alias: f-read
(f-read-text "path/to/file.txt" 'utf-8)
(f-read "path/to/file.txt" 'utf-8)
Write TEXT with CODING to PATH.
TEXT is a multibyte string. CODING is a coding system to encode TEXT with. PATH is a file name to write to.
Alias: f-write
(f-write-text "Hello world" 'utf-8 "path/to/file.txt")
(f-write "Hello world" 'utf-8 "path/to/file.txt")
Append TEXT with CODING to PATH.
If PATH does not exist, it is created.
Alias: f-append
(f-append-text "Hello world" 'utf-8 "path/to/file.txt")
(f-append "Hello world" 'utf-8 "path/to/file.txt")
Append binary DATA to PATH.
If PATH does not exist, it is created.
(f-append-bytes "path/to/file" (unibyte-string 72 101 108 108 111 32 119 111 114 108 100))
Create directories DIRS.
(f-mkdir "dir") ;; => /default/directory/dir
(f-mkdir "other" "dir") ;; => /default/directory/other/dir
Delete PATH, which can be file or directory.
If FORCE is t, a directory will be deleted recursively.
(f-delete "dir")
(f-delete "other/dir" t)
(f-delete "path/to/file.txt")
Create a symlink to SOURCE from PATH.
(f-symlink "path/to/source" "path/to/link")
Move or rename FROM to TO.
(f-move "path/to/file.txt" "new-file.txt")
(f-move "path/to/file.txt" "other/path")
Copy file or directory FROM to TO.
(f-copy "path/to/file.txt" "new-file.txt")
(f-copy "path/to/dir" "other/dir")
Copy contents in directory FROM, to directory TO.
(f-copy-contents "path/to/dir" "path/to/other/dir")
Update PATH last modification date or create if it does not exist.
(f-touch "path/to/existing/file.txt")
(f-touch "path/to/non/existing/file.txt")
Return t if PATH exists, false otherwise.
(f-exists? "path/to/file.txt")
(f-exists? "path/to/dir")
Return t if PATH is directory, false otherwise.
Aliases: f-directory-p f-dir? f-dir-p
(f-directory? "path/to/file.txt") ;; => nil
(f-directory? "path/to/dir") ;; => t
Return t if PATH is file, false otherwise.
Alias: f-file-p
(f-file? "path/to/file.txt") ;; => t
(f-file? "path/to/dir") ;; => nil
Return t if PATH is symlink, false otherwise.
Alias: f-symlink-p
(f-symlink? "path/to/file.txt") ;; => nil
(f-symlink? "path/to/dir") ;; => nil
(f-symlink? "path/to/link") ;; => t
Return t if PATH is readable, false otherwise.
Alias: f-readable-p
(f-readable? "path/to/file.txt")
(f-readable? "path/to/dir")
Return t if PATH is writable, false otherwise.
Alias: f-writable-p
(f-writable? "path/to/file.txt")
(f-writable? "path/to/dir")
Return t if PATH is executable, false otherwise.
Alias: f-executable-p
(f-executable? "path/to/file.txt")
(f-executable? "path/to/dir")
Return t if PATH is absolute, false otherwise.
Alias: f-absolute-p
(f-absolute? "path/to/dir") ;; => nil
(f-absolute? "/full/path/to/dir") ;; => t
Return t if PATH is relative, false otherwise.
Alias: f-relative-p
(f-relative? "path/to/dir") ;; => t
(f-relative? "/full/path/to/dir") ;; => nil
Return t if PATH is root directory, false otherwise.
Alias: f-root-p
(f-root? "/") ;; => t
(f-root? "/not/root") ;; => nil
Return t if extension of PATH is EXT, false otherwise.
If EXT is nil or omitted, return t if PATH has any extension, false otherwise.
The extension, in a file name, is the part that follows the last ’.’, excluding version numbers and backup suffixes.
Alias: f-ext-p
(f-ext? "path/to/file.el" "el") ;; => t
(f-ext? "path/to/file.el" "txt") ;; => nil
(f-ext? "path/to/file.el") ;; => t
(f-ext? "path/to/file") ;; => nil
Return t if PATH-A and PATH-B are references to same file.
Aliases: f-same-p f-equal? f-equal-p
(f-same? "foo.txt" "foo.txt") ;; => t
(f-same? "/path/to/foo.txt" "/path/to/bar.txt") ;; => nil
Return t if PATH-A is parent of PATH-B.
Alias: f-parent-of-p
(f-parent-of? "/path/to" "/path/to/dir") ;; => t
(f-parent-of? "/path/to/dir" "/path/to") ;; => nil
(f-parent-of? "/path/to" "/path/to") ;; => nil
Return t if PATH-A is child of PATH-B.
Alias: f-child-of-p
(f-child-of? "/path/to" "/path/to/dir") ;; => nil
(f-child-of? "/path/to/dir" "/path/to") ;; => t
(f-child-of? "/path/to" "/path/to") ;; => nil
Return t if PATH-A is ancestor of PATH-B.
Alias: f-ancestor-of-p
(f-ancestor-of? "/path/to" "/path/to/dir") ;; => t
(f-ancestor-of? "/path" "/path/to/dir") ;; => t
(f-ancestor-of? "/path/to/dir" "/path/to") ;; => nil
(f-ancestor-of? "/path/to" "/path/to") ;; => nil
Return t if PATH-A is desendant of PATH-B.
Alias: f-descendant-of-p
(f-descendant-of? "/path/to/dir" "/path/to") ;; => t
(f-descendant-of? "/path/to/dir" "/path") ;; => t
(f-descendant-of? "/path/to" "/path/to/dir") ;; => nil
(f-descendant-of? "/path/to" "/path/to") ;; => nil
f-hidden? (path)
Return t if PATH is hidden, nil otherwise.
(f-hidden? "/path/to/foo") ;; => nil
(f-hidden? "/path/to/.foo") ;; => t
Alias: f-hidden-p
Return size of PATH.
If PATH is a file, return size of that file. If PATH is directory, return sum of all files in PATH.
(f-size "path/to/file.txt")
(f-size "path/to/dir")
Return the depth of PATH.
At first, PATH is expanded with ‘f-expand’. Then the full path is used to detect the depth. ’/’ will be zero depth, ’/usr’ will be one depth. And so on.
(f-depth "/") ;; 0
(f-depth "/var/") ;; 1
(f-depth "/usr/local/bin") ;; 3
Return path to this file.
(f-this-file) ;; => /path/to/this/file
Return path separator.
(f-path-separator) ;; => /
Find PATTERN in PATH.
See: file-expand-wildcards
(f-glob "path/to/*.el")
(f-glob "*.el" "path/to")
Find all files and directories in PATH.
FN - called for each found file and directory. If FN returns a thruthy value, file or directory will be included. RECURSIVE - Search for files and directories recursive.
(f-entries "path/to/dir")
(f-entries "path/to/dir" (lambda (file) (s-matches? "test" file)))
(f-entries "path/to/dir" nil t)
(f--entries "path/to/dir" (s-matches? "test" it))
Find all directories in PATH. See ‘f-entries‘.
(f-directories "path/to/dir")
(f-directories "path/to/dir" (lambda (dir) (equal (f-filename dir) "test")))
(f-directories "path/to/dir" nil t)
(f--directories "path/to/dir" (equal (f-filename it) "test"))
Find all files in PATH. See ‘f-entries‘.
(f-files "path/to/dir")
(f-files "path/to/dir" (lambda (file) (equal (f-ext file) "el")))
(f-files "path/to/dir" nil t)
(f--files "path/to/dir" (equal (f-ext it) "el"))
Return absolute root.
(f-root) ;; => "/"
Traverse up as long as FN return nil, starting at PATH.
If FN returns a non-nil value, the path sent as argument to FN is returned. If no function callback return a non-nil value, nil is returned.
(f-traverse-upwards
(lambda (path)
(f-exists? (f-expand ".git" path)))
start-path)
(f--traverse-upwards (f-exists? (f-expand ".git" it)) start-path) ;; same as above
Only allow PATH-OR-PATHS and decendants to be modified in BODY.
(f-with-sandbox foo-path
(f-touch (f-expand "foo" foo-path)))
(f-with-sandbox (list foo-path bar-path)
(f-touch (f-expand "foo" foo-path))
(f-touch (f-expand "bar" bar-path)))
(f-with-sandbox foo-path
(f-touch (f-expand "bar" bar-path))) ;; "Destructive operation outside sandbox"
Here's an example of a function that finds the Git project root.
(defun find-git-root (&optional dir)
(unless dir (setq dir (expand-file-name (file-name-directory (buffer-file-name)))))
(let ((parent (expand-file-name ".." dir)))
(unless (equal parent dir)
(if (file-exists-p (expand-file-name ".git" dir))
dir
(find-git-root parent)))))
(defun find-git-root (&optional dir)
(interactive)
(unless dir (setq dir (f-dirname (buffer-file-name))))
(let ((parent (f-parent dir)))
(unless (f-root? parent)
(if (f-exists? (f-expand ".git" dir))
dir
(find-git-root parent)))))
Now, try writing it even simpler yourself. Hint, check out f-traverse-upwards
.
Be sure to!
Install Cask if you haven't already.
Run the unit tests with:
$ make test
Do not change README.md
directly. If you want to change the README
or if you change any function comments, update the README with:
$ make docs