aboutsummaryrefslogtreecommitdiff
path: root/gnu/gnunet/config/fs.scm
blob: 1a073abe4fa39812a2397f94a919f9d8389ce485 (plain) (blame)
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
;; This file is part of scheme-GNUnet, a partial Scheme port of GNUnet.
;; Copyright (C) 2021 Maxime Devos <maximedevos@telenet.be>
;;
;; scheme-GNUnet is free software: you can redistribute it and/or modify it
;; under the terms of the GNU Affero General Public License as published
;; by the Free Software Foundation, either version 3 of the License,
;; or (at your option) any later version.
;;
;; scheme-GNUnet is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
;; Affero General Public License for more details.
;;
;; You should have received a copy of the GNU Affero General Public License
;; along with this program.  If not, see <http://www.gnu.org/licenses/>.
;;
;; SPDX-License-Identifier: AGPL-3.0-or-later

;; There are three steps to loading configuration files:
;;
;;   * locating the configuration files
;;   * parsing the configuration files into a table of
;;     (section, variable) --> value, without expanding anything
;;   * expanding the previous table
;;
;; Expanding the table and loading the table have to be done separately,
;; because variables do not have to be defined in any specific order,
;; so a variable defined early could refer to a variable defined later.
;;
;; The last two steps are largely implemented by (gnu gnunet config expand)
;; and (gnu gnunet config parser), but some glue is required to make them
;; work with ports.

(define-library (gnu gnunet config fs)
  (export locate-system-configuration
	  locate-user-configuration
	  load-configuration
	  load-configuration/port!
	  make-expanded-configuration)
  (import (only (rnrs base)
		begin define and not or cond define-syntax identifier-syntax
		if ... eq? values + lambda quote vector car cdr cons string?
		string-length vector? vector-ref string=? list)
	  (only (rnrs control)
		when)
	  (prefix (rnrs hashtables)
		  rnrs:)
	  (only (ice-9 optargs)
		define*)
	  (only (ice-9 rdelim)
		read-line)
	  (only (guile)
		getenv in-vicinity string-null? define-syntax-rule eof-object?
		substring error syntax-error define-syntax-parameter
		syntax-parameterize syntax-violation identity
		make-hash-table hash-set! hash-ref hash-for-each
		call-with-input-file for-each file-exists?
		search-path %load-path)
	  (only (gnu gnunet utils hat-let)
		let^)
	  (gnu gnunet config db)
	  (gnu gnunet config expand)
	  (gnu gnunet config parser))
  (begin
    (define (locate-defaults)
      (search-path %load-path "gnu/gnunet/config/default.conf"))

    (define (locate-system-configuration)
      "/etc/gnunet.conf")

    (define* (locate-user-configuration #:key (getenv getenv))
      "Determine the location of the user configuration file,
as a string, or @code{#false} if it could not be determined.
If the location of the user configuration file is known,
but the file does not exist, it is returned anyway, as a string.

If the environment variable @code{XDG_CONFIG_HOME} is set, the
location of the file @code{gnunet.conf} in the directory
@code{XDG_CONFIG_HOME} is returned.  If the environment variable
is not set, the location of the file at @code{.config/gnunet.conf}
in the home directory specified by the environment variable
@code{HOME} is returned, if that environment variable exist.
If both are unset, @code{#false} is returned.

The values of environment variables is determined with the procedure
@var{getenv}."
      (define (locate/HOME)
	(define HOME (getenv "HOME"))
	;; It is possible, though inadvisable, for HOME
	;; to be unset.
	(and HOME (not (string-null? HOME))
	     (in-vicinity HOME (in-vicinity ".config" "gnunet.conf"))))
      (let^ ((! XDG_CONFIG_HOME (getenv "XDG_CONFIG_HOME"))
	     ;; If the environment variable is unset, fall-back to
	     ;; $HOME.
	     (? (or (not XDG_CONFIG_HOME)
		    (string-null? XDG_CONFIG_HOME))
		(locate/HOME)))
	    (in-vicinity XDG_CONFIG_HOME "gnunet.conf")))

    (define (load-configuration/port! set-value! port)
      "Load the configuration from the input port @var{port}.

For each variable, call @code{set-value!} with the section name,
variable name, and a vector of the form @code{#(line line-number value)},
where @var{value} is a list of expansible objects."
      (define (read-object)
	(define line (read-line port))
	(if (eof-object? line)
	    (values line line)
	    (values line (parse-line line))))
      ;; The current line number
      (define-syntax-parameter line-number
	(lambda (stx)
	  (syntax-violation 'ln "line-number outside loop" stx)))
      ;; The current line, as a string
      (define-syntax-parameter line
	(lambda (stx)
	  (syntax-violation 'l "line outside loop" stx)))
      ;; The result of parsing the current line.
      (define-syntax-parameter object
	(lambda (stx)
	  (syntax-violation 'o "object outside loop" stx)))
      (define-syntax-rule (define-loop (loop arg ...) exp ...)
	(define (loop line-number* line* object* arg ...)
	  (syntax-parameterize ((line-number (identifier-syntax line-number*))
				(line (identifier-syntax line*))
				(object (identifier-syntax object*)))
	    exp ...)))
      (define-syntax-rule (define-loops (((loop loop*) arg ...) exp ...) ...)
	(begin
	  (begin
	    (define-loop (loop arg ...)
	      exp ...)
	    (define-syntax-rule (loop* arg ...)
	      (let^ ((<-- (line object) (read-object)))
		    (loop (+ 1 line-number) line object arg ...))))
	  ...))
      (define-loops
	(((no-section no-section*))
	 (cond ((#{[]-position?}# object)
		(section*
		 (substring line
			    (position:section-name-start object)
			    (position:section-name-end object))))
	       ((=-position? object)
		(error "assignment outside section"))
	       ((@inline@-position? object)
		(error "inclusion directives are not supported"))
	       ((eq? object #f)
		(error "unrecognised syntax at line ???"))
	       ((eof-object? object) (values)) ; done
	       ;; comments, empty line
	       (#t (no-section*))))
	(((section section*) section-name)
	 (cond ((#{[]-position?}# object)
		(section*
		 (substring line
			    (position:section-name-start object)
			    (position:section-name-end object))))
	       ((=-position? object)
		(let^ ((! variable-name
			  (substring line
				     (position:variable-start object)
				     (position:variable-end object)))
		       (<-- (expo-list . end)
			    (parse-expandable* line
					       (position:value-start object)
					       (position:value-end object)
					       #f)))
		      (set-value! section-name variable-name
				  (vector line line-number expo-list))
		      (section* section-name)))
	       ((@inline@-position? object)
		(error "inclusion directives are not supported"))
	       ((eq? object #f)
		(error "unrecognised syntax at line ????"))
	       ((eof-object? object) (values)) ; done
	       ;; comments, empty line
	       (#t (section* section-name)))))
      ;; TODO: start lines at 0 or 1?  Likewise for columns.
      (syntax-parameterize ((line-number (identifier-syntax 0)))
	(no-section*)))

    (define* (make-expanded-configuration load! #:key (getenv getenv))
      "Make a configuration object.  To populate the configuration,
call the procedure @var{load!} with a @code{set-value!} procedure as expected
by @code{load-configuration/port!}.  The values from @code{set-value!}
are added to the configuration and every variable is expanded."
      (define hash (make-hash-table))
      (define (set-unexpanded-value! section key value-vector)
	(hash-set! hash (cons section key) value-vector))
      (load! set-unexpanded-value!)
      (define config (hash->configuration (rnrs:make-hashtable hash-key key=?)))
      (define (substring=? line0 start0 end0 line1 start1 end1)
	(string=? (substring line0 start0 end0)
		  (substring line1 start1 end1)))
      (define (query line start end)
	(define variable (substring line start end))
	;; In the section PATHS, variables participating in expansion can be
	;; defined.
	(define unexpanded-value
	  (or (hash-ref hash (cons "PATHS" variable))
	      (getenv variable)))
	(cond ((string? unexpanded-value) ; result of getenv
	       (values unexpanded-value
		       (list (make-literal-position
			      0 (string-length unexpanded-value)))))
	      ((vector? unexpanded-value)
	       (values (vector-ref unexpanded-value 0) ; line
		       (vector-ref unexpanded-value 2))) ; list of expo objects
	      (#t (values)))) ; undefined variable
      (hash-for-each
       (lambda (key value)
	 (define line (vector-ref value 0))
	 (define expo-list (vector-ref value 2))
	 (define expanded-value
	   (expand->string query substring=? line expo-list))
	 (set-value! identity config (car key) (cdr key) expanded-value))
       hash)
      config)

    ;; XXX no tests
    (define* (load-configuration #:key (getenv getenv)
				 (files (list (locate-defaults)
					      (locate-system-configuration)
					      (locate-user-configuration
					       #:getenv getenv))))
      "Load the user configuration, system configuration and defaults.
The configuration files to load can be overridden by setting @var{files}
appropriately."
      (define configurations
	(list (locate-system-configuration)
	      (locate-user-configuration)))
      (define (load! set-value!)
	(define (load-file! file)
	  (when (and file (file-exists? file))
	    (call-with-input-file file
	      (lambda (p)
		(load-configuration/port! set-value! p))
	      #:guess-encoding #t
	      #:encoding "UTF-8")))
	(for-each load-file! configurations))
      (define c (make-expanded-configuration load! #:getenv getenv))
      c)

    ;; TODO error reporting
    ))