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 = '2020-06-03'; # DATE
5             our $DIST = 'App-FileRemoveUtils'; # DIST
6             our $VERSION = '0.005'; # VERSION
7              
8 1     1   103944 use 5.010001;
  1         14  
9 1     1   6 use strict;
  1         2  
  1         20  
10 1     1   4 use warnings;
  1         2  
  1         40  
11 1     1   1873 use Log::ger;
  1         49  
  1         9  
12              
13 1     1   252 use Exporter 'import';
  1         2  
  1         903  
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 12 require File::Find;
26              
27 2         3 my @files;
28             File::Find::find(
29             sub {
30 16     16   161 -l $_; # perform lstat instead of stat
31 16 100       339 return unless -f _;
32 12 100       90 return if -s _;
33 6         73 push @files, "$File::Find::dir/$_";
34             },
35 2         144 '.'
36             );
37              
38 2         16 \@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 13 require File::Find;
58 2         556 require File::MoreUtil;
59              
60 2   50     2276 my $include_would_be_empty = $_[0] // 1;
61              
62 2         32 my %dirs; # key = path, value = {subdir => 1}
63             File::Find::find(
64             sub {
65 26 100 66 26   1096 return if $_ eq '.' || $_ eq '..';
66 24 50       251 return if -l $_;
67 24 100       240 return unless -d _;
68 12 100       36 return if File::MoreUtil::dir_has_non_subdirs($_);
69 10         799 my $path = "$File::Find::dir/$_";
70 10         27 $dirs{$path} = { map {$_=>1} File::MoreUtil::get_dir_entries($_) };
  6         574  
71             },
72 2         111 '.'
73             );
74              
75 2         253 my @dirs;
76 2 50       13 for my $dir (sort { length($b) <=> length($a) || $a cmp $b } keys %dirs) {
  18         39  
77 10 50       15 if (!(keys %{ $dirs{$dir} })) {
  10         26  
78 10         21 push @dirs, $dir;
79 10 50       16 if ($include_would_be_empty) {
80 10 50       36 $dir =~ m!(.+)/(.+)! or next;
81 10         27 my ($parent, $base) = ($1, $2);
82 10         28 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 4652 my %args = @_;
117              
118 2         8 my $files = list_all_empty_files();
119 2         17 for my $f (@$files) {
120 6 100       25 if ($args{-dry_run}) {
121 3         11 log_info "[DRY-RUN] Deleting %s ...", $f;
122             } else {
123 3         13 log_info "Deleting %s ...", $f;
124 3 50       117 unlink $f or do {
125 0         0 log_error "Failed deleting %s: %s", $f, $!;
126             };
127             }
128             }
129              
130 2         16 [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 6212 my %args = @_;
162              
163 2         6 my $dirs = list_all_empty_dirs();
164 2         5 for my $dir (@$dirs) {
165 10 100       32 if ($args{-dry_run}) {
166 5         11 log_info "[DRY-RUN] Deleting %s ...", $dir;
167             } else {
168 5 50       16 if (File::MoreUtil::dir_empty($dir)) {
169 5         369 log_info "Deleting %s ...", $dir;
170 5 50       196 rmdir $dir or do {
171 0         0 log_error "Failed deleting %s: %s", $dir, $!;
172             };
173             }
174             }
175             }
176              
177 2         13 [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.005 of App::FileRemoveUtils (from Perl distribution App-FileRemoveUtils), released on 2020-06-03.
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, msg, payload, 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) is an integer containing HTTP status code
244             (200 means OK, 4xx caller error, 5xx function error). Second element
245             (msg) is a string containing error message, or 'OK' if status is
246             200. Third element (payload) is optional, the actual result. Fourth
247             element (meta) is called result metadata and is optional, a hash
248             that contains extra information.
249              
250             Return value: (any)
251              
252              
253              
254             =head2 delete_all_empty_files
255              
256             Usage:
257              
258             delete_all_empty_files() -> [status, msg, payload, 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) is an integer containing HTTP status code
282             (200 means OK, 4xx caller error, 5xx function error). Second element
283             (msg) is a string containing error message, or 'OK' if status is
284             200. Third element (payload) is optional, the actual result. Fourth
285             element (meta) is called result metadata and is optional, a hash
286             that contains extra information.
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             =head1 AUTHOR
352              
353             perlancar <perlancar@cpan.org>
354              
355             =head1 COPYRIGHT AND LICENSE
356              
357             This software is copyright (c) 2020 by perlancar@cpan.org.
358              
359             This is free software; you can redistribute it and/or modify it under
360             the same terms as the Perl 5 programming language system itself.
361              
362             =cut