/
BaseDirectory.pm
337 lines (225 loc) · 8.58 KB
/
BaseDirectory.pm
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
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
use v6;
=begin pod
=head1 NAME
XDG::BaseDirectory - locate shared data and configuration
=head1 SYNOPSIS
=begin code
use XDG::BaseDirectory;
my $bd = XDG::BaseDirectory.new
for $bd.load-config-paths('mydomain.org', 'MyProg', 'Options') -> $d {
say $d;
}
# Directories can be made available as terms as well
use XDG::BaseDirectory :terms;
say config-home;
=end code
=head1 DESCRIPTION
The freedesktop.org Base Directory specification provides a way for
applications to locate shared data and configuration:
http://standards.freedesktop.org/basedir-spec/
This module can be used to load and save from and to these directories.
The interface is loosely based on that of the C<pyxdg> module, however all
methods that return a string path in that module return an L<IO::Path> here.
=head2 METHODS
=end pod
class XDG::BaseDirectory:ver<0.0.11>:auth<github:jonathanstowe>:api<1.0> {
=begin pod
=head2 data-home
This reflects the base path where local data should be stored. Can be
over-ridden by the environment variable C<XDG_DATA_HOME>.
=end pod
has IO::Path $.data-home;
method data-home( --> IO::Path ) {
$!data-home //= %*ENV<XDG_DATA_HOME>.defined ?? %*ENV<XDG_DATA_HOME>.IO !! $*HOME.add($*SPEC.catfile('.local', 'share'));
}
=begin pod
=head2 data-dirs
This returns a list of the locations where data can be read from, it can
be over-ridden by the colon separated environment variable C<XDG_DATA_DIRS>
but will always prefer C<data-home>.
=end pod
has IO::Path @.data-dirs;
method data-dirs() {
if ! @!data-dirs.elems {
@!data-dirs = ($.data-home, (%*ENV<XDG_DATA_DIRS> || '/usr/local/share:/usr/share').split(':').map({ $_.IO })).flat;
}
@!data-dirs;
}
=begin pod
=head2 config-home
Reflects the location where application should be saved to to. Can
be over-ridden by the enviroment variable C<XDG_CONFIG_HOME>.
=end pod
has IO::Path $.config-home;
method config-home( --> IO::Path ) {
$!config-home //= %*ENV<XDG_CONFIG_HOME>.defined ?? %*ENV<XDG_CONFIG_HOME>.IO !! $*HOME.add('.config');
}
=begin pod
=head2 config-dirs
returns a list of directorys from which configuration can be read, it will
always prefer C<config-home> but the defaults can be over-ridden by the
environment variable C<XDG_CONFIG_DIRS> which can be a list separated by
colons.
=end pod
has IO::Path @.config-dirs;
method config-dirs() {
if ! @!config-dirs.elems {
@!config-dirs = ($.config-home, (%*ENV<XDG_CONFIG_DIRS> || '/etc/xdg' ).split(':').map({ $_.IO })).flat;
}
@!config-dirs;
}
=begin pod
=head2 cache-home
Returns the path where application cache data should be saved. This can be
over-ridden by the environment variable C<XDG_CACHE_HOME>.
=end pod
has IO::Path $.cache-home;
method cache-home( --> IO::Path ) {
$!cache-home //= (%*ENV<XDG_CACHE_HOME> || $*HOME.add('.cache')).IO;
}
=begin pod
=head2 runtime-dir
Returns the directory where user specific, run-time files (and other
filesystem objects such as sockets and named pipes,) should be placed.
The directory will not persist between logins and reboots of the
system. For this to work correctly the system should be managing the
directory and set the environment variable C<XDG_RUNTIME_DIR>, however
if this is not the case the behaviour will be emulated in a less secure
fashion and a warning will be emitted.
=end pod
has IO::Path $.runtime-dir;
method runtime-dir( --> IO::Path ) {
$!runtime-dir //= do {
if %*ENV<XDG_RUNTIME_DIR>:exists {
%*ENV<XDG_RUNTIME_DIR>.IO
}
else {
warn "XDG_RUNTIME_DIR not set -falling back to insecure method";
my $dir = $*SPEC.tmpdir.add($*USER.Int);
$dir.mkdir;
$dir.chmod(0o700);
$dir;
}
}
}
=begin pod
=head2 save-config-path(Str *@resource)
Ensure C< <config-home>/<resource>/> exists, and return its path.
'resource' should normally be the name of your application. Use this
when SAVING configuration settings. Use the C<config-dirs> variable
for loading.
=end pod
method save-config-path(*@resource where @resource.elems > 0 --> IO::Path ) {
self!home-path($.config-home, @resource);
}
=begin pod
=head3 save-data-path(Str *@resource)
Ensure C< <data-home>/<resource>/> exists, and return its path.
'resource' is the name of some shared resource. Use this when updating
a shared (between programs) database. Use the C<data-dirs> variable
for loading.
=end pod
method save-data-path(*@resource where @resource.elems > 0 --> IO::Path ) {
self!home-path($.data-home, @resource);
}
# given an IO::Path and a resource description, will return an IO::Path of the
# appropriate sub-directory which will be created if necessary
method !home-path(IO::Path $home-path, *@resource where @resource.elems > 0 ) {
my Str $resource = self!resource-path(@resource);
my IO::Path $path = $home-path.add($resource);
if ! $path.d {
$path.mkdir(0o700);
}
$path.cleanup;
}
=begin pod
=head3 load-config-paths
Returns an iterator which gives each directory named 'resource' in the
configuration search path. Information provided by earlier directories should
take precedence over later ones (ie, the user's config dir comes first).
=end pod
method load-config-paths(*@resource ) {
self!load-resource-paths(@.config-dirs, @resource);
}
=begin pod
=head3 load-first-config(Str *@resource) returns L<IO::Path>
Returns the first result from load-config-paths, or None if there is
nothing to load.
=end pod
method load-first-config(*@resource) {
self.load-config-paths(@resource)[0];
}
=begin pod
=head3 load-data-paths(Str *@resource)
Returns an iterator which gives each directory named 'resource' in the
shared data search path. Information provided by earlier directories should
take precedence over later ones.
=end pod
method load-data-paths(*@resource where @resource.elems > 0 ) {
self!load-resource-paths(@.data-dirs, @resource);
}
# given an array of IO::Path objects and a resource description
# return those resulting resource paths that actually exist
method !load-resource-paths(@dirs, *@resource where @resource.elems > 0 ) {
my Str $resource = self!resource-path(@resource);
gather {
for @dirs -> $config-dir {
my $path = $config-dir.add($resource);
if $path.d {
take $path;
}
}
}
}
class X::InvalidResource is Exception {
has Str $.message = "invalid resource description";
}
# return a somewhat sanitized path part that can be appended to
# some config path based on the supplied resource description parts
method !resource-path(*@resource where @resource.elems > 0) {
if any(@resource) ~~ $*SPEC.updir {
X::InvalidResource.new.throw;
}
my Str $resource = $*SPEC.catfile(@resource);
if $resource.IO.is-absolute {
X::InvalidResource.new(message => "absolute path $resource is not allowed").throw;
}
$resource;
}
}
=begin pod
=head2 Terms
When XDG::BaseDirectory is C<use>d with the C<:terms> tag, the following
properties of a generic XDG::BaseDirectory object are exported as eponymous
terms:
=item L<data-home>
=item L<data-dirs>
=item L<config-home>
=item L<config-dirs>
=item L<cache-home>
=item L<runtime-dir>
Example:
=begin code
use XDG::BaseDirectory :terms;
say "Put config files into " ~ config-home ~ ", please.";
=end code
You can over-ride the default C<XDG::BaseDirectory> object used
in these C<terms> by assigning to the dynanic variabled C<$*XDG>:
=begin code
use XDG::BaseDirectory :terms;
my $*XDG = XDG::BaseDirectory.new( config-home => "foo".IO );
....
say config-home; # -> "foo";
=end code
=end pod
my $XDG;
my sub xdg-basedirectory( --> XDG::BaseDirectory ) {
$XDG //= ( $*XDG // XDG::BaseDirectory.new );
}
sub term:<data-home> is export(:terms) { xdg-basedirectory.data-home }
sub term:<data-dirs> is export(:terms) { xdg-basedirectory.data-dirs }
sub term:<config-home> is export(:terms) { xdg-basedirectory.config-home }
sub term:<config-dirs> is export(:terms) { xdg-basedirectory.config-dirs }
sub term:<cache-home> is export(:terms) { xdg-basedirectory.cache-home }
sub term:<runtime-dir> is export(:terms) { xdg-basedirectory.runtime-dir }
# vim: expandtab shiftwidth=4 ft=perl6