File Coverage

blib/lib/File/XDG.pm
Criterion Covered Total %
statement 105 128 82.0
branch 42 60 70.0
condition 15 41 36.5
subroutine 25 26 96.1
pod 13 13 100.0
total 200 268 74.6


line stmt bran cond sub pod time code
1             package File::XDG;
2              
3 1     1   62944 use strict;
  1         10  
  1         29  
4 1     1   4 use warnings;
  1         1  
  1         25  
5 1     1   4 use Carp ();
  1         1  
  1         12  
6 1     1   4 use Config;
  1         1  
  1         59  
7 1     1   438 use Ref::Util qw( is_coderef is_arrayref );
  1         1507  
  1         78  
8 1     1   1192 use if $^O eq 'MSWin32', 'Win32';
  1         17  
  1         5  
9              
10             # ABSTRACT: Basic implementation of the XDG base directory specification
11             our $VERSION = '1.01'; # VERSION
12              
13              
14              
15              
16             sub new {
17 17     17 1 48900 my $class = shift;
18 17         57 my %args = (@_);
19              
20 17         39 my $name = delete $args{name};
21 17 50       59 Carp::croak('application name required') unless defined $name;
22              
23 17         33 my $api = delete $args{api};
24 17 100       49 $api = 0 unless defined $api;
25 17 50 66     71 Carp::croak("Unsupported api = $api") unless $api == 0 || $api == 1;
26              
27 17         26 my $path_class = delete $args{path_class};
28              
29 17 100       34 unless(defined $path_class) {
30 12 100       37 if($api >= 1) {
31 3         7 $path_class = 'Path::Tiny';
32             } else {
33 9         20 $path_class = 'Path::Class';
34             }
35             }
36              
37 17 100       44 my $file_class = $path_class eq 'Path::Class' ? 'Path::Class::File' : $path_class;
38 17 100       36 my $dir_class = $path_class eq 'Path::Class' ? 'Path::Class::Dir' : $path_class;
39              
40 17 100       77 if(is_coderef($path_class))
    100          
    100          
    100          
    50          
41             {
42 1         2 $dir_class = $file_class = $path_class;
43             }
44             elsif(is_arrayref($path_class))
45             {
46 1         3 ($file_class, $dir_class) = @$path_class;
47             }
48             elsif($path_class eq 'Path::Tiny')
49             {
50 4         33 require Path::Tiny;
51             }
52             elsif($path_class eq 'Path::Class')
53             {
54 10         75 require Path::Class::File;
55 10         32 require Path::Class::Dir;
56             }
57             elsif($path_class eq 'File::Spec')
58             {
59 1         8 require File::Spec;
60 1     1   4 $file_class = sub { File::Spec->catfile(@_) };
  1         13  
61 1     1   4 $dir_class = sub { File::Spec->catdir(@_) };
  1         13  
62             }
63             else
64             {
65 0         0 Carp::croak("Unknown path class: $path_class");
66             }
67              
68 17         33 my $strict = delete $args{strict};
69 17 50 33     89 Carp::croak("XDG base directory specification cannot strictly implemented on Windows")
70             if $^O eq 'MSWin32' && $strict;
71              
72 17 50       39 Carp::croak("unknown arguments: @{[ sort keys %args ]}") if %args;
  0         0  
73              
74             my $self = bless {
75             name => $name,
76             api => $api,
77             file_class => $file_class,
78             dir_class => $dir_class,
79             strict => $strict,
80             runtime => $ENV{XDG_RUNTIME_DIR},
81 17         80 }, $class;
82              
83 17 50       39 if($^O eq 'MSWin32') {
84 0         0 my $local = Win32::GetFolderPath(Win32::CSIDL_LOCAL_APPDATA(), 1);
85 0         0 $self->{home} = $local;
86 0   0     0 $self->{data} = $ENV{XDG_DATA_HOME} || "$local\\.local\\share\\";
87 0   0     0 $self->{cache} = $ENV{XDG_CACHE_HOME} || "$local\\.cache\\";
88 0   0     0 $self->{config} = $ENV{XDG_CONFIG_HOME} || "$local\\.config\\";
89 0   0     0 $self->{state} = $ENV{XDG_STATE_HOME} || "$local\\.state\\";
90 0   0     0 $self->{data_dirs} = $ENV{XDG_DATA_DIRS} || '';
91 0   0     0 $self->{config_dirs} = $ENV{XDG_CONFIG_DIRS} || '';
92             } else {
93 17   33     50 my $home = $ENV{HOME} || [getpwuid($>)]->[7];
94 17         35 $self->{home} = $home;
95 17   66     100 $self->{data} = $ENV{XDG_DATA_HOME} || "$home/.local/share/";
96 17   66     67 $self->{cache} = $ENV{XDG_CACHE_HOME} || "$home/.cache/";
97 17   33     72 $self->{state} = $ENV{XDG_STATE_HOME} || "$home/.state/";
98 17   66     68 $self->{config} = $ENV{XDG_CONFIG_HOME} || "$home/.config/";
99 17   100     65 $self->{data_dirs} = $ENV{XDG_DATA_DIRS} || '/usr/local/share:/usr/share';
100 17   100     53 $self->{config_dirs} = $ENV{XDG_CONFIG_DIRS} || '/etc/xdg';
101             }
102              
103 17         64 return $self;
104             }
105              
106             sub _dir {
107 26     26   2759 my $self = shift;
108             is_coderef($self->{dir_class})
109             ? $self->{dir_class}->(@_)
110 26 100       141 : $self->{dir_class}->new(@_);
111             }
112              
113             sub _file {
114 31     31   66 my $self = shift;
115             is_coderef($self->{dir_class})
116             ? $self->{file_class}->(@_)
117 31 100       137 : $self->{file_class}->new(@_);
118             }
119              
120             sub _dirs {
121 24     24   79 my($self, $type) = @_;
122 24 50       147 return $self->{"${type}_dirs"} if exists $self->{"${type}_dirs"};
123 0         0 Carp::croak('invalid _dirs requested');
124             }
125              
126             sub _lookup_file {
127 12     12   27 my ($self, $type, @subpath) = @_;
128              
129 12 50       28 Carp::croak('subpath not specified') unless @subpath;
130 12 50       30 Carp::croak("invalid type: $type") unless defined $self->{$type};
131              
132 12         164 my @dirs = ($self->{$type}, split(/\Q$Config{path_sep}\E/, $self->_dirs($type)));
133 12         37 my @paths = map { $self->_file($_, @subpath) } @dirs;
  24         758  
134 12         510 my ($match) = grep { -f $_ } @paths;
  24         432  
135              
136 12         427 return $match;
137             }
138              
139              
140             sub data_home {
141 4     4 1 2258 my $self = shift;
142 4         25 my $xdg = $self->{data};
143 4         13 return $self->_dir($xdg, $self->{name});
144             }
145              
146              
147             sub config_home {
148 4     4 1 715 my $self = shift;
149 4         12 my $xdg = $self->{config};
150 4         15 return $self->_dir($xdg, $self->{name});
151             }
152              
153              
154             sub cache_home {
155 2     2 1 1206 my $self = shift;
156 2         4 my $xdg = $self->{cache};
157 2         5 return $self->_dir($xdg, $self->{name});
158             }
159              
160              
161             sub state_home {
162 0     0 1 0 my $self = shift;
163 0         0 return $self->_dir($self->{state}, $self->{name});
164             }
165              
166              
167             sub runtime_home
168             {
169 2     2 1 4 my($self) = @_;
170 2         6 my $base = $self->_runtime_dir;
171 2 100       10 defined $base ? $self->_dir($base, $self->{name}) : undef;
172             }
173              
174             sub _runtime_dir
175             {
176 2     2   3 my($self) = @_;
177 2 100       7 if(defined $self->{runtime})
178             {
179 1         4 return $self->{runtime};
180             }
181              
182             # the spec says only to look for the environment variable
183 1 50       4 return undef if $self->{strict};
184              
185 0         0 my @maybe;
186              
187 0 0       0 if($^O eq 'linux')
188             {
189 0         0 push @maybe, "/run/user/$<";
190             }
191              
192 0         0 foreach my $maybe (@maybe)
193             {
194             # if we are going rogue and trying to find the runtime dir
195             # on our own, then we hould at least check that the directory
196             # fufills the requirements of the spec: directory, owned by
197             # us, with permission of 0700.
198 0 0       0 next unless -d $maybe;
199 0 0       0 next unless -o $maybe;
200 0         0 my $perm = [stat $maybe]->[2] & oct('0777');
201 0 0       0 next unless $perm == oct('0700');
202 0         0 return $maybe;
203             }
204              
205 0         0 return undef;
206             }
207              
208              
209             sub data_dirs {
210 6     6 1 1453 return shift->_dirs('data');
211             }
212              
213              
214             sub data_dirs_list {
215 2     2 1 4 my $self = shift;
216 2         39 return map { $self->_dir($_) } split /\Q$Config{path_sep}\E/, $self->data_dirs;
  4         89  
217             }
218              
219              
220             sub config_dirs {
221 6     6 1 168 return shift->_dirs('config');
222             }
223              
224              
225             sub config_dirs_list {
226 2     2 1 1827 my $self = shift;
227 2         24 return map { $self->_dir($_) } split /\Q$Config{path_sep}\E/, $self->config_dirs;
  3         42  
228             }
229              
230              
231             sub exe_dir
232             {
233 2     2 1 9 my($self) = @_;
234 2         47 -d "@{[ $self->{home} ]}/.local/bin"
235 2 100       3 ? $self->_dir($self->{home}, '.local', 'bin')
236             : undef;
237             }
238              
239              
240             sub lookup_data_file {
241 6     6 1 4401 my ($self, @subpath) = @_;
242 6 100       25 unshift @subpath, $self->{name} if $self->{api} >= 1;
243 6         17 return $self->_lookup_file('data', @subpath);
244             }
245              
246              
247             sub lookup_config_file {
248 6     6 1 4277 my ($self, @subpath) = @_;
249 6 100       23 unshift @subpath, $self->{name} if $self->{api} >= 1;
250 6         15 return $self->_lookup_file('config', @subpath);
251             }
252              
253              
254             1;
255              
256             __END__