Skip to content

Commit

Permalink
Fetch contributors from an organization
Browse files Browse the repository at this point in the history
  • Loading branch information
Progi1984 committed Mar 10, 2020
1 parent 32cb3b9 commit 0a7390a
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 52 deletions.
3 changes: 2 additions & 1 deletion composer.json
Expand Up @@ -6,7 +6,8 @@
"symfony/console": "v3.2.0",
"symfony/filesystem": "v3.2.0",
"symfony/yaml": "v3.2.0",
"guzzlehttp/guzzle": "~6.0"
"guzzlehttp/guzzle": "~6.0",
"ext-json": "*"
},
"license": "MIT",
"authors": [
Expand Down
4 changes: 4 additions & 0 deletions config.dist.yml
Expand Up @@ -11,6 +11,10 @@ config:
- 'bio'
extractEmailDomain: true
keepExcludedUsers: false
excludeRepositories:
- "PrestaShop/PrestaShop-1.4"
- "PrestaShop/PrestaShop-1.5"
- "PrestaShop/PrestaShop-1.6"
exclusions:
- 'AlexEven'
- 'AntoineMille'
Expand Down
148 changes: 97 additions & 51 deletions traces
Expand Up @@ -36,13 +36,14 @@ use Symfony\Component\Yaml\Yaml;
use GuzzleHttp\Client;

const GITHUB_CONTRIBUTORS_POINT = 'https://api.github.com/repos/{repository}/contributors';
const GITHUB_REPOSITORIES_POINT = 'https://api.github.com/orgs/{organization}/repos';
const GITHUB_USERS_POINT = 'https://api.github.com/users/{login}';
const REGEX = '/=([[:digit:]]+)>; rel="last"/';

(new Application('traces', '2.0.0'))
->register('traces')
->setDescription('A simple CLI tool to generate information about GitHub contributors of a repository.')
->addArgument('repository', InputArgument::REQUIRED, 'GitHub repository, for instance PrestaShop/PrestaShop')
->setDescription('A simple CLI tool to generate information about GitHub contributors of an organization.')
->addArgument('organization', InputArgument::REQUIRED, 'GitHub organization, for instance PrestaShop')
->addArgument('user', InputArgument::REQUIRED, 'GitHub username, for instance `johndoe`')
->addArgument('password', InputArgument::REQUIRED, 'GitHub password, for instance `secr3tSt0rY`')
->addOption('config', 'c', InputOption::VALUE_REQUIRED, 'Configuration file, for exclusions')
Expand All @@ -51,19 +52,22 @@ const REGEX = '/=([[:digit:]]+)>; rel="last"/';
function (InputInterface $input, OutputInterface $output) {
$io = new SymfonyStyle($input, $output);

$repository = $input->getArgument('repository');
$organization = $input->getArgument('organization');
$user = $input->getArgument('user');
$password = $input->getArgument('password');

$configFile = $input->getOption('config');
$timeout = $input->getOption('timeout');

$orgRepositories = $users = [];

// load settings from configuration file
list(
$exclusions,
$keepExcludedUsers,
$extractEmailDomain,
$fieldsWhitelist
$fieldsWhitelist,
$excludeRepositories
) = (function ($file) {
if (empty($file)) {
return [];
Expand All @@ -78,27 +82,61 @@ const REGEX = '/=([[:digit:]]+)>; rel="last"/';
$config['keepExcludedUsers'] ?? false,
$config['extractEmailDomain'] ?? false,
$config['fieldsWhitelist'] ? array_flip($config['fieldsWhitelist']) : [],
$config['excludeRepositories'] ?? [],
];
})(
$configFile
);

$authHeaders = ['auth' => [$user, $password]];
$requestUri = str_replace('{repository}', $repository, GITHUB_CONTRIBUTORS_POINT);

$client = new Client(['timeout' => $timeout,]);

$io->text('Loading contributors...');
$response = $client->get($requestUri);
$io->text('Loading repositories...');
if (substr_count($organization, '/') > 0) {
$orgRepositories = [$organization];
} else {
$requestUri = str_replace('{organization}', $organization, GITHUB_REPOSITORIES_POINT);
$response = $client->get($requestUri, $authHeaders);
if ('application/json; charset=utf-8' === $response->getHeader('Content-Type')[0]) {
if ($response->hasHeader('Link')) {
$headerValue = $response->getHeader('Link')[0];
preg_match(REGEX, $headerValue, $matches);
$nbPages = $matches[1];
} else {
$nbPages = 1;
}
$io->progressStart($nbPages);
for ($i = 1; $i <= $nbPages; $i++) {
$response = $client->get($requestUri . '?page=' . $i, $authHeaders);
$repositories = json_decode($response->getBody(), true);
foreach ($repositories as $repository) {
if (in_array($repository['full_name'], $excludeRepositories)) {
continue;
}
$orgRepositories[] = $repository['full_name'];
}
$io->progressAdvance(1);
}
$io->progressFinish();
}
}

if ('application/json; charset=utf-8' === $response->getHeader('Content-Type')[0]) {
if ($response->hasHeader('Link')) {
$headerValue = $response->getHeader('Link')[0];
preg_match(REGEX, $headerValue, $matches);
$nbPages = $matches[1];
$users = [];
$io->text('Loading contributors...');

$io->progressStart($nbPages);
$io->progressStart(count($orgRepositories));
foreach ($orgRepositories as $repository) {
$requestUri = str_replace('{repository}', $repository, GITHUB_CONTRIBUTORS_POINT);
$response = $client->get($requestUri, $authHeaders);

if ('application/json; charset=utf-8' === $response->getHeader('Content-Type')[0]) {
if ($response->hasHeader('Link')) {
$headerValue = $response->getHeader('Link')[0];
preg_match(REGEX, $headerValue, $matches);
$nbPages = $matches[1];
} else {
$nbPages = 1;
}

for ($i = 1; $i <= $nbPages; $i++) {
$response = $client->get($requestUri . '?page=' . $i, $authHeaders);
Expand All @@ -109,50 +147,58 @@ const REGEX = '/=([[:digit:]]+)>; rel="last"/';
if (!$keepExcludedUsers && in_array($contributor['login'], $exclusions, true)) {
continue;
}
$userRequestUri = str_replace('{login}', $contributor['login'], GITHUB_USERS_POINT);
$response = $client->get($userRequestUri, $authHeaders);

$user = json_decode($response->getBody(), true);

// extract mail domain
$userEmailDomain = '';
if ($extractEmailDomain && !empty($user['email'])) {
$userEmailDomain = substr($user['email'], strpos($user['email'], '@') + 1);
if (!array_key_exists($contributor['login'], $users)) {
$userRequestUri = str_replace('{login}', $contributor['login'], GITHUB_USERS_POINT);
$response = $client->get($userRequestUri, $authHeaders);

$user = json_decode($response->getBody(), true);

// extract mail domain
$userEmailDomain = '';
if ($extractEmailDomain && !empty($user['email'])) {
$userEmailDomain = substr($user['email'], strpos($user['email'], '@') + 1);
}

// clean up response if whitelist is defined
if (!empty($fieldsWhitelist)) {
$user = array_intersect_key($user, $fieldsWhitelist);
}

// add exclusion property if setting enabled
if ($keepExcludedUsers) {
$user['excluded'] = in_array($contributor['login'], $exclusions, true);
}

// add mail domain if setting enabled
if ($extractEmailDomain) {
$user['email_domain'] = $userEmailDomain;
}
$user['contributions'] = 0;
$user['repositories'] = [];

$users[$contributor['login']] = $user;
}

// clean up response if whitelist is defined
if (!empty($fieldsWhitelist)) {
$user = array_intersect_key($user, $fieldsWhitelist);
}

// add exclusion property if setting enabled
if ($keepExcludedUsers) {
$user['excluded'] = in_array($contributor['login'], $exclusions, true);
}

// add mail domain if setting enabled
if ($extractEmailDomain) {
$user['email_domain'] = $userEmailDomain;
}

$user['contributions'] = $contributor['contributions'];

$users[] = $user;
$users[$contributor['login']]['contributions'] += $contributor['contributions'];
$users[$contributor['login']]['repositories'][$repository] = $contributor['contributions'];
}

$io->progressAdvance(1);
}
}
$io->progressAdvance(1);
}
$io->progressFinish();

$io->progressFinish();

$io->text('Writing to file...');
$io->success(sprintf('%s contributors found for the organization "%s".', count($users), $organization));

$fs = new Filesystem();
$fs->dumpFile('contributors.js', json_encode($users, JSON_PRETTY_PRINT));
usort($users, function($userA, $userB) {
if ($userA['contributions'] == $userB['contributions']) {
return 0;
}
}
return ($userA['contributions'] > $userB['contributions']) ? -1 : 1;
});

$io->success(sprintf('%s contributors found for the repository "%s".', count($users), $repository));
$io->text('Writing to file...');
$fs = new Filesystem();
$fs->dumpFile('contributors.js', json_encode($users, JSON_PRETTY_PRINT));
}
)
->getApplication()
Expand Down

0 comments on commit 0a7390a

Please sign in to comment.