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