Skip to content
Snippets Groups Projects
Commit 7df2def4 authored by totten's avatar totten
Browse files

glr - Pull down lib code via packagist

parent 192d2fb0
Branches
No related tags found
No related merge requests found
......@@ -6,55 +6,60 @@ namespace Clippy;
// Usage: ./glr upload <project-url> <version> <asset-files>*
// Example: ./glr https://lab.civicrm.org/foo/bar 1.2.3 foobar-1.2.3.zip
require_once pogo_script_dir() . '/../src/clippy.php';
#!require totten/clippy: 0.1.0
#!require { mnapoli/silly: ~1.7, php: '>=7.0', guzzlehttp/guzzle: ~6.0 }
use GuzzleHttp\HandlerStack;
use Symfony\Component\Console\Style\SymfonyStyle;
function labProject($project, $io) {
assertThat(preg_match(';https?://[^/]+/[^/]+/[^/]+;', $project), "Project URL should match pattern: https:///DOMAIN/OWNER/REPO");
list ($scheme, , $host, $owner, $repo) = explode('/', $project);
$c = clippy()->register(plugins());
$c['gitlab()'] = function($url, Credentials $cred, HandlerStack $guzzleHandler) {
assertThat(preg_match(';https?://[^/]+/[^/]+/[^/]+;', $url), "Project URL should match pattern: https:///DOMAIN/OWNER/REPO");
list ($scheme, , $host, $owner, $repo) = explode('/', $url);
$lab = new \GuzzleHttp\Client([
$client = new \GuzzleHttp\Client([
'base_uri' => "{$scheme}//{$host}/api/v4/projects/{$owner}%2F{$repo}/",
'headers' => [
'PRIVATE-TOKEN' => assertVal(cred('PRIVATE_TOKEN', $host, $io), 'Missing PRIVATE_TOKEN'),
],
'headers' => ['PRIVATE-TOKEN' => $cred->get('PRIVATE_TOKEN', $host)],
'handler' => $guzzleHandler,
]);
return $lab;
}
return $client;
};
$app = new \Silly\Application();
$app->command('upload projectUrl verNum assets*', function ($projectUrl, $verNum, $assets, SymfonyStyle $io) {
$labProject = labProject($projectUrl, $io);
$c['app']->command('upload [-N|--dry-run] projectUrl verNum assets*', function ($projectUrl, $verNum, $assets, SymfonyStyle $io, $gitlab, $input) {
$verbose = function($data) use ($io) { return $io->isVerbose() ? toJSON($data) : ''; };
$client = $gitlab($projectUrl);
assertThat(preg_match('/^\d[0-9a-z\.\-\+]*$/', $verNum));
$io->writeln(sprintf("<info>Release project <comment>%s</comment> at version <comment>%s</comment>:\n<comment> * %s</comment></info>", $projectUrl, $verNum, implode("\n * ", $assets)));
$existingAssets = fromJSON($labProject->get('releases/' . urlencode($verNum) . '/assets/links'));
$existingAssets = fromJSON($client->get('releases/' . urlencode($verNum) . '/assets/links'));
$existingAssets = index(['name'], $existingAssets);
foreach ($assets as $asset) {
assertThat(file_exists($asset), "File $asset does not exist");
$upload = fromJSON($labProject->post('uploads', [
if ($input->getOption('dry-run')) {
$io->note("(DRY-RUN) Skipped upload of $asset");
continue;
}
$upload = fromJSON($client->post('uploads', [
'multipart' => [
['name' => 'file', 'contents' => fopen($asset, 'r')],
],
]));
$io->writeln("<info>Created new upload:</info>\n" . toJSON($upload));
$io->writeln("<info>Created new upload</info> " . $verbose($upload));
if (isset($existingAssets[basename($asset)])) {
$delete = fromJSON($labProject->delete('releases/' . urlencode($verNum) . '/assets/links/' . $existingAssets[basename($asset)]['id']));
$io->writeln("<info>Deleted old upload:</info>\n" . toJSON($delete));
$delete = fromJSON($client->delete('releases/' . urlencode($verNum) . '/assets/links/' . $existingAssets[basename($asset)]['id']));
$io->writeln("<info>Deleted old upload</info> " . $verbose($delete));
// Should we also delete the previous upload? Is that possible?
}
$release = fromJSON($labProject->post('releases/' . urlencode($verNum) . '/assets/links', [
$release = fromJSON($client->post('releases/' . urlencode($verNum) . '/assets/links', [
'form_params' => [
'name' => basename($asset),
'url' => joinUrl($projectUrl, $upload['url']),
],
]));
$io->writeln("<info>Updated release:</info>\n" . toJSON($release));
$io->writeln("<info>Updated release</info> " . $verbose($release));
}
});;
$app->run();
$c['app']->run();
<?php
namespace Clippy;
use Symfony\Component\Console\Style\SymfonyStyle;
// -----------------------------------------------------------------------------
// Assertions
/**
* Assert that $bool is true.
*
* @param bool $bool
* @param string $msg
* @throws \Exception
*/
function assertThat($bool, $msg = '') {
if (!$bool) {
throw new \Exception($msg ? $msg : 'Assertion failed');
}
}
/**
* Assert that $value has an actual value (not null or empty-string)
*
* @param mixed $value
* @param string $msg
* @return mixed
* The approved value
* @throws \Exception
*/
function assertVal($value, $msg) {
if ($value === NULL || $value === '') {
throw new \Exception($msg ? $msg : 'Missing expected value');
}
return $value;
}
function fail($msg) {
throw new \Exception($msg ? $msg : 'Assertion failed');
}
// -----------------------------------------------------------------------------
// IO utilities
/**
* Combine all elements of part, in order, to form a string - using path delimiters.
* Duplicate delimiters are trimmed.
*
* @param array $parts
* A list of strings and/or arrays.
* @return string
*/
function joinPath(...$parts) {
$path = [];
foreach ($parts as $part) {
if (is_array($part)) {
$path = array_merge($path, $part);
}
else {
$path[] = $part;
}
}
$result = implode(DIRECTORY_SEPARATOR, $parts);
$both = "[\\/]";
return preg_replace(";{$both}{$both}+;", '/', $result);
}
/**
* Combine all elements of part, in order, to form a string - using URL delimiters.
* Duplicate delimiters are trimmed.
*
* @param array $parts
* A list of strings and/or arrays.
* @return string
*/
function joinUrl(...$parts) {
$path = [];
foreach ($parts as $part) {
if (is_array($part)) {
$path = array_merge($path, $part);
}
else {
$path[] = $part;
}
}
$result = implode('/', $parts);
return preg_replace(';//+;', '/', $result);
}
function toJSON($data) {
return json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
}
function fromJSON($data) {
#!require psr/http-message: *
if ($data instanceof \Psr\Http\Message\ResponseInterface) {
$data = $data->getBody()->getContents();
}
assertThat(is_string($data));
$result = json_decode($data, 1);
assertThat($result !== NULL || $data === 'null', sprintf("JSON parse error:\n----\n%s\n----\n", $data));
return $result;
}
// -----------------------------------------------------------------------------
// Array utilities
/**
* Builds an array-tree which indexes the records in an array.
*
* @param string[] $keys
* Properties by which to index.
* @param object|array $records
*
* @return array
* Multi-dimensional array, with one layer for each key.
*/
function index($keys, $records) {
$final_key = array_pop($keys);
$result = [];
foreach ($records as $record) {
$node = &$result;
foreach ($keys as $key) {
if (is_array($record)) {
$keyvalue = isset($record[$key]) ? $record[$key] : NULL;
}
else {
$keyvalue = isset($record->{$key}) ? $record->{$key} : NULL;
}
if (isset($node[$keyvalue]) && !is_array($node[$keyvalue])) {
$node[$keyvalue] = [];
}
$node = &$node[$keyvalue];
}
if (is_array($record)) {
$node[$record[$final_key]] = $record;
}
else {
$node[$record->{$final_key}] = $record;
}
}
return $result;
}
// -----------------------------------------------------------------------------
// Higher level services
/**
* @param string $name
* Environment variable
* @param string $context
* @param \Symfony\Component\Console\Style\SymfonyStyle|NULL $io
* @return mixed|null|string
*/
function cred($name, $context = 'default', SymfonyStyle $io = NULL) {
if (getenv($name)) {
return getenv($name);
}
$storage = joinPath(getenv('HOME'), '.config', 'clippy-cred', urlencode($context) . '.json');
if (file_exists($storage)) {
$data = fromJSON(file_get_contents($storage));
if (isset($data[$name])) {
return $data[$name];
}
}
if ($io) {
if ($storage) {
$io->note("Credential $name not found in environment");
$io->note("Credential $name not found in $storage");
}
$pass = $io->askHidden(sprintf('Please enter credential %s for %s:', $name, $context));
// TODO save
return $pass;
}
return NULL;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment