From aede34064fb1fcb039a69db99c18f1cf42ecae08 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 19 Jul 2024 17:28:32 +0400 Subject: [PATCH 1/5] feat: support custom XML config (`./dload.xml` by default); added cli option `config` --- README.md | 22 +++++++ dload.xml | 11 +--- resources/software.json | 58 +++++++++++++++++++ src/Command/Base.php | 31 +++++++++- src/Command/Get.php | 1 + ...egistry.php => CustomSoftwareRegistry.php} | 6 +- src/Module/Common/Config/Embed/File.php | 9 +++ src/Module/Common/Config/Embed/Repository.php | 10 ++++ src/Module/Common/Config/Embed/Software.php | 18 ++++++ .../Internal/Injection/ConfigLoader.php | 6 +- src/Module/Downloader/SoftwareCollection.php | 43 ++++++++++---- 11 files changed, 191 insertions(+), 24 deletions(-) create mode 100644 resources/software.json rename src/Module/Common/Config/{SoftwareRegistry.php => CustomSoftwareRegistry.php} (63%) diff --git a/README.md b/README.md index 26bab6b..0bb8903 100644 --- a/README.md +++ b/README.md @@ -55,3 +55,25 @@ Download all the software from the preset: ```bash ./vendor/bin/dload get ``` + +### Custom software registry + +```xml + + + + + + + + + +``` diff --git a/dload.xml b/dload.xml index 55ea365..a01cd1b 100644 --- a/dload.xml +++ b/dload.xml @@ -1,17 +1,10 @@ + - + - - - - - - - - diff --git a/resources/software.json b/resources/software.json new file mode 100644 index 0000000..050b9da --- /dev/null +++ b/resources/software.json @@ -0,0 +1,58 @@ +[ + { + "name": "RoadRunner", + "alias": "rr", + "homepage": "https://roadrunner.dev", + "description": "High-performance PHP application server, load-balancer and process manager written in Golang", + "repositories": [ + { + "type": "github", + "uri": "roadrunner-server/roadrunner", + "asset-pattern": "/^roadrunner-.*/" + } + ], + "files": [ + { + "pattern": "/^(roadrunner|rr)(?:\\.exe)?$/", + "rename": "rr" + } + ] + }, + { + "name": "Temporal", + "alias": "temporal", + "description": "Temporal SDK", + "homepage": "https://temporal.io", + "repositories": [ + { + "type": "github", + "uri": "temporalio/sdk-php", + "asset-pattern": "/^temporal-.*/" + } + ], + "files": [ + { + "pattern": "/^temporal-.*/", + "rename": "temporal" + } + ] + }, + { + "name": "Dolt", + "alias": "dolt", + "description": "Dolt is a SQL database that you can fork, clone, branch, merge, push and pull just like a git repository.", + "homepage": "https://www.dolthub.com", + "repositories": [ + { + "type": "github", + "uri": "dolthub/dolt", + "asset-pattern": "/^dolt-.*/" + } + ], + "files": [ + { + "pattern": "/^dolt-.*/" + } + ] + } +] diff --git a/src/Command/Base.php b/src/Command/Base.php index e7a3bc9..57fd5ee 100644 --- a/src/Command/Base.php +++ b/src/Command/Base.php @@ -9,6 +9,7 @@ use Internal\DLoad\Service\Logger; use Symfony\Component\Console\Command\Command; use Symfony\Component\Console\Input\InputInterface; +use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; use Symfony\Component\Console\Style\SymfonyStyle; @@ -22,17 +23,24 @@ abstract class Base extends Command protected Container $container; + public function configure(): void + { + parent::configure(); + $this->addOption('config', null, InputOption::VALUE_OPTIONAL, 'Path to the configuration file'); + } + protected function execute( InputInterface $input, OutputInterface $output, ): int { $this->logger = new Logger($output); $this->container = $container = Bootstrap::init()->withConfig( - xml: \dirname(__DIR__, 2) . '/dload.xml', + xml: $this->getConfigFile($input), inputOptions: $input->getOptions(), inputArguments: $input->getArguments(), environment: \getenv(), )->finish(); + $container->set($input, InputInterface::class); $container->set($output, OutputInterface::class); $container->set(new SymfonyStyle($input, $output), StyleInterface::class); @@ -40,4 +48,25 @@ protected function execute( return Command::SUCCESS; } + + /** + * @return non-empty-string|null Path to the configuration file + */ + private function getConfigFile(InputInterface $input): ?string + { + /** @var string|null $config */ + $config = $input->getOption('config'); + $isConfigured = $config !== null; + $config ??= './dload.xml'; + + if (\is_file($config)) { + return $config; + } + + $isConfigured and throw new \InvalidArgumentException( + 'Configuration file not found: ' . $config, + ); + + return null; + } } diff --git a/src/Command/Get.php b/src/Command/Get.php index deb4888..c71a256 100644 --- a/src/Command/Get.php +++ b/src/Command/Get.php @@ -27,6 +27,7 @@ final class Get extends Base implements SignalableCommandInterface { public function configure(): void { + parent::configure(); $this->addArgument('binary', InputArgument::REQUIRED, 'Binary name, e.g. "rr", "dolt", "temporal" etc.'); $this->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to store the binary, e.g. "./bin"', "."); $this->addOption('arch', null, InputOption::VALUE_OPTIONAL, 'Architecture, e.g. "amd64", "arm64" etc.'); diff --git a/src/Module/Common/Config/SoftwareRegistry.php b/src/Module/Common/Config/CustomSoftwareRegistry.php similarity index 63% rename from src/Module/Common/Config/SoftwareRegistry.php rename to src/Module/Common/Config/CustomSoftwareRegistry.php index be299ce..0737a45 100644 --- a/src/Module/Common/Config/SoftwareRegistry.php +++ b/src/Module/Common/Config/CustomSoftwareRegistry.php @@ -4,10 +4,14 @@ namespace Internal\DLoad\Module\Common\Config; +use Internal\DLoad\Module\Common\Internal\Attribute\XPath; use Internal\DLoad\Module\Common\Internal\Attribute\XPathEmbedList; -final class SoftwareRegistry +final class CustomSoftwareRegistry { + #[XPath('/dload/registry/@overwrite')] + public bool $overwrite = false; + /** * @var Embed\Software[] */ diff --git a/src/Module/Common/Config/Embed/File.php b/src/Module/Common/Config/Embed/File.php index ede6d45..61aa2f8 100644 --- a/src/Module/Common/Config/Embed/File.php +++ b/src/Module/Common/Config/Embed/File.php @@ -16,4 +16,13 @@ final class File #[XPath('@pattern')] public string $pattern = '/^.*$/'; + + public static function fromArray(mixed $fileArray): self + { + $self = new self(); + $self->rename = $fileArray['rename'] ?? null; + $self->pattern = $fileArray['pattern'] ?? '/^.*$/'; + + return $self; + } } diff --git a/src/Module/Common/Config/Embed/Repository.php b/src/Module/Common/Config/Embed/Repository.php index 8742fe4..7a50b15 100644 --- a/src/Module/Common/Config/Embed/Repository.php +++ b/src/Module/Common/Config/Embed/Repository.php @@ -16,4 +16,14 @@ final class Repository #[XPath('@asset-pattern')] public string $assetPattern = '/^.*$/'; + + public static function fromArray(mixed $repositoryArray): self + { + $self = new self(); + $self->type = $repositoryArray['type'] ?? 'github'; + $self->uri = $repositoryArray['uri']; + $self->assetPattern = $repositoryArray['asset-pattern'] ?? '/^.*$/'; + + return $self; + } } diff --git a/src/Module/Common/Config/Embed/Software.php b/src/Module/Common/Config/Embed/Software.php index c0894bb..952001b 100644 --- a/src/Module/Common/Config/Embed/Software.php +++ b/src/Module/Common/Config/Embed/Software.php @@ -33,6 +33,24 @@ final class Software #[XPathEmbedList('file', File::class)] public array $files = []; + public static function fromArray(mixed $softwareArray): self + { + $self = new self(); + $self->name = $softwareArray['name']; + $self->alias = $softwareArray['alias'] ?? null; + $self->description = $softwareArray['description'] ?? ''; + $self->repositories = \array_map( + static fn(array $repositoryArray) => Repository::fromArray($repositoryArray), + $softwareArray['repository'] ?? [], + ); + $self->files = \array_map( + static fn(array $fileArray) => File::fromArray($fileArray), + $softwareArray['file'] ?? [], + ); + + return $self; + } + /** * @return non-empty-string */ diff --git a/src/Module/Common/Internal/Injection/ConfigLoader.php b/src/Module/Common/Internal/Injection/ConfigLoader.php index 203a5b1..8aaacc0 100644 --- a/src/Module/Common/Internal/Injection/ConfigLoader.php +++ b/src/Module/Common/Internal/Injection/ConfigLoader.php @@ -126,8 +126,12 @@ private function getXPath(XPath $attribute): mixed private function getXPathEmbeddedList(XPathEmbedList $attribute): array { + if ($this->xml === null) { + return []; + } + $result = []; - $value = $this->xml?->xpath($attribute->path); + $value = $this->xml->xpath($attribute->path); \is_array($value) or throw new \Exception(\sprintf('Invalid XPath `%s`', $attribute->path)); foreach ($value as $xml) { diff --git a/src/Module/Downloader/SoftwareCollection.php b/src/Module/Downloader/SoftwareCollection.php index aad13ef..0038732 100644 --- a/src/Module/Downloader/SoftwareCollection.php +++ b/src/Module/Downloader/SoftwareCollection.php @@ -4,8 +4,9 @@ namespace Internal\DLoad\Module\Downloader; +use Internal\DLoad\Info; +use Internal\DLoad\Module\Common\Config\CustomSoftwareRegistry; use Internal\DLoad\Module\Common\Config\Embed\Software; -use Internal\DLoad\Module\Common\Config\SoftwareRegistry; use IteratorAggregate; /** @@ -13,19 +14,22 @@ */ final class SoftwareCollection implements \IteratorAggregate, \Countable { + /** @var array */ + private array $registry = []; + public function __construct( - private readonly SoftwareRegistry $softwareRegistry, - ) {} + CustomSoftwareRegistry $softwareRegistry, + ) { + foreach ($softwareRegistry->software as $software) { + $this->registry[$software->getId()] = $software; + } + + $softwareRegistry->overwrite or $this->loadDefaultRegistry(); + } public function findSoftware(string $name): ?Software { - foreach ($this->softwareRegistry->software as $software) { - if ($software->getId() === $name) { - return $software; - } - } - - return null; + return $this->registry[$name] ?? null; } /** @@ -33,7 +37,7 @@ public function findSoftware(string $name): ?Software */ public function getIterator(): \Traversable { - yield from $this->softwareRegistry->software; + yield from $this->registry; } /** @@ -41,6 +45,21 @@ public function getIterator(): \Traversable */ public function count(): int { - return \count($this->softwareRegistry->software); + return \count($this->registry); + } + + private function loadDefaultRegistry(): void + { + $json = \json_decode( + \file_get_contents(Info::ROOT_DIR . '/resources/software.json'), + true, + 16, + JSON_THROW_ON_ERROR, + ); + + foreach ($json as $softwareArray) { + $software = Software::fromArray($softwareArray); + $this->registry[$software->getId()] ??= $software; + } } } From f4bcbca59b63756c7ffb382c81392ef8792aa40d Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 19 Jul 2024 22:59:29 +0400 Subject: [PATCH 2/5] chore: reorganize configs --- dload.xml | 11 +++++-- resources/software.json | 2 +- src/Command/ListSoftware.php | 1 + src/DLoad.php | 2 +- src/Module/Common/Architecture.php | 4 +-- .../Common/Config/CustomSoftwareRegistry.php | 2 +- .../{DownloaderConfig.php => Downloader.php} | 5 ++- src/Module/Common/Config/Embed/File.php | 9 ++++++ src/Module/Common/Config/Embed/Repository.php | 10 ++++++ src/Module/Common/Config/Embed/Software.php | 31 +++++++++++++++---- .../Config/{GitHubConfig.php => GitHub.php} | 2 +- .../BuildInput.php => Input/Build.php} | 4 +-- .../Common/{Config => Input}/Destination.php | 2 +- src/Module/Common/OperatingSystem.php | 4 +-- src/Module/Common/Stability.php | 4 +-- src/Module/Downloader/Downloader.php | 2 +- src/Module/Downloader/SoftwareCollection.php | 5 ++- .../Repository/Internal/GitHub/Factory.php | 4 +-- 18 files changed, 76 insertions(+), 28 deletions(-) rename src/Module/Common/Config/{DownloaderConfig.php => Downloader.php} (50%) rename src/Module/Common/Config/{GitHubConfig.php => GitHub.php} (90%) rename src/Module/Common/{Config/BuildInput.php => Input/Build.php} (91%) rename src/Module/Common/{Config => Input}/Destination.php (81%) diff --git a/dload.xml b/dload.xml index a01cd1b..a71a9c8 100644 --- a/dload.xml +++ b/dload.xml @@ -1,8 +1,15 @@ - + + + + - + diff --git a/resources/software.json b/resources/software.json index 050b9da..ab5ca69 100644 --- a/resources/software.json +++ b/resources/software.json @@ -26,7 +26,7 @@ "repositories": [ { "type": "github", - "uri": "temporalio/sdk-php", + "uri": "temporalio/cli", "asset-pattern": "/^temporal-.*/" } ], diff --git a/src/Command/ListSoftware.php b/src/Command/ListSoftware.php index 5edce10..f2a211f 100644 --- a/src/Command/ListSoftware.php +++ b/src/Command/ListSoftware.php @@ -35,6 +35,7 @@ protected function execute( /** @var Software $software */ foreach ($registry->getIterator() as $software) { $output->writeln("{$software->getId()} $software->name"); + $software->homepage and $output->writeln("Homepage: $software->homepage"); foreach ($software->repositories as $repo) { $output->writeln("{$repo->type}: {$repo->uri}"); diff --git a/src/DLoad.php b/src/DLoad.php index 2670b57..63f81fe 100644 --- a/src/DLoad.php +++ b/src/DLoad.php @@ -5,9 +5,9 @@ namespace Internal\DLoad; use Internal\DLoad\Module\Archive\ArchiveFactory; -use Internal\DLoad\Module\Common\Config\Destination; use Internal\DLoad\Module\Common\Config\Embed\File; use Internal\DLoad\Module\Common\Config\Embed\Software; +use Internal\DLoad\Module\Common\Input\Destination; use Internal\DLoad\Module\Downloader\Downloader; use Internal\DLoad\Module\Downloader\SoftwareCollection; use Internal\DLoad\Module\Downloader\Task\DownloadResult; diff --git a/src/Module/Common/Architecture.php b/src/Module/Common/Architecture.php index c38b9f1..54a0a5e 100644 --- a/src/Module/Common/Architecture.php +++ b/src/Module/Common/Architecture.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -19,7 +19,7 @@ enum Architecture: string implements Factoriable private const ERROR_UNKNOWN_ARCH = 'Current architecture `%s` may not be supported.'; - public static function create(BuildInput $config): self + public static function create(Build $config): self { return self::tryFrom((string) $config->arch) ?? self::fromGlobals(); } diff --git a/src/Module/Common/Config/CustomSoftwareRegistry.php b/src/Module/Common/Config/CustomSoftwareRegistry.php index 0737a45..e18b264 100644 --- a/src/Module/Common/Config/CustomSoftwareRegistry.php +++ b/src/Module/Common/Config/CustomSoftwareRegistry.php @@ -13,7 +13,7 @@ final class CustomSoftwareRegistry public bool $overwrite = false; /** - * @var Embed\Software[] + * @var \Internal\DLoad\Module\Common\Config\Embed\Software[] */ #[XPathEmbedList('/dload/registry/software', Embed\Software::class)] public array $software = []; diff --git a/src/Module/Common/Config/DownloaderConfig.php b/src/Module/Common/Config/Downloader.php similarity index 50% rename from src/Module/Common/Config/DownloaderConfig.php rename to src/Module/Common/Config/Downloader.php index f9e9f86..855ca8b 100644 --- a/src/Module/Common/Config/DownloaderConfig.php +++ b/src/Module/Common/Config/Downloader.php @@ -4,7 +4,10 @@ namespace Internal\DLoad\Module\Common\Config; -final class DownloaderConfig +use Internal\DLoad\Module\Common\Internal\Attribute\XPath; + +final class Downloader { + #[XPath('/dload/@temp-dir')] public ?string $tmpDir = null; } diff --git a/src/Module/Common/Config/Embed/File.php b/src/Module/Common/Config/Embed/File.php index 61aa2f8..3f4f60d 100644 --- a/src/Module/Common/Config/Embed/File.php +++ b/src/Module/Common/Config/Embed/File.php @@ -6,6 +6,12 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; +/** + * @psalm-type FileArray = array{ + * rename?: non-empty-string, + * pattern?: non-empty-string + * } + */ final class File { /** @@ -17,6 +23,9 @@ final class File #[XPath('@pattern')] public string $pattern = '/^.*$/'; + /** + * @param FileArray $fileArray + */ public static function fromArray(mixed $fileArray): self { $self = new self(); diff --git a/src/Module/Common/Config/Embed/Repository.php b/src/Module/Common/Config/Embed/Repository.php index 7a50b15..e5e8ae4 100644 --- a/src/Module/Common/Config/Embed/Repository.php +++ b/src/Module/Common/Config/Embed/Repository.php @@ -6,6 +6,13 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; +/** + * @psalm-type RepositoryArray = array{ + * type: non-empty-string, + * uri: non-empty-string, + * asset-pattern?: non-empty-string + * } + */ final class Repository { #[XPath('@type')] @@ -17,6 +24,9 @@ final class Repository #[XPath('@asset-pattern')] public string $assetPattern = '/^.*$/'; + /** + * @param RepositoryArray $repositoryArray + */ public static function fromArray(mixed $repositoryArray): self { $self = new self(); diff --git a/src/Module/Common/Config/Embed/Software.php b/src/Module/Common/Config/Embed/Software.php index 952001b..42f336c 100644 --- a/src/Module/Common/Config/Embed/Software.php +++ b/src/Module/Common/Config/Embed/Software.php @@ -7,6 +7,18 @@ use Internal\DLoad\Module\Common\Internal\Attribute\XPath; use Internal\DLoad\Module\Common\Internal\Attribute\XPathEmbedList; +/** + * @psalm-import-type RepositoryArray from Repository + * @psalm-import-type FileArray from File + * @psalm-type SoftwareArray = array{ + * name: non-empty-string, + * alias?: non-empty-string, + * homepage?: non-empty-string, + * description?: non-empty-string, + * repositories?: list, + * files?: list + * } + */ final class Software { /** @@ -22,30 +34,37 @@ final class Software #[XPath('@alias')] public ?string $alias = null; + #[XPath('@homepage')] + public ?string $homepage = null; + #[XPath('@description')] public string $description = ''; - /** @var list */ + /** @var File */ #[XPathEmbedList('repository', Repository::class)] public array $repositories = []; - /** @var list */ + /** @var File */ #[XPathEmbedList('file', File::class)] public array $files = []; + /** + * @param SoftwareArray $softwareArray + */ public static function fromArray(mixed $softwareArray): self { $self = new self(); $self->name = $softwareArray['name']; $self->alias = $softwareArray['alias'] ?? null; + $self->homepage = $softwareArray['homepage'] ?? null; $self->description = $softwareArray['description'] ?? ''; $self->repositories = \array_map( - static fn(array $repositoryArray) => Repository::fromArray($repositoryArray), - $softwareArray['repository'] ?? [], + static fn(array $repositoryArray): Repository => Repository::fromArray($repositoryArray), + $softwareArray['repositories'] ?? [], ); $self->files = \array_map( - static fn(array $fileArray) => File::fromArray($fileArray), - $softwareArray['file'] ?? [], + static fn(array $fileArray): File => File::fromArray($fileArray), + $softwareArray['files'] ?? [], ); return $self; diff --git a/src/Module/Common/Config/GitHubConfig.php b/src/Module/Common/Config/GitHub.php similarity index 90% rename from src/Module/Common/Config/GitHubConfig.php rename to src/Module/Common/Config/GitHub.php index d123322..34706c8 100644 --- a/src/Module/Common/Config/GitHubConfig.php +++ b/src/Module/Common/Config/GitHub.php @@ -9,7 +9,7 @@ /** * @internal */ -final class GitHubConfig +final class GitHub { #[Env('GITHUB_TOKEN')] public ?string $token = null; diff --git a/src/Module/Common/Config/BuildInput.php b/src/Module/Common/Input/Build.php similarity index 91% rename from src/Module/Common/Config/BuildInput.php rename to src/Module/Common/Input/Build.php index e43aaa1..41bc720 100644 --- a/src/Module/Common/Config/BuildInput.php +++ b/src/Module/Common/Input/Build.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Internal\DLoad\Module\Common\Config; +namespace Internal\DLoad\Module\Common\Input; use Internal\DLoad\Module\Common\Architecture; use Internal\DLoad\Module\Common\Internal\Attribute\InputOption; @@ -12,7 +12,7 @@ /** * @internal */ -final class BuildInput +final class Build { /** * Use {@see Architecture} to get final value. diff --git a/src/Module/Common/Config/Destination.php b/src/Module/Common/Input/Destination.php similarity index 81% rename from src/Module/Common/Config/Destination.php rename to src/Module/Common/Input/Destination.php index c1e2aed..ad0dc09 100644 --- a/src/Module/Common/Config/Destination.php +++ b/src/Module/Common/Input/Destination.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace Internal\DLoad\Module\Common\Config; +namespace Internal\DLoad\Module\Common\Input; use Internal\DLoad\Module\Common\Internal\Attribute\InputOption; diff --git a/src/Module/Common/OperatingSystem.php b/src/Module/Common/OperatingSystem.php index d610665..6a2c2b4 100644 --- a/src/Module/Common/OperatingSystem.php +++ b/src/Module/Common/OperatingSystem.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -22,7 +22,7 @@ enum OperatingSystem: string implements Factoriable private const ERROR_UNKNOWN_OS = 'Current OS `%s` may not be supported'; - public static function create(BuildInput $config): static + public static function create(Build $config): static { return self::tryFrom((string) $config->os) ?? self::fromGlobals(); } diff --git a/src/Module/Common/Stability.php b/src/Module/Common/Stability.php index fb15e60..d211558 100644 --- a/src/Module/Common/Stability.php +++ b/src/Module/Common/Stability.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Common; -use Internal\DLoad\Module\Common\Config\BuildInput; +use Internal\DLoad\Module\Common\Input\Build; use Internal\DLoad\Service\Factoriable; /** @@ -20,7 +20,7 @@ enum Stability: string implements Factoriable case Alpha = 'alpha'; case Dev = 'dev'; - public static function create(BuildInput $config): static + public static function create(Build $config): static { return self::tryFrom((string) $config->stability) ?? self::fromGlobals(); } diff --git a/src/Module/Downloader/Downloader.php b/src/Module/Downloader/Downloader.php index c5e37b4..c1d32fe 100644 --- a/src/Module/Downloader/Downloader.php +++ b/src/Module/Downloader/Downloader.php @@ -6,7 +6,7 @@ use Internal\DLoad\Module\Archive\ArchiveFactory; use Internal\DLoad\Module\Common\Architecture; -use Internal\DLoad\Module\Common\Config\DownloaderConfig; +use Internal\DLoad\Module\Common\Config\Downloader as DownloaderConfig; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Common\OperatingSystem; use Internal\DLoad\Module\Common\Stability; diff --git a/src/Module/Downloader/SoftwareCollection.php b/src/Module/Downloader/SoftwareCollection.php index 0038732..bce7f0f 100644 --- a/src/Module/Downloader/SoftwareCollection.php +++ b/src/Module/Downloader/SoftwareCollection.php @@ -17,9 +17,8 @@ final class SoftwareCollection implements \IteratorAggregate, \Countable /** @var array */ private array $registry = []; - public function __construct( - CustomSoftwareRegistry $softwareRegistry, - ) { + public function __construct(CustomSoftwareRegistry $softwareRegistry) + { foreach ($softwareRegistry->software as $software) { $this->registry[$software->getId()] = $software; } diff --git a/src/Module/Repository/Internal/GitHub/Factory.php b/src/Module/Repository/Internal/GitHub/Factory.php index 712426e..038ad6f 100644 --- a/src/Module/Repository/Internal/GitHub/Factory.php +++ b/src/Module/Repository/Internal/GitHub/Factory.php @@ -4,7 +4,7 @@ namespace Internal\DLoad\Module\Repository\Internal\GitHub; -use Internal\DLoad\Module\Common\Config\GitHubConfig; +use Internal\DLoad\Module\Common\Config\GitHub as GitHubConfig; use Symfony\Component\HttpClient\HttpClient; use Symfony\Contracts\HttpClient\HttpClientInterface; @@ -15,7 +15,7 @@ final class Factory { public function __construct( - private readonly GitHubConfig $config, + private readonly GitHub $config, ) {} /** From 380cba520e9f217010af128feb2d84a374729518 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Fri, 19 Jul 2024 23:17:22 +0400 Subject: [PATCH 3/5] feat: support `actions/download` option into dload.xml --- README.md | 10 +++++--- dload.xml | 2 +- src/Command/Get.php | 27 +++++++++++++++++--- src/Module/Common/Config/Action/Download.php | 19 ++++++++++++++ src/Module/Common/Config/Actions.php | 17 ++++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 src/Module/Common/Config/Action/Download.php create mode 100644 src/Module/Common/Config/Actions.php diff --git a/README.md b/README.md index 0bb8903..fa39be5 100644 --- a/README.md +++ b/README.md @@ -39,18 +39,22 @@ composer require internal/dload -W ./vendor/bin/dload get dolt ``` -### Configure preset for the project (WIP) +### Configure preset for the project Create `dload.xml` file in the root of the project with the following content: ```xml - + + + + ``` -Download all the software from the preset: +There are two software packages to download: `temporal` and `rr`. +To download all the software, run `dload get` without arguments: ```bash ./vendor/bin/dload get diff --git a/dload.xml b/dload.xml index a71a9c8..4d2e45d 100644 --- a/dload.xml +++ b/dload.xml @@ -4,7 +4,7 @@ temp-dir="./runtime" > - + addArgument('binary', InputArgument::REQUIRED, 'Binary name, e.g. "rr", "dolt", "temporal" etc.'); + $this->addArgument(self::ARG_SOFTWARE, InputArgument::OPTIONAL|InputArgument::IS_ARRAY, 'Software name, e.g. "rr", "dolt", "temporal" etc.'); $this->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to store the binary, e.g. "./bin"', "."); $this->addOption('arch', null, InputOption::VALUE_OPTIONAL, 'Architecture, e.g. "amd64", "arm64" etc.'); $this->addOption('os', null, InputOption::VALUE_OPTIONAL, 'Operating system, e.g. "linux", "darwin" etc.'); @@ -65,17 +69,32 @@ protected function execute(InputInterface $input, OutputInterface $output): int $container = $this->container; - $output->writeln('Binary to load: ' . $input->getArgument('binary')); + $software = $input->getArgument(self::ARG_SOFTWARE); + + $output->writeln('Binary to load: ' . \implode(',', $software)); $output->writeln('Path to store the binary: ' . $input->getOption('path')); $output->writeln('Architecture: ' . $container->get(Architecture::class)->name); $output->writeln(' Op. system: ' . $container->get(OperatingSystem::class)->name); $output->writeln(' Stability: ' . $container->get(Stability::class)->name); + // Decide if we use CLI arguments or config + if ($software === []) { + /** @var Actions $actions */ + $actions = $container->get(Actions::class); + $software = \array_map( + static fn(DownloadConfig $download): string => $download->software, + $actions->downloads + ); + } + + $software === [] and throw new \RuntimeException('No software to download.'); + /** @var DLoad $dload */ $dload = $container->get(DLoad::class); - $binary = $input->getArgument('binary'); - $dload->addTask($binary); + foreach ($software as $binary) { + $dload->addTask($binary); + } $dload->run(); return Command::SUCCESS; diff --git a/src/Module/Common/Config/Action/Download.php b/src/Module/Common/Config/Action/Download.php new file mode 100644 index 0000000..2826b07 --- /dev/null +++ b/src/Module/Common/Config/Action/Download.php @@ -0,0 +1,19 @@ + Date: Fri, 19 Jul 2024 23:57:22 +0400 Subject: [PATCH 4/5] feat: support software version constraint in download actions --- README.md | 7 +-- src/Command/Get.php | 48 ++++++++++++------- src/DLoad.php | 17 ++++--- src/Module/Common/Config/Action/Download.php | 12 +++++ src/Module/Common/Config/Actions.php | 1 + src/Module/Downloader/Downloader.php | 16 +++++-- .../Downloader/Internal/DownloadContext.php | 2 + .../Collection/ReleasesCollection.php | 41 +++------------- .../Repository/Internal/GitHub/Factory.php | 2 +- 9 files changed, 78 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index fa39be5..a75f3c9 100644 --- a/README.md +++ b/README.md @@ -47,14 +47,15 @@ Create `dload.xml` file in the root of the project with the following content: - + ``` -There are two software packages to download: `temporal` and `rr`. -To download all the software, run `dload get` without arguments: +There are two software packages to download: `temporal` and `rr` with version `^2.12.0`. +Optionally, you may specify the version of the software package using Composer versioning syntax. +To download all the configured software, run `dload get` without arguments: ```bash ./vendor/bin/dload get diff --git a/src/Command/Get.php b/src/Command/Get.php index 69dd07a..064b8ed 100644 --- a/src/Command/Get.php +++ b/src/Command/Get.php @@ -32,7 +32,7 @@ final class Get extends Base implements SignalableCommandInterface public function configure(): void { parent::configure(); - $this->addArgument(self::ARG_SOFTWARE, InputArgument::OPTIONAL|InputArgument::IS_ARRAY, 'Software name, e.g. "rr", "dolt", "temporal" etc.'); + $this->addArgument(self::ARG_SOFTWARE, InputArgument::OPTIONAL | InputArgument::IS_ARRAY, 'Software name, e.g. "rr", "dolt", "temporal" etc.'); $this->addOption('path', null, InputOption::VALUE_OPTIONAL, 'Path to store the binary, e.g. "./bin"', "."); $this->addOption('arch', null, InputOption::VALUE_OPTIONAL, 'Architecture, e.g. "amd64", "arm64" etc.'); $this->addOption('os', null, InputOption::VALUE_OPTIONAL, 'Operating system, e.g. "linux", "darwin" etc.'); @@ -66,37 +66,51 @@ public function getSubscribedSignals(): array protected function execute(InputInterface $input, OutputInterface $output): int { parent::execute($input, $output); - $container = $this->container; - $software = $input->getArgument(self::ARG_SOFTWARE); + /** @var Actions $actionsConfig */ + $actionsConfig = $container->get(Actions::class); + $actions = $this->getDownloadActions($input, $actionsConfig); - $output->writeln('Binary to load: ' . \implode(',', $software)); + $output->writeln('Binary to load: ' . \implode(',', $actions)); $output->writeln('Path to store the binary: ' . $input->getOption('path')); $output->writeln('Architecture: ' . $container->get(Architecture::class)->name); $output->writeln(' Op. system: ' . $container->get(OperatingSystem::class)->name); $output->writeln(' Stability: ' . $container->get(Stability::class)->name); - // Decide if we use CLI arguments or config - if ($software === []) { - /** @var Actions $actions */ - $actions = $container->get(Actions::class); - $software = \array_map( - static fn(DownloadConfig $download): string => $download->software, - $actions->downloads - ); - } - - $software === [] and throw new \RuntimeException('No software to download.'); + $actions === [] and throw new \RuntimeException('No software to download.'); /** @var DLoad $dload */ $dload = $container->get(DLoad::class); - foreach ($software as $binary) { - $dload->addTask($binary); + foreach ($actions as $action) { + $dload->addTask($action); } $dload->run(); return Command::SUCCESS; } + + /** + * @return list + */ + private function getDownloadActions(InputInterface $input, Actions $actionsConfig): array + { + $argument = $input->getArgument(self::ARG_SOFTWARE); + if ($argument === []) { + // Use configured actions if CLI arguments are empty + return $actionsConfig->downloads; + } + + $toDownload = []; + foreach ($actionsConfig->downloads as $action) { + $toDownload[$action->software] = $action; + } + + return \array_map( + static fn(mixed $software): DownloadConfig => $toDownload[$software] + ?? DownloadConfig::fromSoftwareId((string) $software), + $input->getArgument(self::ARG_SOFTWARE), + ); + } } diff --git a/src/DLoad.php b/src/DLoad.php index 63f81fe..a22499d 100644 --- a/src/DLoad.php +++ b/src/DLoad.php @@ -5,6 +5,7 @@ namespace Internal\DLoad; use Internal\DLoad\Module\Archive\ArchiveFactory; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; use Internal\DLoad\Module\Common\Config\Embed\File; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Common\Input\Destination; @@ -38,18 +39,16 @@ public function __construct( private readonly StyleInterface $io, ) {} - public function addTask( - string $softwareName, - ): void { - $this->useMock and $softwareName = 'rr'; - $this->taskManager->addTask(function () use ($softwareName): void { + public function addTask(DownloadConfig $action): void + { + $this->taskManager->addTask(function () use ($action): void { // Find Software - $software = $this->softwareCollection->findSoftware($softwareName) ?? throw new \RuntimeException( + $software = $this->softwareCollection->findSoftware($action->software) ?? throw new \RuntimeException( 'Software not found.', ); // Create a Download task - $task = $this->prepareDownloadTask($software); + $task = $this->prepareDownloadTask($software, $action); // Extract files ($task->handler)()->then($this->prepareExtractTask($software)); @@ -61,7 +60,7 @@ public function run(): void $this->taskManager->await(); } - private function prepareDownloadTask(Software $software): DownloadTask + private function prepareDownloadTask(Software $software, DownloadConfig $action): DownloadTask { return $this->useMock ? new DownloadTask( @@ -74,7 +73,7 @@ private function prepareDownloadTask(Software $software): DownloadTask ), ), ) - : $this->downloader->download($software, static fn() => null); + : $this->downloader->download($software, $action, static fn() => null); } /** diff --git a/src/Module/Common/Config/Action/Download.php b/src/Module/Common/Config/Action/Download.php index 2826b07..7c616d7 100644 --- a/src/Module/Common/Config/Action/Download.php +++ b/src/Module/Common/Config/Action/Download.php @@ -11,9 +11,21 @@ */ final class Download { + /** @var non-empty-string */ #[XPath('@software')] public string $software; + /** @var non-empty-string|null */ #[XPath('@version')] public ?string $version = null; + + /** + * @param non-empty-string $software + */ + public static function fromSoftwareId(string $software): self + { + $action = new self(); + $action->software = $software; + return $action; + } } diff --git a/src/Module/Common/Config/Actions.php b/src/Module/Common/Config/Actions.php index f072457..ec6daad 100644 --- a/src/Module/Common/Config/Actions.php +++ b/src/Module/Common/Config/Actions.php @@ -12,6 +12,7 @@ */ final class Actions { + /** @var list */ #[XPathEmbedList('/dload/actions/download', Download::class)] public array $downloads = []; } diff --git a/src/Module/Downloader/Downloader.php b/src/Module/Downloader/Downloader.php index c1d32fe..b882beb 100644 --- a/src/Module/Downloader/Downloader.php +++ b/src/Module/Downloader/Downloader.php @@ -6,6 +6,7 @@ use Internal\DLoad\Module\Archive\ArchiveFactory; use Internal\DLoad\Module\Common\Architecture; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; use Internal\DLoad\Module\Common\Config\Downloader as DownloaderConfig; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Common\OperatingSystem; @@ -44,11 +45,13 @@ public function __construct( */ public function download( Software $software, + DownloadConfig $actionConfig, \Closure $onProgress, ): DownloadTask { $context = new DownloadContext( software: $software, onProgress: $onProgress, + actionConfig: $actionConfig, ); $repositories = $software->repositories; @@ -91,10 +94,17 @@ public function download( private function processRepository(RepositoryInterface $repository, DownloadContext $context): \Closure { return function () use ($repository, $context): ReleaseInterface { + $releasesCollection = $repository->getReleases() + ->minimumStability($this->stability); + + // Filter by version if specified + $context->actionConfig->version === null or $releasesCollection = $releasesCollection + ->satisfies($context->actionConfig->version); + /** @var ReleaseInterface[] $releases */ - $releases = $repository->getReleases() - ->minimumStability($this->stability) - ->sortByVersion()->toArray(); + $releases = $releasesCollection->sortByVersion()->toArray(); + + td($releases); $this->logger->debug('%d releases found.', \count($releases)); diff --git a/src/Module/Downloader/Internal/DownloadContext.php b/src/Module/Downloader/Internal/DownloadContext.php index 50b81bc..2ef349e 100644 --- a/src/Module/Downloader/Internal/DownloadContext.php +++ b/src/Module/Downloader/Internal/DownloadContext.php @@ -4,6 +4,7 @@ namespace Internal\DLoad\Module\Downloader\Internal; +use Internal\DLoad\Module\Common\Config\Action\Download as DownloadConfig; use Internal\DLoad\Module\Common\Config\Embed\Repository; use Internal\DLoad\Module\Common\Config\Embed\Software; use Internal\DLoad\Module\Downloader\Progress; @@ -31,5 +32,6 @@ final class DownloadContext public function __construct( public readonly Software $software, public readonly \Closure $onProgress, + public readonly DownloadConfig $actionConfig, ) {} } diff --git a/src/Module/Repository/Collection/ReleasesCollection.php b/src/Module/Repository/Collection/ReleasesCollection.php index c5814a8..7191d88 100644 --- a/src/Module/Repository/Collection/ReleasesCollection.php +++ b/src/Module/Repository/Collection/ReleasesCollection.php @@ -16,33 +16,21 @@ final class ReleasesCollection extends Collection { /** - * @param non-empty-string ...$constraints + * @param non-empty-string $constraint * @return $this */ - public function satisfies(string ...$constraints): self + public function satisfies(string $constraint): self { - $result = $this; - - foreach ($this->constraints($constraints) as $constraint) { - $result = $result->filter(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); - } - - return $result; + return $this->filter(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); } /** - * @param string ...$constraints + * @param non-empty-string $constraint * @return $this */ - public function notSatisfies(string ...$constraints): self + public function notSatisfies(string $constraint): self { - $result = $this; - - foreach ($this->constraints($constraints) as $constraint) { - $result = $result->except(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); - } - - return $result; + return $this->except(static fn(ReleaseInterface $r): bool => $r->satisfies($constraint)); } /** @@ -99,23 +87,6 @@ public function minimumStability(Stability $stability): self ); } - /** - * @param array $constraints - * @return array - */ - private function constraints(array $constraints): array - { - $result = []; - - foreach ($constraints as $constraint) { - foreach (\explode('|', $constraint) as $expression) { - $result[] = $expression; - } - } - - return \array_unique(\array_filter(\array_map('\\trim', $result))); - } - /** * @return non-empty-string */ diff --git a/src/Module/Repository/Internal/GitHub/Factory.php b/src/Module/Repository/Internal/GitHub/Factory.php index 038ad6f..3a9ea3e 100644 --- a/src/Module/Repository/Internal/GitHub/Factory.php +++ b/src/Module/Repository/Internal/GitHub/Factory.php @@ -15,7 +15,7 @@ final class Factory { public function __construct( - private readonly GitHub $config, + private readonly GitHubConfig $config, ) {} /** From 7ca6b39933619a6cceffb9587681f5cc4163b597 Mon Sep 17 00:00:00 2001 From: roxblnfk Date: Sat, 20 Jul 2024 00:43:35 +0400 Subject: [PATCH 5/5] fix: better process informing; fix regex templates in software registry --- README.md | 8 ++ dload.xml | 2 + psalm-baseline.xml | 103 ++++++++++++++++++++++++-- resources/software.json | 7 +- src/Command/Get.php | 2 - src/DLoad.php | 3 + src/Module/Downloader/Downloader.php | 13 +++- src/Module/Downloader/TaskManager.php | 3 +- src/Service/Logger.php | 2 +- 9 files changed, 123 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index a75f3c9..4100fe8 100644 --- a/README.md +++ b/README.md @@ -82,3 +82,11 @@ To download all the configured software, run `dload get` without arguments: ``` + +### GitHub Token + +To increase the rate limit for GitHub API, you can specify the token in the environment variable `GITHUB_TOKEN`: + +```bash +GITHUB_TOKEN=your_token_here ./vendor/bin/dload get +``` diff --git a/dload.xml b/dload.xml index 4d2e45d..c416903 100644 --- a/dload.xml +++ b/dload.xml @@ -5,6 +5,8 @@ > + + + + + + + + + + + + + + + $toDownload[$software] + ?? DownloadConfig::fromSoftwareId((string) $software), + $input->getArgument(self::ARG_SOFTWARE), + )]]> + - + getArgument(self::ARG_SOFTWARE)]]> + + + - + - - getArgument('binary')]]> - getOption('path')]]> - + + ]]> + - - + + + + cancelling]]> + + + + + + + + type]]> + uri]]> + + + + + + + repositories]]> + + + homepage]]> + pattern]]> + + files]]> + @@ -34,12 +79,33 @@ + + + + + + + + + File::fromArray($fileArray), + $softwareArray['files'] ?? [], + )]]> + Repository::fromArray($repositoryArray), + $softwareArray['repositories'] ?? [], + )]]> + + + + + @@ -69,10 +135,16 @@ repoConfig->assetPattern]]> + + + + + + @@ -80,6 +152,12 @@ ($context->onProgress)(]]> + + repoConfig]]> + + + repoConfig]]> + @@ -92,6 +170,15 @@ + + + + + + + + + get(Actions::class); $actions = $this->getDownloadActions($input, $actionsConfig); - $output->writeln('Binary to load: ' . \implode(',', $actions)); - $output->writeln('Path to store the binary: ' . $input->getOption('path')); $output->writeln('Architecture: ' . $container->get(Architecture::class)->name); $output->writeln(' Op. system: ' . $container->get(OperatingSystem::class)->name); $output->writeln(' Stability: ' . $container->get(Stability::class)->name); diff --git a/src/DLoad.php b/src/DLoad.php index a22499d..cabd90a 100644 --- a/src/DLoad.php +++ b/src/DLoad.php @@ -14,6 +14,7 @@ use Internal\DLoad\Module\Downloader\Task\DownloadResult; use Internal\DLoad\Module\Downloader\Task\DownloadTask; use Internal\DLoad\Module\Downloader\TaskManager; +use Internal\DLoad\Service\Logger; use React\Promise\PromiseInterface; use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Style\StyleInterface; @@ -30,6 +31,7 @@ final class DLoad public bool $useMock = false; public function __construct( + private readonly Logger $logger, private readonly TaskManager $taskManager, private readonly SoftwareCollection $softwareCollection, private readonly Downloader $downloader, @@ -85,6 +87,7 @@ private function prepareExtractTask(Software $software): \Closure $fileInfo = $downloadResult->file; $archive = $this->archiveFactory->create($fileInfo); $extractor = $archive->extract(); + $this->logger->info('Extracting %s', $fileInfo->getFilename()); while ($extractor->valid()) { $file = $extractor->current(); diff --git a/src/Module/Downloader/Downloader.php b/src/Module/Downloader/Downloader.php index b882beb..0f4bf81 100644 --- a/src/Module/Downloader/Downloader.php +++ b/src/Module/Downloader/Downloader.php @@ -94,6 +94,12 @@ public function download( private function processRepository(RepositoryInterface $repository, DownloadContext $context): \Closure { return function () use ($repository, $context): ReleaseInterface { + $this->logger->info( + 'Loading releases from `%s` repository %s', + $context->repoConfig->type, + $repository->getName(), + ); + $releasesCollection = $repository->getReleases() ->minimumStability($this->stability); @@ -104,20 +110,19 @@ private function processRepository(RepositoryInterface $repository, DownloadCont /** @var ReleaseInterface[] $releases */ $releases = $releasesCollection->sortByVersion()->toArray(); - td($releases); - $this->logger->debug('%d releases found.', \count($releases)); process_release: $releases === [] and throw new \RuntimeException('No relevant release found.'); $context->release = \array_shift($releases); - $this->logger->debug('Trying to load release `%s`', $context->release->getName()); + $this->logger->info('Loading release `%s`', $context->release->getName()); try { await(coroutine($this->processRelease($context))); return $context->release; } catch (\Throwable $e) { + $this->logger->error('%s', $e->getMessage()); $this->logger->exception($e); goto process_release; } @@ -164,7 +169,7 @@ private function processAsset(DownloadContext $context): \Closure $temp = $this->getTempDirectory() . DIRECTORY_SEPARATOR . $context->asset->getName(); $file = new \SplFileObject($temp, 'wb+'); - $this->logger->debug('Downloading into ' . $temp); + $this->logger->info('Downloading into %s', $temp); await(coroutine( (static function () use ($context, $file): void { diff --git a/src/Module/Downloader/TaskManager.php b/src/Module/Downloader/TaskManager.php index 6959eda..687e7ec 100644 --- a/src/Module/Downloader/TaskManager.php +++ b/src/Module/Downloader/TaskManager.php @@ -41,6 +41,7 @@ public function getProcessor(): \Generator yield $task->resume(); } catch (\Throwable $e) { + $this->logger->error($e->getMessage()); $this->logger->exception($e); unset($this->tasks[$key]); yield $e; @@ -55,7 +56,7 @@ public function await(): void $processor = $this->getProcessor(); $processor->current(); while ($processor->valid()) { - $processor->send(null); + $processor->next(); } } } diff --git a/src/Service/Logger.php b/src/Service/Logger.php index 8fc50e3..6dd0a79 100644 --- a/src/Service/Logger.php +++ b/src/Service/Logger.php @@ -36,7 +36,7 @@ public function status(string $sender, string $message, string|int|float|bool .. public function info(string $message, string|int|float|bool ...$values): void { - $this->echo("\033[32m" . \sprintf($message, ...$values) . "\033[0m\n", !$this->verbose); + $this->echo("\033[32m" . \sprintf($message, ...$values) . "\033[0m\n", false); } public function debug(string $message, string|int|float|bool ...$values): void