atlas-engineer / nfiles

User configuration and data file management
BSD 3-Clause "New" or "Revised" License
18 stars 5 forks source link

+TITLE: NFiles

NFiles is a Common Lisp library that deals with customizable path resolution, file persistence and loading.

Its main use case is help manage user-centric files like configuration files.

In some aspects, it can be seen as "Common Lisp 'logical pathnames' over CLOS".

** Goals

** Features

** Motivation

This package was developed after dealing with a problem when delivering Common Lisp images: when an image is generated, path expansion may already be resolved and thus hard-coded within the image, which makes it unfit for delivery. Consider this:

+begin_src lisp

(defvar foo-path (uiop:xdg-config-home)) FOO-PATH foo-path

P"/home/johndoe/.config/"

+end_src

Now if I ship this image to my friend Kaboom, =foo-path= will expand to

+begin_src lisp

P"/home/johndoe/.config/"

+end_src

on their machine instead of the expected

+begin_src lisp

P"/home/kaboom/.config/"

+end_src

** Examples

A basic session:

+begin_src lisp

(defvar config-file (make-instance 'nfiles:config-file :base-path #p"my-app/init.lisp"))

(nfiles:expand config-file) ; => #P"/home/johndoe/.config/my-app/init.lisp"

(setf (nfiles:content config-file) "Hello file!") ; The file is written to disk.

(nfiles:content config-file) ; => "Hello file!"

+end_src

The following convenience macro ensures the file is updated when done with the body:

+begin_src lisp

(nfiles:with-file-content (content config-file) (format t "Length: ~a~%" (length content)) (setf content (serapeum:string-replace "file" content "config")))

+end_src

The =with-paths= helper allows for let-style bindings of the expanded paths:

+begin_src lisp

(let ((file1 (make-instance 'nfiles:file)) (file2 (make-instance 'nfiles:file :base-path #p"alt"))) (nfiles:with-paths ((path1 file1) (path2 file2)) (list path1 path2)))

+end_src

A =remote-file= works the same but needs some specialization:

+begin_src lisp

(defmethod nfiles:fetch ((profile nfiles:profile) (file remote-counter-file) &key) (dex:get (nfiles:url file)))

;; Optional: (defmethod nfiles:check ((profile nfiles:profile) (file remote-counter-file) content &key) (let ((path (nfiles:expand file))) (ironclad:byte-array-to-hex-string (ironclad:digest-file :sha3 path))))

(let ((file (make-instance 'nfiles:remote-file ;; The URL to download from if the file is not found on disk. :url (quri:uri "https://example.org") ;; Without base-path, the file won't be saved to disk. :base-path #p"/tmp/index.html" :checksum "794df316afac91572b899b52b54f53f04ef71f275a01c44b776013573445868c95317fc4a173a973e90addec7792ff8b637bdd80b1a6c60b03814a6544652a90"))) ;; On access, file is automatically downloaded if needed and the checksum is verified: (nfiles:content file) ;; ... )

+end_src

See the [[file:package.lisp][package]] documentation for a usage guide and more examples.

** Configuration

NFiles was designed with configurability in mind. All configuration happens through subclassing of the =file= and =profile= classes together with method specialization.

All configuration methods are specialized against =profile= and =file= so that the user can easily compose the behaviour:

Of course you can specialize against both!

The specialization methods are divided into the following:

** Conditions and restarts

Some NFiles-specific conditions are raised in case of exceptional situations to provide for interactive and customizable behaviour:

** Shadowing

NFiles 1 shadows =cl:delete=, thus you should not =:use= the package (as with any other library anyways).

** Platform support

It's pure Common Lisp and all compilers plus all operating systems should be supported.

Some notes:

** Roadmap

** Change log

*** 1.1.4

*** 1.1.3

*** 1.1.2

*** 1.1.1

*** 1.1.0

** History

NFiles was originally developed for user file management in [[https://nyxt.atlas.engineer][Nyxt]], so the "N" may stand for it, or "New", or whatever poetic meaning you may find behind it!