Skip to content

Commit

Permalink
Add Repository abstraction
Browse files Browse the repository at this point in the history
  • Loading branch information
roxblnfk committed Jul 17, 2024
1 parent 0c5e513 commit 2501cd7
Show file tree
Hide file tree
Showing 10 changed files with 566 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,4 @@ composer require internal/dload -W
[![PHP](https://img.shields.io/packagist/php-v/internal/dload.svg?style=flat-square&logo=php)](https://packagist.org/packages/internal/dload)
[![Latest Version on Packagist](https://img.shields.io/packagist/v/internal/dload.svg?style=flat-square&logo=packagist)](https://packagist.org/packages/internal/dload)
[![License](https://img.shields.io/packagist/l/internal/dload.svg?style=flat-square)](LICENSE.md)
[![Total DLoads](https://img.shields.io/packagist/dt/internal/dload.svg?style=flat-square)](https://packagist.org/packages/internal/dload)
[![Total DLoads](https://img.shields.io/packagist/dt/internal/dload.svg?style=flat-square)](https://packagist.org/packages/internal/dload/stats)
27 changes: 27 additions & 0 deletions src/Module/Repository/AssetInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Repository;

use Internal\DLoad\Module\Environment\Architecture;
use Internal\DLoad\Module\Environment\OperatingSystem;

interface AssetInterface
{
public function getRelease(): ReleaseInterface;

/**
* @return non-empty-string
*/
public function getName(): string;

/**
* @return non-empty-string
*/
public function getUri(): string;

public function getOperatingSystem(): ?OperatingSystem;

public function getArchitecture(): ?Architecture;
}
50 changes: 50 additions & 0 deletions src/Module/Repository/Internal/Asset.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Repository\Internal;

use Internal\DLoad\Module\Environment\Architecture;
use Internal\DLoad\Module\Environment\OperatingSystem;
use Internal\DLoad\Module\Repository\AssetInterface;
use Internal\DLoad\Module\Repository\ReleaseInterface;

abstract class Asset implements AssetInterface
{
/**
* @param non-empty-string $name
* @param non-empty-string $uri
*/
public function __construct(
protected ReleaseInterface $release,
protected ?OperatingSystem $os,
protected ?Architecture $arch,
protected string $name,
protected string $uri,
) {}

public function getRelease(): ReleaseInterface
{
return $this->release;
}

public function getOperatingSystem(): ?OperatingSystem
{
return $this->os;
}

public function getArchitecture(): ?Architecture
{
return $this->arch;
}

public function getName(): string
{
return $this->name;
}

public function getUri(): string
{
return $this->uri;
}
}
39 changes: 39 additions & 0 deletions src/Module/Repository/Internal/AssetsCollection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Repository\Internal;

use Internal\DLoad\Module\Environment\Architecture;
use Internal\DLoad\Module\Environment\OperatingSystem;
use Internal\DLoad\Module\Repository\AssetInterface;

/**
* @template-extends Collection<AssetInterface>
*/
final class AssetsCollection extends Collection
{
public function exceptDebPackages(): self
{
return $this->except(
static fn(AssetInterface $asset): bool =>
\str_ends_with(\strtolower($asset->getName()), '.deb'),
);
}

public function whereArchitecture(Architecture $arch): self
{
return $this->filter(
static fn(AssetInterface $asset): bool =>
\str_contains($asset->getName(), '-' . \strtolower($arch->name) . '.'),
);
}

public function whereOperatingSystem(OperatingSystem $os): self
{
return $this->filter(
static fn(AssetInterface $asset): bool =>
\str_contains($asset->getName(), '-' . \strtolower($os->name) . '-'),
);
}
}
143 changes: 143 additions & 0 deletions src/Module/Repository/Internal/Collection.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Repository\Internal;

/**
* @template T
* @template-implements \IteratorAggregate<array-key, T>
*
* @internal
* @psalm-internal Internal\DLoad\Module\Repository
*/
abstract class Collection implements \IteratorAggregate, \Countable
{
/**
* @var array<T>
*/
protected array $items;

/**
* @param array<T> $items
*/
final public function __construct(array $items)
{
$this->items = $items;
}

/**
* @param self|iterable|\Closure $items
* @return static
*/
public static function create(mixed $items): static
{
return match (true) {
$items instanceof static => $items,
$items instanceof \Traversable => new static(\iterator_to_array($items)),
\is_array($items) => new static($items),
$items instanceof \Closure => static::from($items),
default => throw new \InvalidArgumentException(
\sprintf('Unsupported iterable type %s.', \get_debug_type($items)),
),
};
}

/**
* @param \Closure $generator
* @return static
*/
public static function from(\Closure $generator): static
{
return static::create($generator());
}

/**
* @param callable(T): bool $filter
* @return $this
*/
public function filter(callable $filter): static
{
return new static(\array_filter($this->items, $filter));
}

/**
* @param callable(T): mixed $map
* @return $this
*/
public function map(callable $map): static
{
return new static(\array_map($map, $this->items));
}

/**
* @param callable(T): bool $filter
* @return $this
*
* @psalm-suppress MissingClosureParamType
* @psalm-suppress MixedArgument
*/
public function except(callable $filter): static
{
$callback = static fn(...$args): bool => ! $filter(...$args);

return new static(\array_filter($this->items, $callback));
}

/**
* @param null|callable(T): bool $filter
* @return T|null
*/
public function first(callable $filter = null): ?object
{
$self = $filter === null ? $this : $this->filter($filter);

return $self->items === [] ? null : \reset($self->items);
}

/**
* @param callable(): T $otherwise
* @param null|callable(T): bool $filter
* @return T
*/
public function firstOr(callable $otherwise, callable $filter = null): object
{
return $this->first($filter) ?? $otherwise();
}

public function getIterator(): \Traversable
{
return new \ArrayIterator($this->items);
}

public function count(): int
{
return \count($this->items);
}

/**
* @param callable $then
* @return $this
*/
public function whenEmpty(callable $then): static
{
if ($this->empty()) {
$then();
}

return $this;
}

public function empty(): bool
{
return $this->items === [];
}

/**
* @return array<T>
*/
public function toArray(): array
{
return \array_values($this->items);
}
}
90 changes: 90 additions & 0 deletions src/Module/Repository/Internal/Release.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<?php

declare(strict_types=1);

namespace Internal\DLoad\Module\Repository\Internal;

use Composer\Semver\VersionParser;
use Internal\DLoad\Module\Environment\Stability;
use Internal\DLoad\Module\Repository\ReleaseInterface;
use Internal\DLoad\Module\Repository\RepositoryInterface;

abstract class Release implements ReleaseInterface
{
/**
* Normalized release name.
*
* @var non-empty-string
*/
protected string $name;

protected Stability $stability;

protected AssetsCollection $assets;

/**
* @param non-empty-string $name
* @param non-empty-string $version
* @param iterable $assets
*/
public function __construct(
protected RepositoryInterface $repository,
string $name,
protected string $version,
iterable $assets = [],
) {
$this->name = $this->simplifyReleaseName($name);
$this->assets = AssetsCollection::create($assets);
$this->stability = $this->parseStability($version);
}

public function getRepository(): RepositoryInterface
{
return $this->repository;
}

public function getName(): string
{
return $this->name;
}

public function getVersion(): string
{
return $this->version;
}

public function getStability(): Stability
{
return $this->stability;
}

public function getAssets(): AssetsCollection
{
return $this->assets;
}

/**
* @param non-empty-string $version
*/
private function parseStability(string $version): Stability
{
return Stability::tryFrom(VersionParser::parseStability($version));
}

/**
* @param non-empty-string $name
* @return non-empty-string
*/
private function simplifyReleaseName(string $name): string
{
$version = (new VersionParser())->normalize($name);

$parts = \explode('-', $version);
$number = \substr($parts[0], 0, -2);

return isset($parts[1])
? $number . '-' . $parts[1]
: $number
;
}
}
Loading

0 comments on commit 2501cd7

Please sign in to comment.