<commit_before>package Devel::Cover::Report::Codecov;
use strict;
use warnings;
use utf8;
our $VERSION = '0.11';

use URI;
use Furl;
use JSON::XS;

use Module::Find;
useall 'Devel::Cover::Report::Codecov::Service';


our $API_ENDPOINT = 'http://codecov.io/upload/v2';

sub report {
    my ($pkg, $db, $options) = @_;

    my @services  = get_services($pkg);
    my ($service) = grep { $_->detect } @services;
    die 'unknown service. could not get configuration' unless $service;

    my $query = get_query($service);
    my $url   = get_request_url($API_ENDPOINT, $query);
    my $json  = get_codecov_json($options->{file}, $db);

    my $res = send_report($url, $json);

    if ($res->{ok}) {
        print $res->{message} . "\n";
    }

    else {
        die $res->{message} . "\n";
    }
}

sub get_services {
    my $pkg = shift;

    my @services = findallmod "${pkg}::Service";
    return sort {
        defined $a->can('fallback') && $a->fallback ?  1 :
        defined $b->can('fallback') && $b->fallback ? -1 : 0
    } @services;
}

sub get_file_lines {
    my ($file) = @_;

    my $lines = 0;

    open my $fp, '<', $file;
    $lines++ while <$fp>;
    close $fp;

    return $lines;
}

sub get_file_coverage {
    my ($file, $db) = @_;

    my $realpath  = get_file_realpath($file);
    my $lines     = get_file_lines($realpath);
    my $statement = $db->cover->file($file)->statement;
    my @coverage  = (undef);

    for (my $i = 1; $i <= $lines; $i++) {
        push @coverage, get_line_coverage($statement, $i);
    }

    return $realpath => \@coverage;
}

sub get_line_coverage {
    my ($statement, $n) = @_;

    my $location = $statement->location($n);
    return get_line_coverage_by_location($location);
}

sub get_line_coverage_by_location {
    my ($location) = @_;

    return $location unless $location;
    return if $location->[0]->uncoverable;
    return $location->[0]->covered;
}

sub get_file_realpath {
    my $file = shift;

    if (-d 'blib') {
        my $realpath = $file;
        $realpath =~ s/blib\/lib/lib/;

        return $realpath if -f $realpath;
    }

    return $file;
}

sub get_codecov_json {
    my ($files, $db) = @_;

    my %coverages = map { get_file_coverage($_, $db) } @$files;
    my $request   = { coverage => \%coverages, messages => {} };

    return encode_json($request);
}

sub get_request_url {
    my ($base_url, $query) = @_;

    my $url = URI->new($base_url);
    $url->query_form($query);

    return $url->canonical;
}

sub get_query {
    my ($service) = @_;

    my $query = $service->configuration;

    if (defined $ENV{CODECOV_TOKEN}) {
        $query->{token} = lc $ENV{CODECOV_TOKEN};
    }

    return $query;
}

sub send_report {
    my ($url, $json) = @_;

    my $furl = Furl->new;
    my $res  = $furl->post($url, [], $json);

    my ($message, $ok);

    if ($res->code == 200) {
        my $content = decode_json($res->content);

        $ok      = 1;
        $message = <<EOF;
@{[$res->code]} @{[$res->message]}
@{[$content->{message}]}
@{[$content->{url}]}
EOF
    }

    else {
        $ok      = 0;
        $message = <<EOF;
@{[$res->code]} @{[$res->message]}
@{[$res->content]}
EOF
    }

    return {
        ok      => $ok,
        message => $message,
    };
}

1;
__END__

=encoding utf-8

=head1 NAME

Devel::Cover::Report::Codecov - Backend for Codecov reporting of coverage statistics

=head1 SYNOPSIS

    $ cover -report codecov

=head1 DESCRIPTION

Devel::Cover::Report::Codecov is coverage reporter for L<Codecov|https://codecov.io>.

=head2 CI Companies Supported

Many CI services supported.
You must set CODECOV_TOKEN environment variables if you don't use Travis CI, Circle CI and AppVeyor.

There are example Codecov CI settings in [example-perl](https://github.com/codecov/example-perl).

=over 4

=item * L<Travis CI|https://travis-ci.org/><commit_msg>docs(README): fix pod syntax<commit_after>package Devel::Cover::Report::Codecov;
use strict;
use warnings;
use utf8;
our $VERSION = '0.11';

use URI;
use Furl;
use JSON::XS;

use Module::Find;
useall 'Devel::Cover::Report::Codecov::Service';


our $API_ENDPOINT = 'http://codecov.io/upload/v2';

sub report {
    my ($pkg, $db, $options) = @_;

    my @services  = get_services($pkg);
    my ($service) = grep { $_->detect } @services;
    die 'unknown service. could not get configuration' unless $service;

    my $query = get_query($service);
    my $url   = get_request_url($API_ENDPOINT, $query);
    my $json  = get_codecov_json($options->{file}, $db);

    my $res = send_report($url, $json);

    if ($res->{ok}) {
        print $res->{message} . "\n";
    }

    else {
        die $res->{message} . "\n";
    }
}

sub get_services {
    my $pkg = shift;

    my @services = findallmod "${pkg}::Service";
    return sort {
        defined $a->can('fallback') && $a->fallback ?  1 :
        defined $b->can('fallback') && $b->fallback ? -1 : 0
    } @services;
}

sub get_file_lines {
    my ($file) = @_;

    my $lines = 0;

    open my $fp, '<', $file;
    $lines++ while <$fp>;
    close $fp;

    return $lines;
}

sub get_file_coverage {
    my ($file, $db) = @_;

    my $realpath  = get_file_realpath($file);
    my $lines     = get_file_lines($realpath);
    my $statement = $db->cover->file($file)->statement;
    my @coverage  = (undef);

    for (my $i = 1; $i <= $lines; $i++) {
        push @coverage, get_line_coverage($statement, $i);
    }

    return $realpath => \@coverage;
}

sub get_line_coverage {
    my ($statement, $n) = @_;

    my $location = $statement->location($n);
    return get_line_coverage_by_location($location);
}

sub get_line_coverage_by_location {
    my ($location) = @_;

    return $location unless $location;
    return if $location->[0]->uncoverable;
    return $location->[0]->covered;
}

sub get_file_realpath {
    my $file = shift;

    if (-d 'blib') {
        my $realpath = $file;
        $realpath =~ s/blib\/lib/lib/;

        return $realpath if -f $realpath;
    }

    return $file;
}

sub get_codecov_json {
    my ($files, $db) = @_;

    my %coverages = map { get_file_coverage($_, $db) } @$files;
    my $request   = { coverage => \%coverages, messages => {} };

    return encode_json($request);
}

sub get_request_url {
    my ($base_url, $query) = @_;

    my $url = URI->new($base_url);
    $url->query_form($query);

    return $url->canonical;
}

sub get_query {
    my ($service) = @_;

    my $query = $service->configuration;

    if (defined $ENV{CODECOV_TOKEN}) {
        $query->{token} = lc $ENV{CODECOV_TOKEN};
    }

    return $query;
}

sub send_report {
    my ($url, $json) = @_;

    my $furl = Furl->new;
    my $res  = $furl->post($url, [], $json);

    my ($message, $ok);

    if ($res->code == 200) {
        my $content = decode_json($res->content);

        $ok      = 1;
        $message = <<EOF;
@{[$res->code]} @{[$res->message]}
@{[$content->{message}]}
@{[$content->{url}]}
EOF
    }

    else {
        $ok      = 0;
        $message = <<EOF;
@{[$res->code]} @{[$res->message]}
@{[$res->content]}
EOF
    }

    return {
        ok      => $ok,
        message => $message,
    };
}

1;
__END__

=encoding utf-8

=head1 NAME

Devel::Cover::Report::Codecov - Backend for Codecov reporting of coverage statistics

=head1 SYNOPSIS

    $ cover -report codecov

=head1 DESCRIPTION

Devel::Cover::Report::Codecov is coverage reporter for L<Codecov|https://codecov.io>.

=head2 CI Companies Supported

Many CI services supported.
You must set CODECOV_TOKEN environment variables if you don't use Travis CI, Circle CI and AppVeyor.

There are example Codecov CI settings in L<example-perl|https://github.com/codecov/example-perl>.

=over 4

=item * L<Travis CI|https://travis-ci.org/>
{'fraction_non_alphanumeric': 0.17533892834086506, 'fraction_numerical': 0.006197546804389929, 'mean_word_length': 2.8537313432835822, 'pattern_counts': {'":': 0, '<': 22, '<?xml version=': 0, '>': 86, 'https://': 6, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4250227', 'n_tokens_mistral': 2869, 'n_tokens_neox': 2752, 'n_words': 674}