File Coverage

blib/lib/App/FileRemoveUtils.pm
Criterion Covered Total %
statement 62 64 96.8
branch 22 30 73.3
condition 3 5 60.0
subroutine 11 11 100.0
pod 4 4 100.0
total 102 114 89.4


line stmt bran cond sub pod time code
1             package App::FileRemoveUtils;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2021-08-02'; # DATE
5             our $DIST = 'App-FileRemoveUtils'; # DIST
6             our $VERSION = '0.006'; # VERSION
7              
8 1     1   85620 use 5.010001;
  1         11  
9 1     1   5 use strict;
  1         2  
  1         22  
10 1     1   4 use warnings;
  1         1  
  1         32  
11 1     1   1981 use Log::ger;
  1         46  
  1         6  
12              
13 1     1   209 use Exporter 'import';
  1         2  
  1         850  
14             our @EXPORT_OK = qw(delete_all_empty_files delete_all_empty_dirs);
15              
16             our %SPEC;
17              
18             $SPEC{list_all_empty_files} = {
19             v => 1.1,
20             summary => 'List all empty (zero-sized) files in the current directory tree',
21             args => {},
22             result_naked => 1,
23             };
24             sub list_all_empty_files {
25 2     2 1 13 require File::Find;
26              
27 2         3 my @files;
28             File::Find::find(
29             sub {
30 16     16   113 -l $_; # perform lstat instead of stat
31 16 100       261 return unless -f _;
32 12 100       68 return if -s _;
33 6         54 push @files, "$File::Find::dir/$_";
34             },
35 2         107 '.'
36             );
37              
38 2         13 \@files;
39             }
40              
41             $SPEC{list_all_empty_dirs} = {
42             v => 1.1,
43             summary => 'List all sempty (zero-entry) subdirectories in the current directory tree',
44             args_as => 'array',
45             args => {
46             include_would_be_empty => {
47             summary => 'Include directories that would be empty if '.
48             'their empty subdirectories are removed',
49             schema => 'bool*',
50             pos => 0,
51             default => 1,
52             },
53             },
54             result_naked => 1,
55             };
56             sub list_all_empty_dirs {
57 2     2 1 19 require File::Find;
58 2         908 require File::MoreUtil;
59              
60 2   50     1986 my $include_would_be_empty = $_[0] // 1;
61              
62 2         4 my %dirs; # key = path, value = {subdir => 1}
63             File::Find::find(
64             sub {
65 26 100 66 26   870 return if $_ eq '.' || $_ eq '..';
66 24 50       186 return if -l $_;
67 24 100       226 return unless -d _;
68 12 100       31 return if File::MoreUtil::dir_has_non_subdirs($_);
69 10         606 my $path = "$File::Find::dir/$_";
70 10         22 $dirs{$path} = { map {$_=>1} File::MoreUtil::get_dir_entries($_) };
  6         538  
71             },
72 2         263 '.'
73             );
74              
75 2         188 my @dirs;
76 2 50       15 for my $dir (sort { length($b) <=> length($a) || $a cmp $b } keys %dirs) {
  18         32  
77 10 50       11 if (!(keys %{ $dirs{$dir} })) {
  10         21  
78 10         15 push @dirs, $dir;
79 10 50       14 if ($include_would_be_empty) {
80 10 50       46 $dir =~ m!(.+)/(.+)! or next;
81 10         22 my ($parent, $base) = ($1, $2);
82 10         20 delete $dirs{$parent}{$base};
83             }
84             }
85             }
86              
87 2         9 \@dirs;
88             }
89              
90             $SPEC{delete_all_empty_files} = {
91             v => 1.1,
92             summary => 'Delete all empty (zero-sized) files recursively',
93             args => {
94             },
95             features => {
96             dry_run=>{default=>1},
97             },
98             examples => [
99             {
100             summary => 'Show what files will be deleted (dry-mode by default)',
101             src => 'delete-all-empty-files',
102             src_plang => 'bash',
103             test => 0,
104             'x.doc.show_result' => 0,
105             },
106             {
107             summary => 'Actually delete files (disable dry-run mode)',
108             src => 'delete-all-empty-files --no-dry-run',
109             src_plang => 'bash',
110             test => 0,
111             'x.doc.show_result' => 0,
112             },
113             ],
114             };
115             sub delete_all_empty_files {
116 2     2 1 3661 my %args = @_;
117              
118 2         6 my $files = list_all_empty_files();
119 2         14 for my $f (@$files) {
120 6 100       20 if ($args{-dry_run}) {
121 3         8 log_info "[DRY-RUN] Deleting %s ...", $f;
122             } else {
123 3         11 log_info "Deleting %s ...", $f;
124 3 50       122 unlink $f or do {
125 0         0 log_error "Failed deleting %s: %s", $f, $!;
126             };
127             }
128             }
129              
130 2         11 [200, "OK", undef, {
131             'func.files' => $files,
132             }];
133             }
134              
135             $SPEC{delete_all_empty_dirs} = {
136             v => 1.1,
137             summary => 'Delete all empty (zero-sized) subdirectories recursively',
138             args => {
139             },
140             features => {
141             dry_run=>{default=>1},
142             },
143             examples => [
144             {
145             summary => 'Show what directories will be deleted (dry-mode by default)',
146             src => 'delete-all-empty-dirs',
147             src_plang => 'bash',
148             test => 0,
149             'x.doc.show_result' => 0,
150             },
151             {
152             summary => 'Actually delete files (disable dry-run mode)',
153             src => 'delete-all-empty-dirs --no-dry-run',
154             src_plang => 'bash',
155             test => 0,
156             'x.doc.show_result' => 0,
157             },
158             ],
159             };
160             sub delete_all_empty_dirs {
161 2     2 1 5272 my %args = @_;
162              
163 2         6 my $dirs = list_all_empty_dirs();
164 2         4 for my $dir (@$dirs) {
165 10 100       25 if ($args{-dry_run}) {
166 5         11 log_info "[DRY-RUN] Deleting %s ...", $dir;
167             } else {
168 5 50       31 if (File::MoreUtil::dir_empty($dir)) {
169 5         280 log_info "Deleting %s ...", $dir;
170 5 50       175 rmdir $dir or do {
171 0         0 log_error "Failed deleting %s: %s", $dir, $!;
172             };
173             }
174             }
175             }
176              
177 2         10 [200];
178             }
179              
180             1;
181             # ABSTRACT: Utilities related to removing/deleting files
182              
183             __END__
184              
185             =pod
186              
187             =encoding UTF-8
188              
189             =head1 NAME
190              
191             App::FileRemoveUtils - Utilities related to removing/deleting files
192              
193             =head1 VERSION
194              
195             This document describes version 0.006 of App::FileRemoveUtils (from Perl distribution App-FileRemoveUtils), released on 2021-08-02.
196              
197             =head1 DESCRIPTION
198              
199             This distribution provides the following command-line utilities:
200              
201             =over
202              
203             =item * L<delete-all-empty-dirs>
204              
205             =item * L<delete-all-empty-files>
206              
207             =item * L<list-all-empty-dirs>
208              
209             =item * L<list-all-empty-files>
210              
211             =back
212              
213             =head1 FUNCTIONS
214              
215              
216             =head2 delete_all_empty_dirs
217              
218             Usage:
219              
220             delete_all_empty_dirs() -> [$status_code, $reason, $payload, \%result_meta]
221              
222             Delete all empty (zero-sized) subdirectories recursively.
223              
224             This function is not exported by default, but exportable.
225              
226             This function supports dry-run operation.
227              
228              
229             No arguments.
230              
231             Special arguments:
232              
233             =over 4
234              
235             =item * B<-dry_run> => I<bool>
236              
237             Pass -dry_run=E<gt>1 to enable simulation mode.
238              
239             =back
240              
241             Returns an enveloped result (an array).
242              
243             First element ($status_code) is an integer containing HTTP-like status code
244             (200 means OK, 4xx caller error, 5xx function error). Second element
245             ($reason) is a string containing error message, or something like "OK" if status is
246             200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
247             element (%result_meta) is called result metadata and is optional, a hash
248             that contains extra information, much like how HTTP response headers provide additional metadata.
249              
250             Return value: (any)
251              
252              
253              
254             =head2 delete_all_empty_files
255              
256             Usage:
257              
258             delete_all_empty_files() -> [$status_code, $reason, $payload, \%result_meta]
259              
260             Delete all empty (zero-sized) files recursively.
261              
262             This function is not exported by default, but exportable.
263              
264             This function supports dry-run operation.
265              
266              
267             No arguments.
268              
269             Special arguments:
270              
271             =over 4
272              
273             =item * B<-dry_run> => I<bool>
274              
275             Pass -dry_run=E<gt>1 to enable simulation mode.
276              
277             =back
278              
279             Returns an enveloped result (an array).
280              
281             First element ($status_code) is an integer containing HTTP-like status code
282             (200 means OK, 4xx caller error, 5xx function error). Second element
283             ($reason) is a string containing error message, or something like "OK" if status is
284             200. Third element ($payload) is the actual result, but usually not present when enveloped result is an error response ($status_code is not 2xx). Fourth
285             element (%result_meta) is called result metadata and is optional, a hash
286             that contains extra information, much like how HTTP response headers provide additional metadata.
287              
288             Return value: (any)
289              
290              
291              
292             =head2 list_all_empty_dirs
293              
294             Usage:
295              
296             list_all_empty_dirs($include_would_be_empty) -> any
297              
298             List all sempty (zero-entry) subdirectories in the current directory tree.
299              
300             This function is not exported.
301              
302             Arguments ('*' denotes required arguments):
303              
304             =over 4
305              
306             =item * B<$include_would_be_empty> => I<bool> (default: 1)
307              
308             Include directories that would be empty if their empty subdirectories are removed.
309              
310              
311             =back
312              
313             Return value: (any)
314              
315              
316              
317             =head2 list_all_empty_files
318              
319             Usage:
320              
321             list_all_empty_files() -> any
322              
323             List all empty (zero-sized) files in the current directory tree.
324              
325             This function is not exported.
326              
327             No arguments.
328              
329             Return value: (any)
330              
331             =head1 HOMEPAGE
332              
333             Please visit the project's homepage at L<https://metacpan.org/release/App-FileRemoveUtils>.
334              
335             =head1 SOURCE
336              
337             Source repository is at L<https://github.com/perlancar/perl-App-FileRemoveUtils>.
338              
339             =head1 BUGS
340              
341             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=App-FileRemoveUtils>
342              
343             When submitting a bug or request, please include a test-file or a
344             patch to an existing test-file that illustrates the bug or desired
345             feature.
346              
347             =head1 SEE ALSO
348              
349             L<rmhere> from L<App::rmhere>
350              
351             Other similar distributions: L<App::FileModifyUtils>,
352             L<App::FileRenameUtilities>.
353              
354             =head1 AUTHOR
355              
356             perlancar <perlancar@cpan.org>
357              
358             =head1 COPYRIGHT AND LICENSE
359              
360             This software is copyright (c) 2021, 2020 by perlancar@cpan.org.
361              
362             This is free software; you can redistribute it and/or modify it under
363             the same terms as the Perl 5 programming language system itself.
364              
365             =cut