File Coverage

blib/lib/Software/Copyright.pm
Criterion Covered Total %
statement 106 106 100.0
branch 17 18 94.4
condition 5 5 100.0
subroutine 22 22 100.0
pod 4 6 66.6
total 154 157 98.0


line stmt bran cond sub pod time code
1             #
2             # This file is part of Software-Copyright
3             #
4             # This software is Copyright (c) 2022 by Dominique Dumont <dod@debian.org>.
5             #
6             # This is free software, licensed under:
7             #
8             # The GNU General Public License, Version 3, June 2007
9             #
10             package Software::Copyright;
11             $Software::Copyright::VERSION = '0.012';
12 1     1   739 use 5.20.0;
  1         3  
13 1     1   5 use warnings;
  1         2  
  1         24  
14 1     1   5 use utf8;
  1         2  
  1         4  
15 1     1   23 use Unicode::Normalize;
  1         2  
  1         52  
16              
17 1     1   537 use Mouse;
  1         29919  
  1         4  
18 1     1   359 use Mouse::Util::TypeConstraints;
  1         2  
  1         3  
19 1     1   621 use MouseX::NativeTraits;
  1         3140  
  1         31  
20              
21 1     1   775 use Storable qw/dclone/;
  1         3502  
  1         67  
22              
23 1     1   646 use Software::Copyright::Statement;
  1         3  
  1         33  
24              
25 1     1   7 use feature qw/postderef signatures/;
  1         2  
  1         94  
26 1     1   6 no warnings qw/experimental::postderef experimental::signatures/;
  1         4  
  1         42  
27              
28 1     1   9 use overload '""' => \&stringify;
  1         8  
  1         6  
29 1     1   135 use overload 'eq' => \&is_equal;
  1         2  
  1         5  
30 1     1   71 use overload 'ne' => \&is_not_equal;
  1         2  
  1         13  
31              
32 43     43   55 sub _clean_copyright ($c) {
  43         67  
  43         55  
33             # cut off everything after and including the first non-printable
34             # (spare \n and \c though)
35 43         118 $c =~ s![\x00-\x09\x0b\x0c\x0e\x1f].*!!;
36 43         88 return $c;
37             }
38              
39 81     81   108 sub _create_or_merge ($result, $c) {
  81         102  
  81         121  
  81         101  
40 81         318 my $st = Software::Copyright::Statement->new($c);
41 81   100     339 my $name = NFKD($st->name // '');
42 81 100       1496 if ($result->{$name}) {
    100          
    100          
43 8         27 $result->{$name}->merge($st);
44             }
45             elsif ($st->name) {
46 67         824 $result->{$name} = $st;
47             }
48             elsif ($st->record) {
49 4         79 $result->{$st->record} = $st;
50             }
51             else {
52 2         35 $result->{unknown} = $st;
53             }
54              
55 81         278 return;
56             }
57              
58             subtype 'Copyright::Software::StatementHash' => as 'HashRef[Software::Copyright::Statement]';
59             coerce 'Copyright::Software::StatementHash' => from 'Str' => via {
60             my $str = $_ ;
61             my $result = {} ;
62             my @year_only_data;
63             my @data = split( m!(?:\s+/\s+)|(?:\s*\n\s*)!, $str);
64             # split statement that can be licensecheck output or debfmt data
65             foreach my $c ( @data ) {
66             if ($c =~ /^[\d\s,.-]+$/) {
67             push @year_only_data, $c;
68             }
69             else {
70             # copyright contain letters, so hopefully some name
71             _create_or_merge($result, $c);
72             }
73             }
74              
75             # year only data is dropped when other more significant data is
76             # present (with names)
77             if (@data eq @year_only_data) {
78             # got only year data, save it.
79             foreach my $c ( @data ) {
80             _create_or_merge($result, $c);
81             }
82             }
83             return $result;
84             };
85              
86             has statement_by_name => (
87             is => 'ro',
88             coerce => 1,
89             traits => ['Hash'],
90             isa => 'Copyright::Software::StatementHash',
91             default => sub { {} },
92             handles => {
93             statement_list => 'values',
94             owners => 'keys',
95             statement => 'get',
96             set_statement => 'set',
97             },
98             required => 1,
99             );
100              
101             around BUILDARGS => sub ($orig, $class, @args) {
102             my $str = _clean_copyright($args[0]);
103              
104             # cleanup
105             $str =~ /^[\s\W]+|[\s\W]+$/g;
106              
107             return $class->$orig({
108             statement_by_name => $str,
109             }) ;
110             };
111              
112 7     7 1 31 sub merge ($self, $input) {
  7         10  
  7         13  
  7         8  
113 7 100       47 my $other = ref($input) ? $input : Software::Copyright->new($input);
114              
115 7         31 foreach my $owner ($other->owners) {
116 8         140 my $from = $other->statement($owner);
117 8         108 my $target = $self->statement($owner);
118 8 100       105 if ($target) {
119 4         14 $target->merge($from);
120             }
121             else {
122 4         415 $self->set_statement($owner, dclone($from));
123             }
124             }
125 7         318 return;
126             }
127              
128 36     36 1 212 sub stringify ($self, $=1, $=1) {
  36         54  
  36         55  
  36         42  
  36         57  
129 36         104 return join("\n", reverse sort $self->statement_list);
130             }
131              
132 1     1 0 248 sub is_equal ($self, $other, $=1) {
  1         2  
  1         3  
  1         2  
  1         2  
133 1         3 return $self->stringify eq $other->stringify;
134             }
135              
136 1     1 0 563 sub is_not_equal ($self, $other, $=1) {
  1         6  
  1         3  
  1         2  
  1         2  
137 1         3 return $self->stringify ne $other->stringify;
138             }
139              
140 23     23 1 48 sub is_valid ($self) {
  23         35  
  23         34  
141 23 100       67 return (scalar grep {$_->name || $_->record } $self->statement_list) ? 1 : 0;
  45 100       515  
142             }
143              
144 4     4 1 7 sub contains($self, $input) {
  4         8  
  4         7  
  4         6  
145 4 50       11 my $other = ref($input) ? $input : Software::Copyright->new($input);
146              
147 4         8 my $result = 1 ;
148 4         14 foreach my $other_owner ($other->owners) {
149 11         153 my $other_st = $other->statement($other_owner);
150 11         135 my $self_st = $self->statement($other_owner);
151 11 100       131 if ($self_st) {
152 10   100     38 $result &&= $self_st->contains($other_st);
153             }
154             else {
155 1         2 $result = 0;
156             }
157             }
158 4         55 return $result;
159             }
160              
161             1;
162              
163             # ABSTRACT: Copyright class
164              
165             __END__
166              
167             =pod
168              
169             =encoding UTF-8
170              
171             =head1 NAME
172              
173             Software::Copyright - Copyright class
174              
175             =head1 VERSION
176              
177             version 0.012
178              
179             =head1 SYNOPSIS
180              
181             use Software::Copyright;
182              
183             my $copyright = Software::Copyright->new('2020,2021, Joe <joe@example.com>');
184              
185             # stringification
186             my $s = "$copyright"; # => is "2020, 2021, Joe <joe\@example.com>"
187              
188             # add with merge
189             $copyright->merge('2018-2020 Averell');
190              
191             # after addition
192             $s = "$copyright"; # => is "2020, 2021, Joe <joe\@example.com>\n2018-2020, Averell"
193              
194             # merge statement which adds email
195             $copyright->merge('2016, Averell <averell@example.com>');
196              
197             $s = "$copyright"; # => is "2020, 2021, Joe <joe\@example.com>\n2016, 2018-2020, Averell <averell\@example.com>"
198              
199             =head1 DESCRIPTION
200              
201             This class holds a copyright statement, i.e. a set of year range, name
202             and email.
203              
204             =head1 CONSTRUCTOR
205              
206             The constructor is called with a copyright statement string. This string can be
207             spread on several lines. The constructor is also compatible with the string given by
208             Debian's L<licensecheck>, i.e. the statements can be separated by "C</>".
209              
210             =head1 Methods
211              
212             =head2 statement
213              
214             Get the L<Software::Copyright::Statement> object of a given user.
215              
216             =head2 statement_list
217              
218             Returns a list of L<Software::Copyright::Statement> object for all users.
219              
220             =head2 stringify
221              
222             Returns a string containing a cleaned up copyright statement.
223              
224             =head2 is_valid
225              
226             Returns true if the copyright contains valid records, i.e. records with names.
227              
228             =head2 owners
229              
230             Return a list of statement owners. An owner is either a name or a record.
231              
232             =head2 statement
233              
234             Returns the L<Software::Copyright::Statement> object for the given owner:
235              
236             my $statement = $copyright->statement('Joe Dalton');
237              
238             =head2 merge
239              
240             Merge in a statement. This statement is either merged with a existing
241             statement when the owner match or appended to the list of statements.
242              
243             The statement parameter can either be a string or an
244             L<Software::Copyright::Statement> object.
245              
246             =head2 contains
247              
248             Return 1 if the other copyright is contained in current copyright,
249             i.e. all other statements are contained in current statements (See
250             L<Copyright::Statement/"contains"> for details on statement
251             containment).
252              
253             For instance:
254              
255             =over
256              
257             =item *
258              
259             C<2016, Joe> copyright is contained in C<2014-2020, Joe> copyright.
260              
261             =item *
262              
263             C<2016, Joe> is contained in C<2014-2020, Joe / 2019, Jack>
264              
265             =item *
266              
267             C<2010, Joe> is B<not> contained in C<2014-2020, Joe>
268              
269             =back
270              
271             =head1 Operator overload
272              
273             Operator C<"">, C<eq> and C<ne> are overloaded.
274              
275             =head1 See also
276              
277             L<Software::Copyright::Statement>, L<Software::Copyright::Owner>
278              
279             =head1 AUTHOR
280              
281             Dominique Dumont
282              
283             =head1 COPYRIGHT AND LICENSE
284              
285             This software is Copyright (c) 2022 by Dominique Dumont <dod@debian.org>.
286              
287             This is free software, licensed under:
288              
289             The GNU General Public License, Version 3, June 2007
290              
291             =cut