/fof

File object finder Common Lisp library

Primary LanguageCommon LispGNU General Public License v3.0GPL-3.0

FOF

Warning: This library is currently experimental. While perfectly usable as is, the application programming interface is prone to change in the future.

Please report any issue or suggestion, including:

  • Better function, variable, slot, class or package naming.
  • Better function arguments.
  • Filesystem issues.

Features

Enable rapid file search, inspection and manipulation.

  • A file class which embeds the path and metadata such as permissions, last access time, etc.
  • Slot writers which commit changes to disk, e.g. permissions, modification time, etc.
  • Various path manipulation functions which supersede Common Lisp pathname related functions.

    Using file instead of pathname saves us from many pitfalls, for instance path with wildcards (such as `[`, `*`) are no longer special.

  • finder and finder* which return a list of files matching predicates.
  • A fof:syntax readtable to enable the #f"/path/to/file"= syntax, which mimicks =#p for pathnames.

In practice, it mostly supersedes:

  • Common Lisp pathnames (at least for existing files).
  • Many Unix tools:
    • find for recursive and programmable file search. Unlike find, finder’s predicates are extensible.
    • ls
    • stat
    • chown
    • chmod
    • du
    • touch

Note that FOF is not meant to manipulate arbitrary paths of non-existing files. Considering using ppath instead.

Portability

For now this is only tested on Unix-based systems. Help welcome if you need support for another system.

Examples

;; Make inspectable file object:
(file "fof.asd")
; => #F"~/co…/fof/fof.asd"

;; Enable reader macro:
(named-readtables:in-readtable fof:syntax)
; => #<NAMED-READTABLE READTABLE {1003035363}>

;; Now you can use the #f syntax:
#f"fof.asd"
; => #F"~/co…/fof/fof.asd"

;; Set permissions
(setf (permissions #f"fof.asd") '(:user-read :user-write :group-read))
; => (:USER-READ :USER-WRITE :GROUP-READ)

;; Recursive disk-usage, in bytes.
(disk-usage #f".")
; => 1298432

;; Custom printer:
(setf *print-abbreviation-length* 0
      *print-size?* t
      *print-date?* t)
; => #F"/home/ambrevar/common-lisp/fof/fof.asd 348 Feb 28 16:56"

Familiar path manipulation functions

(separator)
; => "/"

(current-directory)
; => #F"~/co…/fof/"

(extension #f"fof.asd")
; => "asd"
(basename #f"../fof/fof.asd")
; => "fof.asd"
(parent #f"fof.asd")
; => #F"~/co…/fof/"
(relative-path #f"fof.asd" #f"..")
; => "fof/fof.asd

(file? #f"fof.asd")
; => T
(directory? #f"fof.asd")
; => NIL
(let ((f #f"fof.asd"))
  (delete-file f)
  (exists? f))
; => NIL

File search and recursive listing:

;; List all files in the current directory, recursively.
(finder)
; => (#F"~/co…/fof/LICENSE"
;     #F"~/co…/fof/ffprobe.lisp"
;     #F"~/co…/fof/file.lisp"
;     #F"~/co…/fof/fof.asd"
;     #F"~/co…/fof/mediafile.lisp"
;     ...)

;; Same, with given root, without descending into hidden directories and
;; without descending more than one level:
(finder* :root (file ".") :recur-predicates (list (complement #'fof/p:hidden?)
                                                  (fof/p:depth< 2))

;; List files matching all the given predicates.
;; The `fof/p' package contains numerous useful predicate or predicate
;; generators you can complete against.
(finder (fof/p:path~ "fil") (fof/p:extension= "lisp"))
; => (#F"~/co…/fof/file.lisp" #F"~/co…/fof/mediafile.lisp")

;; Passing a string as a predicate specifier is equivalent to `path~'.
;; Passing a pathname is equivalent to `path$' (match end of path).
;; The following is the same as the previous example:
(finder "fil" (fof/p:extension= "lisp"))
; => (#F"~/co…/fof/file.lisp" #F"~/co…/fof/mediafile.lisp")

;; Passing a list of predicate specifiers connects them with a logical 'or'.
;; In other words, it returns the files matching at least one of the predicate
;; specifiers.
(finder (list "fil" (fof/p:extension= "asd")))
; => (#F"~/co…/fof/file.lisp" #F"~/co…/fof/fof.asd" #F"~/co…/fof/mediafile.lisp")

;; For more complex predicate list nesting, you can leverage
;; `alexandria:disjoin' and `alexandria:conjoin'.

Mediafiles (Work-in-progress)

Load the separate fof/mf system to access the following extensions:

  • mediafile class: leverages ffprobe (from the FFmpeg suite) to extract media metadata.

    It also includes MIME information.

  • mediafinder and mediafinder*: just like finder and finder* respectively, but return mediafile=s instead of =file.
  • The fof/mediafile:syntax.

Why the separate class and helpers? Because collecting media metadata is much slower. You should use fof:finder if you are not interested in the media metadata and you are seeking performance.