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.010';
12 1     1   752 use 5.20.0;
  1         3  
13 1     1   8 use warnings;
  1         2  
  1         24  
14 1     1   6 use utf8;
  1         2  
  1         7  
15 1     1   24 use Unicode::Normalize;
  1         1  
  1         58  
16              
17 1     1   524 use Mouse;
  1         29502  
  1         6  
18 1     1   395 use Mouse::Util::TypeConstraints;
  1         2  
  1         5  
19 1     1   622 use MouseX::NativeTraits;
  1         2886  
  1         32  
20              
21 1     1   696 use Storable qw/dclone/;
  1         3590  
  1         72  
22              
23 1     1   735 use Software::Copyright::Statement;
  1         4  
  1         35  
24              
25 1     1   7 use feature qw/postderef signatures/;
  1         2  
  1         92  
26 1     1   7 no warnings qw/experimental::postderef experimental::signatures/;
  1         2  
  1         43  
27              
28 1     1   5 use overload '""' => \&stringify;
  1         2  
  1         15  
29 1     1   66 use overload 'eq' => \&is_equal;
  1         2  
  1         5  
30 1     1   57 use overload 'ne' => \&is_not_equal;
  1         3  
  1         29  
31              
32 42     42   60 sub _clean_copyright ($c) {
  42         58  
  42         53  
33             # cut off everything after and including the first non-printable
34             # (spare \n and \c though)
35 42         130 $c =~ s![\x00-\x09\x0b\x0c\x0e\x1f].*!!;
36 42         93 return $c;
37             }
38              
39 80     80   105 sub _create_or_merge ($result, $c) {
  80         114  
  80         107  
  80         93  
40 80         339 my $st = Software::Copyright::Statement->new($c);
41 80   100     348 my $name = NFKD($st->name // '');
42 80 100       937 if ($result->{$name}) {
    100          
    100          
43 8         32 $result->{$name}->merge($st);
44             }
45             elsif ($st->name) {
46 67         409 $result->{$name} = $st;
47             }
48             elsif ($st->record) {
49 3         30 $result->{$st->record} = $st;
50             }
51             else {
52 2         25 $result->{unknown} = $st;
53             }
54              
55 80         247 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 35 sub merge ($self, $input) {
  7         11  
  7         10  
  7         9  
113 7 100       47 my $other = ref($input) ? $input : Software::Copyright->new($input);
114              
115 7         27 foreach my $owner ($other->owners) {
116 8         87 my $from = $other->statement($owner);
117 8         117 my $target = $self->statement($owner);
118 8 100       97 if ($target) {
119 4         19 $target->merge($from);
120             }
121             else {
122 4         412 $self->set_statement($owner, dclone($from));
123             }
124             }
125 7         399 return;
126             }
127              
128 35     35 1 212 sub stringify ($self, $=1, $=1) {
  35         53  
  35         52  
  35         40  
  35         49  
129 35         96 return join("\n", reverse sort $self->statement_list);
130             }
131              
132 1     1 0 232 sub is_equal ($self, $other, $=1) {
  1         2  
  1         2  
  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         3  
  1         2  
  1         2  
  1         2  
137 1         5 return $self->stringify ne $other->stringify;
138             }
139              
140 22     22 1 47 sub is_valid ($self) {
  22         39  
  22         31  
141 22 100       71 return (scalar grep {$_->name || $_->record } $self->statement_list) ? 1 : 0;
  44 100       352  
142             }
143              
144 4     4 1 8 sub contains($self, $input) {
  4         6  
  4         9  
  4         6  
145 4 50       11 my $other = ref($input) ? $input : Software::Copyright->new($input);
146              
147 4         8 my $result = 1 ;
148 4         13 foreach my $other_owner ($other->owners) {
149 11         170 my $other_st = $other->statement($other_owner);
150 11         144 my $self_st = $self->statement($other_owner);
151 11 100       132 if ($self_st) {
152 10   100     45 $result &&= $self_st->contains($other_st);
153             }
154             else {
155 1         39 $result = 0;
156             }
157             }
158 4         57 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.010
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