File Coverage

blib/lib/Connector/Builtin/File/Path.pm
Criterion Covered Total %
statement 84 94 89.3
branch 27 42 64.2
condition 7 11 63.6
subroutine 13 14 92.8
pod 4 4 100.0
total 135 165 81.8


line stmt bran cond sub pod time code
1             # Connector::Builtin::File::Path
2             #
3             # Proxy class for accessing files
4             #
5             # Written by Oliver Welter for the OpenXPKI project 2012
6             #
7              
8             use strict;
9 2     2   89016 use warnings;
  2         12  
  2         55  
10 2     2   9 use English;
  2         2  
  2         43  
11 2     2   12 use File::Spec;
  2         4  
  2         14  
12 2     2   777 use Data::Dumper;
  2         4  
  2         58  
13 2     2   511 use Template;
  2         5736  
  2         107  
14 2     2   2265  
  2         37822  
  2         67  
15             use Moose;
16 2     2   448 extends 'Connector::Builtin';
  2         387400  
  2         13  
17              
18             with 'Connector::Role::LocalPath';
19              
20             has content => (
21             is => 'rw',
22             isa => 'Str',
23             );
24              
25             has ifexists => (
26             is => 'rw',
27             isa => 'Str',
28             default => 'replace'
29             );
30              
31             has user => (
32             is => 'rw',
33             isa => 'Str',
34             default => ''
35             );
36              
37             has group => (
38             is => 'rw',
39             isa => 'Str',
40             default => ''
41             );
42              
43             has mode => (
44             is => 'rw',
45             isa => 'Str',
46             default => ''
47             );
48              
49             my $self = shift;
50              
51 0     0   0 if (! -d $self->{LOCATION}) {
52             confess("Cannot open directory " . $self->{LOCATION} );
53 0 0       0 }
54 0         0  
55             return 1;
56             }
57 0         0  
58             # return the content of the file
59              
60             my $self = shift;
61             my $path = shift;
62              
63 6     6 1 1137 my $filename = $self->_sanitize_path( $path );
64 6         37  
65             if (! -r $filename) {
66 6         16 return $self->_node_not_exists( $path );
67             }
68 6 50       106  
69 0         0 my $content = do {
70             local $INPUT_RECORD_SEPARATOR;
71             open my $fh, '<', $filename;
72 6         13 <$fh>;
73 6         26 };
74 6         189 return $content;
75 6         228 }
76              
77 6         50 my $self = shift;
78              
79             # If we have no path, we tell the caller that we are a connector
80             my @path = $self->_build_path_with_prefix( shift );
81 2     2 1 5 if (scalar @path == 0) {
82             return { TYPE => "connector" };
83             }
84 2         6  
85 2 100       9 return {TYPE => "scalar" };
86 1         7 }
87              
88              
89 1         6  
90             my $self = shift;
91              
92             # No path = connector root which always exists
93             my @path = $self->_build_path_with_prefix( shift );
94             if (scalar @path == 0) {
95 4     4 1 7 return 1;
96             }
97              
98 4         13 my $filename = $self->_sanitize_path( \@path );
99 4 100       14  
100 1         5 return -r $filename;
101             }
102              
103 3         21  
104             # return the content of the file
105 3         76  
106             my $self = shift;
107             my $file = shift;
108             my $data = shift;
109              
110             my $filename = $self->_sanitize_path( $file, $data );
111              
112 12     12 1 36 my $content;
113 12         18 if ($self->content()) {
114 12         22 $self->log()->debug('Process template for content ' . $self->content());
115             my $template = Template->new({});
116 12         43  
117             $data = { DATA => $data } if (ref $data eq '');
118 12         21  
119 12 100       322 $template->process( \$self->content(), $data, \$content) || die "Error processing content template.";
120 10         185 } else {
121 10         92 if (ref $data ne '') {
122             die "You need to define a content template if data is not a scalar";
123 10 100       4021 }
124             $content = $data;
125 10 50       333 }
126              
127 2 50       19 my $mode = $self->ifexists();
128 0         0 if ($mode eq 'fail' && -f $filename) {
129             die "File $filename exists";
130 2         6 }
131              
132             if ($mode eq 'silent' && -f $filename) {
133 12         17663 return;
134 12 100 66     63 }
135 1         14  
136             if (my $mode = $self->mode()) {
137             if ($mode =~ m{\A[0-7]{4}\z}) {
138 11 100 66     52 chmod oct($mode), $filename || die "Unable to change mode to $mode";
139 1         7 } else {
140             die "Given umask '$mode' is not valid";
141             }
142 10 100       245 }
143 2 100       9  
144 1   50     31 my $uid = -1;
145             my $gid;
146 1         9 if (my $user = $self->user()) {
147             $uid = getpwnam($user) or die "$user not known";
148             $gid = -1;
149             }
150 9         25  
151 9         17 if (my $group = $self->group()) {
152 9 50       231 $gid = getgrnam($group) or die "$group not known";
153 0 0       0 }
154 0         0  
155             if ($mode eq 'append' && -f $filename) {
156             open (FILE, ">>",$filename) || die "Unable to open file for appending";
157 9 50       230 } else {
158 0 0       0 open (FILE, ">", $filename) || die "Unable to open file for writing";
159             }
160              
161 9 100 66     55 print FILE $content;
162 1 50       35 close FILE;
163              
164 8 100       958 if ($gid) {
165             chown ($uid, $gid, $filename) || die "Unable to chown $filename to $uid/$gid";
166             }
167 7         74  
168 7         508 #FIXME - some error handling might not hurt
169              
170 7 50       35 return 1;
171 0 0       0 }
172              
173              
174              
175             my $self = shift;
176 7         71 my $inargs = shift;
177             my $data = shift;
178              
179             my @args = $self->_build_path_with_prefix( $inargs );
180              
181             my $file = $self->_render_local_path( \@args, $data );
182 21     21   33  
183 21         40 my $filename = $self->{LOCATION}.'/'.$file;
184 21         26  
185             $self->log()->debug('Filename evaluated to ' . $filename);
186 21         76  
187             return $filename;
188 21         104 }
189              
190 21         70 no Moose;
191             __PACKAGE__->meta->make_immutable;
192 21         531  
193             1;
194 21         189  
195             =head1 Name
196              
197 2     2   15653 Connector::Builtin::File::Path
  2         5  
  2         13  
198              
199             =head1 Description
200              
201             Highly configurable file writer/reader.
202              
203             =head1 Parameters
204              
205             =over
206              
207             =item LOCATION
208              
209             The base directory where the files are located. This parameter is mandatory.
210              
211             =item file/path
212              
213             Pattern for Template Toolkit to build the filename.
214             The path components are available in the key ARGS. In set mode the unfiltered
215             data is available in key DATA.
216              
217             See also Connector::Role::LocalPath
218              
219             =item content
220              
221             Pattern for Template Toolkit to build the content. The data is passed
222             "as is". If data is a scalar, it is wrapped into a hash using DATA as key.
223              
224             =item ifexists
225              
226             =over 2
227              
228             =item * append: opens the file for appending write.
229              
230             =item * fail: call C<die>
231              
232             =item * silent: fail silently.
233              
234             =item * replace: replace the file with the new content.
235              
236             =back
237              
238             =item mode
239              
240             Filesystem permissions to apply to the file when a file is written using the
241             set method. Must be given in octal notation, e.g. 0644. Default is to not set
242             the permissions and rely on the systems umask.
243              
244             =item user / group
245              
246             Name of a user / group that the file should belong to.
247              
248             =back
249              
250             =head1 Supported Methods
251              
252             =head2 set
253              
254             Write data to a file.
255              
256             $conn->set('filename', { NAME => 'Oliver', 'ROLE' => 'Administrator' });
257              
258             See the file parameter how to control the filename.
259             By default, files are silently overwritten if they exist. See the I<ifexists>
260             parameter for an alternative behaviour.
261              
262             =head2 get
263              
264             Fetch data from a file. See the file parameter how to control the filename.
265              
266             my $data = $conn->get('filename');
267              
268             =head1 Example
269              
270             my $conn = Connector::Builtin::File::Path->new({
271             LOCATION: /var/data/
272             file: [% ARGS.0 %].txt
273             content: Hello [% NAME %]
274             });
275              
276             $conn->set('test', { NAME => 'Oliver' });
277              
278             Results in a file I</var/data/test.txt> with the content I<Hello Oliver>.