Last active
April 6, 2019 16:02
-
-
Save azjezz/f02daa8c5e57e40c92c9cb7d6721e7ed to your computer and use it in GitHub Desktop.
modified example of https://fredemmott.com/blog/posts/shapes-typeassert-and-json-apis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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