Compare commits
	
		
			3 Commits
		
	
	
		
			master
			...
			refactor/s
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| e012ae2a9c | |||
| 78b596a342 | |||
| afff03166e | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -1,2 +1,3 @@
 | 
			
		||||
/vendor/
 | 
			
		||||
/.idea/
 | 
			
		||||
/.phpunit.cache/
 | 
			
		||||
 | 
			
		||||
@ -3,7 +3,7 @@
 | 
			
		||||
 | 
			
		||||
use IO\ExceptionHandler;
 | 
			
		||||
use IO\Input\FinderArguments;
 | 
			
		||||
use IO\Output\DocumentListingOutput;
 | 
			
		||||
use IO\Output\DocumentListing;
 | 
			
		||||
use PDF\Document;
 | 
			
		||||
 | 
			
		||||
require_once __DIR__ . '/../vendor/autoload.php';
 | 
			
		||||
@ -23,4 +23,4 @@ foreach ($filters as $filter) {
 | 
			
		||||
    $documents = $documents->filter(fn(Document $document) => $filter->allows($document));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
DocumentListingOutput::forDocuments($documents)->render();
 | 
			
		||||
DocumentListing::of($documents)->render();
 | 
			
		||||
 | 
			
		||||
@ -3,17 +3,12 @@
 | 
			
		||||
 | 
			
		||||
use IO\ExceptionHandler;
 | 
			
		||||
use IO\Input\ShowInfoArguments;
 | 
			
		||||
use IO\Output\DocumentOutput;
 | 
			
		||||
use IO\Output\DocumentDetails;
 | 
			
		||||
 | 
			
		||||
require_once __DIR__ . '/../vendor/autoload.php';
 | 
			
		||||
 | 
			
		||||
ExceptionHandler::registerCallback();
 | 
			
		||||
 | 
			
		||||
$arguments = ShowInfoArguments::createFromGlobals();
 | 
			
		||||
$file = $arguments->getFile();
 | 
			
		||||
 | 
			
		||||
$documentFactory = DocumentFactory::create();
 | 
			
		||||
$document = $documentFactory->createDocument($file);
 | 
			
		||||
 | 
			
		||||
$output = DocumentOutput::forDocument($document);
 | 
			
		||||
$output->render();
 | 
			
		||||
$file = ShowInfoArguments::createFromGlobals()->getFile();
 | 
			
		||||
$document = DocumentFactory::create()->fromFile($file);
 | 
			
		||||
DocumentDetails::of($document)->render();
 | 
			
		||||
 | 
			
		||||
@ -2,7 +2,14 @@
 | 
			
		||||
  "name": "joopschilder/pdf-finder",
 | 
			
		||||
  "type": "project",
 | 
			
		||||
  "license": "MIT",
 | 
			
		||||
  "keywords": ["pdf", "documents", "search", "metadata", "info", "portable document format"],
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "pdf",
 | 
			
		||||
    "documents",
 | 
			
		||||
    "search",
 | 
			
		||||
    "metadata",
 | 
			
		||||
    "info",
 | 
			
		||||
    "portable document format"
 | 
			
		||||
  ],
 | 
			
		||||
  "description": "Utility to locate PDF files based on their metadata",
 | 
			
		||||
  "autoload": {
 | 
			
		||||
    "psr-0": {
 | 
			
		||||
@ -11,9 +18,20 @@
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "autoload-dev": {
 | 
			
		||||
    "psr-0": {
 | 
			
		||||
      "": [
 | 
			
		||||
        "src",
 | 
			
		||||
        "tests"
 | 
			
		||||
      ]
 | 
			
		||||
    }
 | 
			
		||||
  },
 | 
			
		||||
  "require": {
 | 
			
		||||
    "symfony/console": "^5.2",
 | 
			
		||||
    "cocur/slugify": "^4.0",
 | 
			
		||||
    "illuminate/collections": "^8.33"
 | 
			
		||||
  },
 | 
			
		||||
  "require-dev": {
 | 
			
		||||
    "phpunit/phpunit": "^9.5"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										2055
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2055
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										25
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								phpunit.xml
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
			
		||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 | 
			
		||||
         xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.5/phpunit.xsd"
 | 
			
		||||
         bootstrap="vendor/autoload.php"
 | 
			
		||||
         cacheResultFile=".phpunit.cache/test-results"
 | 
			
		||||
         executionOrder="depends,defects"
 | 
			
		||||
         forceCoversAnnotation="true"
 | 
			
		||||
         beStrictAboutCoversAnnotation="true"
 | 
			
		||||
         beStrictAboutOutputDuringTests="true"
 | 
			
		||||
         beStrictAboutTodoAnnotatedTests="true"
 | 
			
		||||
         failOnWarning="true"
 | 
			
		||||
         verbose="true">
 | 
			
		||||
    <testsuites>
 | 
			
		||||
        <testsuite name="default">
 | 
			
		||||
            <directory suffix="Test.php">tests</directory>
 | 
			
		||||
        </testsuite>
 | 
			
		||||
    </testsuites>
 | 
			
		||||
 | 
			
		||||
    <coverage cacheDirectory=".phpunit.cache/code-coverage"
 | 
			
		||||
              processUncoveredFiles="true">
 | 
			
		||||
        <include>
 | 
			
		||||
            <directory suffix=".php">src</directory>
 | 
			
		||||
        </include>
 | 
			
		||||
    </coverage>
 | 
			
		||||
</phpunit>
 | 
			
		||||
@ -1,5 +1,6 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use IO\Filesystem\File;
 | 
			
		||||
use IO\Shell\Pdfinfo;
 | 
			
		||||
use PDF\Document;
 | 
			
		||||
 | 
			
		||||
@ -17,7 +18,7 @@ class DocumentFactory
 | 
			
		||||
        return new self();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function createDocument(SplFileInfo $file): Document
 | 
			
		||||
    public function fromFile(File $file): Document
 | 
			
		||||
    {
 | 
			
		||||
        $metadata = $this->pdfinfo->getMetadata($file);
 | 
			
		||||
        return new Document($file, $metadata);
 | 
			
		||||
 | 
			
		||||
@ -2,12 +2,12 @@
 | 
			
		||||
 | 
			
		||||
namespace Filter;
 | 
			
		||||
 | 
			
		||||
class FilterFactory
 | 
			
		||||
class FilterParser
 | 
			
		||||
{
 | 
			
		||||
    public function createFromString(string $string): DocumentFilter
 | 
			
		||||
    public function parse(string $string): DocumentFilter
 | 
			
		||||
    {
 | 
			
		||||
        if (preg_match('/^.+=.*$/', $string)) {
 | 
			
		||||
            [$prop, $term] = explode('=', $string);
 | 
			
		||||
            [$prop, $term] = explode('=', $string, 2);
 | 
			
		||||
            return new SpecificFilter(trim($prop), trim($term));
 | 
			
		||||
        }
 | 
			
		||||
        return new GenericFilter($string);
 | 
			
		||||
							
								
								
									
										42
									
								
								src/IO/Filesystem/Directory.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								src/IO/Filesystem/Directory.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,42 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace IO\Filesystem;
 | 
			
		||||
 | 
			
		||||
use IO\Exception\DirectoryNotFoundException;
 | 
			
		||||
use IO\Exception\DirectoryNotReadableException;
 | 
			
		||||
use IO\Exception\NotADirectoryException;
 | 
			
		||||
 | 
			
		||||
class Directory
 | 
			
		||||
{
 | 
			
		||||
    private string $directory;
 | 
			
		||||
 | 
			
		||||
    private function __construct(string $directory)
 | 
			
		||||
    {
 | 
			
		||||
        $this->directory = rtrim($directory, DIRECTORY_SEPARATOR);
 | 
			
		||||
        $this->guardUnusableDirectory($directory);
 | 
			
		||||
        $this->directory = realpath($directory);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromString(?string $directory): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self($directory ?? getcwd());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __toString(): string
 | 
			
		||||
    {
 | 
			
		||||
        return $this->directory;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function guardUnusableDirectory(string $directory): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!file_exists($directory)) {
 | 
			
		||||
            throw new DirectoryNotFoundException($directory);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_dir($directory)) {
 | 
			
		||||
            throw new NotADirectoryException($directory);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_readable($directory)) {
 | 
			
		||||
            throw new DirectoryNotReadableException($directory);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								src/IO/Filesystem/File.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/IO/Filesystem/File.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace IO\Filesystem;
 | 
			
		||||
 | 
			
		||||
use IO\Exception\FileNotFoundException;
 | 
			
		||||
use IO\Exception\FileNotReadableException;
 | 
			
		||||
use SplFileInfo;
 | 
			
		||||
 | 
			
		||||
class File
 | 
			
		||||
{
 | 
			
		||||
    private SplFileInfo $info;
 | 
			
		||||
 | 
			
		||||
    private function __construct(string $filepath)
 | 
			
		||||
    {
 | 
			
		||||
        $this->guardUnusableFile($filepath);
 | 
			
		||||
        $filepath = realpath($filepath);
 | 
			
		||||
        $this->info = new SplFileInfo($filepath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function fromString(string $filepath): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self($filepath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getInfo(): SplFileInfo
 | 
			
		||||
    {
 | 
			
		||||
        return $this->info;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __toString(): string
 | 
			
		||||
    {
 | 
			
		||||
        return (string)$this->getInfo();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function guardUnusableFile(string $file): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!file_exists($file)) {
 | 
			
		||||
            throw new FileNotFoundException($file);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_readable($file)) {
 | 
			
		||||
            throw new FileNotReadableException($file);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -4,13 +4,13 @@ namespace IO\Input;
 | 
			
		||||
 | 
			
		||||
trait ArgvAccess
 | 
			
		||||
{
 | 
			
		||||
    protected static function getArguments(): array
 | 
			
		||||
    protected static function getScriptArgs(): array
 | 
			
		||||
    {
 | 
			
		||||
        // Get local copy of $argv
 | 
			
		||||
        // local copy of $argv
 | 
			
		||||
        global $argv;
 | 
			
		||||
        $arguments = $argv;
 | 
			
		||||
 | 
			
		||||
        // Lose the script name
 | 
			
		||||
        // shift off the script name
 | 
			
		||||
        array_shift($arguments);
 | 
			
		||||
 | 
			
		||||
        return $arguments;
 | 
			
		||||
 | 
			
		||||
@ -3,38 +3,32 @@
 | 
			
		||||
namespace IO\Input;
 | 
			
		||||
 | 
			
		||||
use Filter\DocumentFilter;
 | 
			
		||||
use Filter\FilterFactory;
 | 
			
		||||
use IO\Exception\DirectoryNotFoundException;
 | 
			
		||||
use IO\Exception\DirectoryNotReadableException;
 | 
			
		||||
use IO\Exception\NotADirectoryException;
 | 
			
		||||
use Filter\FilterParser;
 | 
			
		||||
use IO\Filesystem\Directory;
 | 
			
		||||
 | 
			
		||||
class FinderArguments
 | 
			
		||||
{
 | 
			
		||||
    use ArgvAccess;
 | 
			
		||||
 | 
			
		||||
    private ?string $directory;
 | 
			
		||||
    private Directory $directory;
 | 
			
		||||
    private array $filters;
 | 
			
		||||
 | 
			
		||||
    public static function createFromGlobals(): self
 | 
			
		||||
    {
 | 
			
		||||
        $arguments = self::getArguments();
 | 
			
		||||
        $arguments = self::getScriptArgs();
 | 
			
		||||
        $directory = array_shift($arguments);
 | 
			
		||||
 | 
			
		||||
        $dir = array_shift($arguments) ?? getcwd();
 | 
			
		||||
        $dir = rtrim($dir, DIRECTORY_SEPARATOR);
 | 
			
		||||
 | 
			
		||||
        return new self($dir, $arguments);
 | 
			
		||||
        return new self($directory, $arguments);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __construct(?string $directory, array $filters)
 | 
			
		||||
    {
 | 
			
		||||
        $this->guardUnusableDirectory($directory);
 | 
			
		||||
        $this->directory = realpath($directory);
 | 
			
		||||
 | 
			
		||||
        $factory = new FilterFactory();
 | 
			
		||||
        $this->filters = array_map([$factory, 'createFromString'], $filters);
 | 
			
		||||
        $factory = new FilterParser();
 | 
			
		||||
        $this->directory = Directory::fromString($directory);
 | 
			
		||||
        $this->filters = array_map([$factory, 'parse'], $filters);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getDirectory(): string
 | 
			
		||||
    public function getDirectory(): Directory
 | 
			
		||||
    {
 | 
			
		||||
        return $this->directory;
 | 
			
		||||
    }
 | 
			
		||||
@ -46,17 +40,4 @@ class FinderArguments
 | 
			
		||||
    {
 | 
			
		||||
        return $this->filters;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function guardUnusableDirectory(string $directory): void
 | 
			
		||||
    {
 | 
			
		||||
        if (!file_exists($directory)) {
 | 
			
		||||
            throw new DirectoryNotFoundException($directory);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_dir($directory)) {
 | 
			
		||||
            throw new NotADirectoryException($directory);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_readable($directory)) {
 | 
			
		||||
            throw new DirectoryNotReadableException($directory);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,44 +2,31 @@
 | 
			
		||||
 | 
			
		||||
namespace IO\Input;
 | 
			
		||||
 | 
			
		||||
use IO\Exception\FileNotFoundException;
 | 
			
		||||
use IO\Exception\FileNotReadableException;
 | 
			
		||||
use IO\Exception\MissingFileArgumentException;
 | 
			
		||||
use SplFileInfo;
 | 
			
		||||
use IO\Filesystem\File;
 | 
			
		||||
 | 
			
		||||
class ShowInfoArguments
 | 
			
		||||
{
 | 
			
		||||
    use ArgvAccess;
 | 
			
		||||
 | 
			
		||||
    private SplFileInfo $file;
 | 
			
		||||
    private File $file;
 | 
			
		||||
 | 
			
		||||
    public static function createFromGlobals(): self
 | 
			
		||||
    {
 | 
			
		||||
        $arguments = self::getArguments();
 | 
			
		||||
        $arguments = self::getScriptArgs();
 | 
			
		||||
        return new self(array_shift($arguments));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function __construct(?string $file)
 | 
			
		||||
    public function __construct(?string $filepath)
 | 
			
		||||
    {
 | 
			
		||||
        $this->guardUnusableFile($file);
 | 
			
		||||
        $this->file = new SplFileInfo($file);
 | 
			
		||||
        if (is_null($filepath)) {
 | 
			
		||||
            throw new MissingFileArgumentException();
 | 
			
		||||
        }
 | 
			
		||||
        $this->file = File::fromString($filepath);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getFile(): SplFileInfo
 | 
			
		||||
    public function getFile(): File
 | 
			
		||||
    {
 | 
			
		||||
        return $this->file;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private function guardUnusableFile(string $file): void
 | 
			
		||||
    {
 | 
			
		||||
        if (is_null($file)) {
 | 
			
		||||
            throw new MissingFileArgumentException();
 | 
			
		||||
        }
 | 
			
		||||
        if (!file_exists($file)) {
 | 
			
		||||
            throw new FileNotFoundException($file);
 | 
			
		||||
        }
 | 
			
		||||
        if (!is_readable($file)) {
 | 
			
		||||
            throw new FileNotReadableException($file);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -5,16 +5,16 @@ namespace IO\Output;
 | 
			
		||||
use PDF\Document;
 | 
			
		||||
use Symfony\Component\Console\Output\OutputInterface;
 | 
			
		||||
 | 
			
		||||
class DocumentOutput implements Output
 | 
			
		||||
class DocumentDetails implements Output
 | 
			
		||||
{
 | 
			
		||||
    private Document $document;
 | 
			
		||||
 | 
			
		||||
    public function __construct(Document $document)
 | 
			
		||||
    private function __construct(Document $document)
 | 
			
		||||
    {
 | 
			
		||||
        $this->document = $document;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function forDocument(Document $document): self
 | 
			
		||||
    public static function of(Document $document): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self($document);
 | 
			
		||||
    }
 | 
			
		||||
@ -5,17 +5,17 @@ namespace IO\Output;
 | 
			
		||||
use PDF\Document;
 | 
			
		||||
use Symfony\Component\Console\Output\OutputInterface;
 | 
			
		||||
 | 
			
		||||
class DocumentListingOutput implements Output
 | 
			
		||||
class DocumentListing implements Output
 | 
			
		||||
{
 | 
			
		||||
    /** @var Document[] */
 | 
			
		||||
    private iterable $documents;
 | 
			
		||||
 | 
			
		||||
    public function __construct(iterable $documents)
 | 
			
		||||
    private function __construct(iterable $documents)
 | 
			
		||||
    {
 | 
			
		||||
        $this->documents = $documents;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function forDocuments(iterable $documents): self
 | 
			
		||||
    public static function of(iterable $documents): self
 | 
			
		||||
    {
 | 
			
		||||
        return new self($documents);
 | 
			
		||||
    }
 | 
			
		||||
@ -55,10 +55,10 @@ class DocumentListingOutput implements Output
 | 
			
		||||
 | 
			
		||||
        foreach ($this->documents as $document) {
 | 
			
		||||
            $template->addRow([
 | 
			
		||||
                $document->file->getBasename(),
 | 
			
		||||
                $document->file->getInfo()->getBasename(),
 | 
			
		||||
                $document->metadata->title,
 | 
			
		||||
                $document->metadata->author,
 | 
			
		||||
                $document->file->getPath(),
 | 
			
		||||
                $document->file->getInfo()->getPath(),
 | 
			
		||||
            ]);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -13,13 +13,11 @@ class Pdfinfo
 | 
			
		||||
        $lines = $this->shellExec('pdfinfo', '-isodates', $filepath);
 | 
			
		||||
 | 
			
		||||
        $data = [];
 | 
			
		||||
        foreach ($lines as $line) {
 | 
			
		||||
            $parts = explode(':', $line, 2);
 | 
			
		||||
            if (count($parts) === 2) {
 | 
			
		||||
                $data[trim($parts[0])] = trim($parts[1]);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        collect($lines)
 | 
			
		||||
            ->map(fn(string $line) => explode(':', $line, 2))
 | 
			
		||||
            ->filter(fn(array $parts) => count($parts) === 2)
 | 
			
		||||
            ->each(fn(array $parts) => $data[trim($parts[0])] = trim($parts[1]));
 | 
			
		||||
 | 
			
		||||
        return (new Metadata)->fillWith($data);
 | 
			
		||||
        return Metadata::fill($data);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -2,28 +2,28 @@
 | 
			
		||||
 | 
			
		||||
namespace PDF;
 | 
			
		||||
 | 
			
		||||
use IO\Filesystem\File;
 | 
			
		||||
use RuntimeException;
 | 
			
		||||
use SplFileInfo;
 | 
			
		||||
 | 
			
		||||
class Document
 | 
			
		||||
{
 | 
			
		||||
    public SplFileInfo $file;
 | 
			
		||||
    public File $file;
 | 
			
		||||
    public Metadata $metadata;
 | 
			
		||||
 | 
			
		||||
    public function __construct(SplFileInfo $file, ?Metadata $metadata = null)
 | 
			
		||||
    public function __construct(File $file, Metadata $metadata)
 | 
			
		||||
    {
 | 
			
		||||
        $this->file = $file;
 | 
			
		||||
        $this->metadata = $metadata ?? new Metadata();
 | 
			
		||||
        $this->metadata = $metadata;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function getProperty(string $prop): ?string
 | 
			
		||||
    {
 | 
			
		||||
        if (in_array($prop, ['path', 'filepath'])) {
 | 
			
		||||
            return $this->file->getPath();
 | 
			
		||||
            return (string)$this->file;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (in_array($prop, ['file', 'name', 'filename'])) {
 | 
			
		||||
            return $this->file->getBasename();
 | 
			
		||||
            return $this->file->getInfo()->getBasename();
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if (property_exists($this->metadata, $prop)) {
 | 
			
		||||
@ -35,9 +35,11 @@ class Document
 | 
			
		||||
 | 
			
		||||
    public function getProperties(): array
 | 
			
		||||
    {
 | 
			
		||||
        $info = $this->file->getInfo();
 | 
			
		||||
 | 
			
		||||
        return [
 | 
			
		||||
                'filepath' => $this->file->getPath(),
 | 
			
		||||
                'filename' => $this->file->getBasename(),
 | 
			
		||||
                'filepath' => $info->getPath(),
 | 
			
		||||
                'filename' => $info->getBasename(),
 | 
			
		||||
            ] + $this->metadata->toArray();
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -31,19 +31,28 @@ class Metadata
 | 
			
		||||
    public ?string $title = null;
 | 
			
		||||
    public ?string $userproperties = null;
 | 
			
		||||
 | 
			
		||||
    public function fillWith(array $array): Metadata
 | 
			
		||||
    private function __construct(array $array)
 | 
			
		||||
    {
 | 
			
		||||
        $slugify = new Slugify(['separator' => '_']);
 | 
			
		||||
        $notEmpty = static fn(string $v) => trim($v) !== '';
 | 
			
		||||
 | 
			
		||||
        $array = array_filter($array, static fn(string $v) => trim($v) !== '');
 | 
			
		||||
        $array = array_filter($array, $notEmpty);
 | 
			
		||||
        foreach ($array as $key => $value) {
 | 
			
		||||
            $key = $slugify->slugify($key);
 | 
			
		||||
            if (property_exists(__CLASS__, $key)) {
 | 
			
		||||
                $this->{$key} = trim($value);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        return $this;
 | 
			
		||||
    public static function fill(array $data): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        return new self($data);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public static function empty(): Metadata
 | 
			
		||||
    {
 | 
			
		||||
        return new self([]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function toArray(): array
 | 
			
		||||
 | 
			
		||||
@ -25,7 +25,7 @@ class RecursiveDocumentLocator
 | 
			
		||||
        $documents = [];
 | 
			
		||||
        foreach ($iterator as $file) {
 | 
			
		||||
            if ($this->validate($file)) {
 | 
			
		||||
                $documents[] = $this->documentFactory->createDocument($file);
 | 
			
		||||
                $documents[] = $this->documentFactory->fromFile($file);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										29
									
								
								tests/ExampleDocumentAccess.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								tests/ExampleDocumentAccess.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,29 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
use IO\Filesystem\File;
 | 
			
		||||
use PDF\Document;
 | 
			
		||||
use PDF\Metadata;
 | 
			
		||||
 | 
			
		||||
trait ExampleDocumentAccess
 | 
			
		||||
{
 | 
			
		||||
    protected function exampleDocument(?Metadata $metadata = null): Document
 | 
			
		||||
    {
 | 
			
		||||
        return new Document(
 | 
			
		||||
            $this->exampleFile(),
 | 
			
		||||
            $metadata ?? Metadata::empty()
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function exampleDocumentWithMetadata(array $data): Document
 | 
			
		||||
    {
 | 
			
		||||
        return new Document(
 | 
			
		||||
            $this->exampleFile(),
 | 
			
		||||
            Metadata::fill($data)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected function exampleFile(): File
 | 
			
		||||
    {
 | 
			
		||||
        return File::fromString(__DIR__ . '/resources/example.pdf');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								tests/Filter/FilterParserTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								tests/Filter/FilterParserTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,47 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Filter;
 | 
			
		||||
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use TypeError;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @covers \Filter\FilterParser
 | 
			
		||||
 */
 | 
			
		||||
class FilterParserTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    private FilterParser $parser;
 | 
			
		||||
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->parser = new FilterParser();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testCreatesSpecificFilters(): void
 | 
			
		||||
    {
 | 
			
		||||
        self::assertContainsOnlyInstancesOf(SpecificFilter::class, [
 | 
			
		||||
            $this->parser->parse('title=poo'),
 | 
			
		||||
            $this->parser->parse('1='),
 | 
			
		||||
            $this->parser->parse('filepath=some_thing_else'),
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        self::assertNotInstanceOf(SpecificFilter::class, $this->parser->parse('='));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testCreatesGenericFilters(): void
 | 
			
		||||
    {
 | 
			
		||||
        // Basically, anything that does not match the pattern .+=.*
 | 
			
		||||
        self::assertContainsOnlyInstancesOf(GenericFilter::class, [
 | 
			
		||||
            $this->parser->parse('scoobypoo'),
 | 
			
		||||
            $this->parser->parse('324234'),
 | 
			
		||||
            $this->parser->parse(324234),
 | 
			
		||||
            $this->parser->parse('baz'),
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDoesNotTakeNull(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->expectException(TypeError::class);
 | 
			
		||||
        $this->parser->parse(null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										84
									
								
								tests/Filter/GenericFilterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										84
									
								
								tests/Filter/GenericFilterTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,84 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Filter;
 | 
			
		||||
 | 
			
		||||
use ExampleDocumentAccess;
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
use TypeError;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @covers \Filter\GenericFilter
 | 
			
		||||
 */
 | 
			
		||||
class GenericFilterTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    use ExampleDocumentAccess;
 | 
			
		||||
 | 
			
		||||
    public function testAllowsWhenMatchBasenameOfFile(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('example');
 | 
			
		||||
        $document = $this->exampleDocument();
 | 
			
		||||
 | 
			
		||||
        self::assertTrue($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAllowsWhenMatchesPathToFile(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('resources');
 | 
			
		||||
        $document = $this->exampleDocument();
 | 
			
		||||
 | 
			
		||||
        self::assertTrue($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testAllowsWhenMatchesAnyMetadataField(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('John Snow');
 | 
			
		||||
        $document = $this->exampleDocumentWithMetadata([
 | 
			
		||||
            'title'  => 'some title',
 | 
			
		||||
            'author' => 'John Snow',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        self::assertTrue($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDisallowsWhenNotMatchingBasenameOfFile(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('not-an-example');
 | 
			
		||||
        $document = $this->exampleDocument();
 | 
			
		||||
 | 
			
		||||
        self::assertFalse($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDisallowsWhenNotMatchingPathToFile(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('this-is-definitely-not-in-the-path' . md5(time()));
 | 
			
		||||
        $document = $this->exampleDocument();
 | 
			
		||||
 | 
			
		||||
        self::assertFalse($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDisallowsWhenNotMatchingAnyMetadataField(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('peepee');
 | 
			
		||||
        $document = $this->exampleDocumentWithMetadata([
 | 
			
		||||
            'title'  => 'some title',
 | 
			
		||||
            'author' => 'John Snow',
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        self::assertFalse($filter->allows($document));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testStringRepresentation(): void
 | 
			
		||||
    {
 | 
			
		||||
        $filter = new GenericFilter('i-am-a-field');
 | 
			
		||||
        self::assertEquals(
 | 
			
		||||
            '[*] contains \'i-am-a-field\'',
 | 
			
		||||
            (string)$filter
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    public function testDoesNotAcceptNull(): void
 | 
			
		||||
    {
 | 
			
		||||
        $this->expectException(TypeError::class);
 | 
			
		||||
        new GenericFilter(null);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								tests/Filter/SpecificFilterTest.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								tests/Filter/SpecificFilterTest.php
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
namespace Filter;
 | 
			
		||||
 | 
			
		||||
use PHPUnit\Framework\TestCase;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * @covers \Filter\GenericFilter
 | 
			
		||||
 */
 | 
			
		||||
class SpecificFilterTest extends TestCase
 | 
			
		||||
{
 | 
			
		||||
    protected function setUp(): void
 | 
			
		||||
    {
 | 
			
		||||
        self::markTestIncomplete('Not implemented');
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										0
									
								
								tests/resources/example.pdf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								tests/resources/example.pdf
									
									
									
									
									
										Normal file
									
								
							
		Loading…
	
		Reference in New Issue
	
	Block a user