File Coverage

blib/lib/Namespace/Subroutines.pm
Criterion Covered Total %
statement 60 60 100.0
branch 14 20 70.0
condition 0 4 0.0
subroutine 9 9 100.0
pod 0 1 0.0
total 83 94 88.3


line stmt bran cond sub pod time code
1             package Namespace::Subroutines;
2 2     2   240643 use v5.18;
  2         14  
3 2     2   9 use strict;
  2         5  
  2         38  
4 2     2   9 use warnings;
  2         4  
  2         45  
5 2     2   638 use attributes;
  2         1289  
  2         10  
6 2     2   137 use Carp qw( carp );
  2         4  
  2         106  
7 2     2   14 use File::Find ();
  2         4  
  2         33  
8 2     2   8 use feature 'say';
  2         4  
  2         2006  
9              
10             our $VERSION = '0.02';
11              
12             my %skip = (
13             AUTOLOAD => 1,
14             BEGIN => 1,
15             MODIFY_CODE_ATTRIBUTES => 1,
16             FETCH_CODE_ATTRIBUTES => 1,
17             );
18              
19             sub find {
20 1     1 0 89 my ( $ns, $cb ) = @_;
21              
22             # 'My::App::Controller' -> 'My/App/Controller'
23 1         5 my $ns2 = $ns =~ s/::/\//gr;
24              
25 1         3 my @modules;
26 1         3 foreach my $path (@INC) {
27 9 50       129 next unless -d $path;
28             File::Find::find(
29             sub {
30 4244 100   4244   98626 return unless /\.pm$/;
31 1483         6470 my $name = $File::Find::name =~ s/$path\///r;
32 1483 100       27416 return unless $name =~ /^$ns2/;
33 1         36 push @modules, [ $name, $File::Find::name ];
34             },
35 9         567 $path
36             );
37             }
38              
39 1         26 foreach my $m (@modules) {
40 1         6 my ( $modname, $path ) = @$m;
41              
42             # 'Data/Dumper.pm' -> qw(Data Dumper.pm)
43 1         8 my @a = split( m{/}, $modname );
44 1         10 pop @a; # qw(Data)
45 1         6 my $namespace = join( '/', @a ); # 'Data'
46              
47             # 'My/App/Controller/Users.pm', 'My/App/Controller/Inventory.pm', etc.
48 1 50       25 next unless $namespace =~ /^$ns2/;
49 1 50       586 require $modname unless defined $INC{$modname};
50              
51 1         6222 my $module = $modname; # 'My/App/Controller/Users.pm'
52 1         7 $module =~ s/\.pm$//; # 'My/App/Controller/Users'
53 1         6 $module =~ s/\//::/g; # 'My::App::Controller::Users'
54 1         3 $module .= '::'; # 'My::App::Controller::Users::'
55 1         3 my $table = '%' . $module; # '%My::App::Controller::Users::'
56              
57             ## no critic (BuiltinFunctions::ProhibitStringyEval)
58 1         84 my @symbols = split( /\|/, eval "join('|', keys $table)" );
59 1         8 my @subroutines = grep { defined &{ $module . $_ } } @symbols;
  5         8  
  5         17  
60 1         3 my %subroutines;
61              
62 1 50 0     45 open my $fh, '<', $path or ( carp "unable to open $!" and next );
63 1         23 while ( my $line = <$fh> ) {
64 10 100       45 next unless $line =~ /^sub\s+(\w+)[\:\(\s]/;
65 2         9 $subroutines{$1} = 1;
66             }
67 1 50 0     15 close $fh or ( carp "error closing $!" and next );
68              
69             # 'My::App::Controller::Users::' -> 'Users'
70 1         30 $module =~ s/^$ns\::(.+)::$/$1/;
71              
72 1         4 foreach my $sub (@subroutines) {
73 4 50       20 next if $skip{$sub};
74 4 100       11 next unless $subroutines{$sub};
75 2         6 my $name = join( '::', $ns, $module, $sub );
76 2         7 my $ref = \&$name;
77 2         9 my @attrs = attributes::get( \&$name );
78 2         72 $cb->( [ split( /::/, $module ) ], $sub, $ref, \@attrs );
79             }
80             }
81             }
82              
83             1;
84             __END__