Devel::PerlySense::Document::Api - The methods (and their locations)


Devel-PerlySense documentation Contained in the Devel-PerlySense distribution.

Index


Code Index:

NAME

Top

Devel::PerlySense::Document::Api - The methods (and their locations) of a package

DESCRIPTION

Top

An API is the methods/subs a module/package supports (or in some cases _may_ support).

PROPERTIES

Top

rhSub

Hash ref with (keys: method/sub name; values: Document::Location objects).

Default: {}

The Location objects have a sub property which is the name of the sub.

API METHODS

Top

new()

Create new Api object.

aNameSubVisible(oPerlySense => $oPs, fileCurrent => $file)

Return array with the method/sub names in the interface that are visible.

A method is invisible if it's a private method in a base class of $fileCurrent, outside the current Project, according to $oPerlySense.

isSubVisible($oPerlySense, $fileCurrent, $nameSub)

Return true if the Sub name is visibl, else false.

A sub/method is invisible if it's a private method in a base class of $fileCurrent, outside the current Project, according to $oPerlySense.

rsSortSub($fileCurrent)

Return sub ref for sorting sub names of this Api, using the rhSub property and given the $fileCurrent.

parsePackageSetSub(raNodeSub => $raNodeSub, source => $source, oDocument => $oDocument)

Parse the entire package data, both $source and found method nodes. Add both found subs and $raNodeSub to the rhSub property.

Return 1 or die on errors.

parseSourceSetSub(source => $source, oDocument => $oDocument)

Parse the $source, looking for $self->method calls, and $self->{hash_key}, and add them to the rhSub property.

Return 1 or die on errors.

oLocationSetSub(nameSub => $nameSub, oDocument => $oDocument, [oNode => $oNode])

Set the $self->rhSub->{$nameSub} to a new Document::Location with $oDocument and possibly a row/col for $oNode. Set the rhProperty for:

  sub

If no $oNode is passed, the location is supposed to be unknown, with row/col: 0/0.

Return the new oLocation. Die on errors.

mergeWithBase($oApiBase)

Adjust this object by adding appropriate parts of $oApiBase, i.e. the methods in $oApiBase->rhSub that aren't overridden in this class.

If a method has no row/col in neither base or self, it's supposed to be defined in the base class. Any method definition with row/col in self overrides one in base.

Return 1 on success. Die on errors.

isSubSupported($nameSub)

Return true if $nameSub is supported by this API, else false.

percentSupportedOf($raNameSub)

Return percent (0..100) of how many of the sub names in raNameSub that are present in the api.

percentConsistsOf($raNameSub)

Return percent (0..100) of how much of the api consists of the sub names in raNameSub.

I.e. a large API will have a low percentage. Extra sub names in $raNameSub will not affect the percentage.

AUTHOR

Top

Johan Lindström, <johanl[ÄT]DarSerMan.com>

BUGS

Top

Please report any bugs or feature requests to bug-devel-perlysense@rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Devel-PerlySense. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes.

ACKNOWLEDGEMENTS

Top

COPYRIGHT & LICENSE

Top


Devel-PerlySense documentation Contained in the Devel-PerlySense distribution.




use strict;
use warnings;

package Devel::PerlySense::Document::Api;
our $VERSION = '0.01';





use Spiffy -Base;
use Carp;
use Data::Dumper;

use Devel::PerlySense::Document;
use Devel::PerlySense::Document::Location;





field "rhSub" => {};





sub new(@) {
    my $pkg = shift;
    my (%p) = @_;

    my $self = bless {}, $pkg;

    return($self);
}





sub aNameSubVisible {
    my ($oPerlySense, $fileCurrent) = Devel::PerlySense::Util::aNamedArg(["oPerlySense", "fileCurrent"], @_);

    my $rsSortSub = $self->rsSortSub($fileCurrent);
    my @aNameSubVisible =
            sort $rsSortSub
            grep { ! $self->isSubVisible($oPerlySense, $fileCurrent, $_) }
            keys %{$self->rhSub};

    return(@aNameSubVisible);
}





sub isSubVisible {
    my ($oPerlySense, $fileCurrent, $nameMethod) = @_;

    my $file = $self->rhSub->{$_}->file;
    my $isInvisible =
            #Is it a base class (file ne current file)?
            $file ne $fileCurrent
            #Is it a private method?   ###TODO: Extract to method, then class *::Method->isPrivate
            && $_ =~ /^_/
            #Is it outside the project?
            && ! $oPerlySense->isFileInProject(
                file => $file,
                fileProjectOf => $fileCurrent,
            );

    return $isInvisible;
}





sub rsSortSub {
    my ($fileCurrent) = @_;

    my $rhSub = $self->rhSub();
    return sub {
        (
            #If unknown location, display it first no matter what
            ( ! $rhSub->{$b}->row ) <=> ( ! $rhSub->{$a}->row)
                    or # Then alphabetically (case insensitive)
             uc($a) cmp uc($b)
         )
                || #Display the current file's methods first
         ($rhSub->{$a}->file eq $fileCurrent) <=> ($rhSub->{$b}->file eq $fileCurrent)
                 || #then alphabetically
         $rhSub->{$a}->file cmp $rhSub->{$b}->file   ###TODO: inheritance tree
                 || # Then the order in the file
         $rhSub->{$a}->row <=> $rhSub->{$b}->row
                 || # Then method name alphabetically (case insensitive) (if on the same row)
         uc($a) cmp uc($b)
     };
}





sub parsePackageSetSub {
    my ($raNodeSub, $source, $oDocument) = Devel::PerlySense::Util::aNamedArg(["raNodeSub", "source", "oDocument"], @_);

    #Temporal cohesion: let the sub declarations overwrite the called subs
      #TODO: The called subs shouldn't overwrite sub declarations of a base class
    $self->parseSourceSetSub(source => $source, oDocument => $oDocument);

    for my $oNodeSub (@$raNodeSub) {
        $self->oLocationSetSub(nameSub => $oNodeSub->name, oDocument => $oDocument, oNode => $oNodeSub);
    }

    return(1);
}





sub parseSourceSetSub {
    my ($source, $oDocument) = Devel::PerlySense::Util::aNamedArg(["source", "oDocument"], @_);

    ###TODO: ignore comments, POD

    #Look for $self->method calls
    my @aSelfMethod = $source =~ / \$self \s* -> \s* (\w+) /gsx;

    #Look for $self->{property_name}
    my @aSelfHash =
            #Remove quotes
            map { s/ ^ (["'])  ( [^\1]* )   \1 $ /$2/x; $_ }  ## no critic
            $source =~
            /
                          \$self \s* -> \s* {
                          (
                              (?:  " [^"\ ]+ "  )
                              |
                              (?:  ' [^'\ ]+ '  )
                              |
                              (?:    \w+      )
                          )
                          /gsx;

    my %hSeen;
    for my $method ( grep { ! $hSeen{$_} ++ }  @aSelfMethod, @aSelfHash ) {
        $self->oLocationSetSub(nameSub => $method, oDocument => $oDocument);
    }

    return(1);
}





sub oLocationSetSub {
    my ($nameSub, $oDocument) = Devel::PerlySense::Util::aNamedArg(["nameSub", "oDocument"], @_);
    my %p = @_;  my ($oNode) = ($p{oNode});

    my $oLocation;

    if($oNode) {
        $oLocation = $oDocument->oLocationOfNode($oNode);
    } else {
        $oLocation = Devel::PerlySense::Document::Location->new(
            file => $oDocument->file,
        );
    }

    $oLocation->rhProperty->{sub} = $nameSub;
    $self->rhSub->{$nameSub} = $oLocation;

    return($oLocation);
}





sub mergeWithBase {
    my ($oApiBase) = @_;

    my $rhSub = $self->rhSub;
    while(my ($method, $oLocationBase) = each %{$oApiBase->rhSub}) {

        if(my $oLocation = $rhSub->{$method}) {

            #If both are just seen as $self->X, go with the base one
            if($oLocation->row == 0 && $oLocationBase->row == 0) {   #TODO: refactor: ! hasPosition
                $rhSub->{$method} = $oLocationBase;
            }

            #If the base one is a real declaration and self is only seen as $self->X, go with the base one
            if($oLocationBase->row != 0 && $oLocation->row == 0) {
                $rhSub->{$method} = $oLocationBase;
            }

        } else {
            #Not present in self, copy from base
            $rhSub->{$method} = $oLocationBase;
        }
    }

    return(1);
}





sub isSubSupported {
    my ($nameSub) = @_;
    return( exists $self->rhSub->{$nameSub} );
}





sub percentSupportedOf {
    my ($raNameSub) = @_;

    my $countSupported = grep { $self->isSubSupported($_) } @$raNameSub;
    my $percent = $countSupported / (scalar(@$raNameSub) || 1);

    return($percent * 100);
}





sub percentConsistsOf {
    my ($raNameSub) = @_;

    my %hNameSub = map { $_ => 1 } @$raNameSub;
    my $countConsists = grep { $hNameSub{$_} } keys %{$self->rhSub};
    my $percent = $countConsists / (scalar(keys %{$self->rhSub}) || 1);

    return($percent * 100);
}





1;





__END__