Skip to content

Instantly share code, notes, and snippets.

@azjezz
Last active April 6, 2019 16:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azjezz/f02daa8c5e57e40c92c9cb7d6721e7ed to your computer and use it in GitHub Desktop.
Save azjezz/f02daa8c5e57e40c92c9cb7d6721e7ed to your computer and use it in GitHub Desktop.
require __DIR__.'/vendor/autoload.hack';
use namespace HH\Asio;
use namespace HH\Lib\Str;
use namespace Facebook\TypeAssert;
final class GithubPublicInformation {
const type TFileInfo = shape(
'sha' => string,
'filename' => string,
'status' => string,
'additions' => int,
'deletions' => int,
'changes' => int,
'blob_url' => string,
'raw_url' => string,
'contents_url' => string,
'patch' => string,
);
const type TUserInfo = shape(
'login' => string,
'id' => int,
'node_id' => string,
'avatar_url' => string,
'gravatar_id' => string,
'url' => string,
'html_url' => string,
'followers_url' => string,
'following_url' => string,
'gists_url' => string,
'starred_url' => string,
'subscriptions_url' => string,
'organizations_url' => string,
'repos_url' => string,
'events_url' => string,
'received_events_url' => string,
'type' => string,
'site_admin' => bool,
);
const type TTeamInfo = shape(
'id' => int,
'node_id' => string,
'url' => string,
'name' => string,
'slug' => string,
'description' => string,
'privacy' => string,
'permission' => string,
'members_url' => string,
'repositories_url' => string,
// this is actually ?TTeamInfo
'parent' => ?dict<string, mixed>
);
const type TContributorInfo = shape(
'login' => string,
'id' => int,
'node_id' => string,
'avatar_url' => string,
'gravatar_id' => string,
'url' => string,
'html_url' => string,
'followers_url' => string,
'following_url' => string,
'gists_url' => string,
'starred_url' => string,
'subscriptions_url' => string,
'organizations_url' => string,
'repos_url' => string,
'events_url' => string,
'received_events_url' => string,
'type' => string,
'site_admin' => bool,
'contributions' => int,
);
const type TContributorsInfo = vec<self::TContributorInfo>;
const type TTagInfo = shape(
'name' => string,
'commit' => shape(
'sha' => string,
'url' => string,
),
'zipball_url' => string,
'tarball_url' => string,
);
const type TAuthorInfo = shape(
'name' => string,
'email' => string,
'date' => string
);
const type TLanguagesInfo = dict<string, int>;
const type TCommitInfo = shape(
'sha' => string,
'node_id' => string,
'commit' => shape(
'author' => self::TAuthorInfo,
'committer' => ?self::TAuthorInfo,
'message' => string,
'tree' => shape(
'sha' => string,
'url' => string,
),
'url' => string,
'comment_count' => int,
'verification' => shape(
'verified' => bool,
'reason' => ?string,
'signature' => ?string,
'payload' => string,
)
),
'url' => string,
'html_url' => string,
'comments_url' => string,
'author' => self::TUserInfo,
'committer' => self::TUserInfo,
'parents' => vec<shape(
'sha' => string,
'url' => string,
'html_url' => string,
)>,
'stats' => shape(
'total' => int,
'additions' => int,
'deletions' => int,
),
'files' => vec<self::TFileInfo>,
);
private async function fetch<T>(
string $url,
TypeStructure<T> $structure
): Awaitable<T> {
$ch = curl_init(Str\format('%s%s', 'https://api.github.com/', $url));
curl_setopt($ch, CURLOPT_USERAGENT, 'Nuxed');
$json = await Asio\curl_exec($ch);
$vec = json_decode($json, /* associative = */ true, 1024, JSON_FB_HACK_ARRAYS);
return TypeAssert\matches_type_structure($structure, $vec);
}
public async function head(string $repository): Awaitable<void> {
$info = await $this->fetch(Str\format('repos/%s/commits/master', $repository), type_structure(self::class, 'TCommitInfo'));
// This is entirely typesafe
$commit = $info['commit'];
$message = $commit['message'];
if (Str\contains($message, "\n")) {
$title = Str\slice($message, 0, Str\search($message, "\n"));
} else {
$title = $message;
}
echo Str\format(
"Commit %.8s (%s)\n".
" Authored by:\t%s\n".
" Committed by:\t%s\n".
" Affected files:\n",
$info['sha'],
$title,
$commit['author']['name'],
($commit['committer'] ?? $commit['author'])['name'],
);
foreach ($info['files'] as $file_info) {
echo Str\format(
" - %s %s (+%d -%d)\n",
Str\pad_right($file_info['status'], 12),
$file_info['filename'],
$file_info['additions'],
$file_info['deletions'],
);
}
}
public async function contributors(string $repository): Awaitable<void> {
$contributors = await $this->fetch(Str\format('repos/%s/contributors', $repository), type_structure(self::class, 'TContributorsInfo'));
echo "Contributors : \n\n";
foreach ($contributors as $contributor) {
echo Str\format(
" Login: %s\n".
" Type: %s\n".
" Url: %s\n\n",
$contributor['login'],
$contributor['type'],
$contributor['html_url'],
);
}
}
}
<<__EntryPoint>>
async function main(): Awaitable<noreturn> {
Facebook\AutoloadMap\initialize();
/**
* composer init
* composer require hhvm/hhvm-autoload
* composer require hhvm/hsl
* composer require facebook/type-assert
*/
$github = new GithubPublicInformation();
await $github->head('nuxed/framework');
echo "\n\n";
await $github->contributors('nuxed/framework');
exit(0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment