Skip to content

Commit

Permalink
Processor implementation.
Browse files Browse the repository at this point in the history
  • Loading branch information
LastDragon-ru committed May 18, 2024
1 parent 531874a commit c6dfebb
Show file tree
Hide file tree
Showing 19 changed files with 564 additions and 0 deletions.
22 changes: 22 additions & 0 deletions packages/documentator/src/Processor/Contracts/Task.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Contracts;

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

interface Task {
/**
* Should return all files on which `$file` depends.
*
* @return array<array-key, string>
*/
public function getDependencies(Directory $directory, File $file): array;

/**
* Performs action on the `$file`.
*
* @param array<array-key, File> $dependencies
*/
public function run(Directory $directory, File $file, array $dependencies): bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

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

use function array_map;
use function implode;
use function sprintf;

class CircularDependency extends ProcessorError {
/**
* @param list<File> $stack
*/
public function __construct(
protected Directory $root,
protected readonly File $target,
protected readonly array $stack,
Throwable $previous = null,
) {
parent::__construct(
sprintf(
<<<'MESSAGE'
Circular Dependency detected:
%2$s
! %1$s
(root: `%3$s`)
MESSAGE,
$this->target->getRelativePath($this->root),
'* '.implode("\n* ", array_map(fn ($f) => $f->getRelativePath($this->root), $this->stack)),
$this->root->getPath(),
),
$previous,
);
}

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

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

/**
* @return list<File>
*/
public function getStack(): array {
return $this->stack;
}
}
34 changes: 34 additions & 0 deletions packages/documentator/src/Processor/Exceptions/FileSaveFailed.php
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\FileSystem\Directory;
use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\File;
use Throwable;

use function sprintf;

class FileSaveFailed extends ProcessorError {
public function __construct(
protected Directory $root,
protected readonly File $target,
Throwable $previous = null,
) {
parent::__construct(
sprintf(
'Failed to save `%s` file (root: `%s`).',
$this->target->getRelativePath($this->root),
$this->root->getPath(),
),
$previous,
);
}

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

public function getTarget(): File {
return $this->target;
}
}
41 changes: 41 additions & 0 deletions packages/documentator/src/Processor/Exceptions/FileTaskFailed.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

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

use function sprintf;

class FileTaskFailed extends ProcessorError {
public function __construct(
protected Directory $root,
protected readonly File $target,
protected readonly Task $task,
Throwable $previous = null,
) {
parent::__construct(
sprintf(
'The `%s` task failed for `%s` file (root: `%s`).',
$this->target->getRelativePath($this->root),
$this->task::class,
$this->root->getPath(),
),
$previous,
);
}

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

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

public function getTask(): Task {
return $this->task;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

use LastDragon_ru\LaraASP\Documentator\Processor\FileSystem\Directory;
use Throwable;

use function sprintf;

class ProcessingFailed extends ProcessorError {
public function __construct(
protected Directory $root,
Throwable $previous = null,
) {
parent::__construct(
sprintf(
'Processing failed (root: `%s`)',
$this->root->getPath(),
),
$previous,
);
}

public function getRoot(): Directory {
return $this->root;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor\Exceptions;

use LastDragon_ru\LaraASP\Documentator\PackageException;

abstract class ProcessorError extends PackageException {
// empty
}
173 changes: 173 additions & 0 deletions packages/documentator/src/Processor/Processor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
<?php declare(strict_types = 1);

namespace LastDragon_ru\LaraASP\Documentator\Processor;

use Closure;
use Exception;
use Iterator;
use LastDragon_ru\LaraASP\Documentator\Processor\Contracts\Task;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\CircularDependency;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileSaveFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\FileTaskFailed;
use LastDragon_ru\LaraASP\Documentator\Processor\Exceptions\ProcessingFailed;
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 WeakMap;

use function array_keys;
use function array_map;
use function array_values;
use function microtime;

class Processor {
/**
* @var array<string, list<Task>>
*/
private array $tasks = [];

public function __construct() {
// empty
}

/**
* @param non-empty-list<string>|non-empty-string $extension
*/
public function task(array|string $extension, Task $task): static {
foreach ((array) $extension as $ext) {
$this->tasks[$ext][] = $task;
}

return $this;
}

/**
* @param Closure(string $path, bool $result, float $duration): void|null $listener
*/
public function run(string $path, ?Closure $listener = null): bool {
$root = new Directory($path, true);

try {
foreach ($this->getIterator($root) as [$file, $dependencies]) {
$tasks = $this->tasks[$file->getExtension()] ?? [];
$start = microtime(true);

try {
foreach ($tasks as $task) {
try {
if (!$task->run($root, $file, $dependencies[$task] ?? [])) {
throw new FileTaskFailed($root, $file, $task);
}
} catch (ProcessorError $exception) {
throw $exception;
} catch (Exception $exception) {
throw new FileTaskFailed($root, $file, $task, $exception);
}
}

if (!$file->save()) {
throw new FileSaveFailed($root, $file);
}

if ($listener) {
$listener($file->getRelativePath($root), true, microtime(true) - $start);
}
} catch (Exception $exception) {
if ($listener) {
$listener($file->getRelativePath($root), false, microtime(true) - $start);
}

throw $exception;
}
}
} catch (ProcessorError $exception) {
throw $exception;
} catch (Exception $exception) {
throw new ProcessingFailed($root, $exception);
}

return true;
}

/**
* @return Iterator<array-key, array{File, WeakMap<Task, array<string, File>>}>
*/
protected function getIterator(Directory $root): Iterator {
$extensions = array_map(static fn ($e) => "*.{$e}", array_keys($this->tasks));
$processed = [];

foreach ($root->getFilesIterator($extensions) as $item) {
foreach ($this->getFileIterator($root, $item, [$item->getPath() => $item]) as [$file, $dependencies]) {
if (isset($processed[$file->getPath()])) {
continue;
}

$processed[$file->getPath()] = true;

yield [$file, $dependencies];
}
}
}

/**
* @param array<string, File> $stack
*
* @return Iterator<array-key, array{File, WeakMap<Task, array<string, File>>}>
*/
private function getFileIterator(Directory $root, File $file, array $stack): Iterator {
// Prepare
$directory = $root->getDirectory($file);

if (!$directory) {
return;
}

// Dependencies
/** @var WeakMap<Task, array<string, File>> $dependencies */
$dependencies = new WeakMap();
$tasks = $this->tasks[$file->getExtension()] ?? [];
$map = [];

foreach ($tasks as $task) {
$taskDependencies = [];
$deps = $task->getDependencies($directory, $file);

foreach ($deps as $path) {
// File?
$dependency = $map[$path] ?? $directory->getFile($path);
$dependency = $map[$dependency?->getPath()] ?? $dependency;
$key = $dependency?->getPath();

if ($dependency === null) {
continue;
}

// Circular?
if (isset($stack[$key])) {
throw new CircularDependency($root, $dependency, array_values($stack));
}

// Save
$map[$key] = $dependency;
$map[$path] = $dependency;
$stack[$key] = $dependency;
$taskDependencies[$path] = $dependency;

// Tasks?
if (!isset($this->tasks[$dependency->getExtension()])) {
continue;
}

// Yield
yield from $this->getFileIterator($root, $dependency, $stack);

unset($stack[$key]);
}

$dependencies[$task] = $taskDependencies;
}

// File
yield [$file, $dependencies];
}
}
Loading

0 comments on commit c6dfebb

Please sign in to comment.