File Coverage

blib/lib/File/Digest.pm
Criterion Covered Total %
statement 59 62 95.1
branch 16 18 88.8
condition 3 6 50.0
subroutine 8 8 100.0
pod 2 2 100.0
total 88 96 91.6


line stmt bran cond sub pod time code
1             package File::Digest;
2              
3             our $AUTHORITY = 'cpan:PERLANCAR'; # AUTHORITY
4             our $DATE = '2019-11-29'; # DATE
5             our $DIST = 'File-Digest'; # DIST
6             our $VERSION = '0.011'; # VERSION
7              
8 1     1   101799 use 5.010001;
  1         11  
9 1     1   7 use strict;
  1         2  
  1         20  
10 1     1   4 use warnings;
  1         2  
  1         37  
11 1     1   1740 use Log::ger;
  1         52  
  1         5  
12              
13 1     1   260 use Exporter qw(import);
  1         1  
  1         59  
14             our @EXPORT_OK = qw(digest_files);
15              
16 1     1   471 use Perinci::Object;
  1         437  
  1         788  
17              
18             our %SPEC;
19              
20             my %arg_file = (
21             file => {
22             summary => 'Filename ("-" means stdin)',
23             schema => ['filename*'],
24             req => 1,
25             pos => 0,
26             cmdline_aliases => {f=>{}},
27             },
28             );
29              
30             my %arg_files = (
31             files => {
32             'x.name.is_plural' => 1,
33             'x.name.singular' => 'file',
34             summary => 'Array of filenames (filename "-" means stdin)',
35             schema => ['array*', of=>'filename*'],
36             req => 1,
37             pos => 0,
38             greedy => 1,
39             cmdline_aliases => {f=>{}},
40             },
41             );
42              
43             my %args_algorithm = (
44             algorithm => {
45             schema => ['str*', in=>[qw/crc32 md5 sha1 sha224 sha256 sha384 sha512 sha512224 sha512256 Digest/]],
46             default => 'md5',
47             cmdline_aliases => {a=>{}},
48             },
49             digest_args => {
50             schema => ['array*', of=>'str*', 'x.perl.coerce_rules'=>['From_str::comma_sep']],
51             cmdline_aliases => {A=>{}},
52             },
53             );
54              
55             $SPEC{digest_file} = {
56             v => 1.1,
57             summary => 'Calculate digest of file',
58             description => <<'_',
59              
60             Return 400 status when algorithm is unknown/unsupported.
61              
62             _
63             args => {
64             %arg_file,
65             %args_algorithm,
66             },
67             };
68             sub digest_file {
69 31     31 1 114 my %args = @_;
70              
71 31         56 my $file = $args{file};
72 31   50     63 my $algo = $args{algorithm} // 'md5';
73              
74 31         39 my $fh;
75 31 50       71 if ($file eq '-') {
76 0         0 $fh = \*STDIN;
77             } else {
78 31 100       489 unless (-f $file) {
79 10         43 log_warn("Can't open %s: no such file", $file);
80 10         69 return [404, "No such file '$file'"];
81             }
82 21 50       731 open $fh, "<", $file or do {
83 0         0 log_warn("Can't open %s: %s", $file, $!);
84 0         0 return [500, "Can't open '$file': $!"];
85             };
86             }
87              
88 21 100       136 if ($algo eq 'md5') {
    100          
    100          
    100          
89 2         14 require Digest::MD5;
90 2         18 my $ctx = Digest::MD5->new;
91 2         46 $ctx->addfile($fh);
92 2         42 return [200, "OK", $ctx->hexdigest];
93             } elsif ($algo =~ /\Asha(512224|512256|224|256|384|512|1)\z/) {
94 14         655 require Digest::SHA;
95 14         3226 my $ctx = Digest::SHA->new($1);
96 14         254 $ctx->addfile($fh);
97 14         808 return [200, "OK", $ctx->hexdigest];
98             } elsif ($algo eq 'crc32') {
99 2         553 require Digest::CRC;
100 2         2612 my $ctx = Digest::CRC->new(type=>'crc32');
101 2         164 $ctx->addfile($fh);
102 2         133 return [200, "OK", $ctx->hexdigest];
103             } elsif ($algo eq 'Digest') {
104 2         624 require Digest;
105 2   50     599 my $ctx = Digest->new(@{ $args{digest_args} // [] });
  2         19  
106 2         85 $ctx->addfile($fh);
107 2         42 return [200, "OK", $ctx->hexdigest];
108             } else {
109 1         16 return [400, "Invalid/unsupported algorithm '$algo'"];
110             }
111             }
112              
113             $SPEC{digest_files} = {
114             v => 1.1,
115             summary => 'Calculate digests of files',
116             description => <<'_',
117              
118             Dies when algorithm is unsupported/unknown.
119              
120             _
121             args => {
122             %arg_files,
123             %args_algorithm,
124             },
125             };
126             sub digest_files {
127 11     11 1 44831 my %args = @_;
128              
129 11         22 my $files = $args{files};
130 11   50     32 my $algo = $args{algorithm} // 'md5';
131              
132 11         29 my $envres = envresmulti();
133 11         2368 my @res;
134              
135 11         27 for my $file (@$files) {
136 31         106 my $itemres = digest_file(file => $file, algorithm=>$algo, digest_args=>$args{digest_args});
137 31 100       228 die $itemres->[1] if $itemres->[0] == 400;
138 30         141 $envres->add_result($itemres->[0], $itemres->[1], {item_id=>$file});
139 30 100       1541 push @res, {file=>$file, digest=>$itemres->[2]} if $itemres->[0] == 200;
140             }
141              
142 10         56 $envres = $envres->as_struct;
143 10         73 $envres->[2] = \@res;
144 10         25 $envres->[3]{'table.fields'} = [qw/file digest/];
145 10         33 $envres;
146             }
147              
148             1;
149             # ABSTRACT: Calculate digests of files
150              
151             __END__
152              
153             =pod
154              
155             =encoding UTF-8
156              
157             =head1 NAME
158              
159             File::Digest - Calculate digests of files
160              
161             =head1 VERSION
162              
163             This document describes version 0.011 of File::Digest (from Perl distribution File-Digest), released on 2019-11-29.
164              
165             =head1 SYNOPSIS
166              
167             use File::Digest qw(digest_files);
168              
169             my $res = digest_files(
170             files => ["file1", "file2"],
171             algorithm => 'md5', # default md5, available also: crc32, sha1, sha256
172             );
173              
174             =head1 DESCRIPTION
175              
176             This module provides some convenience when you want to use L<Digest> against
177             files.
178              
179             =head1 FUNCTIONS
180              
181              
182             =head2 digest_file
183              
184             Usage:
185              
186             digest_file(%args) -> [status, msg, payload, meta]
187              
188             Calculate digest of file.
189              
190             Return 400 status when algorithm is unknown/unsupported.
191              
192             This function is not exported.
193              
194             Arguments ('*' denotes required arguments):
195              
196             =over 4
197              
198             =item * B<algorithm> => I<str> (default: "md5")
199              
200             =item * B<digest_args> => I<array[str]>
201              
202             =item * B<file>* => I<filename>
203              
204             Filename ("-" means stdin).
205              
206             =back
207              
208             Returns an enveloped result (an array).
209              
210             First element (status) is an integer containing HTTP status code
211             (200 means OK, 4xx caller error, 5xx function error). Second element
212             (msg) is a string containing error message, or 'OK' if status is
213             200. Third element (payload) is optional, the actual result. Fourth
214             element (meta) is called result metadata and is optional, a hash
215             that contains extra information.
216              
217             Return value: (any)
218              
219              
220              
221             =head2 digest_files
222              
223             Usage:
224              
225             digest_files(%args) -> [status, msg, payload, meta]
226              
227             Calculate digests of files.
228              
229             Dies when algorithm is unsupported/unknown.
230              
231             This function is not exported by default, but exportable.
232              
233             Arguments ('*' denotes required arguments):
234              
235             =over 4
236              
237             =item * B<algorithm> => I<str> (default: "md5")
238              
239             =item * B<digest_args> => I<array[str]>
240              
241             =item * B<files>* => I<array[filename]>
242              
243             Array of filenames (filename "-" means stdin).
244              
245             =back
246              
247             Returns an enveloped result (an array).
248              
249             First element (status) is an integer containing HTTP status code
250             (200 means OK, 4xx caller error, 5xx function error). Second element
251             (msg) is a string containing error message, or 'OK' if status is
252             200. Third element (payload) is optional, the actual result. Fourth
253             element (meta) is called result metadata and is optional, a hash
254             that contains extra information.
255              
256             Return value: (any)
257              
258             =head1 HOMEPAGE
259              
260             Please visit the project's homepage at L<https://metacpan.org/release/File-Digest>.
261              
262             =head1 SOURCE
263              
264             Source repository is at L<https://github.com/perlancar/perl-File-Digest>.
265              
266             =head1 BUGS
267              
268             Please report any bugs or feature requests on the bugtracker website L<https://rt.cpan.org/Public/Dist/Display.html?Name=File-Digest>
269              
270             When submitting a bug or request, please include a test-file or a
271             patch to an existing test-file that illustrates the bug or desired
272             feature.
273              
274             =head1 SEE ALSO
275              
276             L<Digest>
277              
278             L<xsum> from L<App::xsum> is a CLI for File::Digest. It can also check digests
279             stored in checksum files against the actual digests computed from the original
280             files.
281              
282             =head1 AUTHOR
283              
284             perlancar <perlancar@cpan.org>
285              
286             =head1 COPYRIGHT AND LICENSE
287              
288             This software is copyright (c) 2019, 2017, 2016 by perlancar@cpan.org.
289              
290             This is free software; you can redistribute it and/or modify it under
291             the same terms as the Perl 5 programming language system itself.
292              
293             =cut