-
Notifications
You must be signed in to change notification settings - Fork 5
/
package.lisp
118 lines (88 loc) · 4.64 KB
/
package.lisp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
;;;; SPDX-FileCopyrightText: Atlas Engineer LLC
;;;; SPDX-License-Identifier: BSD-3-Clause
(in-package :cl-user)
(uiop:define-package nfiles
(:use #:common-lisp #:nfiles/pathname)
(:reexport #:nfiles/pathname)
(:shadow #:delete) ; TODO: Rename (and unshadow) with 2.0.0.
(:import-from #:nclasses
#:define-class)
(:import-from #:serapeum
#:export-always
#:->)
(:import-from #:trivial-types
#:pathname-designator)
(:documentation "The main data structures are `nfiles:file' and `nfiles:profile'.
Call `nfiles:expand' to return the final `nfiles:file' path.
Call `nfiles:content' (setf-able) to get the `nfiles:file' content.
A basic session:
(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!\"
The following convenience macro ensures the file is updated when done with the
body:
(nfiles:with-file-content (content *config-file*)
(format t \"Length: ~a~%\" (length content))
(setf content (serapeum:string-replace \"file\" content \"config\")))
The `nfiles:with-paths' helper allows for let-style bindings of the expanded paths:
(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)))
Specialize `nfiles:resolve' to configure how a file path is expanded depending
on the file type and the `nfiles:profile'.
The content serialization and deserialization can be specialized via the
`nfiles:serialize' and `nfiles:deserialize' methods.
The file reading and writing can be specialized via the `nfiles:read-file' and
`nfiles:write-file' methods. These specializations are in charge for calling
the (de)serialization methods.
A `nfiles:remote-file' works the same but needs some specialization to handle
the remote fetching and checksum validation:
(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)
;; ...
)
A word of caution: sometimes you may need the handle both the raw form and the
deserialized form. Then what should `nfiles:content' return?
In this use case, your class has 2 semantic values: that of carrying the raw
form and that of being an `nfiels:file' which carries the deserialized form.
To keep things simple, use a dedicated slot to store the raw form.
For instance `nfiles:remote-file' has a `nfiles:url-content' slot. If your file
is stored remotely, a `nfiles:remote-file' gives access to both forms.
Also avoid calling `nfiles:content' (or its setf-function) in the
`nfiles:read-file' - `nfiles:deserialize' / `nfiles:write-file' -
`nfiles:serialize' pipeline as it would make it horribly confusing to the
user (and probably break the logic)."))
(uiop:define-package nfiles/gpg
(:use #:common-lisp)
(:import-from #:nclasses
#:define-class)
(:import-from #:serapeum
#:export-always
#:->)
(:import-from #:trivial-types
#:pathname-designator)
(:documentation "A thin wrapper around the GPG command line tool.
Do not expect too much from it."))
(eval-when (:compile-toplevel :load-toplevel :execute)
(dolist (package '(:nfiles/gpg :nfiles))
(trivial-package-local-nicknames:add-package-local-nickname :alex :alexandria package)
(trivial-package-local-nicknames:add-package-local-nickname :sera :serapeum package)))
(when (uiop:getenvp "FLATPAK_ID") (push :flatpak *features*))