| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184 | #!/usr/bin/env perluse strict;use warnings;use HTTP::Tiny;use IO::Socket::SSL 1.52;use utf8;use Getopt::Long;my $Base_URL  = "https://api.github.com/repos/";my $User_Repo = 'elastic/elasticsearch/';my $Issue_URL = "https://github.com/${User_Repo}issues";use JSON();use URI();use URI::Escape qw(uri_escape_utf8);our $json = JSON->new->utf8(1);our $http = HTTP::Tiny->new(    default_headers => {        Accept        => "application/vnd.github.v3+json",        Authorization => load_github_key()    });my %Opts = ( state => 'open' );GetOptions(    \%Opts,    #    'state=s', 'labels=s', 'add=s', 'remove=s') || exit usage();die usage('--state must be one of open|all|closed')    unless $Opts{state} =~ /^(open|all|closed)$/;die usage('--labels is required') unless $Opts{labels};die usage('Either --add or --remove is required')    unless $Opts{add} || $Opts{remove};relabel();#===================================sub relabel {#===================================    my @remove = split /,/, ( $Opts{remove} || '' );    my @add    = split /,/, ( $Opts{add}    || '' );    my $add_json = $json->encode( \@add );    my $url      = URI->new( $Base_URL . $User_Repo . 'issues' );    $url->query_form(        state    => $Opts{state},        labels   => $Opts{labels},        per_page => 100    );    my $spool = Spool->new($url);    while ( my $issue = $spool->next ) {        my $id = $issue->{number};        print "$Issue_URL/$id\n";        if (@add) {            add_label( $id, $add_json );        }        for (@remove) {            remove_label( $id, $_ );        }    }    print "Done\n";}#===================================sub add_label {#===================================    my ( $id, $json ) = @_;    my $response = $http->post(        $Base_URL . $User_Repo . "issues/$id/labels",        {   content => $json,            headers => { "Content-Type" => "application/json; charset=utf-8" }        }    );    die "$response->{status} $response->{reason}\n"        unless $response->{success};}#===================================sub remove_label {#===================================    my ( $id, $name ) = @_;    my $url        = $Base_URL        . $User_Repo        . "issues/$id/labels/"        . uri_escape_utf8($name);    my $response = $http->delete($url);    die "$response->{status} $response->{reason}\n"        unless $response->{success};}#===================================sub load_github_key {#===================================    my ($file) = glob("~/.github_auth");    unless ( -e $file ) {        warn "File ~/.github_auth doesn't exist - using anonymous API. "            . "Generate a Personal Access Token at https://github.com/settings/applications\n";        return '';    }    open my $fh, $file or die "Couldn't open $file: $!";    my ($key) = <$fh> || die "Couldn't read $file: $!";    $key =~ s/^\s+//;    $key =~ s/\s+$//;    die "Invalid GitHub key: $key"        unless $key =~ /^[0-9a-f]{40}$/;    return "token $key";}#===================================sub usage {#===================================    my $msg = shift || '';    if ($msg) {        $msg = "\nERROR: $msg\n\n";    }    return $msg . <<"USAGE";$0 --state=open|closed|all --labels=foo,bar --add=new1,new2 --remove=old1,old2USAGE}package Spool;use strict;use warnings;#===================================sub new {#===================================    my $class = shift;    my $url   = shift;    return bless {        url    => $url,        buffer => []        },        $class;}#===================================sub next {#===================================    my $self = shift;    if ( @{ $self->{buffer} } == 0 ) {        $self->refill;    }    return shift @{ $self->{buffer} };}#===================================sub refill {#===================================    my $self = shift;    return unless $self->{url};    my $response = $http->get( $self->{url} );    die "$response->{status} $response->{reason}\n"        unless $response->{success};    $self->{url} = '';    if ( my $link = $response->{headers}{link} ) {        my @links = ref $link eq 'ARRAY' ? @$link : $link;        for ($link) {            next unless $link =~ /<([^>]+)>; rel="next"/;            $self->{url} = $1;            last;        }    }    push @{ $self->{buffer} }, @{ $json->decode( $response->{content} ) };}
 |