Giter Site home page Giter Site logo

f.el's Introduction

f.el

https://github.com/rejeep/f.el/actions/workflows/workflow.yml/badge.svg https://img.shields.io/coveralls/rejeep/f.el.svg https://melpa.org/packages/f-badge.svg https://stable.melpa.org/packages/f-badge.svg

Much inspired by @magnarss excellent s.el and dash.el, f.el is a modern API for working with files and directories in Emacs.

Installation

It’s available on Melpa and Melpa Stable.

M-x package-install f

Or you can just dump f.el in your load path somewhere.

Table of Contents

Contributing

Check ./CONTRIBUTING.org

Documentation and examples

Paths

f-join

(f-join &rest args)

Join ARGS to a single path.

Be aware if one of the arguments is an absolute path, ‘f-join’
will discard all the preceeding arguments and make this absolute
path the new root of the generated path.
(f-join "path") ;; => "path"
(f-join "path" "to") ;; => "path/to"
(f-join "/" "path" "to" "heaven") ;; => "/path/to/heaven"
(f-join "path" "/to" "file") ;; => "/to/file"

f-split

(f-split path)

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")
(f-split "~/back/to/earth") ;; => '("~" "back" "to" "earth")

f-expand

(f-expand path &optional dir)

Expand PATH relative to DIR (or ‘default-directory’).
PATH and DIR can be either a directory names or directory file
names.  Return a directory name if PATH is a directory name, and
a directory file name otherwise.  File name handlers are
ignored.
(f-expand "name") ;; => "/default/directory/name"
(f-expand "name" "other/directory") ;; => "other/directory/name"

f-filename

(f-filename path)

Return the name of PATH.
(f-filename "path/to/file.ext") ;; => "file.ext"
(f-filename "path/to/directory") ;; => "directory"

f-dirname

(f-dirname path)

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

f-common-parent

(f-common-parent paths)

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")) ;; => ""

f-ext

(f-ext path)

Alias of file-name-extension

(f-ext "path/to/file") ;; => nil
(f-ext "path/to/file.txt") ;; => txt
(f-ext "path/to/file.txt.org") ;; => org

f-no-ext

(f-no-ext path)

Alias of file-name-sans-extension

(f-no-ext "path/to/file") ;; => path/to/file
(f-no-ext "path/to/file.txt") ;; => path/to/file
(f-no-ext "path/to/file.txt.org") ;; => path/to/file.txt

f-swap-ext

(f-swap-ext path ext)

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

f-base

(f-base path)

Return the name of PATH, excluding the extension of file.
(f-base "path/to/file.ext") ;; => "file"
(f-base "path/to/directory") ;; => nil

f-relative

(f-relative path &optional dir)

Convert FILENAME to be relative to DIRECTORY (default: ‘default-directory’).
This function returns a relative file name that is equivalent to FILENAME
when used with that default directory as the default.
If FILENAME is a relative file name, it will be interpreted as existing in
‘default-directory’.
If FILENAME and DIRECTORY lie on different machines or on different drives
on a DOS/Windows machine, it returns FILENAME in expanded form.

(fn FILENAME &optional DIRECTORY)
(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

f-short

(f-short path)

Alias of abbreviate-file-name

Alias: f-abbrev

(f-short "/Users/foo/Code/bar") ;; => ~/Code/bar
(f-short "/path/to/Code/bar") ;; => /path/to/Code/bar

f-long

(f-long path)

Return long version of PATH.
(f-long "~/Code/bar") ;; => /Users/foo/Code/bar
(f-long "/path/to/Code/bar") ;; => /path/to/Code/bar

f-canonical

(f-canonical path)

Alias of file-truename

(f-canonical "/path/to/real/file") ;; => /path/to/real/file
(f-canonical "/link/to/file") ;; => /path/to/real/file

f-slash

(f-slash path)

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/

f-full

(f-full path)

Return absolute path to PATH, with ending slash.
(f-full "~/path/to/file") ;; => /home/foo/path/to/file
(f-full "~/path/to/dir") ;; => /home/foo/path/to/dir/
(f-full "~/path/to/dir/") ;; => /home/foo/path/to/dir/

f-uniquify

(f-uniquify paths)

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

f-uniquify-alist

(f-uniquify-alist paths)

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

I/O

f-read-bytes

(f-read-bytes path)

Read binary data from PATH.

Return the binary data as unibyte string.  The optional second
and third arguments BEG and END specify what portion of the file
to read.
(f-read-bytes "path/to/binary/data")

f-write-bytes

(f-write-bytes data path)

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

f-append-bytes

(f-append-bytes text coding path)

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

f-read-text

(f-read-text path &optional coding)

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)

f-write-text

(f-write-text text coding path)

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

f-append-text

(f-append-text text coding path)

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

Destructive

f-mkdir

(f-mkdir &rest dirs)

Create directories DIRS.

DIRS should be a successive list of directories forming together
a full path.  The easiest way to call this function with a fully
formed path is using ‘f-split’ alongside it:

    (apply #'f-mkdir (f-split "path/to/file"))

Although it works sometimes, it is not recommended to use fully
formed paths in the function. In this case, it is recommended to
use ‘f-mkdir-full-path’ instead.
(f-mkdir "dir") ;; creates /default/directory/dir
(f-mkdir "other" "dir") ;; creates /default/directory/other/dir
(f-mkdir "/" "some" "path") ;; creates /some/path
(f-mkdir "~" "yet" "another" "dir") ;; creates ~/yet/another/dir

f-mkdir-full-path

(f-mkdir-full-path dir)

Create DIR from a full path.

This function is similar to ‘f-mkdir’ except it can accept a full
path instead of requiring several successive directory names.
(f-mkdir-full-path "dir") ;; creates /default/directory/dir
(f-mkdir-full-path "other/dir") ;; creates /default/directory/other/dir
(f-mkdir-full-path "/some/path") ;; creates /some/path
(f-mkdir-full-path "~/yet/another/dir") ;; creates ~/yet/another/dir

f-delete

(f-delete path &optional force)

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

f-symlink

(f-symlink source path)

Create a symlink to SOURCE from PATH.
(f-symlink "path/to/source" "path/to/link")

f-move

(f-move from to)

Move or rename FROM to TO.
If TO is a directory name, move FROM into TO.
(f-move "path/to/file.txt" "new-file.txt")
(f-move "path/to/file.txt" "other/path")

f-copy

(f-copy from to)

Copy file or directory FROM to TO.
If FROM names a directory and TO is a directory name, copy FROM
into TO as a subdirectory.
(f-copy "path/to/file.txt" "new-file.txt")
(f-copy "path/to/dir" "other/dir")

f-copy-contents

(f-copy-contents from to)

Copy contents in directory FROM, to directory TO.
(f-copy-contents "path/to/dir" "path/to/other/dir")

f-touch

(f-touch path)

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

Predicates

f-exists-p

(f-exists-p path)

Alias of file-exists-p

Alias: f-exists?

(f-exists-p "path/to/file.txt")
(f-exists-p "path/to/dir")

f-directory-p

(f-directory-p path)

Alias of file-directory-p

Aliases:

  • f-directory?
  • f-dir-p
  • f-dir?
(f-directory-p "path/to/file.txt") ;; => nil
(f-directory-p "path/to/dir") ;; => t

f-file-p

(f-file-p path)

Alias of file-regular-p

Alias: f-file?

(f-file-p "path/to/file.txt") ;; => t
(f-file-p "path/to/dir") ;; => nil

f-symlink-p

(f-symlink-p path)

Return t if PATH is symlink, false otherwise.

Alias: f-symlink?

(f-symlink-p "path/to/file.txt") ;; => nil
(f-symlink-p "path/to/dir") ;; => nil
(f-symlink-p "path/to/link") ;; => t

f-readable-p

(f-readable-p path)

Alias of file-readable-p

Alias: f-readable?

(f-readable-p "path/to/file.txt")
(f-readable-p "path/to/dir")

f-writable-p

(f-writable-p path)

Alias of file-writable-p

Alias: f-writable?

(f-writable-p "path/to/file.txt")
(f-writable-p "path/to/dir")

f-executable-p

(f-executable-p path)

Alias of file-executable-p

Alias: f-executable?

(f-executable-p "path/to/file.txt")
(f-executable-p "path/to/dir")

f-absolute-p

(f-absolute-p path)

Alias of file-name-absolute-p

Alias: f-absolute?

(f-absolute-p "path/to/dir") ;; => nil
(f-absolute-p "/full/path/to/dir") ;; => t

f-relative-p

(f-relative-p path)

Return t if PATH is relative, false otherwise.

Alias: f-relative?

(f-relative-p "path/to/dir") ;; => t
(f-relative-p "/full/path/to/dir") ;; => nil

f-root-p

(f-root-p path)

Return t if PATH is root directory, false otherwise.

Alias: f-root?

(f-root-p "/") ;; => t
(f-root-p "/not/root") ;; => nil

f-ext-p

(f-ext-p path ext)

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?

(f-ext-p "path/to/file.el" "el") ;; => t
(f-ext-p "path/to/file.el" "txt") ;; => nil
(f-ext-p "path/to/file.el") ;; => t
(f-ext-p "path/to/file") ;; => nil

f-same-p

(f-same-p path-a path-b)

Return t if PATH-A and PATH-B are references to same file.

Aliases:

  • f-same?
  • f-equal-p
  • f-equal?
(f-same-p "foo.txt" "foo.txt") ;; => t
(f-same-p "/path/to/foo.txt" "/path/to/bar.txt") ;; => nil
(f-same-p "foo/bar/../baz" "foo/baz") ;; => t

f-parent-of-p

(f-parent-of-p path-a path-b)

Return t if PATH-A is parent of PATH-B.

Alias: f-parent-of?

(f-parent-of-p "/path/to" "/path/to/dir") ;; => t
(f-parent-of-p "/path/to/dir" "/path/to") ;; => nil
(f-parent-of-p "/path/to" "/path/to") ;; => nil

f-child-of-p

(f-child-of-p path-a path-b)

Return t if PATH-A is child of PATH-B.

Alias: f-child-of?

(f-child-of-p "/path/to" "/path/to/dir") ;; => nil
(f-child-of-p "/path/to/dir" "/path/to") ;; => t
(f-child-of-p "/path/to" "/path/to") ;; => nil

f-ancestor-of-p

(f-ancestor-of-p path-a path-b)

Return t if PATH-A is ancestor of PATH-B.

Alias: f-ancestor-of?

(f-ancestor-of-p "/path/to" "/path/to/dir") ;; => t
(f-ancestor-of-p "/path" "/path/to/dir") ;; => t
(f-ancestor-of-p "/path/to/dir" "/path/to") ;; => nil
(f-ancestor-of-p "/path/to" "/path/to") ;; => nil

f-descendant-of-p

(f-descendant-of-p path-a path-b)

Return t if PATH-A is desendant of PATH-B.

Alias: f-descendant-of?

(f-descendant-of-p "/path/to/dir" "/path/to") ;; => t
(f-descendant-of-p "/path/to/dir" "/path") ;; => t
(f-descendant-of-p "/path/to" "/path/to/dir") ;; => nil
(f-descendant-of-p "/path/to" "/path/to") ;; => nil

f-hidden-p

(f-hidden-p path)

Return t if PATH is hidden, nil otherwise.

BEHAVIOR controls when a path should be considered as hidden
depending on its value.  Beware, if PATH begins with "./", the
current dir "." will not be considered as hidden.

When BEHAVIOR is nil, it will only check if the path begins with
a dot, as in .a/b/c, and return t if there is one.  This is the
old behavior of f.el left as default for backward-compatibility
purposes.

When BEHAVIOR is ANY, return t if any of the elements of PATH is
hidden, nil otherwise.

When BEHAVIOR is LAST, return t only if the last element of PATH
is hidden, nil otherwise.

TODO: Hidden directories and files on Windows are marked
differently than on *NIX systems.  This should be properly
implemented.

Alias: f-hidden?

(f-hidden-p "path/to/foo") ;; => nil
(f-hidden-p ".path/to/foo") ;; => t
(f-hidden-p "path/.to/foo") ;; => nil
(f-hidden-p "path/to/.foo") ;; => nil
(f-hidden-p ".path/to/foo" 'any) ;; => t
(f-hidden-p "path/.to/foo" 'any) ;; => t
(f-hidden-p "path/to/.foo" 'any) ;; => t
(f-hidden-p ".path/to/foo" 'last) ;; => nil
(f-hidden-p "path/.to/foo" 'last) ;; => nil
(f-hidden-p "path/to/.foo" 'last) ;; => t

f-empty-p

(f-empty-p path)

If PATH is a file, return t if the file in PATH is empty, nil otherwise.
If PATH is directory, return t if directory has no files, nil otherwise.

Alias: f-empty?

(f-empty-p "/path/to/empty-file") ;; => t
(f-empty-p "/path/to/file-with-contents") ;; => nil
(f-empty-p "/path/to/empty-dir/") ;; => t
(f-empty-p "/path/to/dir-with-contents/") ;; => nil

f-newer-p

(f-newer-p file other &optional method)

Compare if FILE is newer than OTHER.

For more info on METHOD, see ‘f--date-compare’.

Alias: f-newer?

(f-newer-p "newer.txt" "older.txt") ;; t
(f-newer-p "older.txt""newer.txt" ) ;; nil
(f-newer-p "same1.txt" "same2.txt") ;; nil

f-older-p

(f-older-p file other &optional method)

Compare if FILE is older than OTHER.

For more info on METHOD, see ‘f--date-compare’.

Alias: f-older?

(f-older-p "older.txt" "newer.txt") ;; t
(f-older-p "newer.txt""older.txt" ) ;; nil
(f-older-p "same1.txt" "same2.txt") ;; nil

f-same-time-p

(f-same-time-p file other &optional method)

Check if FILE and OTHER share the same access or modification time.

For more info on METHOD, see ‘f--date-compare’.

Alias: f-same-time?

(f-same-time-p "same1.txt" "same2.txt") ;; t
(f-same-time-p "newer.txt" "older.txt") ;; nil
(f-same-time-p "older.txt" "newer.txt") ;; nil

Stats

f-size

(f-size path)

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

f-depth

(f-depth path)

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

f-change-time

(f-change-time path &optional timestamp-p)

Return the last status change time of PATH.

The status change time (ctime) of PATH in the same format as
‘current-time’.  For details on TIMESTAMP-P and the format of the
returned value, see ‘f--get-time’.
(f-change-time "path/to/file.txt")         ;; (25517 48756 26337 111000)
(f-change-time "path/to/dir")              ;; (25517 57887 344657 210000)
(f-change-time "path/to/file.txt" t)       ;; (1672330868026337111 . 1000000000)
(f-change-time "path/to/dir" t)            ;; (1672339999344657210 . 1000000000)
(f-change-time "path/to/file.txt"'seconds) ;; 1672330868
(f-change-time "path/to/dir"'seconds)      ;; 1672339999

f-modification-time

(f-modification-time path &optional timestamp-p)

Return the last modification time of PATH.
The modification time (mtime) of PATH in the same format as
‘current-time’.  For details on TIMESTAMP-P and the format of the
returned value, see ‘f--get-time’.
(f-modification-time "path/to/file.txt")          ;; (25517 48756 26337 111000)
(f-modification-time "path/to/dir")               ;; (25517 57887 344657 210000)
(f-modification-time "path/to/file.txt" t)        ;; (1672330868026337111 . 1000000000)
(f-modification-time "path/to/dir" t)             ;; (1672339999344657210 . 1000000000)
(f-modification-time "path/to/file.txt" 'seconds) ;; 1672330868
(f-modification-time "path/to/dir" 'seconds)      ;; 1672339999

f-access-time

(f-access-time path &optional timestamp-p)

Return the last access time of PATH.
The access time (atime) of PATH is in the same format as
‘current-time’.  For details on TIMESTAMP-P and the format of the
returned value, see ‘f--get-time’.
(f-access-time "path/to/file.txt")          ;; (25517 48756 26337 111000)
(f-access-time "path/to/dir")               ;; (25517 57887 344657 210000)
(f-access-time "path/to/file.txt" t)        ;; (1672330868026337111 . 1000000000)
(f-access-time "path/to/dir" t)             ;; (1672339999344657210 . 1000000000)
(f-access-time "path/to/file.txt" 'seconds) ;; 1672330868
(f-access-time "path/to/dir" 'seconds)      ;; 1672339999

Misc

f-this-file

(f-this-file)

Return path to this file.
(f-this-file) ;; => /path/to/this/file

f-path-separator

(f-path-separator)

Return path separator.
(f-path-separator) ;; => /

f-glob

(f-glob pattern &optional path)

Find PATTERN in PATH.
(f-glob "path/to/*.el")
(f-glob "*.el" "path/to")

f-entries

(f-entries path &optional fn recursive)

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

f-directories

(f-directories path &optional fn recursive)

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

f-files

(f-files path &optional fn recursive)

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

f-root

(f-root)

Return absolute root.
(f-root) ;; => "/"

f-traverse-upwards

(f-traverse-upwards fn &optional path)

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

f-with-sandbox

(f-with-sandbox path-or-paths &rest body)

Only allow PATH-OR-PATHS and descendants 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"

Example

Here’s an example of a function that finds the Git project root.

Using standard Emacs builtin functions

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

Using f.el

(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.

f.el's People

Contributors

darkfeline avatar dr-scsi avatar dsx avatar ebpa avatar fuco1 avatar github-actions[bot] avatar jaremko avatar leodag avatar marcinant avatar mwfogleman avatar non-nil avatar pd avatar phillord-ncl avatar phst avatar phundrak avatar randomwangran avatar rejeep avatar sachac avatar seanfisk avatar sergv avatar shlevy avatar silex avatar spwhitton avatar sviridov avatar swsnr avatar syohex avatar tarsius avatar tmalsburg avatar victorteokw avatar wilfred avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

f.el's Issues

f-up return value

Currently f-up returns the file system root, if the given predicate never returns non-nil.

Hence, the caller can't use the truthyness of the return value to determine whether the predicate ever matched. Instead, they need to check the return value with f-root? and with the predicate (in case that the file system root could have matched).

That is, instead of (when (f-up #'my-predicate directory) …), you need to use (let ((ancestor (f-up #'my-predicate directory))) (when (or (not (f-root? ancestor)) (my-predicate ancestor) …).

I think f-up should be deprecated, and replaced with a function f-traverse-upwards that behaves the same, but returns nil, if the predicate never returned non-nil.

f-entries can't handle unreadable directories

If the directory tree being traversed by f-entries has any unreadable directories,
f-entries still tries to recurse into it and throws an error. Here ~/.cache/dconf is
not readable by the emacs process, and f-entries returns no results:

ELISP> (f-entries "/home/work/.cache/" nil t)
*** Eval error ***  Opening directory: Permission denied, /home/work/.cache/dconf

The expected result should be that unreadable directories are in the results, but no recursion is performed on them.

`f-copy` to copy all files

Hi,

How could one simulate the following cp action:
cp -r ~/path/to/source/ ~/path/to/target/

The following syntax does not have the same effect:
(f-copy "~/path/to/source/" "~/path/to/target/"

As it copies source dir into target dir.

Thank you so much for f.el.

Perhaps tag a release?

The ebal package depends on the f-swap-ext function that isn't in any released version of f. That means it can't depend on anything but a melpa version.

Version 1.18.1 or 0.18.1?

The new release is tagged 1.18.1, but in f.el the release is given as 0.18.1. Which is correct? Since the previous release was 0.17.3 I assume that the new one is 0.18.1 and the tag is incorrect, but please confirm. Thanks!

[Bug]: f-shortdoc.el:31:1:Error: Cannot open load file: No such file or directory, shortdoc

Contact Details

No response

Expected behavior

Hi. I've been getting an error when updating recently. I suspect that this is because shortdoc is only available for more recent versions of Emacs (I'm using version 27.2).

Actual behavior

When updating over the last couple of weeks I get following output.

`Compiling internal form(s) at Wed Jun 8 07:35:09 2022
Leaving directory ‘/home/david/.emacs.d/elpa/f-20220607.1618’

Compiling file /home/david/.emacs.d/elpa/f-20220607.1618/f-shortdoc.el at Wed Jun 8 07:35:09 2022
Entering directory ‘/home/david/.emacs.d/elpa/f-20220607.1618/’
f-shortdoc.el:31:1:Error: Cannot open load file: No such file or directory, shortdoc

Compiling file /home/david/.emacs.d/elpa/f-20220607.1618/f.el at Wed Jun 8 07:35:09 2022`

f.el version

master

Emacs version

27.2

Relevant code or log output

No response

Loading f-shortdoc slows down the loading process

Contact Details

No response

Expected behavior

Loading f.el on Emacs 28+ shouldn't be slower than older versions

Actual behavior

Requiring the uncompiled "f-shortdoc" make loading "f.el" ~3 times slower.

Just using an newer version of Emacs makes startup a bit slower doesn't sound right.

f.el version

master

Emacs version

master

Relevant code or log output

With s, dash, shortdoc already loaded. f, f-short aren't.

(benchmark-progn (require 'f-shortdoc))
> Elapsed time: 0.001459s
(benchmark-progn (require 'f))
> Elapsed time: 0.000756s

f-up misbehaving for top-level directories

There is a simple test for f-up:

(ert-deftest f-up-test/true ()
  (with-playground
   (should (equal f-test/playground-path (f-up (lambda (path) t))))))

This test seems to imply that dir and (f-up (lambda (path) t) dir) should always be equal, which seems reasonable. However, the current implementation of f-up doesn't have this invariant for top-level directories:

(mapcar
 (lambda (dir)
   (list dir (f-up (lambda (path) t) dir)))
 '("/usr/share" "/usr" "/"))
;; Result:
(("/usr/share" "/usr/share") ("/usr" "/") ("/" "/"))

This is version 20150113.24 of f.el on Emacs 25.0.50.1.

f-uniquify-alist performance issue on files vs directories

Great library guys, real help.

So I've been using f-uniquify-alist along with projectile which is cool. I just noticed in a big project where there are 642 files and 140 directories, said function hangs when I run it with projectile-current-project-dirs and weirdly it doesn't hang when it is run with projectile-current-project-files. This is sort of weird when there are more files than there are directories and yet it chokes.

After some investigation, given a minimal test path list of (list "a/b/c/d/" "a/B/c/d/"); this creates an infinite loop with (s-chop-prefix "d" "a/b/c/d/") instead of the correct (s-chop-prefix "d/" "a/b/c/d/").

f not in melpa through emacs package manager; but in "https://melpa.org/#/" website. it's so surprise

eyedropper 20150509.1345 available melpa Pick foreground and background colors at cursor or pointer.
eyuml 20141028.1527 available melpa Write textual uml diagram from emacs using yuml.me
ez-query-replace 20140810.517 available melpa a smarter context-sensitive query-replace that can be reapplied
f90-interface-b... 1.1 available gnu Parse and browse f90 interfaces
fabric 20141024.322 available melpa Launch Fabric using Emacs
face-remap+ 20150104.1358 available melpa Extensions to standard library face-remap.el'. facemenu+ 20150816.1953 available melpa Extensions tofacemenu.el'.
faces+ 20150104.1400 available melpa Extensions to `faces.el'.

f-parent is inconsistent wrt ending slash

(f-parent "/foo/bar/baz") ;; => "/foo/bar"
(f-parent "foo/bar/baz")  ;; => "foo/bar/"

I find the kind of "reverse symmetry" pretty funny :D

Anyway, this also breaks f-common-parent. I tried wrapping it with f-slash but that doesn't append the final slash in some cases, which leaves me puzzled as to what is its purpose then?

[feature-request + contribution] recursive f-mkdir

Hi,
I was looking for a way to implement recursive mkdir (so mkdir -p). Since the library does not have such a function out of the box, I would like to make a feature request for this. I am not sure how to do feature requests...

I can go one step further, and propose some code which does this. I have called it f-mkdir-recursive. It uses the sneaky f-traverse-upwards function and FILO (push/pop). If it is good, quality-wise, I would like to contribute it to f.el.

(defun f-mkdir-recursive (dir &optional ack-p)
  "Recursively create a directory if it doesn't already exist. The second argument prints 
   an acknowledgment with the path of the newly created directory."
  (interactive)

  (let (dirs-to-mkdir)

    (f-traverse-upwards
     (lambda (path)
       (let ((f-exists-p (f-exists? path)))
         (unless f-exists-p
           (push path dirs-to-mkdir))
         f-exists-p))
     dir)

    (dolist (dir-to-mkdir dirs-to-mkdir)
      (f-mkdir dir-to-mkdir))

    (when (and ack-p dirs-to-mkdir)
      (message "Created directory %s." dir))))

All suggestions about how to make the function more efficient are super welcome. And if the maintainers think I can contribute to f.el, I'll be happy to! I just don't know how contributing code works in terms of the git commands etc....

EDIT: I renamed the function from f-mkdir-p to f-mkdir-recursive since -p in elisp is used for other things (I am still not clear on what these things are, but they definitely don't include recursion).

f requires dash 2.2 but that does not yet exist

Hi

Maybe I am misreading things, but I just noticed that Dash on elpa is at version: 2.12.0. However, f on melpa "Requires: dash-2.2.0".

I noticed this when running emacs -Q and package-initialize

Unable to activate package ‘f’.
Required package ‘dash-2.2.0’ is unavailable

When I run emacs (without the -Q) this does not seem to be a problem (and that is confusing).

New Release Request

Hi, thanks for the great package! Are you planning on making a new stable/versioned release soon? It'd be really nice to have a stable version assigned to the recent backwards incompatible changes.

f-join not working as expected

I am trying to join two path using f-join function.

(message "%s" (f-join "C:/Program Files (x86)/Microsoft Visual Studio/" "/Community/VC/Tools/MSVC/"))

Output

c:/Community/VC/Tools/MSVC/

Expected Output

C:/Program Files (x86)/Microsoft Visual Studio/Community/VC/Tools/MSVC/

Any idea? 😕

Clarify f-ext documentation

Currently, f-ext is described as follows:

Return the file extension of PATH.

However, it uses file-name-extension, which strips version numbers and backup suffixes. It would be nice if that fact could be mentioned as well. I fell for that today, because

(f-files "/some/path"
         (lambda (x)
           (f-ext? x "md")))

will also return all backup files (ending in "md~")

Chmod and chown functionality

Should functionality related to chmod and chown introduced? Making files executable, for example. Emacs has set-file-modes, but it requires an integer for a 2nd argument, which is unintuitive.

`f-join` does not behave like `expand-file-name`.

I beleaved that f-join behaves like expand-file-name (or concat if args are valid). However it does not currently:

(expand-file-name "dir/" "/path/to")                  ; => "/path/to/dir/"
(f-join "/path" "to" "dir/")                          ; => "/path/to/dir"
(file-name-as-directory (f-join "/path" "to" "dir/")) ; => "/path/to/dir/"

Sometimes this difference causes bugs. IMHO lisp functions should not depend on whether directory name ends slash or not, but in practice, for example,

(setq auto-save-list-file-prefix (f-join "~" ".emacs.d" "auto-save-list/"))

will cause unpleasant result. I feel the name auto-save-list-file-prefix is fair and the "bug" is due to the difference between expected behavior from the name f-join and its actual behavior.

It is easy to fix but the change breaks adjointness (relation nearly inverse) of f-join and f-split. How do you think of it?

If you agree, I'll send PR. If not, please declare that and note this case in doc-string and test.

Posix stat metadata

Would it be possible to add something like f-stat, which would return Posix stat metadata about a given path?

Right now one would need to have a system call to fecth that metadata.

(f-stat "file.txt")
;; device  16777222
;; inode   4080486
;; mode    33188
;; nlink   1
;; uid     501
;; gid     20
;; rdev    0
;; size    1500520
;; atime   1451758319
;; mtime   1423242348
;; ctime   1451831422
;; blksize 4096
;; blocks  2936
;; link nil

I find f.el extremely useful. Thank you so much.

Thoughts on accepting a PR for f-contract

You have f-expand. What about supporting f-contract?

(f-contract "/home/wpcarro/file.txt") ;; => "~/file.txt"

I often need this to write predicates that work across my different machines that return different values for ~. I'm happy to create a PR if you're open to the idea.

If this already exists, and I've missed the function, please let me know!

No changes in v0.18.1?

Given

artemis ~/src/f-el % git diff v0.17.3..v0.18.1
diff --git a/f.el b/f.el
index 8d3223e..f2e077c 100644
--- a/f.el
+++ b/f.el
@@ -4,7 +4,7 @@

 ;; Author: Johan Andersson <[email protected]>
 ;; Maintainer: Johan Andersson <[email protected]>
-;; Version: 0.17.3
+;; Version: 0.18.1
 ;; Keywords: files, directories
 ;; URL: http://github.com/rejeep/f.el
 ;; Package-Requires: ((s "1.7.0") (dash "2.2.0"))

did you perhaps forget to merge in the development branch before tagging the new release? Thanks.

v0.18.0 tag

Hello,

Version 0.18.0 has not yet been released, but the f.el repository has a v0.18.0 tag. This is confusing Debian tooling which thinks that a new upstream version is available. I would really appreciate it if this false tag could be removed:

git tag -d v0.18.0
git push origin :refs/tags/v0.18.0

Thanks.

Sean

Tests broken on Emacs master

Emacs master (and the pretest for 26) have changed the semantics of various file operations due to security concerns, causing several f.el tests to fail. Specifically:

$ make EMACS=/home/p/emacs-master/src/emacs
rm -f f.elc
make unit
make[1]: Entering directory '/home/p/f.el'
cask exec ert-runner
...........................Test f-copy-contents-test/copy-directory backtrace:

  copy-file("/home/p/f.el/test/playgrou
  (if (f-file\? from) (copy-file from to) (if (> emacs-major-version 2
  (if f--guard-paths (if (---truthy\? (let (needle) (let ((list f--gua
  f-copy("/home/p/f.el/test/playground/
  (let ((it (car list))) (f-copy it to))
  (while list (let ((it (car list))) (f-copy it to)) (setq it-index (1
  (let ((list (f-entries from)) (it-index 0)) (while list (let ((it (c
  f-copy-contents("from" "to")
  (let ((default-directory f-test/playground-path)) (mapc (function (l
  (closure (t) nil (let ((default-directory f-test/playground-path)) (
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name f-copy-contents-test/copy-directory :
  ert-run-or-rerun-test(#s(ert--stats :selector (and t) :tests [#s(ert
  ert-run-tests((and t) (lambda (event-type &rest event-args) (cond ((
  ert-runner/run-tests-batch((and t))
  (let ((stats (ert-runner/run-tests-batch selector))) (kill-emacs (if
  ert-runner/run-tests-batch-and-exit((and t))
  (if ert-runner-verbose (ert-runner/run-tests-batch-and-exit ert-runn
  (let ((test-files (ert-runner--test-files tests)) (test-helper (f-ex
  ert-runner/run()
  apply(ert-runner/run nil)
  commander--handle-command(nil)
  commander-parse(nil)
  (if commander-parsing-done nil (commander-parse (or commander-args (
  eval-buffer(#<buffer  *load*> nil "/home/p
  load-with-code-conversion("/home/p/f.
  load("/home/p/f.el/.cask/27.0/elpa/er
  command-line-1(("-scriptload" "/home/p
  command-line()
  normal-top-level()

Test f-copy-contents-test/copy-directory condition:

    (file-already-exists "File already exists" "/home/p/f.el/test/playground/to")

F...Test f-copy-test/copy-absolute-dir-exists backtrace:

  make-directory-internal("/home/p/f.el
  make-directory("/home/p/f.el/test/pla
  copy-directory("/home/p/f.el/test/pla
  (if (> emacs-major-version 23) (copy-directory from to) (if (f-dir\?
  (if (f-file\? from) (copy-file from to) (if (> emacs-major-version 2
  (if f--guard-paths (if (---truthy\? (let (needle) (let ((list f--gua
  f-copy("/home/p/f.el/test/playground/
  (let ((default-directory f-test/playground-path)) (mapc (function (l
  (closure (t) nil (let ((default-directory f-test/playground-path)) (
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name f-copy-test/copy-absolute-dir-exists 
  ert-run-or-rerun-test(#s(ert--stats :selector (and t) :tests [#s(ert
  ert-run-tests((and t) (lambda (event-type &rest event-args) (cond ((
  ert-runner/run-tests-batch((and t))
  (let ((stats (ert-runner/run-tests-batch selector))) (kill-emacs (if
  ert-runner/run-tests-batch-and-exit((and t))
  (if ert-runner-verbose (ert-runner/run-tests-batch-and-exit ert-runn
  (let ((test-files (ert-runner--test-files tests)) (test-helper (f-ex
  ert-runner/run()
  apply(ert-runner/run nil)
  commander--handle-command(nil)
  commander-parse(nil)
  (if commander-parsing-done nil (commander-parse (or commander-args (
  eval-buffer(#<buffer  *load*> nil "/home/p
  load-with-code-conversion("/home/p/f.
  load("/home/p/f.el/.cask/27.0/elpa/er
  command-line-1(("-scriptload" "/home/p
  command-line()
  normal-top-level()

Test f-copy-test/copy-absolute-dir-exists condition:

    (file-already-exists "File exists" "/home/p/f.el/test/playground/bar")

F..Test f-copy-test/copy-relative-dir-exists backtrace:

  make-directory-internal("/home/p/f.el
  make-directory("/home/p/f.el/test/pla
  copy-directory("foo" "bar")
  (if (> emacs-major-version 23) (copy-directory from to) (if (f-dir\?
  (if (f-file\? from) (copy-file from to) (if (> emacs-major-version 2
  (if f--guard-paths (if (---truthy\? (let (needle) (let ((list f--gua
  f-copy("foo" "bar")
  (let ((default-directory f-test/playground-path)) (mapc (function (l
  (closure (t) nil (let ((default-directory f-test/playground-path)) (
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name f-copy-test/copy-relative-dir-exists 
  ert-run-or-rerun-test(#s(ert--stats :selector (and t) :tests [#s(ert
  ert-run-tests((and t) (lambda (event-type &rest event-args) (cond ((
  ert-runner/run-tests-batch((and t))
  (let ((stats (ert-runner/run-tests-batch selector))) (kill-emacs (if
  ert-runner/run-tests-batch-and-exit((and t))
  (if ert-runner-verbose (ert-runner/run-tests-batch-and-exit ert-runn
  (let ((test-files (ert-runner--test-files tests)) (test-helper (f-ex
  ert-runner/run()
  apply(ert-runner/run nil)
  commander--handle-command(nil)
  commander-parse(nil)
  (if commander-parsing-done nil (commander-parse (or commander-args (
  eval-buffer(#<buffer  *load*> nil "/home/p
  load-with-code-conversion("/home/p/f.
  load("/home/p/f.el/.cask/27.0/elpa/er
  command-line-1(("-scriptload" "/home/p
  command-line()
  normal-top-level()

Test f-copy-test/copy-relative-dir-exists condition:

    (file-already-exists "File exists" "/home/p/f.el/test/playground/bar")

F........................................................................................................Test f-move-test/move-absolute-path backtrace:

  rename-file("/home/p/f.el/test/playgr
  (if f--guard-paths (if (---truthy\? (let (needle) (let ((list f--gua
  f-move("/home/p/f.el/test/playground/
  (let ((default-directory f-test/playground-path)) (mapc (function (l
  (closure (t) nil (let ((default-directory f-test/playground-path)) (
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name f-move-test/move-absolute-path :docum
  ert-run-or-rerun-test(#s(ert--stats :selector (and t) :tests [#s(ert
  ert-run-tests((and t) (lambda (event-type &rest event-args) (cond ((
  ert-runner/run-tests-batch((and t))
  (let ((stats (ert-runner/run-tests-batch selector))) (kill-emacs (if
  ert-runner/run-tests-batch-and-exit((and t))
  (if ert-runner-verbose (ert-runner/run-tests-batch-and-exit ert-runn
  (let ((test-files (ert-runner--test-files tests)) (test-helper (f-ex
  ert-runner/run()
  apply(ert-runner/run nil)
  commander--handle-command(nil)
  commander-parse(nil)
  (if commander-parsing-done nil (commander-parse (or commander-args (
  eval-buffer(#<buffer  *load*> nil "/home/p
  load-with-code-conversion("/home/p/f.
  load("/home/p/f.el/.cask/27.0/elpa/er
  command-line-1(("-scriptload" "/home/p
  command-line()
  normal-top-level()

Test f-move-test/move-absolute-path condition:

    (file-error "Renaming" "Is a directory" "/home/p/f.el/test/playground/foo.txt" "/home/p/f.el/test/playground/bar")

FTest f-move-test/move-relative-path backtrace:

  rename-file("foo.txt" "bar" t)
  (if f--guard-paths (if (---truthy\? (let (needle) (let ((list f--gua
  f-move("foo.txt" "bar")
  (let ((default-directory f-test/playground-path)) (mapc (function (l
  (closure (t) nil (let ((default-directory f-test/playground-path)) (
  ert--run-test-internal(#s(ert--test-execution-info :test #s(ert-test
  ert-run-test(#s(ert-test :name f-move-test/move-relative-path :docum
  ert-run-or-rerun-test(#s(ert--stats :selector (and t) :tests [#s(ert
  ert-run-tests((and t) (lambda (event-type &rest event-args) (cond ((
  ert-runner/run-tests-batch((and t))
  (let ((stats (ert-runner/run-tests-batch selector))) (kill-emacs (if
  ert-runner/run-tests-batch-and-exit((and t))
  (if ert-runner-verbose (ert-runner/run-tests-batch-and-exit ert-runn
  (let ((test-files (ert-runner--test-files tests)) (test-helper (f-ex
  ert-runner/run()
  apply(ert-runner/run nil)
  commander--handle-command(nil)
  commander-parse(nil)
  (if commander-parsing-done nil (commander-parse (or commander-args (
  eval-buffer(#<buffer  *load*> nil "/home/p
  load-with-code-conversion("/home/p/f.
  load("/home/p/f.el/.cask/27.0/elpa/er
  command-line-1(("-scriptload" "/home/p
  command-line()
  normal-top-level()

Test f-move-test/move-relative-path condition:

    (file-error "Renaming" "Is a directory" "/home/p/f.el/test/playground/foo.txt" "/home/p/f.el/test/playground/bar")

F........................................................................................

Ran 229 tests in 29.509 seconds
5 unexpected results:
   FAILED  f-copy-contents-test/copy-directory
   FAILED  f-copy-test/copy-absolute-dir-exists
   FAILED  f-copy-test/copy-relative-dir-exists
   FAILED  f-move-test/move-absolute-path
   FAILED  f-move-test/move-relative-path
Makefile:13: recipe for target 'unit' failed
make[1]: *** [unit] Error 1
make[1]: Leaving directory '/home/p/f.el'
Makefile:7: recipe for target 'test' failed
make: *** [test] Error 2

Please consider keeping changelog in its own file

Hi,

I'd be grateful if you'd consider keeping the changelog in its own file (e.g. CHANGELOG.md). That way, the Debian packaging of f.el can just install that file. At present, I have to manually extract the changelog data on each release.

Thanks!

Unintuitive f-join behavior

ELISP> (f-join "a" "/b")
"../../../../b"
ELISP> (cd "~")
"/home/sindikat/"
ELISP> (f-join "a" "/b")
"../../b"

f-join behaves strange when meets an argument starting with slash, depending on a current directory in Emacs. In Python, for example, os.path.join simply discards all previous arguments when meets an argument starting with slash. Do you think it is better to make f-join behave like Python's?

Add f-current-library

What about f-current-library which returns the file name of the currently loaded library?

I should cover the following three cases:

  • The file being byte compiled
  • The file being loaded
  • And the file being evaluated with eval-buffer.

An implementation of this is already contained in cask-cli.el.

f-split doesn't handle tilde paths

It's considered an absolute path by the function underlying f-absolute?, and so f-split prepends a path separator.

(f-split "~/.zshrc") ;; => ("/" "~" ".zshrc")

f-join is trying to open a tramp connection when it shouldn't

(f-join (f-root) "C:\\Users\\61169.html")

Surprisingly, this gives me the following when executed on linux:

ssh: Could not resolve hostname c: Name or service not known

and

Tramp: Waiting for prompts from remote shell...failed
Tramp: Opening connection for C using scp...failed
tramp-file-name-handler: Tramp failed to connect.  If this happens repeatedly, try
    ‘M-x tramp-cleanup-this-connection’

I understand that I'm feeding f-join with garbage but I think it should nevertheless not attempt to open connections.

Redundant buffer-substring-no-properties for f-read-bytes?

As I was studying your library, I was wondering if in this case buffer-substring-no-properties is redundant for f-read-bytes?

(defun f-read-bytes (path)
"Read binary data from PATH.
Return the binary data as unibyte string."
(with-temp-buffer
(set-buffer-multibyte nil)
(setq buffer-file-coding-system 'binary)
(insert-file-contents-literally path)
(buffer-substring-no-properties (point-min) (point-max))))

Since insert-file-contents-literally does not run find-file-hook (nor format decoding, character code conversion, automatic uncompression, and so on), wouldn't buffer-string suffice?

In that way, buffer-string would make it a little simpler?

[Help]: Faster way to search for directories recursively

Contact Details

@jingxlim

New feature

Hi, I wanted to preface this by saying that this is neither a feature request nor a bug report, and more like a cry for help.

I wanted my little script to help me find directories that contain .org files. I also wanted these directories to not be at any point, hidden. To this end, I am glad that I found f.el, which did all the heavy-lifting.

Here are the few lines of code I wrote. Bear in mind that the following are literally the first few lines of Emacs Lisp I have written, ever, which is why I'm hoping to get some feedback. I also never took a course or went through a beginners tutorial on Emacs Lisp; and instead just decided to dive right into it just by looking particular things up. It's probably not the most Lisp-y (if there's even such a thing) and very verbose, but please bear with me!

(defun is-dotstring (str)
  (integerp(string-match-p "^\\.\\w.*$" str))
  )

(defun path-has-dotstring (path)
  (consp (member t (mapcar #'is-dotstring (f-split path))))
  )

(defun dir-has-filetype (path filetype)
  (consp (f--files path (equal (f-ext it) filetype)))
  )

(f-directories root_directory (lambda (dir) (and (not (path-has-dotstring dir)) (dir-has-filetype dir "org"))) t)

So for the root_directory that I specified, this piece of code took somewhere from 1-2 minutes to run. Because I also hope to run this at the startup of Emacs, this speed won't do.

On the other hand, something like the Linux tool find achieved the same thing in less than 5 seconds.

find [root_directory] -name '*.org' -not -path '*/.*' -printf '%h\n' | sort -u

Of course, I am not claiming that I wrote them the same way, so differences in performance is expected.

However, I do want to do this in Emacs, so I'm wondering if there are any ways I could improve on it, using f.el or otherwise (e.g. piping in the results from find). In the case of f-directories, I understand that every directory is visited recursively, even if it is many layers deep into a hidden directory (e.g. .git). Perhaps one way is to write the program in a way that prevents this from happening.

Any help is greatly appreciated! Thanks!

Why this new feature

f.el is great! I just need help using it!

Implementation ideas and additional thoughts

It could be implemented doing foo, bar, and baz

Add f-split

It would be nice to add an f-split function that does the inverse of f-join, so (f-split "foo/bar/baz.txt") would return ("foo" "bar" "baz.txt").

I've looked through the docs, but I don't think f.el currently offers this functionality.

f-slash for non-existent directories

This is a terrific and useful package, thanks.

There are times when I need to manipulate directory names that do not (yet) exist. But f-slash, and thus f-full, do not append the slash in this case. I'm not sure whether f-slash should have the requirement of an existing directory. I think I prefer that it not, but I can see arguments both ways. However, if there is that requirement, I think it should be made clear in the documentation.

If the existence requirement is kept for f-slash, it might be a nice alternative to allow an optional argument to force the behavior:

(defun f-slash (path &optional if-not-exists)
  "Append slash to PATH unless one is already present.
A slash will not be appended for a non-existent directory path
unless IF-NOT-EXISTS is non-nil. Some functions, such as
 `call-process' requires there to be an ending slash."
  (if (or (f-dir? path) if-not-exists)
      (file-name-as-directory path)
    path))

This could be propagated to f-full (e.g., as-directory) which is the only function calling f-slash. I see that you need the requirement for f-full so that it can distinguish an arbitrary path as file or directory. However, it seems to me that that is where the requirement belongs rather than in f-slash, which by name just adds a slash. (UPDATED)

One could, of course, just call file-name-as-directory in this case, but we might as
well keep the advantage of a unified interface.

Make f-directories optionally ignore permission errors

Hi!

I constructed the following little helper function to help me fill up my projectile index just now:

(defun my/find-git-projects (dir)
  "Find all git projects under DIR."
  (mapcar #'f-dirname
          (f-directories "~/Documents/"
                         (lambda () (equal (f-filename dir) ".git"))
                         t)))

Unfortunately I get the following error:

f--collect-entries: Opening directory: Permission denied, c:/Users/mattias.bengtsson/AppData/Local/Application Data

Would it be possible to teach f-directories to optionally ignore directories it can't reach due to permissions instead of erroring out?

Remaining file vs directory issues

A few things stand out as left over from #86 (and #89), but I'm not completely familiar with f.el and intended behaviour.

  1. I think additional file-name-as-directory calls in tests (added by #86) should be removed as part of #89.
  2. f-dirname/f-parent should probably return a trailing slash, and thus tests modifications in #89 should be reverted. This has knock-on effects for several other functions that could be quite tricky.
  3. Potentially f-filename should return a trailing slash for inputs with a trailing slash. It sounds odd to have a function called f-filename return a directory, but it seems intended to be equivalent to basename. This also has knock-on effects for other functions.

I haven't checked the full range of tests but neither of those PRs added many tests, whilst behaviour of underlying Emacs functions became more subtle and behaviour of f.el changed in some cases. I'm looking for @phst to add to the discussion, since they seem to know more about it than I do and were responsible for those PRs. #70 also seems relevant.

Optimization for directory-files

Hi there,

In this interesting discussion with Alexander Miller, he mentioned that directory-files is faster when called in a temp buffer. This quick test seems to show that it is indeed:

#+BEGIN_SRC elisp
  (defun f--collect-entries--tb (path recursive)
    (with-temp-buffer
      (let (result
            (entries
             (-reject
              (lambda (file)
                (or
                 (equal (f-filename file) ".")
                 (equal (f-filename file) "..")))
              (directory-files path t))))
        (cond (recursive
               (-map
                (lambda (entry)
                  (if (f-file? entry)
                      (setq result (cons entry result))
                    (when (f-directory? entry)
                      (setq result (cons entry result))
                      (setq result (append result (f--collect-entries entry recursive))))))
                entries))
              (t (setq result entries)))
        result)))


  (list (cons "without temp buffer"
              (progn
                (garbage-collect)
                (benchmark-run-compiled 100
                  (f--collect-entries "~/.emacs.d" nil))))
        (cons "with temp buffer"
              (progn
                (garbage-collect)
                (benchmark-run-compiled 100
                  (f--collect-entries--tb "~/.emacs.d" nil)))))
#+END_SRC

#+RESULTS:
| without temp buffer | 0.31999160200000004 | 0 | 0.0 |
| with temp buffer    |         0.191897104 | 0 | 0.0 |

I thought I should let you know in so that it could be added to f--collect-entries. Seems like a nice, free optimization. :)

Thanks for your work on f.el!

wait-timeout for f--collecting-entries

Hi,

I found out that f--collecting-entries function takes quite a lot of time when it recursively collects entries (where a directory has many entries). I wanted to use wait-timeout to wrap f--collecting-entries but it doesn't work with the current implementation because f--collecting-entries never wait for an input.

My current work-around is to insert (sit-for 0.000001) at https://github.com/rejeep/f.el/blob/master/f.el#L425 . I'm wondering whether there is a better solution for my problem. Any suggestion is welcome.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.