Initial import from aternosorg/codex-minecraft
Some checks failed
Tests / Run tests on PHP v8.4 (push) Failing after 32s
Tests / Run tests on PHP v8.5 (push) Failing after 2s

This commit is contained in:
2026-04-30 09:56:57 -05:00
commit 7c7fe5ca80
94 changed files with 7003 additions and 0 deletions

27
src/Analyser/Analyser.php Normal file
View File

@@ -0,0 +1,27 @@
<?php
namespace Aternos\Codex\Analyser;
use Aternos\Codex\Log\AnalysableLogInterface;
/**
* Class Analyser
*
* @package Aternos\Codex\Analyser
*/
abstract class Analyser implements AnalyserInterface
{
protected ?AnalysableLogInterface $log = null;
/**
* Set the log
*
* @param AnalysableLogInterface $log
* @return $this
*/
public function setLog(AnalysableLogInterface $log): static
{
$this->log = $log;
return $this;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Aternos\Codex\Analyser;
use Aternos\Codex\Analysis\AnalysisInterface;
use Aternos\Codex\Log\AnalysableLogInterface;
/**
* Interface AnalyserInterface
*
* @package Aternos\Codex\Analyser
*/
interface AnalyserInterface
{
/**
* Set the log
*
* @param AnalysableLogInterface $log
* @return $this
*/
public function setLog(AnalysableLogInterface $log): static;
/**
* Analyse a log and return an Analysis
*
* @return AnalysisInterface
*/
public function analyse(): AnalysisInterface;
}

View File

@@ -0,0 +1,159 @@
<?php
namespace Aternos\Codex\Analyser;
use Aternos\Codex\Analysis\Analysis;
use Aternos\Codex\Analysis\AnalysisInterface;
use Aternos\Codex\Analysis\PatternInsightInterface;
use Aternos\Codex\Log\EntryInterface;
use InvalidArgumentException;
/**
* Class PatternAnalyser
*
* @package Aternos\Codex\Analyser
*/
class PatternAnalyser extends Analyser
{
/**
* @var class-string<PatternInsightInterface>[]
*/
protected array $possibleInsightClasses = [];
/**
* Set possible insight classes
*
* Every class must implement PatternInsightInterface
*
* @param class-string<PatternInsightInterface>[] $insightClasses
* @return $this
*/
public function setPossibleInsightClasses(array $insightClasses): static
{
$this->possibleInsightClasses = [];
foreach ($insightClasses as $insightClass) {
$this->addPossibleInsightClass($insightClass);
}
return $this;
}
/**
* Add a possible insight class
*
* The class must implement PatternInsightInterface
*
* @param class-string<PatternInsightInterface> $insightClass
* @return $this
*/
public function addPossibleInsightClass(string $insightClass): static
{
if (!is_subclass_of($insightClass, PatternInsightInterface::class)) {
throw new InvalidArgumentException("Class " . $insightClass . " does not implement " . PatternInsightInterface::class . ".");
}
$this->possibleInsightClasses[] = $insightClass;
return $this;
}
/**
* Find a possible insight class
*
* @param class-string<PatternInsightInterface> $insightClass
* @return int
*/
protected function findPossibleInsightClass(string $insightClass): int
{
$index = array_search($insightClass, $this->possibleInsightClasses);
if ($index === false) {
throw new InvalidArgumentException("Class " . $insightClass . " not found in possible insight classes.");
}
return $index;
}
/**
* Remove a possible insight class
*
* @param class-string<PatternInsightInterface> $insightClass
*/
public function removePossibleInsightClass(string $insightClass): void
{
$index = $this->findPossibleInsightClass($insightClass);
unset($this->possibleInsightClasses[$index]);
}
/**
* Override a possible insight class with a child class
*
* The $childInsightClass has to extend $parentInsightClass
*
* @param class-string<PatternInsightInterface> $parentInsightClass
* @param class-string<PatternInsightInterface> $childInsightClass
*/
public function overridePossibleInsightClass(string $parentInsightClass, string $childInsightClass): void
{
if (!is_subclass_of($childInsightClass, $parentInsightClass)) {
throw new InvalidArgumentException("Class " . $childInsightClass . " does not extend " . $parentInsightClass . ".");
}
$index = $this->findPossibleInsightClass($parentInsightClass);
$this->possibleInsightClasses[$index] = $childInsightClass;
}
/**
* Analyse a log and return an Analysis
*
* @return AnalysisInterface
*/
public function analyse(): AnalysisInterface
{
$analysis = new Analysis();
$analysis->setLog($this->log);
foreach ($this->log as $entry) {
foreach ($this->possibleInsightClasses as $possibleInsightClass) {
/** @var PatternInsightInterface $possibleInsightClass */
$patterns = $possibleInsightClass::getPatterns();
foreach ($patterns as $patternKey => $pattern) {
$insights = $this->analyseEntry($entry, $possibleInsightClass, $patternKey, $pattern);
if ($insights) {
foreach ($insights as $insight) {
$analysis->addInsight($insight);
}
}
}
}
}
return $analysis;
}
/**
* Compare the entry against the given pattern and create an insight object if it matches
*
* @param EntryInterface $entry
* @param string $possibleInsightClass
* @param mixed $patternKey
* @param string $pattern
* @return null|PatternInsightInterface[]
*/
protected function analyseEntry(EntryInterface $entry, string $possibleInsightClass, mixed $patternKey, string $pattern): ?array
{
$result = preg_match_all($pattern, $entry, $matches, PREG_SET_ORDER);
if ($result === false || $result === 0) {
return null;
}
$return = [];
foreach ($matches as $match) {
/** @var PatternInsightInterface $insight */
$insight = new $possibleInsightClass();
$insight->setMatches($match, $patternKey);
$insight->setEntry($entry);
$return[] = $insight;
}
return $return;
}
}

238
src/Analysis/Analysis.php Normal file
View File

@@ -0,0 +1,238 @@
<?php
namespace Aternos\Codex\Analysis;
use Aternos\Codex\Log\LogInterface;
/**
* Class Analysis
*
* @package Aternos\Codex\Analysis
*/
class Analysis implements AnalysisInterface
{
/**
* @var InsightInterface[]
*/
protected array $insights = [];
protected int $iterator = 0;
protected ?LogInterface $log = null;
/**
* Set all insights at once in an array replacing the current insights
*
* @param InsightInterface[] $insights
* @return $this
*/
public function setInsights(array $insights = []): static
{
foreach ($insights as $insight) {
$insight->setAnalysis($this);
}
$this->insights = $insights;
return $this;
}
/**
* Add an insight.
* If the insight already exists, we increase its counter.
*
* @param InsightInterface $insight
* @return $this
*/
public function addInsight(InsightInterface $insight): static
{
$insight->setAnalysis($this);
foreach ($this as $existingInsight) {
if (get_class($insight) === get_class($existingInsight) && $existingInsight->isEqual($insight)) {
$existingInsight->increaseCounter();
return $this;
}
}
$this->insights[] = $insight;
return $this;
}
/**
* Get all insights
*
* @return InsightInterface[]
*/
public function getInsights(): array
{
return $this->insights;
}
/**
* Get all insights that are extended from $extendedFrom (class name)
*
* @param class-string<InsightInterface> $extendedFrom
* @return InsightInterface[]
*/
public function getFilteredInsights(string $extendedFrom): array
{
$returnInsights = [];
foreach ($this->getInsights() as $insight) {
if ($insight instanceof $extendedFrom) {
$returnInsights[] = $insight;
}
}
return $returnInsights;
}
/**
* Get all problem insights
*
* @return ProblemInterface[]
*/
public function getProblems(): array
{
return $this->getFilteredInsights(ProblemInterface::class);
}
/**
* Get all information insights
*
* @return InformationInterface[]
*/
public function getInformation(): array
{
return $this->getFilteredInsights(InformationInterface::class);
}
/**
* Return the current element
*
* @return InsightInterface
*/
public function current(): InsightInterface
{
return $this->insights[$this->iterator];
}
/**
* Move forward to next element
*
* @return void
*/
public function next(): void
{
$this->iterator++;
}
/**
* Return the key of the current element
*
* @return int
*/
public function key(): int
{
return $this->iterator;
}
/**
* Checks if current position is valid
*
* @return boolean
*/
public function valid(): bool
{
return array_key_exists($this->iterator, $this->insights);
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind(): void
{
$this->iterator = 0;
}
/**
* Count elements of an object
*
* @return int
*/
public function count(): int
{
return count($this->insights);
}
/**
* Whether an offset exists
*
* @param mixed $offset
* @return bool
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->insights[$offset]);
}
/**
* Offset to retrieve
*
* @param mixed $offset
* @return InsightInterface
*/
public function offsetGet(mixed $offset): InsightInterface
{
return $this->insights[$offset];
}
/**
* Offset to set
*
* @param mixed $offset
* @param InsightInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$value->setAnalysis($this);
$this->insights[$offset] = $value;
}
/**
* Offset to unset
*
* @param mixed $offset
*/
public function offsetUnset(mixed $offset): void
{
unset($this->insights[$offset]);
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
"problems" => $this->getProblems(),
"information" => $this->getInformation()
];
}
/**
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static
{
$this->log = $log;
return $this;
}
/**
* @return LogInterface|null
*/
public function getLog(): ?LogInterface
{
return $this->log;
}
}

View File

@@ -0,0 +1,77 @@
<?php
namespace Aternos\Codex\Analysis;
use ArrayAccess;
use Aternos\Codex\Log\LogInterface;
use Countable;
use Iterator;
use JsonSerializable;
/**
* Interface AnalysisInterface
*
* @package Aternos\Codex\Analysis
*/
interface AnalysisInterface extends Iterator, Countable, ArrayAccess, JsonSerializable
{
/**
* Set the log
*
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static;
/**
* Get the log
*
* @return LogInterface|null
*/
public function getLog(): ?LogInterface;
/**
* Set all insights at once in an array replacing the current insights
*
* @param InsightInterface[] $insights
* @return $this
*/
public function setInsights(array $insights = []): static;
/**
* Add an insight
*
* @param InsightInterface $insight
* @return $this
*/
public function addInsight(InsightInterface $insight): static;
/**
* Get all insights
*
* @return InsightInterface[]
*/
public function getInsights(): array;
/**
* Get all problem insights
*
* @return ProblemInterface[]
*/
public function getProblems(): array;
/**
* Get all information insights
*
* @return InformationInterface[]
*/
public function getInformation(): array;
/**
* Get all insights that are extended from $extendedFrom (class name)
*
* @param class-string<InsightInterface> $extendedFrom
* @return InsightInterface[]
*/
public function getFilteredInsights(string $extendedFrom): array;
}

View File

@@ -0,0 +1,17 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Interface AutomatableSolutionInterface
*
* This interface should be used to indicate
* that a solution can be solved automatically
* e.g. deletion/creation/modification of files
*
* @package Aternos\Codex\Analysis
*/
interface AutomatableSolutionInterface extends SolutionInterface
{
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Class Information
*
* @package Aternos\Codex\Analysis
*/
abstract class Information extends Insight implements InformationInterface
{
protected ?string $label = null;
protected mixed $value = null;
/**
* Get the information label
*
* @return string
*/
public function getLabel(): string
{
return $this->label;
}
/**
* Set the information label
*
* @param string $label
* @return $this
*/
protected function setLabel(string $label): static
{
$this->label = $label;
return $this;
}
/**
* Get the information value
*
* @return mixed
*/
public function getValue(): mixed
{
return $this->value;
}
/**
* Set the information value
*
* @param mixed $value
* @return $this
*/
public function setValue(mixed $value): static
{
$this->value = $value;
return $this;
}
/**
* Get a human-readable message
*
* @return string
*/
public function getMessage(): string
{
return $this->getLabel() . ": " . $this->getValue();
}
/**
* Check if the $insight object is equal with the current object
*
* @param InsightInterface $insight
* @return bool
*/
public function isEqual(InsightInterface $insight): bool
{
return $insight instanceof InformationInterface && $this->getLabel() === $insight->getLabel() && $this->getValue() === $insight->getValue();
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return array_merge(parent::jsonSerialize(), [
"label" => $this->getLabel(),
"value" => $this->getValue()
]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Interface InformationInterface
*
* @package Aternos\Codex\Analysis
*/
interface InformationInterface extends InsightInterface
{
/**
* Get the information label
*
* @return string
*/
public function getLabel(): string;
/**
* Set the information value
*
* @param mixed $value
* @return $this
*/
public function setValue(mixed $value): static;
/**
* Get the information value
*
* @return mixed
*/
public function getValue(): mixed;
}

119
src/Analysis/Insight.php Normal file
View File

@@ -0,0 +1,119 @@
<?php
namespace Aternos\Codex\Analysis;
use Aternos\Codex\Log\EntryInterface;
use Aternos\Codex\Log\LogInterface;
/**
* Class Insight
*
* @package Aternos\Codex\Analysis
*/
abstract class Insight implements InsightInterface
{
protected ?AnalysisInterface $analysis = null;
protected ?EntryInterface $entry = null;
protected int $counter = 1;
/**
* Set the related entry
*
* @param EntryInterface $entry
* @return $this
*/
public function setEntry(EntryInterface $entry): static
{
$this->entry = $entry;
return $this;
}
/**
* Get the related entry
*
* @return EntryInterface
*/
public function getEntry(): EntryInterface
{
return $this->entry;
}
/**
* Increase the counter for this insight
*
* @return $this
*/
public function increaseCounter(): static
{
$this->counter++;
return $this;
}
/**
* Get the current counter value
*
* @return int
*/
public function getCounterValue(): int
{
return $this->counter;
}
/**
* @return string
*/
public function __toString(): string
{
return $this->getMessage();
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'message' => $this->getMessage(),
'counter' => $this->getCounterValue(),
'entry' => $this->getEntry()
];
}
/**
* Set the related analysis
*
* @param AnalysisInterface $analysis
* @return $this
*/
public function setAnalysis(AnalysisInterface $analysis): static
{
$this->analysis = $analysis;
return $this;
}
/**
* Get the related analysis
*
* @return AnalysisInterface|null
*/
public function getAnalysis(): ?AnalysisInterface
{
return $this->analysis;
}
/**
* @return LogInterface|null
*/
protected function getLog(): ?LogInterface
{
return $this->getAnalysis()?->getLog();
}
/**
* @return string|null
*/
protected function getLogContent(): ?string
{
return $this->getLog()?->getLogFile()?->getContent();
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Aternos\Codex\Analysis;
use Aternos\Codex\Log\EntryInterface;
use JsonSerializable;
/**
* Interface InsightInterface
*
* @package Aternos\Codex\Analysis
*/
interface InsightInterface extends JsonSerializable
{
/**
* Get a human-readable message
*
* @return string
*/
public function getMessage(): string;
/**
* @return string
*/
public function __toString(): string;
/**
* Set the related entry
*
* @param EntryInterface $entry
* @return $this
*/
public function setEntry(EntryInterface $entry): static;
/**
* Get the related entry
*
* @return EntryInterface
*/
public function getEntry(): EntryInterface;
/**
* Check if the $insight object is equal with the current object
*
* @param InsightInterface $insight
* @return bool
*/
public function isEqual(InsightInterface $insight): bool;
/**
* Increase the counter for this insight
*
* @return $this
*/
public function increaseCounter(): static;
/**
* Get the current counter value
*
* @return int
*/
public function getCounterValue(): int;
/**
* Set the related analysis
*
* @param AnalysisInterface $analysis
* @return $this
*/
public function setAnalysis(AnalysisInterface $analysis): static;
/**
* Get the related analysis
*
* @return AnalysisInterface|null
*/
public function getAnalysis(): ?AnalysisInterface;
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Interface PatternInsightInterface
*
* @package Aternos\Codex\Analysis
*/
interface PatternInsightInterface extends InsightInterface
{
/**
* Get an array of possible patterns
*
* The array key of the pattern will be passed to setMatches()
*
* @return string[]
*/
public static function getPatterns(): array;
/**
* Apply the matches from the pattern
*
* @param array $matches
* @param mixed $patternKey
* @return void
*/
public function setMatches(array $matches, mixed $patternKey): void;
}

168
src/Analysis/Problem.php Normal file
View File

@@ -0,0 +1,168 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Class Problem
*
* @package Aternos\Codex\Analysis
*/
abstract class Problem extends Insight implements ProblemInterface
{
/**
* @var SolutionInterface[]
*/
protected array $solutions = [];
/**
* @var int
*/
protected int $iterator = 0;
/**
* Set all solutions at once in an array replacing the current solutions
*
* @param SolutionInterface[] $solutions
* @return $this
*/
public function setSolutions(array $solutions = []): static
{
$this->solutions = $solutions;
return $this;
}
/**
* Add a solution
*
* @param SolutionInterface $solution
* @return $this
*/
public function addSolution(SolutionInterface $solution): static
{
$this->solutions[] = $solution;
return $this;
}
/**
* Get all solutions
*
* @return array
*/
public function getSolutions(): array
{
return $this->solutions;
}
/**
* Return the current element
*
* @return SolutionInterface
*/
public function current(): SolutionInterface
{
return $this->solutions[$this->iterator];
}
/**
* Move forward to next element
*
* @return void
*/
public function next(): void
{
$this->iterator++;
}
/**
* Return the key of the current element
*
* @return int
*/
public function key(): int
{
return $this->iterator;
}
/**
* Checks if current position is valid
*
* @return boolean
*/
public function valid(): bool
{
return array_key_exists($this->iterator, $this->solutions);
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind(): void
{
$this->iterator = 0;
}
/**
* Count elements of an object
*
* @return int
*/
public function count(): int
{
return count($this->solutions);
}
/**
* Whether an offset exists
*
* @param mixed $offset
* @return bool
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->solutions[$offset]);
}
/**
* Offset to retrieve
*
* @param mixed $offset
* @return SolutionInterface
*/
public function offsetGet(mixed $offset): SolutionInterface
{
return $this->solutions[$offset];
}
/**
* Offset to set
*
* @param mixed $offset
* @param SolutionInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$this->solutions[$offset] = $value;
}
/**
* Offset to unset
*
* @param mixed $offset
*/
public function offsetUnset(mixed $offset): void
{
unset($this->solutions[$offset]);
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return array_merge(parent::jsonSerialize(), [
"solutions" => $this->getSolutions()
]);
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Aternos\Codex\Analysis;
use ArrayAccess;
use Countable;
use Iterator;
/**
* Interface ProblemInterface
*
* @package Aternos\Codex\Analysis
*/
interface ProblemInterface extends Iterator, Countable, ArrayAccess, InsightInterface
{
/**
* Set all solutions at once in an array replacing the current solutions
*
* @param SolutionInterface[] $solutions
* @return $this
*/
public function setSolutions(array $solutions = []): static;
/**
* Add a solution
*
* @param SolutionInterface $solution
* @return $this
*/
public function addSolution(SolutionInterface $solution): static;
/**
* Get all solutions
*
* @return SolutionInterface[]
*/
public function getSolutions(): array;
}

29
src/Analysis/Solution.php Normal file
View File

@@ -0,0 +1,29 @@
<?php
namespace Aternos\Codex\Analysis;
/**
* Class Solution
*
* @package Aternos\Codex\Analysis
*/
abstract class Solution implements SolutionInterface
{
/**
* @return string
*/
public function __toString(): string
{
return $this->getMessage();
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'message' => $this->getMessage()
];
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Aternos\Codex\Analysis;
use JsonSerializable;
/**
* Interface SolutionInterface
*
* @package Aternos\Codex\Analysis
*/
interface SolutionInterface extends JsonSerializable
{
/**
* Get the solution as a human-readable message
*
* @return string
*/
public function getMessage(): string;
public function __toString(): string;
}

149
src/Detective/Detective.php Normal file
View File

@@ -0,0 +1,149 @@
<?php
namespace Aternos\Codex\Detective;
use Aternos\Codex\Log\DetectableLogInterface;
use Aternos\Codex\Log\File\LogFileInterface;
use Aternos\Codex\Log\Log;
use Aternos\Codex\Log\LogInterface;
use InvalidArgumentException;
/**
* Class Detective
*
* @package Aternos\Codex\Detective
*/
class Detective implements DetectiveInterface
{
/**
* @var class-string<LogInterface>[]
*/
protected array $possibleLogClasses = [];
/**
* @var class-string<LogInterface>
*/
protected string $defaultLogClass = Log::class;
protected ?LogFileInterface $logFile = null;
/**
* Set possible log classes
*
* Every class must implement DetectableLogInterface
*
* @param class-string<LogInterface>[] $logClasses
* @return $this
*/
public function setPossibleLogClasses(array $logClasses): static
{
$this->possibleLogClasses = [];
foreach ($logClasses as $logClass) {
$this->addPossibleLogClass($logClass);
}
return $this;
}
/**
* Add a possible insight class
*
* The class must implement DetectableLogInterface
*
* @param class-string<LogInterface> $logClass
* @return $this
*/
public function addPossibleLogClass(string $logClass): static
{
if (!is_subclass_of($logClass, DetectableLogInterface::class)) {
throw new InvalidArgumentException("Class " . $logClass . " does not implement " . DetectableLogInterface::class . ".");
}
$this->possibleLogClasses[] = $logClass;
return $this;
}
/**
* Add all possible log classes from another detective
*
* @param DetectiveInterface $detective
* @return $this
*/
public function addDetective(DetectiveInterface $detective): static
{
foreach ($detective->getPossibleLogClasses() as $logClass) {
$this->addPossibleLogClass($logClass);
}
return $this;
}
/**
* @inheritDoc
*/
public function getPossibleLogClasses(): array
{
return $this->possibleLogClasses;
}
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static
{
$this->logFile = $logFile;
return $this;
}
/**
* Detect a log type out of possible classes by using detector
*
* @return LogInterface
*/
public function detect(): LogInterface
{
$detectionResults = [];
foreach ($this->possibleLogClasses as $possibleLogClass) {
/** @var DetectableLogInterface $possibleLogClass */
$detectors = $possibleLogClass::getDetectors();
foreach ($detectors as $detector) {
if (!$detector instanceof DetectorInterface) {
throw new InvalidArgumentException("Class " . get_class($detector) . " does not implement " . DetectorInterface::class . ".");
}
$detector->setLogFile($this->logFile);
$result = $detector->detect();
if ($result === true) {
return (new $possibleLogClass())->setLogFile($this->logFile);
}
if ($result === false) {
continue;
}
if (!is_numeric($result) || $result < 0 || $result > 1) {
throw new InvalidArgumentException("Detector " . get_class($detector) . " returned " . var_export($result));
}
$detectionResults[] = ["class" => $possibleLogClass, "result" => $result];
}
}
if (count($detectionResults) === 0) {
return (new $this->defaultLogClass())->setLogFile($this->logFile);
}
usort($detectionResults, function ($a, $b) {
if ($a["result"] < $b["result"]) {
return 1;
}
if ($a["result"] > $b["result"]) {
return -1;
}
return 0;
});
return (new $detectionResults[0]["class"]())->setLogFile($this->logFile);
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Aternos\Codex\Detective;
use Aternos\Codex\Log\File\LogFileInterface;
use Aternos\Codex\Log\LogInterface;
/**
* Interface DetectiveInterface
*
* @package Aternos\Codex\Detective
*/
interface DetectiveInterface
{
/**
* Set possible log classes
*
* Every class must implement DetectableLogInterface
*
* @param class-string<LogInterface>[] $logClasses
* @return $this
*/
public function setPossibleLogClasses(array $logClasses): static;
/**
* Add a possible log class
*
* The class must implement DetectableLogInterface
*
* @param class-string<LogInterface> $logClass
* @return $this
*/
public function addPossibleLogClass(string $logClass): static;
/**
* Get all possible log classes
*
* @return class-string<LogInterface>[]
*/
public function getPossibleLogClasses(): array;
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static;
/**
* Detect a log type out of possible classes by using detector
*
* @return LogInterface
*/
public function detect(): LogInterface;
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Aternos\Codex\Detective;
use Aternos\Codex\Log\File\LogFileInterface;
/**
* Class Detector
*
* @package Aternos\Codex\Detective
*/
abstract class Detector implements DetectorInterface
{
protected ?LogFileInterface $logFile = null;
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static
{
$this->logFile = $logFile;
return $this;
}
/**
* Get the log content as string
*
* @return string
*/
protected function getLogContent(): string
{
return $this->logFile->getContent();
}
/**
* Get the log content as array split by line
*
* @return string[]
*/
protected function getLogContentAsArray(): array
{
return explode(PHP_EOL, $this->getLogContent());
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Aternos\Codex\Detective;
use Aternos\Codex\Log\File\LogFileInterface;
/**
* Interface DetectorInterface
*
* @package Aternos\Codex\Detective
*/
interface DetectorInterface
{
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static;
/**
* Detect if the log matches
*
* Return true to directly force the detective to accept your result without considering any other detector
* Return false to force the detective to never use your result
* Return a number between 0 and 1 as probability for this detector
* Possible algorithm to get this number would be (matching lines) / (total lines)
*
* The detective decides which detector wins (and which related log class to use) in this order:
* return === true
* highest return
* default log
*
* @return bool|float
*/
public function detect(): float|bool;
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Aternos\Codex\Detective;
/**
* Class LinePatternDetector
*
* @package Aternos\Codex\Detective
*/
class LinePatternDetector extends PatternDetector
{
/**
* Detect if the log matches
*
* Counts the lines matching the pattern and returns the percentage from 0-1 for the detective
*
* Returns false when no match is found
*
* @return bool|float
*/
public function detect(): bool|float
{
$lines = $this->getLogContentAsArray();
$matchingCounter = 0;
foreach ($lines as $line) {
if (preg_match($this->pattern, $line) === 1) {
$matchingCounter++;
}
}
if ($matchingCounter === 0) {
return false;
}
return $matchingCounter / count($lines);
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Aternos\Codex\Detective;
/**
* MultiPatternDetector can detect multiple patterns in a log and return true if all patterns are found
*/
class MultiPatternDetector extends Detector
{
protected array $patterns = [];
/**
* Add a pattern to the list of patterns to detect
*
* @param string $pattern
* @return $this
*/
public function addPattern(string $pattern): static
{
$this->patterns[] = $pattern;
return $this;
}
/**
* Detects if the log matches all patterns
*
* Returns true if all patterns are found, false otherwise
*
* @return bool|float
*/
public function detect(): bool|float
{
foreach ($this->patterns as $pattern) {
if (preg_match($pattern, $this->getLogContent()) !== 1) {
return false;
}
}
return true;
}
}

View File

@@ -0,0 +1,25 @@
<?php
namespace Aternos\Codex\Detective;
/**
* Class PatternDetector
*
* @package Aternos\Codex\Detective
*/
abstract class PatternDetector extends Detector
{
protected ?string $pattern = null;
/**
* Set the matching pattern for one line
*
* @param string $pattern
* @return $this
*/
public function setPattern(string $pattern): static
{
$this->pattern = $pattern;
return $this;
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Aternos\Codex\Detective;
/**
* Class SinglePatternDetector
*
* @package Aternos\Codex\Detective
*/
class SinglePatternDetector extends PatternDetector
{
/**
* Detect if the log matches
*
* Checks if the pattern matches anywhere in the log file
*
* Returns either true or false
*
* @return bool|float
*/
public function detect(): bool|float
{
if (preg_match($this->pattern, $this->getLogContent()) === 1) {
return true;
} else {
return false;
}
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Aternos\Codex\Detective;
/**
* Class WeightedSinglePatternDetector
*
* @package Aternos\Codex\Detective
*/
class WeightedSinglePatternDetector extends SinglePatternDetector
{
protected ?float $weight = null;
/**
* Set the weight that will be returned if the pattern matches
*
* @param float $weight
* @return $this
*/
public function setWeight(float $weight): static
{
$this->weight = $weight;
return $this;
}
/**
* Detect if the log matches
*
* Checks if the pattern matches anywhere in the log file
*
* Returns either true or false
*
* @return bool|float
*/
public function detect(): bool|float
{
if (parent::detect()) {
return $this->weight;
} else {
return false;
}
}
}

50
src/Log/AnalysableLog.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace Aternos\Codex\Log;
use Aternos\Codex\Analyser\AnalyserInterface;
use Aternos\Codex\Analysis\AnalysisInterface;
/**
* Class AnalysableLog
*
* @package Aternos\Codex\Log
*/
abstract class AnalysableLog extends Log implements AnalysableLogInterface
{
protected ?AnalysisInterface $analysis = null;
/**
* Analyse a log file with an analyser
*
* Every log type should have a default analyser,
* but the $analyser argument can be used to override
* the default analyser
*
* @param AnalyserInterface|null $analyser
* @return AnalysisInterface
*/
public function analyse(?AnalyserInterface $analyser = null): AnalysisInterface
{
if ($this->analysis !== null) {
return $this->analysis;
}
if ($analyser === null) {
$analyser = static::getDefaultAnalyser();
}
$analyser->setLog($this);
return $this->analysis = $analyser->analyse();
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return array_merge(parent::jsonSerialize(), [
'analysis' => $this->analyse()
]);
}
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Aternos\Codex\Log;
use Aternos\Codex\Analyser\AnalyserInterface;
use Aternos\Codex\Analysis\AnalysisInterface;
/**
* Interface AnalysableLogInterface
*
* @package Aternos\Codex\Log
*/
interface AnalysableLogInterface
{
/**
* Get the default analyser
*
* @return AnalyserInterface
*/
public static function getDefaultAnalyser(): AnalyserInterface;
/**
* Analyse a log file with an analyser
*
* Every log type should have a default analyser,
* but the $analyser argument can be used to override
* the default analyser
*
* @param AnalyserInterface|null $analyser
* @return AnalysisInterface
*/
public function analyse(?AnalyserInterface $analyser = null): AnalysisInterface;
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Aternos\Codex\Log;
use Aternos\Codex\Detective\DetectorInterface;
/**
* Interface DetectableLogInterface
*
* @package Aternos\Codex\Log
*/
interface DetectableLogInterface extends LogInterface
{
/**
* Get an array of detectors matching DetectorInterface
*
* @return DetectorInterface[]
*/
public static function getDetectors(): array;
}

244
src/Log/Entry.php Normal file
View File

@@ -0,0 +1,244 @@
<?php
namespace Aternos\Codex\Log;
/**
* Class Entry
*
* @package Aternos\Codex\Log
*/
class Entry implements EntryInterface
{
/**
* @var LineInterface[]
*/
protected array $lines = [];
protected ?LevelInterface $level = null;
protected ?int $time = null;
protected ?string $prefix = null;
protected int $iterator = 0;
/**
* Set all lines at once in an array replacing the current lines
*
* @param LineInterface[] $lines
* @return $this
*/
public function setLines(array $lines = []): static
{
$this->lines = $lines;
return $this;
}
/**
* Add a line
*
* @param LineInterface $line
* @return $this
*/
public function addLine(LineInterface $line): static
{
$this->lines[] = $line;
return $this;
}
/**
* Get all lines
*
* @return array
*/
public function getLines(): array
{
return $this->lines;
}
/**
* Set the log level of the entry
*
* @param LevelInterface $level
* @return $this
*/
public function setLevel(LevelInterface $level): static
{
$this->level = $level;
return $this;
}
/**
* Get the log level of the entry
*
* @return LevelInterface
*/
public function getLevel(): LevelInterface
{
return $this->level ?? Level::INFO;
}
/**
* Set the timestamp of the entry
*
* @param int $time
* @return $this
*/
public function setTime(int $time): static
{
$this->time = $time;
return $this;
}
/**
* Get the timestamp of the entry
*
* @return int|null
*/
public function getTime(): ?int
{
return $this->time;
}
/**
* Set the prefix
*
* @param string $prefix
* @return $this
*/
public function setPrefix(string $prefix): static
{
$this->prefix = $prefix;
return $this;
}
/**
* Get the prefix
*
* @return string|null
*/
public function getPrefix(): ?string
{
return $this->prefix;
}
/**
* Return the current element
*
* @return Line
*/
public function current(): Line
{
return $this->lines[$this->iterator];
}
/**
* Move forward to next element
*
* @return void
*/
public function next(): void
{
$this->iterator++;
}
/**
* Return the key of the current element
*
* @return int
*/
public function key(): int
{
return $this->iterator;
}
/**
* Checks if current position is valid
*
* @return boolean
*/
public function valid(): bool
{
return array_key_exists($this->iterator, $this->lines);
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind(): void
{
$this->iterator = 0;
}
/**
* Count elements of an object
*
* @return int
*/
public function count(): int
{
return count($this->lines);
}
/**
* Whether an offset exists
*
* @param mixed $offset
* @return bool
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->lines[$offset]);
}
/**
* Offset to retrieve
*
* @param mixed $offset
* @return LineInterface
*/
public function offsetGet(mixed $offset): LineInterface
{
return $this->lines[$offset];
}
/**
* Offset to set
*
* @param mixed $offset
* @param LineInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$this->lines[$offset] = $value;
}
/**
* Offset to unset
*
* @param mixed $offset
*/
public function offsetUnset(mixed $offset): void
{
unset($this->lines[$offset]);
}
/**
* @return string
*/
public function __toString(): string
{
return implode("\n", $this->getLines());
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'level' => $this->getLevel(),
'time' => $this->getTime(),
'prefix' => $this->getPrefix(),
'lines' => $this->getLines()
];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Aternos\Codex\Log;
use ArrayAccess;
use Countable;
use Iterator;
use JsonSerializable;
/**
* Interface EntryInterface
*
* @package Aternos\Codex\Log
*/
interface EntryInterface extends Iterator, Countable, ArrayAccess, JsonSerializable
{
/**
* Set all lines at once in an array replacing the current lines
*
* @param LineInterface[] $lines
* @return $this
*/
public function setLines(array $lines = []): static;
/**
* Add a line
*
* @param LineInterface $line
* @return $this
*/
public function addLine(LineInterface $line): static;
/**
* Get all lines
*
* @return LineInterface[]
*/
public function getLines(): array;
/**
* @return string
*/
public function __toString(): string;
/**
* Return the current element
*
* @return LineInterface
*/
public function current(): LineInterface;
/**
* Offset to set
*
* @param mixed $offset
* @param LineInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void;
/**
* Offset to retrieve
*
* @param mixed $offset
* @return LineInterface
*/
public function offsetGet(mixed $offset): LineInterface;
}

23
src/Log/File/LogFile.php Normal file
View File

@@ -0,0 +1,23 @@
<?php
namespace Aternos\Codex\Log\File;
/**
* Class LogFile
*
* @package Aternos\Codex\Log\File
*/
abstract class LogFile implements LogFileInterface
{
protected ?string $content = null;
/**
* Get the log file content
*
* @return string
*/
public function getContent(): string
{
return $this->content;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Aternos\Codex\Log\File;
/**
* Interface LogFileInterface
*
* @package Aternos\Codex\Log\File
*/
interface LogFileInterface
{
/**
* Get the log file content
*
* @return string
*/
public function getContent(): string;
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Aternos\Codex\Log\File;
use InvalidArgumentException;
/**
* Class PathLogFile
*
* @package Aternos\Codex\Log\File
*/
class PathLogFile extends LogFile
{
/**
* PathLogFile constructor.
*
* @param string $path
*/
public function __construct(string $path)
{
if (!file_exists($path)) {
throw new InvalidArgumentException("File '" . $path . "' not found.");
}
$this->content = file_get_contents($path);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Aternos\Codex\Log\File;
use InvalidArgumentException;
/**
* Class StreamLogFile
*
* @package Aternos\Codex\Log\File
*/
class StreamLogFile extends LogFile
{
/**
* StreamLogFile constructor.
*
* @param resource $streamResource
*/
public function __construct($streamResource)
{
if (!is_resource($streamResource)) {
throw new InvalidArgumentException("Stream argument is not a resource");
}
$this->content = '';
while (!feof($streamResource)) {
$this->content .= fread($streamResource, 8192);
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Aternos\Codex\Log\File;
/**
* Class StringLogFile
*
* @package Aternos\Codex\Log\File
*/
class StringLogFile extends LogFile
{
/**
* StringLogFile constructor.
*
* @param string $string
*/
public function __construct(string $string)
{
$this->content = $string;
}
}

66
src/Log/Level.php Normal file
View File

@@ -0,0 +1,66 @@
<?php
namespace Aternos\Codex\Log;
enum Level: int implements LevelInterface
{
case EMERGENCY = 0;
case ALERT = 1;
case CRITICAL = 2;
case ERROR = 3;
case WARNING = 4;
case NOTICE = 5;
case INFO = 6;
case DEBUG = 7;
/**
* @param string $level
* @return Level
*/
public static function fromString(string $level): Level
{
return match (strtolower($level)) {
"emergency" => Level::EMERGENCY,
"alert" => Level::ALERT,
"critical", "severe", "fatal" => Level::CRITICAL,
"error", "stderr" => Level::ERROR,
"warning", "warn" => Level::WARNING,
"notice", "fine" => Level::NOTICE,
"debug", "finer", "finest" => Level::DEBUG,
default => Level::INFO
};
}
/**
* @return string
*/
public function asString(): string
{
return match ($this) {
Level::EMERGENCY => "emergency",
Level::ALERT => "alert",
Level::CRITICAL => "critical",
Level::ERROR => "error",
Level::WARNING => "warning",
Level::NOTICE => "notice",
Level::INFO => "info",
Level::DEBUG => "debug"
};
}
/**
* @return int
*/
public function asInt(): int
{
return $this->value;
}
/**
* @return int
*/
public function jsonSerialize(): int
{
return $this->value;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Aternos\Codex\Log;
use JsonSerializable;
interface LevelInterface extends JsonSerializable
{
/**
* @param string $level
* @return LevelInterface
*/
public static function fromString(string $level): LevelInterface;
/**
* @return string
*/
public function asString(): string;
/**
* @return int
*/
public function asInt(): int;
}

84
src/Log/Line.php Normal file
View File

@@ -0,0 +1,84 @@
<?php
namespace Aternos\Codex\Log;
/**
* Class Line
*
* @package Aternos\Codex\Log
*/
class Line implements LineInterface
{
/**
* @param int $number
* @param string $text
*/
public function __construct(
protected int $number,
protected string $text)
{
}
/**
* Set the text of the line
*
* @param string $text
* @return $this
*/
public function setText(string $text): static
{
$this->text = $text;
return $this;
}
/**
* Get the text of the line
*
* @return string
*/
public function getText(): string
{
return $this->text;
}
/**
* Set the line number
*
* @param int $number
* @return $this
*/
public function setNumber(int $number): static
{
$this->number = $number;
return $this;
}
/**
* Get the line number
*
* @return int
*/
public function getNumber(): int
{
return $this->number;
}
/**
* @return string
*/
public function __toString(): string
{
return $this->getText();
}
/**
* @return array
*/
public function jsonSerialize(): array
{
return [
'number' => $this->getNumber(),
'content' => $this->getText()
];
}
}

48
src/Log/LineInterface.php Normal file
View File

@@ -0,0 +1,48 @@
<?php
namespace Aternos\Codex\Log;
use JsonSerializable;
/**
* Interface LineInterface
*
* @package Aternos\Codex\Log
*/
interface LineInterface extends JsonSerializable
{
/**
* Set the text of the line
*
* @param string $text
* @return $this
*/
public function setText(string $text): static;
/**
* Get the text of the line
*
* @return string
*/
public function getText(): string;
/**
* Set the line number
*
* @param int $number
* @return $this
*/
public function setNumber(int $number): static;
/**
* Get the line number
*
* @return int
*/
public function getNumber(): int;
/**
* @return string
*/
public function __toString(): string;
}

253
src/Log/Log.php Normal file
View File

@@ -0,0 +1,253 @@
<?php
namespace Aternos\Codex\Log;
use Aternos\Codex\Log\File\LogFileInterface;
use Aternos\Codex\Parser\DefaultParser;
use Aternos\Codex\Parser\ParserInterface;
/**
* Class Log
*
* @package Aternos\Codex\Log
*/
class Log implements LogInterface
{
/**
* @var EntryInterface[]
*/
protected array $entries = [];
protected int $iterator = 0;
protected ?LogFileInterface $logFile = null;
protected bool $includeEntries = true;
/**
* Get the default parser
*
* @return ParserInterface
*/
public static function getDefaultParser(): ParserInterface
{
return new DefaultParser();
}
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static
{
$this->logFile = $logFile;
return $this;
}
/**
* Get the log file
*
* @return LogFileInterface
*/
public function getLogfile(): LogFileInterface
{
return $this->logFile;
}
/**
* Parse a log file with a parser
*
* Every log type should have a default parser,
* but the $parser argument can be used to override
* the default parser
*
* @param ParserInterface|null $parser
* @return $this
*/
public function parse(?ParserInterface $parser = null): static
{
if ($parser === null) {
$parser = static::getDefaultParser();
}
$parser->setLog($this)->parse();
return $this;
}
/**
* Set all entries of the log at once replacing the current entries
*
* @param EntryInterface[] $entries
* @return $this
*/
public function setEntries(array $entries = []): static
{
$this->entries = $entries;
return $this;
}
/**
* Add an entry to the log
*
* @param EntryInterface $entry
* @return $this
*/
public function addEntry(EntryInterface $entry): static
{
$this->entries[] = $entry;
return $this;
}
/**
* Get all entries of the log
*
* @return EntryInterface[]
*/
public function getEntries(): array
{
return $this->entries;
}
/**
* Return the current element
*
* @return EntryInterface
*/
public function current(): EntryInterface
{
return $this->entries[$this->iterator];
}
/**
* Move forward to next element
*
* @return void
*/
public function next(): void
{
$this->iterator++;
}
/**
* Return the key of the current element
*
* @return int
*/
public function key(): int
{
return $this->iterator;
}
/**
* Checks if current position is valid
*
* @return boolean
*/
public function valid(): bool
{
return array_key_exists($this->iterator, $this->entries);
}
/**
* Rewind the Iterator to the first element
*
* @return void
*/
public function rewind(): void
{
$this->iterator = 0;
}
/**
* Count elements of an object
*
* @return int
*/
public function count(): int
{
return count($this->entries);
}
/**
* Whether an offset exists
*
* @param mixed $offset
* @return bool
*/
public function offsetExists(mixed $offset): bool
{
return isset($this->entries[$offset]);
}
/**
* Offset to retrieve
*
* @param mixed $offset
* @return EntryInterface
*/
public function offsetGet(mixed $offset): EntryInterface
{
return $this->entries[$offset];
}
/**
* Offset to set
*
* @param mixed $offset
* @param EntryInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void
{
$this->entries[$offset] = $value;
}
/**
* Offset to unset
*
* @param mixed $offset
*/
public function offsetUnset(mixed $offset): void
{
unset($this->entries[$offset]);
}
/**
* @return string
*/
public function __toString(): string
{
return implode("\n", $this->getEntries());
}
/**
* @param bool $includeEntries
* @return $this
*/
public function setIncludeEntries(bool $includeEntries): static
{
$this->includeEntries = $includeEntries;
return $this;
}
/**
* @return array
*/
public function jsonSerialize(): array
{
if (!$this->includeEntries) {
return [];
}
return [
"entries" => $this->getEntries()
];
}
/**
* @inheritDoc
*/
public function getTitle(): string
{
return "Log";
}
}

110
src/Log/LogInterface.php Normal file
View File

@@ -0,0 +1,110 @@
<?php
namespace Aternos\Codex\Log;
use ArrayAccess;
use Aternos\Codex\Log\File\LogFileInterface;
use Aternos\Codex\Parser\ParserInterface;
use Countable;
use Iterator;
use JsonSerializable;
/**
* Interface LogInterface
*
* @package Aternos\Codex\Log
*/
interface LogInterface extends Iterator, Countable, ArrayAccess, JsonSerializable
{
/**
* Get the default parser
*
* @return ParserInterface
*/
public static function getDefaultParser(): ParserInterface;
/**
* Set the log file
*
* @param LogFileInterface $logFile
* @return $this
*/
public function setLogFile(LogFileInterface $logFile): static;
/**
* Get the log file
*
* @return LogFileInterface
*/
public function getLogFile(): LogFileInterface;
/**
* Get a human-readable title for the log
*
* @return string
*/
public function getTitle(): string;
/**
* Parse a log file with a parser
*
* Every log type should have a default parser,
* but the $parser argument can be used to override
* the default parser
*
* @param ParserInterface|null $parser
* @return $this
*/
public function parse(?ParserInterface $parser = null): static;
/**
* Set all entries of the log at once replacing the current entries
*
* @param EntryInterface[] $entries
* @return $this
*/
public function setEntries(array $entries = []): static;
/**
* Add an entry to the log
*
* @param EntryInterface $entry
* @return $this
*/
public function addEntry(EntryInterface $entry): static;
/**
* Get all entries of the log
*
* @return EntryInterface[]
*/
public function getEntries(): array;
/**
* @return string
*/
public function __toString(): string;
/**
* Return the current element
*
* @return EntryInterface
*/
public function current(): EntryInterface;
/**
* Offset to set
*
* @param mixed $offset
* @param EntryInterface $value
*/
public function offsetSet(mixed $offset, mixed $value): void;
/**
* Offset to retrieve
*
* @param mixed $offset
* @return EntryInterface
*/
public function offsetGet(mixed $offset): EntryInterface;
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Aternos\Codex\Parser;
use Aternos\Codex\Log\Entry;
use Aternos\Codex\Log\Line;
/**
* Class DefaultParser
*
* @package Aternos\Codex\Parser
*/
class DefaultParser extends Parser
{
/**
* Parse a log from resource to Log object
*/
public function parse(): void
{
foreach ($this->getLogContentAsArray() as $number => $logLineString) {
$this->log->addEntry((new Entry())
->addLine(new Line($number + 1, $logLineString))
);
}
}
}

47
src/Parser/Parser.php Normal file
View File

@@ -0,0 +1,47 @@
<?php
namespace Aternos\Codex\Parser;
use Aternos\Codex\Log\LogInterface;
/**
* Class Parser
*
* @package Aternos\Codex\Parser
*/
abstract class Parser implements ParserInterface
{
protected ?LogInterface $log = null;
/**
* Set the output log object
*
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static
{
$this->log = $log;
return $this;
}
/**
* Get the log content as string
*
* @return string
*/
protected function getLogContent(): string
{
return $this->log->getLogFile()->getContent();
}
/**
* Get the log content as array split by line
*
* @return string[]
*/
protected function getLogContentAsArray(): array
{
return explode(PHP_EOL, $this->getLogContent());
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Aternos\Codex\Parser;
use Aternos\Codex\Log\LogInterface;
/**
* Interface ParserInterface
*
* @package Aternos\Codex\Parser
*/
interface ParserInterface
{
/**
* Set the output log object
*
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static;
/**
* Parse a log from resource to Log object
*/
public function parse(): void;
}

View File

@@ -0,0 +1,183 @@
<?php
namespace Aternos\Codex\Parser;
use Aternos\Codex\Log\Entry;
use Aternos\Codex\Log\EntryInterface;
use Aternos\Codex\Log\Level;
use Aternos\Codex\Log\LevelInterface;
use Aternos\Codex\Log\Line;
use DateTime;
use DateTimeZone;
use InvalidArgumentException;
/**
* Class PatternParser
*
* @package Aternos\Codex\Parser
*/
class PatternParser extends Parser
{
/**
* Match constants, see setMatches()
*/
public const string TIME = "time";
public const string LEVEL = "level";
public const string PREFIX = "prefix";
/**
* @var class-string<EntryInterface>
*/
protected string $entryClass = Entry::class;
/**
* @noinspection PhpDocFieldTypeMismatchInspection
* @var class-string<LevelInterface>|LevelInterface
*/
protected string $levelClass = Level::class;
protected ?string $pattern = null;
protected array $matches = [];
protected ?string $timeFormat = null;
protected ?DateTimeZone $timeZone = null;
/**
* Set the entry pattern
*
* Every line matching this pattern is defined as
* new entry, all other lines are added to the
* previous entry
*
* @param string $pattern
* @return $this
*/
public function setPattern(string $pattern): static
{
$this->pattern = $pattern;
return $this;
}
/**
* Get the entry pattern
*
* @return string
*/
public function getPattern(): string
{
return $this->pattern;
}
/**
* Set the array of match constants
*
* The position/key in the array defines
* the position of the matching capturing
* group in the $pattern
*
* @param array $matches
* @return $this
*/
public function setMatches(array $matches): static
{
$this->matches = $matches;
return $this;
}
/**
* Set the time format
*
* Time is parsed with the DateTime::createFromFormat() function,
* see this for format information:
*
* http://php.net/manual/en/datetime.createfromformat.php
*
* @param string $timeFormat
* @return $this
*/
public function setTimeFormat(string $timeFormat): static
{
$this->timeFormat = $timeFormat;
return $this;
}
/**
* Set the time zone
*
* Optional, uses OS timezone otherwise
*
* @param DateTimeZone $timeZone
* @return $this
*/
public function setTimezone(DateTimeZone $timeZone): static
{
$this->timeZone = $timeZone;
return $this;
}
/**
* Parse a log from resource to Log object
*/
public function parse(): void
{
foreach ($this->getLogContentAsArray() as $number => $lineString) {
$line = new Line($number + 1, $lineString);
$result = preg_match($this->pattern, $lineString, $matches);
if ($result !== 1) {
if (!isset($entry)) {
/** @var Entry $entry */
$entry = new $this->entryClass();
$this->log->addEntry($entry);
}
$entry->addLine($line);
continue;
}
/** @var Entry $entry */
$entry = new $this->entryClass();
$this->log->addEntry($entry);
foreach ($matches as $key => $match) {
if ($key === 0) {
continue;
}
$matchKey = $key - 1;
if (!isset($this->matches[$matchKey])) {
throw new InvalidArgumentException("More matches found in string than defined in PatternParser::setMatches().");
}
$this->parseEntryMatch($entry, $this->matches[$matchKey], $match);
}
$entry->addLine($line);
}
}
/**
* Parse an entry match
*
* Overwrite this function to add more different
* match types and call the parent function (this function)
* if you don't know the match type (default in a switch)
*
* @param Entry $entry
* @param string $matchType One of the match constants
* @param string $matchString
*/
protected function parseEntryMatch(Entry $entry, string $matchType, string $matchString): void
{
switch ($matchType) {
case static::TIME:
$date = DateTime::createFromFormat($this->timeFormat, $matchString, $this->timeZone);
if ($date) {
$entry->setTime($date->getTimestamp());
}
break;
case static::LEVEL:
$entry->setLevel($this->levelClass::fromString($matchString));
break;
case static::PREFIX:
$entry->setPrefix($matchString);
break;
default:
throw new InvalidArgumentException("Match type '" . $matchType . "' is not defined.");
}
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Aternos\Codex\Printer;
use Aternos\Codex\Log\LineInterface;
/**
* Class DefaultPrinter
*
* @package Aternos\Codex\Printer
*/
class DefaultPrinter extends Printer
{
/**
* Print a line
*
* @param LineInterface $line
* @return string
*/
protected function printLine(LineInterface $line): string
{
return $line->getText() . PHP_EOL;
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace Aternos\Codex\Printer;
use Aternos\Codex\Log\LineInterface;
/**
* Class ModifiableDefaultPrinter
*
* @package Aternos\Codex\Printer
*/
class ModifiableDefaultPrinter extends ModifiablePrinter
{
/**
* Print a line
*
* @param LineInterface $line
* @return string
*/
protected function printLine(LineInterface $line): string
{
return $this->runModifications($line->getText()) . PHP_EOL;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Aternos\Codex\Printer;
/**
* Class ModifiablePrinter
*
* @package Aternos\Codex\Printer
*/
abstract class ModifiablePrinter extends Printer implements ModifiablePrinterInterface
{
/**
* @var ModificationInterface[]
*/
protected array $modifications = [];
/**
* Set all modifications replacing the current modifications
*
* @param ModificationInterface[] $modifications
* @return $this
*/
public function setModifications(array $modifications): static
{
$this->modifications = [];
foreach ($modifications as $modification) {
$this->addModification($modification);
}
return $this;
}
/**
* Add a modification
*
* @param ModificationInterface $modification
* @return $this
*/
public function addModification(ModificationInterface $modification): static
{
$this->modifications[] = $modification;
return $this;
}
/**
* Run the set modifications for a string
*
* @param string $text
* @return string
*/
protected function runModifications(string $text): string
{
foreach ($this->modifications as $modification) {
$text = $modification->modify($text);
}
return $text;
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Aternos\Codex\Printer;
/**
* Interface ModifiablePrinterInterface
*
* @package Aternos\Codex\Printer
*/
interface ModifiablePrinterInterface extends PrinterInterface
{
/**
* Set all modifications replacing the current modifications
*
* @param ModificationInterface[] $modifications
* @return $this
*/
public function setModifications(array $modifications): static;
/**
* Add a modification
*
* @param ModificationInterface $modification
* @return $this
*/
public function addModification(ModificationInterface $modification): static;
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Aternos\Codex\Printer;
/**
* Class Modification
*
* @package Aternos\Codex\Printer
*/
abstract class Modification implements ModificationInterface
{
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Aternos\Codex\Printer;
/**
* Interface ModificationInterface
*
* @package Aternos\Codex\Printer
*/
interface ModificationInterface
{
/**
* Modify the given string and return it
*
* @param string $text
* @return string
*/
public function modify(string $text): string;
}

View File

@@ -0,0 +1,60 @@
<?php
namespace Aternos\Codex\Printer;
/**
* Class PatternModification
*
* @package Aternos\Codex\Printer
*/
class PatternModification extends Modification
{
/**
* @param string $pattern
* @param string $replacement
*/
public function __construct(
protected string $pattern,
protected string $replacement)
{
}
/**
* Set the pattern
*
* See http://php.net/manual/de/function.preg-replace.php
*
* @param string $pattern
* @return $this
*/
public function setPattern(string $pattern): PatternModification
{
$this->pattern = $pattern;
return $this;
}
/**
* Set the replacement string
*
* See http://php.net/manual/de/function.preg-replace.php
*
* @param string $replacement
* @return $this
*/
public function setReplacement(string $replacement): PatternModification
{
$this->replacement = $replacement;
return $this;
}
/**
* Modify the given string and return it
*
* @param string $text
* @return string
*/
public function modify(string $text): string
{
return preg_replace($this->pattern, $this->replacement, $text);
}
}

99
src/Printer/Printer.php Normal file
View File

@@ -0,0 +1,99 @@
<?php
namespace Aternos\Codex\Printer;
use Aternos\Codex\Log\EntryInterface;
use Aternos\Codex\Log\LineInterface;
use Aternos\Codex\Log\LogInterface;
/**
* Class Printer
*
* @package Aternos\Codex\Printer
*/
abstract class Printer implements PrinterInterface
{
protected ?LogInterface $log = null;
protected ?EntryInterface $entry = null;
/**
* Set the log
*
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static
{
$this->log = $log;
return $this;
}
/**
* Set the entry
*
* @param EntryInterface $entry
* @return $this
*/
public function setEntry(EntryInterface $entry): static
{
$this->entry = $entry;
return $this;
}
/**
* Print the log
*
* @return string
*/
public function print(): string
{
if ($this->entry) {
return $this->printEntry();
} else {
return $this->printLog();
}
}
/**
* Print a log
*
* @return string
*/
protected function printLog(): string
{
$return = "";
foreach ($this->log as $entry) {
$return .= $this->printEntry($entry);
}
return $return;
}
/**
* Print an entry
*
* @param EntryInterface|null $entry
* @return string
*/
protected function printEntry(?EntryInterface $entry = null): string
{
if ($entry === null) {
$entry = $this->entry;
}
$return = "";
foreach ($entry as $line) {
$return .= $this->printLine($line);
}
return $return;
}
/**
* Print a line
*
* @param LineInterface $line
* @return string
*/
abstract protected function printLine(LineInterface $line): string;
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Aternos\Codex\Printer;
use Aternos\Codex\Log\EntryInterface;
use Aternos\Codex\Log\LogInterface;
/**
* Interface PrinterInterface
*
* @package Aternos\Codex\Printer
*/
interface PrinterInterface
{
/**
* Set the log
*
* @param LogInterface $log
* @return $this
*/
public function setLog(LogInterface $log): static;
/**
* Set the entry
*
* @param EntryInterface $entry
* @return $this
*/
public function setEntry(EntryInterface $entry): static;
/**
* Print the log
*
* @return string
*/
public function print(): string;
}