File Coverage

blib/lib/File/Temp/MoreUtils.pm
Criterion Covered Total %
statement 42 44 95.4
branch 17 20 85.0
condition 4 5 80.0
subroutine 7 7 100.0
pod 1 1 100.0
total 71 77 92.2


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-12'; # DATE
5             our $DIST = 'File-Temp-MoreUtils'; # DIST
6             our $VERSION = '0.002'; # VERSION
7              
8 1     1   96609 use 5.010001; # for // & state var
  1         11  
9 1     1   5 use strict;
  1         2  
  1         20  
10 1     1   4 use warnings;
  1         2  
  1         26  
11              
12 1     1   6 use Fcntl ':DEFAULT';
  1         2  
  1         329  
13 1     1   7 use File::Temp ();
  1         1  
  1         17  
14              
15 1     1   4 use Exporter 'import';
  1         2  
  1         538  
16             our @EXPORT_OK = qw(tempfile_named);
17              
18             our %SPEC;
19              
20             $SPEC{tempfile_named} = {
21             v => 1.1,
22             summary => 'Try to create a temporary file with certain name '.
23             '(but used .1, .2, ... suffix if already exists) ',
24             description => <<'_',
25              
26             Unlike 's `tempfile()` which creates a temporary file with a
27             unique random name, this routine tries to create a temporary file with a
28             specific name, but adds a counter suffix when the specified name already exists.
29             Care has been taken to avoid race condition (using `O_EXCL` flag of `sysopen`).
30             This is often desirable in the case when we want the temporary file to have a
31             name similarity with another file.
32              
33             And like 's `tempfile()`, will return:
34              
35             ($fh, $filename)
36              
37             _
38             result_naked => 1,
39             args => {
40             name => {
41             schema => 'filename*',
42             pos => 0,
43             req => 1,
44             },
45             dir => {
46             summary => 'If specified, will create the temporary file here',
47             description => <<'_',
48              
49             If specified and set to `undef`, will create new temporary directory using
50             's `tempdir` (with CLEANUP option set to true unless DEBUG
51             environment variable is set to true) and use this temporary directory for the
52             directory, including for subsequent invocation for the same process whenever
53             `dir` is set to `undef` again.
54              
55             _
56             schema => 'dirname',
57             },
58             suffix_start => {
59             schema => ['str*', min_len=>1],
60             default => 1,
61             description => <<'_',
62              
63             Will use Perl's post-increment operator (`++`) to increment the suffix, so this
64             works with number as well as letter combinations, e.g. `aa` will be incremented
65             to `ab`, `ac` and so on.
66              
67             _
68             },
69             },
70             examples => [
71             {
72             args => {name=>'source.pdf'},
73             summary => 'Attempt to create source.pdf, then if already exists source.1.pdf, and so on',
74             test => 0,
75             'x.doc.show_result' => 0,
76             },
77             {
78             args => {name=>'source', dir=>undef},
79             summary => 'Attempt to create source.pdf in a temporary directory, then if already exists source.1, and so on',
80             test => 0,
81             'x.doc.show_result' => 0,
82             },
83             {
84             args => {name=>'source.pdf', suffix_start=>'tmp1'},
85             summary => 'Attempt to create source.pdf, then if already exists source.tmp1.pdf, then source.tmp2.pdf, and so on',
86             test => 0,
87             'x.doc.show_result' => 0,
88             },
89             ],
90             };
91             sub tempfile_named {
92 13     13 1 4504 my %args = @_;
93              
94 13 100       55 die "tempfile_named(): Please specify name" unless defined $args{name};
95              
96 12         18 state $tempdir;
97 12         18 my $dir;
98 12 100       33 if (exists $args{dir}) {
99 4 100       10 if (defined $args{dir}) {
100 2         5 $dir = $args{dir};
101             } else {
102 2   66     14 $tempdir //= File::Temp::tempdir(CLEANUP => !$ENV{DEBUG});
103 2         456 $dir = $tempdir;
104             }
105 4 50       61 die "tempfile_named(): dir '$dir' is not a directory" unless -d $dir;
106             }
107              
108 12   100     49 my $suffix_start = $args{suffix_start} // 1;
109 12         22 my $suffix;
110             my $fh;
111 12 100       173 my $name0 = defined $dir ? "$dir/" . File::Basename::basename($args{name}) : $args{name};
112 12         25 my $counter = 1;
113 12         17 while (1) {
114 26         50 my $name = $name0;
115 26 100       53 if (defined $suffix) {
116 14 100       58 $name =~ s/(.+\.)(?=.)/"$1$suffix."/e
  3         16  
117             or $name .= ".$suffix";
118 14         25 $suffix++;
119             } else {
120 12         19 $suffix = $suffix_start;
121             }
122 26 100       898 if (sysopen $fh, $name, O_CREAT | O_CREAT | O_EXCL) {
123 12         165 return ($fh, $name);
124             }
125 14 50       123 unless ($! =~ /File exists/) {
126 0         0 die "tempfile_named(): Can't create temporary file '$name': $!";
127             }
128 14 50       40 if ($counter++ > 10_000) {
129 0           die "tempfile_named(): Can't create temporary file after many retries: $!";
130             }
131             }
132             }
133              
134             1;
135             # ABSTRACT: Provide more routines related to creating temporary files/dirs
136              
137             __END__