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.
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 ofpathname
saves us from many pitfalls, for instance path with wildcards (such as `[`, `*`) are no longer special. finder
andfinder*
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. Unlikefind
,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.
For now this is only tested on Unix-based systems. Help welcome if you need support for another system.
;; 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'.
Load the separate fof/mf
system to access the following extensions:
mediafile
class: leveragesffprobe
(from the FFmpeg suite) to extract media metadata.It also includes MIME information.
mediafinder
andmediafinder*
: just likefinder
andfinder*
respectively, but returnmediafile=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.