File Coverage

blib/lib/File/Temp/MoreUtils.pm
Criterion Covered Total %
statement 70 74 94.5
branch 36 40 90.0
condition 8 10 80.0
subroutine 9 9 100.0
pod 2 2 100.0
total 125 135 92.5


line stmt bran cond sub pod time code
1             package File::Temp::MoreUtils;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-07-20'; # DATE
5             our $DIST = 'File-Temp-MoreUtils'; # DIST
6             our $VERSION = '0.005'; # VERSION
7              
8 1     1   99442 use 5.010001; # for // & state var
  1         13  
9 1     1   5 use strict;
  1         2  
  1         21  
10 1     1   5 use warnings;
  1         1  
  1         28  
11              
12 1     1   5 use Errno 'EEXIST';
  1         1  
  1         112  
13 1     1   7 use Fcntl ':DEFAULT';
  1         2  
  1         342  
14 1     1   8 use File::Temp ();
  1         2  
  1         20  
15              
16 1     1   4 use Exporter 'import';
  1         2  
  1         984  
17             our @EXPORT_OK = qw(tempfile_named tempdir_named);
18              
19             our %SPEC;
20              
21             $SPEC{tempfile_named} = {
22             v => 1.1,
23             summary => 'Try to create a temporary file with certain name '.
24             '(but used .1, .2, ... suffix if already exists) ',
25             description => <<'_',
26              
27             Unlike 's `tempfile()` which creates a temporary file with a
28             unique random name, this routine tries to create a temporary file with a
29             specific name, but adds a counter suffix when the specified name already exists.
30             Care has been taken to avoid race condition (using `O_EXCL` flag of `sysopen`).
31             This is often desirable in the case when we want the temporary file to have a
32             name similarity with another file.
33              
34             And like 's `tempfile()`, will return:
35              
36             ($fh, $filename)
37              
38             _
39             result_naked => 1,
40             args => {
41             name => {
42             schema => 'filename*',
43             pos => 0,
44             req => 1,
45             },
46             dir => {
47             summary => 'If specified, will create the temporary file here',
48             description => <<'_',
49              
50             If specified and set to `undef`, will create new temporary directory using
51             's `tempdir` (with CLEANUP option set to true unless DEBUG
52             environment variable is set to true) and use this temporary directory for the
53             directory, including for subsequent invocation for the same process whenever
54             `dir` is set to `undef` again.
55              
56             _
57             schema => 'dirname',
58             },
59             suffix_start => {
60             schema => ['str*', min_len=>1],
61             default => 1,
62             description => <<'_',
63              
64             Will use Perl's post-increment operator (`++`) to increment the suffix, so this
65             works with number as well as letter combinations, e.g. `aa` will be incremented
66             to `ab`, `ac` and so on.
67              
68             _
69             },
70             },
71             examples => [
72             {
73             args => {name=>'source.pdf'},
74             summary => 'Attempt to create source.pdf, and if already exists source.1.pdf, and so on',
75             test => 0,
76             'x.doc.show_result' => 0,
77             },
78             {
79             args => {name=>'source', dir=>undef},
80             summary => 'Attempt to create source.pdf in a temporary directory, and if already exists source.1, and so on',
81             test => 0,
82             'x.doc.show_result' => 0,
83             },
84             {
85             args => {name=>'source.pdf', suffix_start=>'tmp1'},
86             summary => 'Attempt to create source.pdf, and if already exists source.tmp1.pdf, then source.tmp2.pdf, and so on',
87             test => 0,
88             'x.doc.show_result' => 0,
89             },
90             ],
91             };
92             sub tempfile_named {
93 15     15 1 4382 my %args = @_;
94              
95 15 100       59 die "tempfile_named(): Please specify name" unless defined $args{name};
96              
97 14         21 state $tempdir;
98 14         21 my $dir;
99 14 100       32 if (exists $args{dir}) {
100 5 100       13 if (defined $args{dir}) {
101 3         7 $dir = $args{dir};
102             } else {
103 2   66     10 $tempdir //= File::Temp::tempdir(CLEANUP => !$ENV{DEBUG});
104 2         397 $dir = $tempdir;
105             }
106 5 100       97 die "tempfile_named(): dir '$dir' is not a directory" unless -d $dir;
107             }
108              
109 13   100     53 my $suffix_start = $args{suffix_start} // 1;
110 13         31 my $suffix;
111             my $fh;
112 13 100       179 my $name0 = defined $dir ? "$dir/" . File::Basename::basename($args{name}) : $args{name};
113 13         22 my $counter = 1;
114 13         21 while (1) {
115 28         43 my $name = $name0;
116 28 100       54 if (defined $suffix) {
117 15 100       71 $name =~ s/(.+\.)(?=.)/"$1$suffix."/e
  3         14  
118             or $name .= ".$suffix";
119 15         24 $suffix++;
120             } else {
121 13         20 $suffix = $suffix_start;
122             }
123 28 100       1033 if (sysopen $fh, $name, O_CREAT | O_CREAT | O_EXCL) {
124 13         186 return ($fh, $name);
125             }
126 15 50       111 unless ($! == EEXIST) {
127 0         0 die "tempfile_named(): Can't create temporary file '$name': $!";
128             }
129 15 50       45 if ($counter++ > 10_000) {
130 0         0 die "tempfile_named(): Can't create temporary file after many retries: $!";
131             }
132             }
133             }
134              
135             $SPEC{tempdir_named} = {
136             v => 1.1,
137             summary => 'Try to create a temporary directory with certain name '.
138             '(but used .1, .2, ... suffix if already exists) ',
139             description => <<'_',
140              
141             This is similar to `tempfile_named()`, but since there is no `O_EXCL` flag
142             similar to opening a file, there is a race condition possible where you create a
143             certain temporary directory and before you open/read the directory, someone else
144             has replaced the directory with another. Therefore it is best if you create the
145             specifically-named temporary directory _inside_ another temporary directory.
146              
147             Like 's `tempdir()`, it will return the path of the created
148             temporary directory:
149              
150             $dir
151              
152             _
153             result_naked => 1,
154             args => {
155             name => {
156             schema => 'filename*',
157             pos => 0,
158             req => 1,
159             },
160             dir => {
161             summary => 'If specified, will create the temporary directory here',
162             description => <<'_',
163              
164             If specified and set to `undef`, will create new temporary directory using
165             's `tempdir` (with CLEANUP option set to true unless DEBUG
166             environment variable is set to true) and use this temporary directory for the
167             directory, including for subsequent invocation for the same process whenever
168             `dir` is set to `undef` again.
169              
170             _
171             schema => 'dirname',
172             },
173             suffix_start => {
174             schema => ['str*', min_len=>1],
175             default => 1,
176             description => <<'_',
177              
178             Will use Perl's post-increment operator (`++`) to increment the suffix, so this
179             works with number as well as letter combinations, e.g. `aa` will be incremented
180             to `ab`, `ac` and so on.
181              
182             _
183             },
184             },
185             examples => [
186             {
187             args => {name=>'/foo/source.dir'},
188             summary => 'Attempt to create /foo/source.dir/, and if already exists /foo/source.1.dir/ instead, and so on',
189             test => 0,
190             'x.doc.show_result' => 0,
191             },
192             {
193             args => {name=>'source', dir=>undef},
194             summary => 'Attempt to create source/ inside another temporary directory, and if already exists source.1/, and so on',
195             test => 0,
196             'x.doc.show_result' => 0,
197             },
198             {
199             args => {name=>'source.dir', suffix_start=>'tmp1'},
200             summary => 'Attempt to create source.dir/, and if already exists source.tmp1.dir/, then source.tmp2.pdf, and so on',
201             test => 0,
202             'x.doc.show_result' => 0,
203             },
204             ],
205             };
206             sub tempdir_named {
207 15     15 1 6138 my %args = @_;
208              
209 15 100       55 die "tempdir_named(): Please specify name" unless defined $args{name};
210              
211 14         26 state $tempdir;
212 14         14 my $dir;
213 14 100       30 if (exists $args{dir}) {
214 5 100       13 if (defined $args{dir}) {
215 3         6 $dir = $args{dir};
216             } else {
217 2   66     11 $tempdir //= File::Temp::tempdir(CLEANUP => !$ENV{DEBUG});
218 2         393 $dir = $tempdir;
219             }
220 5 100       90 die "tempdir_named(): dir '$dir' is not a directory" unless -d $dir;
221             }
222              
223 13   100     57 my $suffix_start = $args{suffix_start} // 1;
224 13         20 my $suffix;
225             my $fh;
226 13 100       135 my $name0 = defined $dir ? "$dir/" . File::Basename::basename($args{name}) : $args{name};
227 13         25 my $counter = 1;
228 13         19 while (1) {
229 28         43 my $name = $name0;
230 28 100       54 if (defined $suffix) {
231 15 100       72 $name =~ s/(.+\.)(?=.)/"$1$suffix."/e
  3         15  
232             or $name .= ".$suffix";
233 15         28 $suffix++;
234             } else {
235 13         22 $suffix = $suffix_start;
236             }
237 28 100       850 if (mkdir $name, 0700) {
238 13         156 return $name;
239             }
240 15 50       100 unless ($! == EEXIST) {
241 0         0 die "tempdir_named(): Can't create temporary dir '$name': $!";
242             }
243 15 50       59 if ($counter++ > 10_000) {
244 0           die "tempdir_named(): Can't create temporary dir after many retries: $!";
245             }
246             }
247             }
248              
249             1;
250             # ABSTRACT: Provide more routines related to creating temporary files/dirs
251              
252             __END__