#!/usr/bin/env perl
use strict;
use warnings;
use 5.010000;
use Pithub;
use LWP::UserAgent;
use HTTP::Request;
use JSON;
use Data::Dumper; sub p { warn Dumper(@_); }
use autodie ':system';
use File::Basename ();
use Cwd ();
use Getopt::Long;
use Term::ReadKey;
use Sys::Hostname;
use Pod::Usage;
use Term::ANSIColor;
use Encode ();
use Encode::Locale;
our $VERSION = '0.08';
if (-t) {
binmode STDOUT, ":encoding(console_out)";
binmode STDERR, ":encoding(console_out)";
}
sub prompt {
my ($prompt, @args) = @_;
push @args, 1 if @args % 2;
my %args = @args;
local $| = 1;
my $phrase = '';
print $prompt;
ReadMode 'cbreak';
while (1) {
my $c = ReadKey ~0/2-1;
if ($c =~ /[\r\n]/) {
print "\n";
last;
}
elsif ($c eq "\b" || ord $c == 127) {
next unless length $phrase;
chop $phrase;
next if defined $args{-echo} && length $args{-echo} eq 0;
print "\b \b";
}
elsif (ord $c) {
$phrase .= $c;
print defined $args{-echo} ? $args{-echo} : $c;
}
}
ReadMode 'restore';
$args{-yes} ? ($phrase =~ /^y/i) : $phrase;
}
my $agent = LWP::UserAgent->new(timeout => 10);
$agent->env_proxy(); # for mattn
my $token = `git config --get ph.token`;
my $me = `git config --get ph.user`;
chomp $me;
unless ($token && $me) {
setup();
}
my $pithub = Pithub->new(
token => $token,
ua => $agent,
);
main();
exit;
# -------------------------------------------------------------------------
sub main {
my $cmd = shift @ARGV || 'help';
my $cmd_code = __PACKAGE__->can("CMD_$cmd")
or die "Unknown command $cmd\n";
$cmd_code->();
}
# -------------------------------------------------------------------------
sub CMD_help {
pod2usage(-verbose => 2);
}
sub CMD_version {
print "$VERSION\n";
exit 0;
}
sub CMD_issues {
my ($user, $repo) = _get_user_repo_with_current_repo('issues');
my $res = $pithub->issues->list(user => $user, repo => $repo);
while ( my $row = $res->next ) {
printf("#%s %s - %s\n", colored(['green'], $row->{number}), $row->{title}, colored(['Cyan'], $row->{user}->{login}));
printf("%s\n\n", colored(['white'], $row->{html_url}));
}
}
sub CMD_info {
my ($user, $repo) = _get_user_repo('info');
my $res = $pithub->repos->get(user => $user, repo => $repo);
$res->success or die dump_content($res->content);
dump_content($res->content);
}
sub CMD_clone {
my $fork;
if ($ARGV[0] eq '--fork') {
$fork = 1;
shift @ARGV;
}
my ($user, $repo) = _get_user_repo('clone');
if ($fork) {
my $res = $pithub->repos->forks->create(user => $user, repo => $repo);
$res->success or die dump_content($res->content);
$user = $me;
}
my $res = $pithub->repos->get(user => $user, repo => $repo);
$res->success or die dump_content($res->content);
my $url = $res->content->{ssh_url} or die "Cannot get ssh_url";
system("git", "clone", $url);
}
sub CMD_fork {
my ($user, $repo) = _get_user_repo_with_current_repo('fork');
print("Forking $user/$repo\n");
my $res = $pithub->repos->forks->create(user => $user, repo => $repo);
$res->success or die dump_content($res->content);
my $ssh_url = $res->content->{ssh_url} // die dump_content($res->content);
system("git", "remote", "add", $me, $ssh_url);
system("git remote -v"); # show current settings.
}
sub CMD_pull {
my ($num) = pop @ARGV;
my $remote;
if (@ARGV) {
$remote = shift @ARGV;
}
else {
my $branch = `git symbolic-ref -q HEAD`;
chomp $branch;
$branch ||= 'master';
$branch =~ s{refs/heads/}{};
warn $branch;
$remote = `git config branch.$branch.remote`;
chomp $remote;
}
warn join(" ", "git", "fetch", $remote, "refs/pull/$num/head:pull-$num");
system("git", "fetch", $remote, "refs/pull/$num/head:pull-$num");
}
sub CMD_pullreq {
my ($title, $branch, $desc) = splice(@ARGV, 2);
$title or die "Usage: $0 pullreq tokuhirom ph 'pull req title'\n";
$branch ||= `git symbolic-ref -q HEAD` || 'master';
$desc ||= '';
my ($user, $repo) = _get_user_repo('clone');
my $res = $pithub->pull_requests->create(
user => $user,
repo => $repo,
data => {
title => $title,
body => $desc,
head => "$me:$branch",
base => 'master',
},
);
$res->success or die dump_content($res->content);
}
sub CMD_add {
my ($user, $repo) = _get_user_repo('clone');
my $res = $pithub->repos->get(user => $user, repo => $repo);
$res->success or die dump_content($res->content);
my $url = $res->content->{ssh_url} or die "Cannot get ssh_url";
system("git remote add $user $url");
system("git fetch $user");
}
sub CMD_all {
my ($user) = shift @ARGV;
$user or die "Usage: $0 all tokuhirom\n";
my $res = $pithub->repos->list(user => $user);
$res->success or die dump_content($res->content);
$res->auto_pagination(1);
while (my $row = $res->next) {
say("-------- $row->{name} --------");
next if $row->{fork}; # skip forked repos
next if -e $row->{name};
system("git", "clone", $row->{ssh_url});
}
}
sub CMD_import {
my $remote = 'origin';
my $homepage = '';
my $description = '';
my $private = 0;
GetOptions(
'remote=s' => \$remote,
'homepage=s' => \$homepage,
'description=s' => \$description,
'private' => \$private,
);
if (`git config --local --get remote.$remote.fetch`) {
Carp::croak "Remote [$remote] already exists. Try specifying another one using --remote.";
}
my $pwd = Cwd::getcwd();
my $name = File::Basename::basename($pwd);
exit unless prompt("Can I import $pwd to $name in github? [y/N] ", -yes);
my $res = $pithub->repos->create(
data => {
name => $name,
homepage => $homepage,
description => $description,
public => $private ? 0 : 1,
}
);
$res->success or die dump_content($res->content);
dump_content($res->content);
my $ssh_url = $res->content->{ssh_url} // die "Missing ssh_url";
print "Adding GitHub repo $name as remote [$remote].\n";
system(qw(git remote add), $remote, $ssh_url);
if (!`git config --get branch.master.remote`) {
print "Setting up remote [$remote] for master branch.\n";
system(qw(git config branch.master.remote), $remote);
system(qw(git config branch.master.merge refs/heads/master));
my $rebase = `git config --get branch.autosetuprebase`;
chomp $rebase;
if ($rebase && ($rebase eq 'remote' || $rebase eq 'always')) {
system(qw(git config branch.master.rebase true));
}
}
print "Pushing to remote [$remote]\n";
system(qw(git push), $remote, qw(master));
print "Done.\n";
}
# -------------------------------------------------------------------------
sub _get_user_repo_simple() {
my ($repo, $user);
if (@ARGV==2) {
($user, $repo) = @ARGV;
} elsif (@ARGV==1) {
if ($ARGV[0] =~ m{/}) {
($user, $repo) = split m{/}, $ARGV[0];
}
else {
($user, $repo) = ($me, $ARGV[0]);
}
}
return ($user, $repo);
}
sub _get_user_repo {
my ($cmd) = @_;
my ($user, $repo) = _get_user_repo_simple();
$user // die "Usage: $0 $cmd miyagawa/Plack\n";
return ($user, $repo);
}
sub _get_user_repo_with_current_repo {
my ($cmd) = @_;
my ($user, $repo) = _get_user_repo_simple();
unless ($user && $repo) {
my $remote = `git remote -v`;
if ($remote =~ m{git://github\.com\/([^/ ]+)\/([^/ ]+)\.git}) {
($user, $repo) = ($1, $2);
} elsif ($remote =~ m{git\@github\.com:([^/]+)/([^/]+)\.git}) {
($user, $repo) = ($1, $2);
} else {
die "Usage: $0 $cmd plack/Plack\n";
}
}
return ($user, $repo);
}
our $DUMP_DEPTH = 0;
sub dump_content {
local $DUMP_DEPTH = 0;
my $c = shift;
_dump_content($c);
}
sub _dump_content {
my $c = shift;
if (ref $c eq 'HASH') {
for my $key (sort keys %$c) {
print(' ' x ($DUMP_DEPTH*2));
print($key . ': ');
if (ref $c->{$key} eq 'HASH') {
print("\n");
local $DUMP_DEPTH = $DUMP_DEPTH + 1;
_dump_content($c->{$key});
} else {
print("$c->{$key}");
print("\n");
}
}
}
}
# -------------------------------------------------------------------------
sub setup {
say("setup ph. please input your id/pw to this prompt. password will not save to any location.");
$me = prompt('user: ');
my $pass = prompt('pass: ', -echo => '*');
my $res = $agent->request(
do {
my $req = HTTP::Request->new(
POST => 'https://api.github.com/authorizations'
# GET => 'https://github.com/login/oauth/authorize?client_id=45ee252fecc56cd85629&scopes=delete_repo,public_repo,repo,gist,user'
);
$req->content(
encode_json({
note => 'App::ph on ' . Sys::Hostname::hostname,
scopes => [qw(delete_repo public_repo repo gist user)],
})
);
$req->authorization_basic($me, $pass);
$req;
}
);
$res->is_success or die $res->as_string;
my $dat = decode_json($res->content);
$token = $dat->{token} || die "Cannot get token from API:\n\n" . $res->as_string;
$token =~ /^[A-Za-z0-9_-]+$/ or die "API token contains bad char: $token";
{
no autodie;
system("git config --global --unset-all ph.user");
system("git config --global --unset-all ph.token");
}
system("git config --global --add ph.user $me") == 0 or die;
system("git config --global --add ph.token $token") == 0 or die;
return;
}
__END__
=pod
=head1 NAME
ph - Github CLI client
=head1 SYNOPSIS
$ ph info mattn gal-vim
$ ph clone mattn gal-vim
$ ph import
=head1 DESCRIPTION
ph is yet another Github CLI client.
=head1 SUB COMMANDS
=head2 ph import
% ph import
This command import current directory to the github.
=head2 ph fork
% ph fork tokuhirom Minilla
This command forks the repository to your github account.
=head2 ph info
% ph info mattn gal-vim
This command displays information of the repository.
=head2 ph help
% ph help
Shows this help page.
=head2 ph all
% ph all $USERNAME
Clone all repository in your account
=head2 ph version
Show the version number.
=head2 ph issues
% ph issues
Show list of issues for current project.