GitHub-RSS

 view release on metacpan or  search on metacpan

lib/GitHub/RSS.pm  view on Meta::CPAN


=head1 NAME

GitHub::RSS - collect data from Github.com for feeding into RSS

=head1 SYNOPSIS

    my $gh = GitHub::RSS->new(
        dbh => {
            dsn => "dbi:SQLite:dbname=$store",
        },
    );

    my $last_updated = $gh->last_check;
    $gh->fetch_and_store( $github_user => $github_repo, $last_updated );
    if( $verbose ) {
        print "Updated from $last_updated to " . $gh->last_check, "\n";
    };

=head1 DESCRIPTION

This module provides a cache database for GitHub issues and scripts to
periodically update the database from GitHub.

This is mainly used for creating an RSS feed from the database, hence the
name.

=head1 METHODS

=head2 C<< ->new >>

  my $gh = GitHub::RSS->new(
      dbh => {
          dsn => 'dbi:SQLite:dbname=db/issues.sqlite',
      },
  );

Constructs a new GitHub::RSS instance

=over 4

=item *

B<gh> - instance of L<Net::GitHub>

=cut

has 'gh' => (
    is => 'ro',
    default => sub( $self ) {
        Net::GitHub->new(
            maybe access_token => $self->token
        ),
    },
);

=item *

B<token_file> - name and path of the JSON-format token file containing the
GitHub API token By default, that file is searched for under the name
C<github.credentials> in C<.>, C<$ENV{XDG_DATA_HOME}>, C<$ENV{USERPROFILE}>
and C<$ENV{HOME}>.

=cut

has 'token_file' => (
    is => 'lazy',
    default => \&_find_gh_token_file,
);

=item *

B<token> - GitHub API token. If this is missing, it will be attempted to read
it from the C<token_file>.

=cut

has 'token' => (
    is => 'lazy',
    default => \&_read_gh_token,
);

=item *

B<default_user> - name of the GitHub user whose repos will be read

=cut

has default_user => (
    is => 'ro',
);

=item *

B<default_repo> - name of the GitHub repo whose issues will be read

=cut

has default_repo => (
    is => 'ro',
);

=item *

B<dbh> - premade database handle or alternatively a hashref containing
the L<DBI> arguments

  dbh => $dbh,

or alternatively

  dbh => {
      user     => 'scott',
      password => 'tiger',
      dsn      => 'dbi:SQLite:dbname=db/issues.sqlite',
  }

=cut

has dbh => (
    is       => 'ro',
    required => 1,
    coerce   => \&_build_dbh,
);

sub _build_dbh( $args ) {
    return $args if ref($args) eq 'DBI::db';
    ref($args) eq 'HASH' or die 'Not a DB handle nor a hashref';
    return DBI->connect( @{$args}{qw/dsn user password options/} );
}

=item *

B<fetch_additional_pages> - number of additional pages to fetch from GitHub.
This is relevant when catching up a database for a repository with many issues.

=back

=cut

has fetch_additional_pages => (
    is => 'ro',
    default => '1',
);

sub _find_gh_token_file( $self, $env=undef ) {
    $env //= \%ENV;

    my $token_file;

    # This should use File::User
    for my $candidate_dir ('.',
                           $ENV{XDG_DATA_HOME},
                           $ENV{USERPROFILE},
                           $ENV{HOME}
    ) {
        next unless defined $candidate_dir;
        if( -f "$candidate_dir/github.credentials" ) {
            $token_file = "$candidate_dir/github.credentials";
            last
        };
    };

    return $token_file
}

sub _read_gh_token( $self, $token_file=undef ) {
    my $file = $token_file // $self->token_file;

    if( $file ) {
        open my $fh, '<', $file
            or die "Couldn't open file '$file': $!";
        binmode $fh;
        local $/;
        my $json = <$fh>;
        my $token_json = decode_json( $json );
        return $token_json->{token};
    } else {
        # We'll run without a known account
        return
    }
}

sub fetch_all_issues( $self,
    $user = $self->default_user,
    $repo = $self->default_repo,
    $since=undef ) {
    my @issues = $self->fetch_issues( $user, $repo, $since );
    my $gh = $self->gh;
    while ($gh->issue->has_next_page) {
        push @issues, $gh->issue->next_page;
    }
    @issues
}

sub fetch_issues( $self,
    $user = $self->default_user,
    $repo = $self->default_repo,
    $since=undef ) {
    my $gh = $self->gh;
    my @issues = $gh->issue->repos_issues($user => $repo,
                                          { sort => 'updated',
                                          direction => 'asc', # so we can interrupt any time
                                          state => 'all', # so we find issues that got closed
                                          maybe since => $since,
                                          }
                                         );
};

=head2 C<< ->fetch_issue_comments >>

=cut

sub fetch_issue_comments( $self, $issue_number,
        $user=$self->default_user,
        $repo=$self->default_repo
    ) {
    # Shouldn't this loop as well, just like with the issues?!
    return $self->gh->issue->comments($user, $repo, $issue_number );



( run in 1.881 second using v1.01-cache-2.11-cpan-cdf2f3d4e48 )