Initial commit

This commit is contained in:
2021-03-23 21:58:40 +01:00
commit 61623f5a35
30 changed files with 1936 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
<?php
namespace IO\Exception;
class DirectoryNotFoundException extends IOException
{
public function __construct(string $directory)
{
parent::__construct(sprintf('Directory \'%s\' not found', $directory));
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace IO\Exception;
class FileNotFoundException extends IOException
{
public function __construct(string $file)
{
parent::__construct(sprintf('File \'%s\' not found', $file));
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace IO\Exception;
class FileNotReadableException extends IOException
{
public function __construct(string $file)
{
parent::__construct(sprintf('File \'%s\' is not readable', $file));
}
}

View File

@@ -0,0 +1,9 @@
<?php
namespace IO\Exception;
use RuntimeException;
abstract class IOException extends RuntimeException
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace IO\Exception;
class MissingFileArgumentException extends IOException
{
public function __construct()
{
parent::__construct('Missing file argument');
}
}

View File

@@ -0,0 +1,11 @@
<?php
namespace IO\Exception;
class NotADirectoryException extends IOException
{
public function __construct(string $directory)
{
parent::__construct(sprintf('Argument \'%s\' is not a directory', $directory));
}
}

View File

@@ -0,0 +1,24 @@
<?php
namespace IO;
use Throwable;
class ExceptionHandler
{
private static bool $registered = false;
public static function registerCallback(): void
{
if (self::$registered) {
return;
}
set_exception_handler(static function (Throwable $t) {
print($t->getMessage());
exit(1);
});
self::$registered = true;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace IO\Input;
trait ArgvAccess
{
protected static function getArguments(): array
{
// Get local copy of $argv
global $argv;
$arguments = $argv;
// Lose the script name
array_shift($arguments);
return $arguments;
}
}

View File

@@ -0,0 +1,55 @@
<?php
namespace IO\Input;
use Filter\DocumentFilter;
use Filter\FilterFactory;
use IO\Exception\DirectoryNotFoundException;
use IO\Exception\NotADirectoryException;
class FinderArguments
{
use ArgvAccess;
private ?string $directory;
private array $filters;
public function __construct(?string $directory, array $filters)
{
$this->directory = $directory;
$this->filters = $filters;
$factory = new FilterFactory();
$this->filters = array_map([$factory, 'createFromString'], $this->filters);
}
public static function createFromGlobals(): self
{
$arguments = self::getArguments();
$dir = array_shift($arguments) ?? getcwd();
$dir = rtrim($dir, DIRECTORY_SEPARATOR);
return new self($dir, $arguments);
}
public function getDirectory(): string
{
if (!file_exists($this->directory)) {
throw new DirectoryNotFoundException($this->directory);
}
if (!is_dir($this->directory)) {
throw new NotADirectoryException($this->directory);
}
return $this->directory;
}
/**
* @return DocumentFilter[]
*/
public function getFilters(): array
{
return $this->filters;
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace IO\Input;
use IO\Exception\FileNotFoundException;
use IO\Exception\FileNotReadableException;
use IO\Exception\MissingFileArgumentException;
use SplFileInfo;
class ShowInfoArguments
{
use ArgvAccess;
private ?string $file;
public function __construct(?string $file)
{
$this->file = $file;
}
public static function createFromGlobals(): self
{
$arguments = self::getArguments();
return new self(array_shift($arguments));
}
public function getFile(): SplFileInfo
{
if (is_null($this->file)) {
throw new MissingFileArgumentException();
}
if (!file_exists($this->file)) {
throw new FileNotFoundException($this->file);
}
if (!is_readable($this->file)) {
throw new FileNotReadableException($this->file);
}
return new SplFileInfo($this->file);
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace IO\Output;
use PDF\Document;
use Symfony\Component\Console\Output\OutputInterface;
class DocumentListingOutput implements Output
{
/** @var Document[] */
private iterable $documents;
public function __construct(iterable $documents)
{
$this->documents = $documents;
}
public static function forDocuments(iterable $documents): self
{
return new self($documents);
}
public function render(?OutputInterface $output = null): void
{
if (count($this->documents) === 0) {
print('Your search yielded no results.' . PHP_EOL);
return;
}
$template = new TableTemplate([
'Filename' => [
'min_width' => 40,
'max_width' => 80,
],
'Title' => [
'min_width' => 40,
'max_width' => 80,
'null_value' => '-',
],
'Author' => [
'min_width' => 16,
'max_width' => 32,
'null_value' => '-',
],
'Path' => [
'min_width' => 16,
'max_width' => 32,
'formatter' => static function (string $path) {
$search = sprintf('/home/%s', get_current_user());
return str_replace($search, '~', $path);
},
],
]);
foreach ($this->documents as $document) {
$template->addRow([
$document->file->getBasename(),
$document->metadata->title,
$document->metadata->author,
$document->file->getPath(),
]);
}
$template->generate($output)->render();
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace IO\Output;
use PDF\Document;
use Symfony\Component\Console\Output\OutputInterface;
class DocumentOutput implements Output
{
private Document $document;
public function __construct(Document $document)
{
$this->document = $document;
}
public static function forDocument(Document $document): self
{
return new self($document);
}
public function render(?OutputInterface $output = null): void
{
$template = new TableTemplate([
'Property' => [
'min_width' => 20,
'max_width' => 20,
],
'Value' => [
'min_width' => 80,
'max_width' => 80,
'null_value' => '-',
],
]);
foreach ($this->document->getProperties() as $property => $value) {
$template->addRow([$property, $value]);
}
$template->generate($output)->render();
}
}

8
src/IO/Output/Output.php Normal file
View File

@@ -0,0 +1,8 @@
<?php
namespace IO\Output;
interface Output
{
public function render(): void;
}

View File

@@ -0,0 +1,74 @@
<?php
namespace IO\Output;
use Symfony\Component\Console\Helper\Table;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Output\OutputInterface;
class TableTemplate
{
private array $headers;
private array $properties;
private array $rows = [];
public function __construct(array $properties)
{
$this->headers = array_keys($properties);
$this->properties = array_values($properties);
}
public function addRow(array $row): void
{
$row = array_values($row);
foreach ($row as $columnIndex => &$value) {
if (isset($this->properties[$columnIndex]['null_value'])) {
$value ??= $this->properties[$columnIndex]['null_value'];
}
if (isset($this->properties[$columnIndex]['formatter'])) {
$value = call_user_func($this->properties[$columnIndex]['formatter'], $value);
}
if (isset($this->properties[$columnIndex]['max_width'])) {
$value = $this->trim($value, $this->properties[$columnIndex]['max_width']);
}
}
unset($value);
$this->rows[] = $row;
}
public function generate(?OutputInterface $output = null): Table
{
$table = new Table($output ?? new ConsoleOutput());
$table->setStyle('box-double');
$table->setHeaders($this->headers);
foreach ($this->properties as $columnIndex => $columnProperties) {
if (isset($columnProperties['min_width'])) {
$table->setColumnWidth($columnIndex, $columnProperties['min_width']);
}
if (isset($columnProperties['max_width'])) {
$table->setColumnMaxWidth($columnIndex, $columnProperties['max_width']);
}
}
$table->setRows($this->rows);
return $table;
}
/**
* Trims a string if it's longer than $length and adds '...' to the end if trimmed.
* @param string $string
* @param int $length
* @return string
*/
private function trim(string $string, int $length): string
{
if (strlen($string) <= $length) {
return $string;
}
return '' . substr($string, 0, $length - 3) . '...';
}
}

25
src/IO/Shell/Pdfinfo.php Normal file
View File

@@ -0,0 +1,25 @@
<?php
namespace IO\Shell;
use PDF\Metadata;
class Pdfinfo
{
use ShellCommandExecutor;
public function getMetadata(string $filepath): Metadata
{
$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]);
}
}
return (new Metadata)->fillWith($data);
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace IO\Shell;
trait ShellCommandExecutor
{
protected function shellExec(string $command, string ...$args): array
{
$args = array_map('escapeshellarg', $args);
$output = shell_exec(sprintf(
'%s %s 2>/dev/null',
escapeshellcmd($command),
implode(' ', $args)
));
return explode(PHP_EOL, $output);
}
}