• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

briandfoy / module-extract-declaredversion / 15514603779

08 Jun 2025 04:10AM CUT coverage: 94.872% (-0.1%) from 95.0%
15514603779

push

github

37 of 39 relevant lines covered (94.87%)

101.28 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

94.87
/lib/Module/Extract/DeclaredVersion.pm
1
use utf8;
10✔
2
use v5.10;
10✔
3

4
package Module::Extract::DeclaredVersion;
5
use strict;
10✔
6

7
use warnings;
10✔
8
no warnings;
10✔
9

10
our $VERSION = '1.026';
11

12
=encoding utf8
13

14
=head1 NAME
15

16
Module::Extract::DeclaredVersion - Extract the version of Perl a module declares
17

18
=head1 SYNOPSIS
19

20
        use Module::Extract::DeclaredVersion;
21

22
        my $extor = Module::Extract::DeclaredVersion->new;
23

24
        my $version = $extor->get_declared_version( $file );
25
        if( $extor->error ) { ... }
26

27
=head1 DESCRIPTION
28

29
Extract the largest declared Perl version and returns it as a
30
version object. For instance, in a script you might have:
31

32
  use v5.16;
33

34
This module will extract that C<v5.16> and return it.
35

36
This module tries to handle any format that PPI will recognize, passing
37
them through version.pm to normalize them.
38

39
=cut
40

41
=over 4
42

43
=item new
44

45
Makes an object. The object doesn't do anything just yet, but you need
46
it to call the methods.
47

48
=cut
49

50
sub new {
51
        my $class = shift;
5✔
52

53
        my $self = bless {}, $class;
5✔
54

55
        $self->init;
5✔
56

57
        $self;
5✔
58
        }
59

60
=item init
61

62
Set up the object. You shouldn't need to call this yourself. You can
63
override it though!
64

65
=cut
66

67
sub init {
68
        $_[0]->_clear_error;
5✔
69
        }
70

71
=item get_declared_version( FILE )
72

73
Extracts all of the declared minimum versions for Perl, sorts them,
74
and returns the largest a version object.
75

76
=cut
77

78
sub get_declared_version {
79
        my( $self, $file ) = @_;
15✔
80

81
        $self->_clear_error;
15✔
82

83
        my $versions = $self->_get_ppi_for_file( $file );
15✔
84
        return unless defined $versions;
15✔
85

86
        my @sorted = sort {
87
                eval { version->parse( $b->{version} ) }
10✔
88
                  <=>
89
                eval { version->parse( $a->{version} ) }
5✔
90
                } @$versions;
91

92
        eval { version->parse( $sorted[0]->{version} ) };
10✔
93
        }
94

95
sub _get_ppi_for_file {
96
        my( $self, $file ) = @_;
15✔
97

98
        unless( -e $file ) {
15✔
99
                $self->_set_error( ref( $self ) . ": File [$file] does not exist!" );
5✔
100
                return;
5✔
101
                }
102

103
        require PPI;
10✔
104

105
        my $Document = eval { PPI::Document->new( $file ) };
10✔
106
        unless( $Document ) {
10✔
107
                $self->_set_error( ref( $self ) . ": Could not parse file [$file]" );
×
108
                return;
×
109
                }
110

111
        my $modules = $Document->find(
112
                sub {
113
                        $_[1]->isa( 'PPI::Statement::Include' )  &&
3,330✔
114
                                ( $_[1]->type eq 'use' || $_[1]->type eq 'require' )
115
                        }
116
                );
10✔
117

118
        return unless $modules;
10✔
119

120
        my %Seen;
10✔
121
        my @versions =
122
                grep { $_->{version_literal} }
75✔
123
                map  {
124
                        my $literal = $_->version_literal;
10✔
125
                        $literal =~ s/\s//g;
75✔
126
                        $literal = undef unless length $literal;
75✔
127
                        my $hash = {
75✔
128
                                version         => $_->version,
129
                                version_literal => ( $literal // $_->version ), #/
130
                                };
131
                        } @$modules;
132

133
        return \@versions;
10✔
134
        }
135

136
=item error
137

138
Return the error from the last call to C<get_modules>.
139

140
=cut
141

142
sub _set_error   { $_[0]->{error} = $_[1]; }
5✔
143

144
sub _clear_error { $_[0]->{error} = '' }
20✔
145

146
sub error        { $_[0]->{error} }
15✔
147

148
=back
149

150
=head1 TO DO
151

152
=over 4
153

154
=item * Make it recursive, so it scans the source for any module that it finds.
155

156
=back
157

158
=head1 SEE ALSO
159

160
L<Module::Extract::Use>
161

162
=head1 SOURCE AVAILABILITY
163

164
The source code is in Github
165

166
        https://github.com/briandfoy/module-extract-declaredversion
167

168
=head1 AUTHOR
169

170
brian d foy, C<< <briandfoy@pobox.com> >>
171

172
=head1 COPYRIGHT AND LICENSE
173

174
Copyright © 2011-2025, brian d foy <briandfoy@pobox.com>. All rights reserved.
175

176
You may redistribute this under the terms of the Artistic License 2.0.
177

178
=cut
179

180
1;
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc