Initial commit
This commit is contained in:
11
src/IO/Exception/DirectoryNotFoundException.php
Normal file
11
src/IO/Exception/DirectoryNotFoundException.php
Normal 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));
|
||||
}
|
||||
}
|
||||
11
src/IO/Exception/FileNotFoundException.php
Normal file
11
src/IO/Exception/FileNotFoundException.php
Normal 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));
|
||||
}
|
||||
}
|
||||
11
src/IO/Exception/FileNotReadableException.php
Normal file
11
src/IO/Exception/FileNotReadableException.php
Normal 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));
|
||||
}
|
||||
}
|
||||
9
src/IO/Exception/IOException.php
Normal file
9
src/IO/Exception/IOException.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
namespace IO\Exception;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
abstract class IOException extends RuntimeException
|
||||
{
|
||||
}
|
||||
12
src/IO/Exception/MissingFileArgumentException.php
Normal file
12
src/IO/Exception/MissingFileArgumentException.php
Normal file
@@ -0,0 +1,12 @@
|
||||
<?php
|
||||
|
||||
namespace IO\Exception;
|
||||
|
||||
|
||||
class MissingFileArgumentException extends IOException
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('Missing file argument');
|
||||
}
|
||||
}
|
||||
11
src/IO/Exception/NotADirectoryException.php
Normal file
11
src/IO/Exception/NotADirectoryException.php
Normal 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));
|
||||
}
|
||||
}
|
||||
24
src/IO/ExceptionHandler.php
Normal file
24
src/IO/ExceptionHandler.php
Normal 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;
|
||||
}
|
||||
}
|
||||
18
src/IO/Input/ArgvAccess.php
Normal file
18
src/IO/Input/ArgvAccess.php
Normal 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;
|
||||
}
|
||||
}
|
||||
55
src/IO/Input/FinderArguments.php
Normal file
55
src/IO/Input/FinderArguments.php
Normal 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;
|
||||
}
|
||||
}
|
||||
41
src/IO/Input/ShowInfoArguments.php
Normal file
41
src/IO/Input/ShowInfoArguments.php
Normal 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);
|
||||
}
|
||||
}
|
||||
67
src/IO/Output/DocumentListingOutput.php
Normal file
67
src/IO/Output/DocumentListingOutput.php
Normal 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();
|
||||
}
|
||||
}
|
||||
42
src/IO/Output/DocumentOutput.php
Normal file
42
src/IO/Output/DocumentOutput.php
Normal 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
8
src/IO/Output/Output.php
Normal file
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace IO\Output;
|
||||
|
||||
interface Output
|
||||
{
|
||||
public function render(): void;
|
||||
}
|
||||
74
src/IO/Output/TableTemplate.php
Normal file
74
src/IO/Output/TableTemplate.php
Normal 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
25
src/IO/Shell/Pdfinfo.php
Normal 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);
|
||||
}
|
||||
}
|
||||
19
src/IO/Shell/ShellCommandExecutor.php
Normal file
19
src/IO/Shell/ShellCommandExecutor.php
Normal 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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user