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   55039 use strict;
  1         9  
  1         24  
4 1     1   5 use warnings;
  1         1  
  1         17  
5 1     1   4 use Carp ();
  1         1  
  1         11  
6 1     1   4 use Config;
  1         1  
  1         64  
7 1     1   408 use Ref::Util qw( is_coderef is_arrayref );
  1         1315  
  1         58  
8 1     1   532 use if $^O eq 'MSWin32', 'Win32';
  1         11  
  1         5  
9              
10             # ABSTRACT: Basic implementation of the XDG base directory specification
11             our $VERSION = '1.02'; # VERSION
12              
13              
14              
15              
16             sub new {
17 17     17 1 43674 my $class = shift;
18 17         43 my %args = (@_);
19              
20 17         27 my $name = delete $args{name};
21 17 50       35 Carp::croak('application name required') unless defined $name;
22              
23 17         22 my $api = delete $args{api};
24 17 100       61 $api = 0 unless defined $api;
25 17 50 66     44 Carp::croak("Unsupported api = $api") unless $api == 0 || $api == 1;
26              
27 17         27 my $path_class = delete $args{path_class};
28              
29 17 100       28 unless(defined $path_class) {
30 12 100       25 if($api >= 1) {
31 3         5 $path_class = 'Path::Tiny';
32             } else {
33 9         12 $path_class = 'Path::Class';
34             }
35             }
36              
37 17 100       27 my $file_class = $path_class eq 'Path::Class' ? 'Path::Class::File' : $path_class;
38 17 100       23 my $dir_class = $path_class eq 'Path::Class' ? 'Path::Class::Dir' : $path_class;
39              
40 17 100       50 if(is_coderef($path_class))
    100          
    100          
    100          
    50          
41             {
42 1         3 $dir_class = $file_class = $path_class;
43             }
44             elsif(is_arrayref($path_class))
45             {
46 1         2 ($file_class, $dir_class) = @$path_class;
47             }
48             elsif($path_class eq 'Path::Tiny')
49             {
50 4         17 require Path::Tiny;
51             }
52             elsif($path_class eq 'Path::Class')
53             {
54 10         44 require Path::Class::File;
55 10         26 require Path::Class::Dir;
56             }
57             elsif($path_class eq 'File::Spec')
58             {
59 1         5 require File::Spec;
60 1     1   6 $file_class = sub { File::Spec->catfile(@_) };
  1         11  
61 1     1   3 $dir_class = sub { File::Spec->catdir(@_) };
  1         10  
62             }
63             else
64             {
65 0         0 Carp::croak("Unknown path class: $path_class");
66             }
67              
68 17         21 my $strict = delete $args{strict};
69 17 50 33     92 Carp::croak("XDG base directory specification cannot strictly implemented on Windows")
70             if $^O eq 'MSWin32' && $strict;
71              
72 17 50       27 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         67 }, $class;
82              
83 17 50       27 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\\.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     35 my $home = $ENV{HOME} || [getpwuid($>)]->[7];
94 17         31 $self->{home} = $home;
95 17   66     68 $self->{data} = $ENV{XDG_DATA_HOME} || "$home/.local/share/";
96 17   66     48 $self->{cache} = $ENV{XDG_CACHE_HOME} || "$home/.cache/";
97 17   33     51 $self->{state} = $ENV{XDG_STATE_HOME} || "$home/.local/state/";
98 17   66     58 $self->{config} = $ENV{XDG_CONFIG_HOME} || "$home/.config/";
99 17   100     47 $self->{data_dirs} = $ENV{XDG_DATA_DIRS} || '/usr/local/share:/usr/share';
100 17   100     39 $self->{config_dirs} = $ENV{XDG_CONFIG_DIRS} || '/etc/xdg';
101             }
102              
103 17         60 return $self;
104             }
105              
106             sub _dir {
107 26     26   2406 my $self = shift;
108             is_coderef($self->{dir_class})
109             ? $self->{dir_class}->(@_)
110 26 100       116 : $self->{dir_class}->new(@_);
111             }
112              
113             sub _file {
114 31     31   50 my $self = shift;
115             is_coderef($self->{dir_class})
116             ? $self->{file_class}->(@_)
117 31 100       102 : $self->{file_class}->new(@_);
118             }
119              
120             sub _dirs {
121 24     24   43 my($self, $type) = @_;
122 24 50       118 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   24 my ($self, $type, @subpath) = @_;
128              
129 12 50       23 Carp::croak('subpath not specified') unless @subpath;
130 12 50       27 Carp::croak("invalid type: $type") unless defined $self->{$type};
131              
132 12         101 my @dirs = ($self->{$type}, split(/\Q$Config{path_sep}\E/, $self->_dirs($type)));
133 12         28 my @paths = map { $self->_file($_, @subpath) } @dirs;
  24         634  
134 12         462 my ($match) = grep { -f $_ } @paths;
  24         375  
135              
136 12         379 return $match;
137             }
138              
139              
140             sub data_home {
141 4     4 1 1990 my $self = shift;
142 4         7 my $xdg = $self->{data};
143 4         11 return $self->_dir($xdg, $self->{name});
144             }
145              
146              
147             sub config_home {
148 4     4 1 585 my $self = shift;
149 4         4 my $xdg = $self->{config};
150 4         9 return $self->_dir($xdg, $self->{name});
151             }
152              
153              
154             sub cache_home {
155 2     2 1 1242 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 3 my($self) = @_;
170 2         5 my $base = $self->_runtime_dir;
171 2 100       8 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         3 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 1325 return shift->_dirs('data');
211             }
212              
213              
214             sub data_dirs_list {
215 2     2 1 4 my $self = shift;
216 2         21 return map { $self->_dir($_) } split /\Q$Config{path_sep}\E/, $self->data_dirs;
  4         76  
217             }
218              
219              
220             sub config_dirs {
221 6     6 1 114 return shift->_dirs('config');
222             }
223              
224              
225             sub config_dirs_list {
226 2     2 1 1681 my $self = shift;
227 2         21 return map { $self->_dir($_) } split /\Q$Config{path_sep}\E/, $self->config_dirs;
  3         38  
228             }
229              
230              
231             sub exe_dir
232             {
233 2     2 1 8 my($self) = @_;
234 2         43 -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 3773 my ($self, @subpath) = @_;
242 6 100       18 unshift @subpath, $self->{name} if $self->{api} >= 1;
243 6         12 return $self->_lookup_file('data', @subpath);
244             }
245              
246              
247             sub lookup_config_file {
248 6     6 1 3598 my ($self, @subpath) = @_;
249 6 100       19 unshift @subpath, $self->{name} if $self->{api} >= 1;
250 6         14 return $self->_lookup_file('config', @subpath);
251             }
252              
253              
254             1;
255              
256             __END__