File Coverage

blib/lib/Software/LicenseUtils.pm
Criterion Covered Total %
statement 66 66 100.0
branch 24 36 66.6
condition 5 7 71.4
subroutine 12 12 100.0
pod 5 5 100.0
total 112 126 88.8


line stmt bran cond sub pod time code
1 7     7   269328 use strict;
  7         55  
  7         221  
2 7     7   38 use warnings;
  7         14  
  7         185  
3 7     7   37 use Carp;
  7         13  
  7         643  
4              
5             package Software::LicenseUtils;
6             # ABSTRACT: little useful bits of code for licensey things
7             $Software::LicenseUtils::VERSION = '0.104004';
8 7     7   49 use File::Spec;
  7         14  
  7         186  
9 7     7   3369 use IO::Dir;
  7         143124  
  7         382  
10 7     7   3486 use Module::Load;
  7         7601  
  7         52  
11              
12             #pod =method guess_license_from_pod
13             #pod
14             #pod my @guesses = Software::LicenseUtils->guess_license_from_pod($pm_text);
15             #pod
16             #pod Given text containing POD, like a .pm file, this method will attempt to guess
17             #pod at the license under which the code is available. This method will return
18             #pod either a list of Software::License classes names (as strings) or false.
19             #pod
20             #pod This method looks for a POD heading like 'license', 'copyright', or 'legal'.
21             #pod
22             #pod Calling this method in scalar context is a fatal error.
23             #pod
24             #pod =cut
25              
26             my $_v = qr/(?:v(?:er(?:sion|\.))?(?: |\.)?)/i;
27             my @phrases = (
28             "under the same (?:terms|license) as perl $_v?6" => [],
29             'under the same (?:terms|license) as (?:the )?perl' => 'Perl_5',
30             'affero g' => 'AGPL_3',
31             "GNU (?:general )?public license,? $_v?([123])" => sub { "GPL_$_[0]" },
32             'GNU (?:general )?public license' => [ map {"GPL_$_"} (1..3) ],
33             "GNU (?:lesser|library) (?:general )?public license,? $_v?([23])\\D" => sub {
34             $_[0] == 2 ? 'LGPL_2_1' : $_[0] == 3 ? 'LGPL_3_0' : ()
35             },
36             'GNU (?:lesser|library) (?:general )?public license' => [ qw(LGPL_2_1 LGPL_3_0) ],
37             '(?:the )?2[-\s]clause (?:Free)?BSD' => 'FreeBSD',
38             'BSD license' => 'BSD',
39             'FreeBSD license' => 'FreeBSD',
40             "Artistic license $_v?(\\d)" => sub { "Artistic_$_[0]_0" },
41             'Artistic license' => [ map { "Artistic_$_\_0" } (1..2) ],
42             "LGPL,? $_v?(\\d)" => sub {
43             $_[0] == 2 ? 'LGPL_2_1' : $_[0] == 3 ? 'LGPL_3_0' : ()
44             },
45             'LGPL' => [ qw(LGPL_2_1 LGPL_3_0) ],
46             "GPL,? $_v?(\\d)" => sub { "GPL_$_[0]" },
47             'GPL' => [ map { "GPL_$_" } (1..3) ],
48             'FreeBSD' => 'FreeBSD',
49             'BSD' => 'BSD',
50             'Artistic' => [ map { "Artistic_$_\_0" } (1..2) ],
51             'MIT' => 'MIT',
52             'has dedicated the work to the Commons' => 'CC0_1_0',
53             'waiving all of his or her rights to the work worldwide under copyright law' => 'CC0_1_0',
54             'has waived all copyright and related or neighboring rights to' => 'CC0_1_0',
55             'apache(?: |-)1.1' => "Apache_1_1",
56             "Apache Software License(\\s)+Version 1.1" => "Apache_1_1",
57             'apache(?: |-)2.0' => "Apache_2_0",
58             "Apache License(\\s)+Version 2.0" => "Apache_2_0",
59             'No license is granted to other entities' => 'None',
60             );
61              
62             my %meta_keys = ();
63             my %meta1_keys = ();
64             my %meta2_keys = ();
65             my %spdx_expression = ();
66              
67             # find all known Software::License::* modules and get identification data
68             #
69             # XXX: Grepping over @INC is dangerous, as it means that someone can change the
70             # behavior of your code by installing a new library that you don't load. rjbs
71             # is not a fan. On the other hand, it will solve a real problem. One better
72             # solution is to check "core" licenses first, then fall back, and to skip (but
73             # warn about) bogus libraries. Another is, at least when testing S-L itself,
74             # to only scan lib/ blib. -- rjbs, 2013-10-20
75             for my $lib (map { "$_/Software/License" } @INC) {
76             next unless -d $lib;
77             for my $file (IO::Dir->new($lib)->read) {
78             next unless $file =~ m{\.pm$};
79              
80             # if it fails, ignore it
81             eval {
82             (my $mod = $file) =~ s{\.pm$}{};
83             my $class = "Software::License::$mod";
84             load $class;
85             $meta_keys{ $class->meta_name }{$mod} = undef;
86             $meta1_keys{ $class->meta_name }{$mod} = undef;
87             $meta_keys{ $class->meta2_name }{$mod} = undef;
88             $meta2_keys{ $class->meta2_name }{$mod} = undef;
89             if (defined $class->spdx_expression) {
90             $spdx_expression{ $class->spdx_expression }{$class} = undef;
91             }
92             my $name = $class->name;
93             unshift @phrases, qr/\Q$name\E/, [$mod];
94             if ((my $name_without_space = $name) =~ s/\s+\(.+?\)//) {
95             unshift @phrases, qr/\Q$name_without_space\E/, [$mod];
96             }
97             };
98             }
99             }
100              
101             sub guess_license_from_pod {
102 32     32 1 16791 my ($class, $pm_text) = @_;
103 32 50       102 die "can't call guess_license_* in scalar context" unless wantarray;
104 32 50       264 return unless $pm_text =~ /
105             (
106             =head \d \s+
107             (?:licen[cs]e|licensing|copyright|legal)\b
108             )
109             /ixmsg;
110              
111 32         113 my $header = $1;
112              
113 32 50       623 if (
114             $pm_text =~ m/
115             \G
116             (
117             .*?
118             )
119             (=head\\d.*|=cut.*|)
120             \z
121             /ixms
122             ) {
123 32         122 my $license_text = "$header$1";
124              
125 32         103 for (my $i = 0; $i < @phrases; $i += 2) {
126 3099         7464 my ($pattern, $license) = @phrases[ $i .. $i+1 ];
127 3099 100       7651 $pattern =~ s{\s+}{\\s+}g
128             unless ref $pattern eq 'Regexp';
129 3099 100       32801 if ( $license_text =~ /\b$pattern\b/i ) {
130 31         102 my $match = $1;
131             # if ( $osi and $license_text =~ /All rights reserved/i ) {
132             # warn "LEGAL WARNING: 'All rights reserved' may invalidate Open Source licenses. Consider removing it.";
133             # }
134 31 100 100     224 my @result = (ref $license||'') eq 'CODE' ? $license->($match)
    100 100        
135             : (ref $license||'') eq 'ARRAY' ? @$license
136             : $license;
137              
138 31 50       82 return unless @result;
139 31         97 return map { "Software::License::$_" } sort @result;
  31         250  
140             }
141             }
142             }
143              
144 1         12 return;
145             }
146              
147             #pod =method guess_license_from_meta
148             #pod
149             #pod my @guesses = Software::LicenseUtils->guess_license_from_meta($meta_str);
150             #pod
151             #pod Given the content of the META.(yml|json) file found in a CPAN distribution, this
152             #pod method makes a guess as to which licenses may apply to the distribution. It
153             #pod will return a list of zero or more Software::License instances or classes.
154             #pod
155             #pod =cut
156              
157             sub guess_license_from_meta {
158 26     26 1 9452 my ($class, $meta_text) = @_;
159 26 50       67 die "can't call guess_license_* in scalar context" unless wantarray;
160              
161 26         191 my ($license_text) = $meta_text =~ m{\b["']?license["']?\s*:\s*["']?([a-z_0-9]+)["']?}gm;
162              
163 26 50 33     159 return unless $license_text and my $license = $meta_keys{ $license_text };
164              
165 26         118 return map { "Software::License::$_" } sort keys %$license;
  28         119  
166             }
167              
168             {
169 7     7   7823 no warnings 'once';
  7         20  
  7         4188  
170             *guess_license_from_meta_yml = \&guess_license_from_meta;
171             }
172              
173             #pod =method guess_license_from_meta_key
174             #pod
175             #pod my @guesses = Software::LicenseUtils->guess_license_from_meta_key($key, $v);
176             #pod
177             #pod This method returns zero or more Software::License classes known to use C<$key>
178             #pod as their META key. If C<$v> is supplied, it specifies whether to treat C<$key>
179             #pod as a v1 or v2 meta entry. Any value other than 1 or 2 will raise an exception.
180             #pod
181             #pod =cut
182              
183             sub guess_license_from_meta_key {
184 3     3 1 302 my ($self, $key, $v) = @_;
185              
186 3 50       19 my $src = (! defined $v) ? \%meta_keys
    100          
    50          
187             : $v eq '1' ? \%meta1_keys
188             : $v eq '2' ? \%meta2_keys
189             : Carp::croak("illegal META version: $v");
190              
191 3 100       15 return unless $src->{$key};
192 2         6 return map { "Software::License::$_" } sort keys %{ $src->{$key} };
  2         16  
  2         11  
193             }
194              
195             my %short_name = (
196             'GPL-1' => 'Software::License::GPL_1',
197             'GPL-2' => 'Software::License::GPL_2',
198             'GPL-3' => 'Software::License::GPL_3',
199             'LGPL-2' => 'Software::License::LGPL_2',
200             'LGPL-2.1' => 'Software::License::LGPL_2_1',
201             'LGPL-3' => 'Software::License::LGPL_3_0',
202             'LGPL-3.0' => 'Software::License::LGPL_3_0',
203             'Artistic' => 'Software::License::Artistic_1_0',
204             'Artistic-1' => 'Software::License::Artistic_1_0',
205             'Artistic-2' => 'Software::License::Artistic_2_0',
206             );
207              
208             #pod =method new_from_short_name
209             #pod
210             #pod my $license_object = Software::LicenseUtils->new_from_short_name( {
211             #pod short_name => 'GPL-1',
212             #pod holder => 'X. Ample'
213             #pod }) ;
214             #pod
215             #pod Create a new L object from the license specified
216             #pod with C. Known short license names are C, C ,
217             #pod C and C
218             #pod
219             #pod =cut
220              
221             sub new_from_short_name {
222 1     1 1 597 my ( $class, $arg ) = @_;
223              
224             Carp::croak "no license short name specified"
225 1 50       6 unless defined $arg->{short_name};
226 1         3 my $short = delete $arg->{short_name};
227             Carp::croak "Unknown license with short name $short"
228 1 50       5 unless $short_name{$short};
229              
230 1         3 my $lic_file = my $lic_class = $short_name{$short} ;
231 1         6 $lic_file =~ s!::!/!g;
232 1         6 require "$lic_file.pm";
233 1         9 return $lic_class->new( $arg );
234             }
235              
236             #pod =method new_from_spdx_expression
237             #pod
238             #pod my $license_object = Software::LicenseUtils->new_from_spdx_expression( {
239             #pod spdx_expression => 'MPL-2.0',
240             #pod holder => 'X. Ample'
241             #pod }) ;
242             #pod
243             #pod Create a new L object from the license specified
244             #pod with C. Some licenses doesn't have an spdx
245             #pod identifier (for example L), so you can pass
246             #pod spdx identifier but also expressions.
247             #pod Known spdx license identifiers are C, C.
248             #pod
249             #pod =cut
250              
251             sub new_from_spdx_expression {
252 1     1 1 569 my ( $class, $arg ) = @_;
253              
254             Carp::croak "no license spdx name specified"
255 1 50       5 unless defined $arg->{spdx_expression};
256 1         3 my $spdx = delete $arg->{spdx_expression};
257             Carp::croak "Unknown license with spdx name $spdx"
258 1 50       5 unless $spdx_expression{$spdx};
259              
260 1         3 my ($lic_file) = my ($lic_class) = keys %{$spdx_expression{$spdx}} ;
  1         5  
261 1         6 $lic_file =~ s!::!/!g;
262 1         7 require "$lic_file.pm";
263 1         10 return $lic_class->new( $arg );
264             }
265              
266             1;
267              
268             __END__