Skip to content

Commit

Permalink
feat(documentator)!: Processor File metadata (#181)
Browse files Browse the repository at this point in the history
Allows cache data between tasks while processing `File`.
  • Loading branch information
LastDragon-ru authored Aug 13, 2024
2 parents 0e2b70d + 719f465 commit 4d6c019
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 52 deletions.
4 changes: 2 additions & 2 deletions packages/documentator/src/Processor/Contracts/Dependency.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@

namespace LastDragon_ru\LaraASP\Documentator\Processor\Contracts;

use Iterator;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\DependencyNotFound;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\FileSystem;
use Stringable;
use Traversable;

/**
* Task dependency (= another file).
*
* @template TValue of Iterator<mixed, Directory|File>|Directory|File|null
* @template TValue of Traversable<mixed, Directory|File>|Directory|File|null
*/
interface Dependency extends Stringable {
/**
Expand Down
19 changes: 19 additions & 0 deletions packages/documentator/src/Processor/Contracts/Metadata.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Contracts;

use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;

/**
* @template TValue
*/
interface Metadata {
/**
* Resolves the metadata.
*
* The method should not be called directly, use {@see File::getMetadata()}.
*
* @return TValue
*/
public function __invoke(File $file): mixed;
}
4 changes: 2 additions & 2 deletions packages/documentator/src/Processor/Dependencies/Optional.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@

namespace LastDragon_ru\LaraASP\Documentator\Processor\Dependencies;

use Iterator;
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\DependencyNotFound;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\FileSystem;
use Override;
use Traversable;

/**
* @template TValue of Iterator<mixed, Directory|File>|Directory|File|null
* @template TValue of Traversable<mixed, Directory|File>|Directory|File|null
*
* @implements Dependency<TValue|null>
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Metadata;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Throwable;

/**
* @internal
*/
class FileMetadataError extends MetadataError {
public function __construct(
protected readonly File $target,
/**
* @var Metadata<*>
*/
protected readonly Metadata $metadata,
Throwable $previous,
) {
parent::__construct('', $previous);
}

public function getTarget(): File {
return $this->target;
}

/**
* @return Metadata<*>
*/
public function getMetadata(): Metadata {
return $this->metadata;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Metadata;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Throwable;

use function sprintf;

class FileMetadataFailed extends MetadataError {
public function __construct(
protected Directory $root,
protected readonly File $target,
/**
* @var Metadata<*>
*/
protected readonly Metadata $metadata,
?Throwable $previous = null,
) {
parent::__construct(
sprintf(
'Failed to retrieve `%s` metadata for `%s` file (root: `%s`).',
$this->metadata::class,
$this->target->getRelativePath($this->root),
$this->root->getPath(),
),
$previous,
);
}

public function getRoot(): Directory {
return $this->root;
}

public function getTarget(): File {
return $this->target;
}

/**
* @return Metadata<*>
*/
public function getMetadata(): Metadata {
return $this->metadata;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

abstract class MetadataError extends ProcessorError {
// empty
}
18 changes: 14 additions & 4 deletions packages/documentator/src/Processor/Executor.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency;
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Task;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\CircularDependency;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileMetadataError;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileMetadataFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileSaveFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileTaskFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\ProcessorError;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\FileSystem;
use Throwable;
use Traversable;

use function array_merge;
use function array_values;
Expand Down Expand Up @@ -122,15 +125,15 @@ private function runFile(File $file): float {
$dependency = $generator->current();
$resolved = $dependency($this->fs, $this->root, $file);

if ($resolved instanceof Iterator) {
$resolved = new ExecutorIterator($dependency, $resolved, $this->runDependency(...));
if ($resolved instanceof Traversable) {
$resolved = new ExecutorTraversable($dependency, $resolved, $this->runDependency(...));
} else {
$paused += $this->runDependency($dependency, $resolved);
}

$generator->send($resolved);

if ($resolved instanceof ExecutorIterator) {
if ($resolved instanceof ExecutorTraversable) {
$paused += $resolved->getDuration();
}
}
Expand All @@ -143,14 +146,21 @@ private function runFile(File $file): float {
if ($result !== true) {
throw new FileTaskFailed($this->root, $file, $task);
}
} catch (FileMetadataError $exception) {
throw new FileMetadataFailed(
$this->root,
$exception->getTarget(),
$exception->getMetadata(),
$exception->getPrevious(),
);
} catch (ProcessorError $exception) {
throw $exception;
} catch (Exception $exception) {
throw new FileTaskFailed($this->root, $file, $task, $exception);
}
}

if (!$file->save()) {
if (!$this->fs->save($file)) {
throw new FileSaveFailed($this->root, $file);
}
} catch (Throwable $exception) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@

use Closure;
use Generator;
use Iterator;
use IteratorAggregate;
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Dependency;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Override;
use Traversable;

/**
* @internal
Expand All @@ -19,20 +19,20 @@
*
* @implements IteratorAggregate<TKey, TValue>
*/
class ExecutorIterator implements IteratorAggregate {
class ExecutorTraversable implements IteratorAggregate {
private float $duration = 0;

public function __construct(
/**
* @var Dependency<Iterator<TKey, TValue>>
* @var Dependency<Traversable<TKey, TValue>>
*/
private readonly Dependency $dependency,
/**
* @var Iterator<TKey, TValue>
* @var Traversable<TKey, TValue>
*/
private readonly Iterator $resolved,
private readonly Traversable $resolved,
/**
* @var Closure(Dependency<Iterator<TKey, TValue>>, TValue): float
* @var Closure(Dependency<Traversable<TKey, TValue>>, TValue): float
*/
private readonly Closure $handler,
) {
Expand Down
43 changes: 34 additions & 9 deletions packages/documentator/src/Processor/FileSystem/File.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@

namespace LastDragon_ru\LaraASP\Documentator\Processor\FileSystem;

use Exception;
use InvalidArgumentException;
use LastDragon_ru\LaraASP\Core\Utils\Path;
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Metadata;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileMetadataError;
use Override;
use Stringable;

use function array_key_exists;
use function dirname;
use function file_get_contents;
use function file_put_contents;
use function is_file;
use function is_writable;
use function pathinfo;
Expand All @@ -20,6 +23,12 @@

class File implements Stringable {
private ?string $content = null;
private bool $modified = false;

/**
* @var array<class-string<Metadata<mixed>>, mixed>
*/
private array $metadata = [];

public function __construct(
private readonly string $path,
Expand Down Expand Up @@ -69,6 +78,10 @@ public function isWritable(): bool {
return $this->writable && is_writable($this->path);
}

public function isModified(): bool {
return $this->modified;
}

public function getContent(): string {
if ($this->content === null) {
$this->content = (string) file_get_contents($this->path);
Expand All @@ -78,20 +91,32 @@ public function getContent(): string {
}

public function setContent(string $content): static {
$this->content = $content;
if ($this->content !== $content) {
$this->content = $content;
$this->modified = true;
$this->metadata = [];
}

return $this;
}

public function save(): bool {
// Changed?
if ($this->content === null) {
return true;
/**
* @template T
*
* @param Metadata<T> $metadata
*
* @return T
*/
public function getMetadata(Metadata $metadata): mixed {
if (!array_key_exists($metadata::class, $this->metadata)) {
try {
$this->metadata[$metadata::class] = $metadata($this);
} catch (Exception $exception) {
throw new FileMetadataError($this, $metadata, $exception);
}
}

// Save
return $this->isWritable()
&& file_put_contents($this->path, $this->content) !== false;
return $this->metadata[$metadata::class];
}

public function getRelativePath(Directory|self $root): string {
Expand Down
6 changes: 6 additions & 0 deletions packages/documentator/src/Processor/FileSystem/FileSystem.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Symfony\Component\Finder\Finder;

use function dirname;
use function file_put_contents;
use function is_dir;
use function is_file;

Expand Down Expand Up @@ -151,4 +152,9 @@ protected function getIterator(

yield from [];
}

public function save(File $file): bool {
return !$file->isModified()
|| ($file->isWritable() && file_put_contents($file->getPath(), $file->getContent()) !== false);
}
}
30 changes: 30 additions & 0 deletions packages/documentator/src/Processor/FileSystem/FileSystemTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use function array_map;
use function basename;
use function file_get_contents;
use function iterator_to_array;

/**
Expand Down Expand Up @@ -236,4 +237,33 @@ public function testGetDirectoriesIterator(): void {
array_map($map, iterator_to_array($fs->getDirectoriesIterator($directory, exclude: '#^[^/]*?/a$#'))),
);
}


public function testSave(): void {
$fs = new FileSystem();
$temp = Path::normalize(self::getTempFile(__FILE__)->getPathname());
$file = new File($temp, true);

self::assertTrue($fs->save($file)); // because no changes

self::assertSame($file, $file->setContent(__METHOD__));

self::assertTrue($fs->save($file));

self::assertEquals(__METHOD__, file_get_contents($temp));
}

public function testSaveReadonly(): void {
$fs = new FileSystem();
$temp = Path::normalize(self::getTempFile(__FILE__)->getPathname());
$file = new File($temp, false);

self::assertTrue($fs->save($file)); // because no changes

self::assertSame($file, $file->setContent(__METHOD__));

self::assertFalse($fs->save($file));

self::assertEquals(__FILE__, file_get_contents($temp));
}
}
Loading

0 comments on commit 4d6c019

Please sign in to comment.