modifs.mineures sans commentaires
This commit is contained in:
parent
77c90f02a4
commit
bc115a3e6c
|
@ -3,6 +3,7 @@ namespace nur\ref;
|
||||||
|
|
||||||
use nur\data\types\Metadata;
|
use nur\data\types\Metadata;
|
||||||
use nur\md;
|
use nur\md;
|
||||||
|
use nur\sery\php\content\content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class ref_type: référence des types utilisables dans les schémas
|
* Class ref_type: référence des types utilisables dans les schémas
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class EOFException: indiquer que plus aucune donnée n'est disponible sur un
|
||||||
|
* flux
|
||||||
|
*/
|
||||||
|
class EOFException extends IOException {
|
||||||
|
static final function no_more_data(): self {
|
||||||
|
return new self("no more data");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function ensure_not_eof($data, bool $throw=true, $eof=false) {
|
||||||
|
if (!$throw) return null;
|
||||||
|
elseif ($data !== $eof) return $data;
|
||||||
|
else throw self::no_more_data();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class IOException: erreur sur un flux ou un système de fichiers
|
||||||
|
*/
|
||||||
|
class IOException extends RuntimeException {
|
||||||
|
static final function last_error(?string $prefix=null): ?self {
|
||||||
|
$error = error_get_last();
|
||||||
|
if ($error === null) return null;
|
||||||
|
$message = $error["message"];
|
||||||
|
if ($prefix) $message = "$prefix: $message";
|
||||||
|
return new static($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function generic_error(?string $message=null): self {
|
||||||
|
if ($message === null) $message = "generic error";
|
||||||
|
return new static($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function error(?string $prefix=null): self {
|
||||||
|
$exception = self::last_error($prefix);
|
||||||
|
if ($exception !== null) throw $exception;
|
||||||
|
else throw self::generic_error($prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function ensure_value($value, bool $throw=true, $invalid=false) {
|
||||||
|
if (!$throw) return null;
|
||||||
|
elseif ($value !== $invalid) return $value;
|
||||||
|
else throw self::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function already_closed(): self {
|
||||||
|
return new static("already closed");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function ensure_open(bool $closed): void {
|
||||||
|
if ($closed) throw self::already_closed();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,41 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FileReader: un fichier accédé en lecture
|
||||||
|
*/
|
||||||
|
class FileReader extends Stream {
|
||||||
|
const DEFAULT_MODE = "rb";
|
||||||
|
|
||||||
|
function __construct($input, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||||
|
if ($input === null) {
|
||||||
|
$fd = STDIN;
|
||||||
|
$close = false;
|
||||||
|
} elseif (is_resource($input)) {
|
||||||
|
$fd = $input;
|
||||||
|
$close = false;
|
||||||
|
} else {
|
||||||
|
if ($mode === null) $mode = static::DEFAULT_MODE;
|
||||||
|
$this->file = $input;
|
||||||
|
$this->mode = $mode;
|
||||||
|
$fd = null;
|
||||||
|
$close = true;
|
||||||
|
}
|
||||||
|
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $file;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $mode;
|
||||||
|
|
||||||
|
function getResource() {
|
||||||
|
if ($this->fd === null && $this->file !== null) {
|
||||||
|
$this->fd = IOException::ensure_value(@fopen($this->file, $this->mode));
|
||||||
|
}
|
||||||
|
return parent::getResource();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
use nur\sery\os\sh;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class FileWriter: un fichier accédé en lecture/écriture
|
||||||
|
*/
|
||||||
|
class FileWriter extends Stream {
|
||||||
|
const DEFAULT_MODE = "a+b";
|
||||||
|
|
||||||
|
function __construct($output, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||||
|
if ($output === null) {
|
||||||
|
$fd = STDOUT;
|
||||||
|
$close = false;
|
||||||
|
} elseif (is_resource($output)) {
|
||||||
|
$fd = $output;
|
||||||
|
$close = false;
|
||||||
|
} else {
|
||||||
|
if ($mode === null) $mode = static::DEFAULT_MODE;
|
||||||
|
$this->file = $output;
|
||||||
|
$this->mode = $mode;
|
||||||
|
$fd = null;
|
||||||
|
$close = true;
|
||||||
|
}
|
||||||
|
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $file;
|
||||||
|
|
||||||
|
/** @var string */
|
||||||
|
protected $mode;
|
||||||
|
|
||||||
|
function getResource() {
|
||||||
|
if ($this->fd === null && $this->file !== null) {
|
||||||
|
IOException::ensure_value(sh::mkdirof($this->file));
|
||||||
|
$this->fd = IOException::ensure_value(@fopen($this->file, $this->mode));
|
||||||
|
}
|
||||||
|
return parent::getResource();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\sery\os\EOFException;
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface IReader: un objet depuis lequel on peut lire des données
|
||||||
|
*/
|
||||||
|
interface IReader extends _IFile {
|
||||||
|
/** @throws IOException */
|
||||||
|
function fread(int $length): string;
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fgets(): string;
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fpassthru(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lire la prochaine ligne. la ligne est retournée *sans* le caractère de fin
|
||||||
|
* de ligne [\r]\n
|
||||||
|
*
|
||||||
|
* @throws EOFException si plus aucune ligne n'est disponible
|
||||||
|
* @throws IOException si une autre erreur se produit
|
||||||
|
*/
|
||||||
|
function readLine(): ?string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* essayer de verrouiller le fichier en lecture. retourner true si l'opération
|
||||||
|
* réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
function canRead(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||||
|
* fichier.
|
||||||
|
*/
|
||||||
|
function getReader(bool $lockedByCanRead=false): IReader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lire tout le contenu du fichier en une seule fois, puis, si $close==true,
|
||||||
|
* le fermer
|
||||||
|
*
|
||||||
|
* @throws IOException si une erreur se produit
|
||||||
|
*/
|
||||||
|
function getContents(bool $close=true, bool $lockedByCanRead=false): string;
|
||||||
|
|
||||||
|
/** désérialiser le contenu du fichier, puis, si $close===true, le fermer */
|
||||||
|
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false);
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface IWriter: un objet dans lequel on peut écrire des données
|
||||||
|
*/
|
||||||
|
interface IWriter extends _IFile {
|
||||||
|
/** @throws IOException */
|
||||||
|
function fwrite(string $data, int $length=0): int;
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fflush(): void;
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function ftruncate(int $size): void;
|
||||||
|
|
||||||
|
/** afficher les lignes */
|
||||||
|
function writeLines(?iterable $lines): self;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* essayer de verrouiller le fichier en écriture. retourner true si l'opération
|
||||||
|
* réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
function canWrite(): bool;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||||
|
* dans le fichier
|
||||||
|
*/
|
||||||
|
function getWriter(bool $lockedByCanWrite=false): self;
|
||||||
|
|
||||||
|
/** écrire le contenu spécifié dans le fichier */
|
||||||
|
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void;
|
||||||
|
|
||||||
|
/** sérialiser l'objet dans la destination */
|
||||||
|
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void;
|
||||||
|
}
|
|
@ -0,0 +1,15 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nulib\ValueException;
|
||||||
|
|
||||||
|
class SharedFile extends FileWriter {
|
||||||
|
const ALLOW_LOCKING = true;
|
||||||
|
|
||||||
|
const DEFAULT_MODE = "c+b";
|
||||||
|
|
||||||
|
function __construct($file, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||||
|
if ($file === null) throw ValueException::null("file");
|
||||||
|
parent::__construct($file, $mode, $throwOnError, $allowLocking);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,286 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nulib\str;
|
||||||
|
use nulib\ValueException;
|
||||||
|
use nur\sery\os\EOFException;
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
use nur\sery\php\iter\AbstractIterator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class Stream: lecture/écriture générique dans un flux
|
||||||
|
*/
|
||||||
|
class Stream extends AbstractIterator implements IReader, IWriter {
|
||||||
|
use TStreamFilter;
|
||||||
|
|
||||||
|
/** @var bool les opérations de verrouillages sont-elle activées? */
|
||||||
|
const ALLOW_LOCKING = false;
|
||||||
|
|
||||||
|
/** @var resource */
|
||||||
|
protected $fd;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $close;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $throwOnError;
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $allowLocking;
|
||||||
|
|
||||||
|
/** @var int|null */
|
||||||
|
protected $serial;
|
||||||
|
|
||||||
|
/** @var array */
|
||||||
|
protected $stat;
|
||||||
|
|
||||||
|
function __construct($fd, bool $close=true, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||||
|
if ($fd === null) throw ValueException::null("resource");
|
||||||
|
$this->fd = $fd;
|
||||||
|
$this->close = $close;
|
||||||
|
$this->throwOnError = $throwOnError;
|
||||||
|
if ($allowLocking === null) $allowLocking = static::ALLOW_LOCKING;
|
||||||
|
$this->allowLocking = $allowLocking;
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# File
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return resource|null retourner la resource associée à ce fichier si cela
|
||||||
|
* a du sens
|
||||||
|
*/
|
||||||
|
function getResource() {
|
||||||
|
IOException::ensure_open($this->fd === null);
|
||||||
|
$this->_streamAppendFilters($this->fd);
|
||||||
|
return $this->fd;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function lock(int $operation, ?int &$wouldBlock=null): bool {
|
||||||
|
$locked = flock($this->getResource(), $operation, $wouldBlock);
|
||||||
|
if ($locked) return true;
|
||||||
|
if ($operation & LOCK_NB) return false;
|
||||||
|
else throw IOException::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function unlock(bool $close=false): void {
|
||||||
|
if ($this->fd !== null) {
|
||||||
|
flock($this->fd, LOCK_UN);
|
||||||
|
if ($close) $this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isatty(): bool {
|
||||||
|
return stream_isatty($this->getResource());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir des informations sur le fichier */
|
||||||
|
function fstat(bool $reload=false): array {
|
||||||
|
if ($this->stat === null || $reload) {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
$this->stat = IOException::ensure_value(fstat($fd), $this->throwOnError);
|
||||||
|
}
|
||||||
|
return $this->stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function ftell(): int {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
return IOException::ensure_value(ftell($fd), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int la position après avoir déplacé le pointeur
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
function fseek(int $offset, int $whence=SEEK_SET): int {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
IOException::ensure_value(fseek($fd, $offset, $whence), $this->throwOnError, -1);
|
||||||
|
return $this->ftell();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** fermer le fichier si c'est nécessaire */
|
||||||
|
function close(bool $close=true, ?int $ifSerial=null): void {
|
||||||
|
AbstractIterator::rewind();
|
||||||
|
if ($this->fd !== null && $close && $this->close && ($ifSerial === null || $this->serial === $ifSerial)) {
|
||||||
|
fclose($this->fd);
|
||||||
|
$this->fd = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Reader
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fread(int $length): string {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
return IOException::ensure_value(fread($fd, $length), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fgets(?int $length=null): string {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
return EOFException::ensure_not_eof(fgets($fd, $length), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fpassthru(): int {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
return IOException::ensure_value(fpassthru($fd), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function readLine(): ?string {
|
||||||
|
return str::strip_nl($this->fgets());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* essayer de verrouiller le fichier en lecture. retourner true si l'opération
|
||||||
|
* réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
function canRead(): bool {
|
||||||
|
if ($this->allowLocking) return $this->lock(LOCK_SH + LOCK_NB);
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verrouiller en mode partagé puis retourner un objet permettant de lire le
|
||||||
|
* fichier.
|
||||||
|
*/
|
||||||
|
function getReader(bool $lockedByCanRead=false): IReader {
|
||||||
|
if ($this->allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
||||||
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||||
|
function __construct($fd, int $serial, Stream $parent) {
|
||||||
|
$this->parent = $parent;
|
||||||
|
parent::__construct($fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Stream */
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
function close(bool $close=true): void {
|
||||||
|
if ($this->parent !== null && $close) {
|
||||||
|
$this->parent->close(true, $this->serial);
|
||||||
|
$this->fd = null;
|
||||||
|
$this->parent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** retourner le contenu du fichier sous forme de chaine */
|
||||||
|
function getContents(bool $close=true, bool $lockedByCanRead=false): string {
|
||||||
|
$allowLocking = $this->allowLocking;
|
||||||
|
if ($allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
||||||
|
try {
|
||||||
|
return IOException::ensure_value(stream_get_contents($this->fd), $this->throwOnError);
|
||||||
|
} finally {
|
||||||
|
if ($allowLocking) $this->unlock($close);
|
||||||
|
elseif ($close) $this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) {
|
||||||
|
$args = [$this->getContents($lockedByCanRead)];
|
||||||
|
if ($options !== null) $args[] = $options;
|
||||||
|
return unserialize(...$args);
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Iterator
|
||||||
|
|
||||||
|
protected function _setup(): void {
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _next(&$key) {
|
||||||
|
return $this->fgets();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function _teardown(): void {
|
||||||
|
$this->fseek(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Writer
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fwrite(string $data, ?int $length=null): int {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
if ($length === null) $r = fwrite($fd, $data);
|
||||||
|
else $r = fwrite($fd, $data, $length);
|
||||||
|
return IOException::ensure_value($r, $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function fflush(): void {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
IOException::ensure_value(fflush($fd), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @throws IOException */
|
||||||
|
function ftruncate(int $size): void {
|
||||||
|
$fd = $this->getResource();
|
||||||
|
IOException::ensure_value(ftruncate($fd, $size), $this->throwOnError);
|
||||||
|
}
|
||||||
|
|
||||||
|
function writeLines(?iterable $lines): IWriter {
|
||||||
|
if ($lines !== null) {
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$this->fwrite($line);
|
||||||
|
$this->fwrite("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* essayer de verrouiller le fichier en écriture. retourner true si l'opération
|
||||||
|
* réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument
|
||||||
|
* true
|
||||||
|
*/
|
||||||
|
function canWrite(): bool {
|
||||||
|
if ($this->allowLocking) return $this->lock(LOCK_EX + LOCK_NB);
|
||||||
|
else return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
|
||||||
|
* dans le fichier
|
||||||
|
*/
|
||||||
|
function getWriter(bool $lockedByCanWrite=false): IWriter {
|
||||||
|
if ($this->allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
||||||
|
return new class($this->fd, ++$this->serial, $this) extends Stream {
|
||||||
|
function __construct($fd, int $serial, Stream $parent) {
|
||||||
|
$this->parent = $parent;
|
||||||
|
$this->serial = $serial;
|
||||||
|
parent::__construct($fd);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var Stream */
|
||||||
|
private $parent;
|
||||||
|
|
||||||
|
function close(bool $close=true): void {
|
||||||
|
if ($this->parent !== null && $close) {
|
||||||
|
$this->parent->close(true, $this->serial);
|
||||||
|
$this->fd = null;
|
||||||
|
$this->parent = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void {
|
||||||
|
$allowLocking = $this->allowLocking;
|
||||||
|
if ($allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
||||||
|
try {
|
||||||
|
$this->fwrite($contents);
|
||||||
|
} finally {
|
||||||
|
if ($allowLocking) $this->unlock($close);
|
||||||
|
elseif ($close) $this->close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void {
|
||||||
|
$this->putContents(serialize($object), $lockedByCanWrite);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,49 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\A;
|
||||||
|
use nur\b\io\IOException;
|
||||||
|
|
||||||
|
trait TStreamFilter {
|
||||||
|
private $filters = null;
|
||||||
|
|
||||||
|
function appendFilter(string $filterName, ?int $readWrite=null, $params=null): void {
|
||||||
|
A::append($this->filters, [$filterName, $readWrite, $params]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function prependFilter(string $filterName, ?int $readWrite=null, $params=null): void {
|
||||||
|
A::prepend($this->filters, [$filterName, $readWrite, $params]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function setEncodingFilter(string $from, string $to): void {
|
||||||
|
if ($to !== $from) {
|
||||||
|
$this->appendFilter("convert.iconv.$from.$to");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $fd resource
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
protected function _streamAppendFilters($fd): void {
|
||||||
|
if ($this->filters !== null) {
|
||||||
|
foreach ($this->filters as [$filterName, $readWrite, $params]) {
|
||||||
|
if (stream_filter_append($fd, $filterName, $readWrite, $params) === false) {
|
||||||
|
throw new IOException("unable to add filter $filterName");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->filters = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $file _IFile
|
||||||
|
*/
|
||||||
|
protected function _appendFilters($file): void {
|
||||||
|
if ($this->filters !== null) {
|
||||||
|
foreach ($this->filters as [$filterName, $readWrite, $params]) {
|
||||||
|
$file->appendFilter($filterName, $readWrite, $params);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
use nur\sery\web\http;
|
||||||
|
|
||||||
|
class TmpfileWriter extends FileWriter {
|
||||||
|
const DEFAULT_MODE = "w+b";
|
||||||
|
|
||||||
|
function __construct(?string $destdir=null, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||||
|
if ($destdir === null) $destdir = sys_get_temp_dir();
|
||||||
|
if (is_dir($destdir)) {
|
||||||
|
# si on spécifie un répertoire, créer un fichier temporaire dedans
|
||||||
|
$file = tempnam($destdir, "tmp_nulib_");
|
||||||
|
$this->delete = true;
|
||||||
|
} elseif (is_file($destdir)) {
|
||||||
|
# si on spécifie un fichier qui existe le prendre comme "fichier
|
||||||
|
# temporaire" mais ne pas le supprimer automatiquement
|
||||||
|
$file = $destdir;
|
||||||
|
$this->delete = false;
|
||||||
|
} else {
|
||||||
|
# un chemin qui n'existe pas: ne le sélectionner que si le répertoire
|
||||||
|
# existe. dans ce cas, le fichier sera créé automatiquement, mais pas
|
||||||
|
# supprimé
|
||||||
|
if (!is_dir(dirname($destdir))) {
|
||||||
|
throw new IOException("$destdir: no such file or directory");
|
||||||
|
}
|
||||||
|
$file = $destdir;
|
||||||
|
$this->delete = false;
|
||||||
|
}
|
||||||
|
parent::__construct($file, $mode, $throwOnError, $allowLocking);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var bool */
|
||||||
|
protected $delete;
|
||||||
|
|
||||||
|
function __destruct() {
|
||||||
|
$this->close();
|
||||||
|
if ($this->delete) $this->delete();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** supprimer le fichier. NB: le flux **n'est pas** fermé au préalable */
|
||||||
|
function delete(): self {
|
||||||
|
if (file_exists($this->file)) unlink($this->file);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* renommer le fichier. le flux est fermé d'abord
|
||||||
|
*
|
||||||
|
* @param int|null $defaultMode mode par défaut si le fichier destination
|
||||||
|
* n'existe pas. sinon, changer le mode du fichier temporaire à la valeur du
|
||||||
|
* fichier destination après renommage
|
||||||
|
* @param bool $setOwner si le propriétaire et/ou le groupe du fichier
|
||||||
|
* temporaire ne sont pas les mêmes que le fichier destination, tenter de
|
||||||
|
* changer le propriétaire et le groupe du fichier temporaire à la valeur
|
||||||
|
* du fichier destination après le renommage (nécessite les droits de root)
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
function rename(string $dest, ?int $defaultMode=0644, bool $setOwner=true): void {
|
||||||
|
$this->close();
|
||||||
|
$file = $this->file;
|
||||||
|
if (file_exists($dest)) {
|
||||||
|
$mode = fileperms($dest);
|
||||||
|
if ($setOwner) {
|
||||||
|
$tmpowner = fileowner($file);
|
||||||
|
$owner = fileowner($dest);
|
||||||
|
$tmpgroup = filegroup($file);
|
||||||
|
$group = filegroup($dest);
|
||||||
|
} else {
|
||||||
|
$owner = $group = null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$mode = $defaultMode;
|
||||||
|
$owner = $group = null;
|
||||||
|
}
|
||||||
|
if (!rename($file, $dest)) {
|
||||||
|
throw new IOException("$file: unable to rename to $dest");
|
||||||
|
}
|
||||||
|
$this->file = $dest;
|
||||||
|
if ($mode !== null) chmod($dest, $mode);
|
||||||
|
if ($owner !== null) {
|
||||||
|
if ($owner !== $tmpowner) chown($dest, $owner);
|
||||||
|
if ($group !== $tmpgroup) chgrp($dest, $group);
|
||||||
|
}
|
||||||
|
if ($mode !== null || $owner !== null) clearstatcache(true, $file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** streamer le contenu du fichier en sortie */
|
||||||
|
function readfile(?string $contentType=null, ?string $charset=null, ?string $filename=null, string $disposition=null): bool {
|
||||||
|
if ($contentType !== null) http::content_type($contentType, $charset);
|
||||||
|
if ($filename !== null) http::download_as($filename, $disposition);
|
||||||
|
return readfile($this->file) !== false;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os\file;
|
||||||
|
|
||||||
|
use Iterator;
|
||||||
|
use nur\sery\os\IOException;
|
||||||
|
use nur\sery\php\ICloseable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface _IFile: méthodes communes entre {@link IReader} et {@link IWriter}
|
||||||
|
*/
|
||||||
|
interface _IFile extends Iterator, ICloseable {
|
||||||
|
/**
|
||||||
|
* @return resource|null retourner la resource associée à ce fichier si cela
|
||||||
|
* a du sens
|
||||||
|
*/
|
||||||
|
function getResource();
|
||||||
|
|
||||||
|
/** si ce fichier est basé sur une resource, ajouter un filtre. */
|
||||||
|
function appendFilter(string $filterName, ?int $readWrite=null, $params=null): void;
|
||||||
|
|
||||||
|
/** si ce fichier est basé sur une resource, ajouter un filtre. */
|
||||||
|
function prependFilter(string $filterName, ?int $readWrite=null, $params=null): void;
|
||||||
|
|
||||||
|
/** si ce fichier est basé sur une resource, spécifier l'encoding. */
|
||||||
|
function setEncodingFilter(string $from, string $to): void;
|
||||||
|
|
||||||
|
/** vérifier si la resource est associée à un terminal */
|
||||||
|
function isatty(): bool;
|
||||||
|
|
||||||
|
/** obtenir des informations sur le fichier */
|
||||||
|
function fstat(): array;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner la position actuelle de lecture/écriture
|
||||||
|
*
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
function ftell(): int;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modifier la position actuelle de lecture/écriture
|
||||||
|
*
|
||||||
|
* @return int la position après avoir déplacé le pointeur
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
function fseek(int $offset, int $whence=SEEK_SET): int;
|
||||||
|
|
||||||
|
/** fermer le fichier si c'est nécessaire */
|
||||||
|
function close(bool $close=true): void;
|
||||||
|
}
|
|
@ -0,0 +1,295 @@
|
||||||
|
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||||
|
namespace nur\sery\os;
|
||||||
|
|
||||||
|
use nulib\str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class path: manipulation de chemins du système de fichiers
|
||||||
|
*/
|
||||||
|
class path {
|
||||||
|
/**
|
||||||
|
* Normaliser le chemin spécifié:
|
||||||
|
* - supprimer si possible les ocurrences de ./ et ../
|
||||||
|
* - supprimer les slash multiples, sauf s'il y en a exactement 2 au début de
|
||||||
|
* la chaine
|
||||||
|
* - "~/path" est transformé en "$HOME/path"
|
||||||
|
*
|
||||||
|
* cas particuliers:
|
||||||
|
* - retourner la valeur null inchangée
|
||||||
|
* - retourner '.' pour un chemin vide
|
||||||
|
*/
|
||||||
|
static final function normalize(?string $path): ?string {
|
||||||
|
if ($path === null) return null;
|
||||||
|
if ($path !== "") {
|
||||||
|
if (substr($path, 0, 2) == "~/") {
|
||||||
|
$path = sh::homedir().substr($path, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
$initial_slashes = strpos($path, "/") === 0;
|
||||||
|
if ($initial_slashes &&
|
||||||
|
strpos($path, "//") === 0 &&
|
||||||
|
strpos($path, "///") === false) {
|
||||||
|
$initial_slashes = 2;
|
||||||
|
}
|
||||||
|
$initial_slashes = intval($initial_slashes);
|
||||||
|
|
||||||
|
$comps = explode("/", $path);
|
||||||
|
$new_comps = array();
|
||||||
|
foreach ($comps as $comp) {
|
||||||
|
if ($comp === "" || $comp === ".") continue;
|
||||||
|
if ($comp != ".." ||
|
||||||
|
(!$initial_slashes && !$new_comps) ||
|
||||||
|
($new_comps && end($new_comps) == "..")) {
|
||||||
|
array_push($new_comps, $comp);
|
||||||
|
} elseif ($new_comps) {
|
||||||
|
array_pop($new_comps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$comps = $new_comps;
|
||||||
|
|
||||||
|
$path = implode("/", $comps);
|
||||||
|
if ($initial_slashes) $path = str_repeat("/", $initial_slashes) . $path;
|
||||||
|
}
|
||||||
|
return $path !== ""? $path: ".";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Comme normalize() mais retourner inchangée la valeur false */
|
||||||
|
static final function with($path) {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
else return self::normalize(strval($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir le chemin absolu normalisé correspondant à $path */
|
||||||
|
static final function abspath($path, ?string $cwd=null) {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
$path = strval($path);
|
||||||
|
if (substr($path, 0, 1) !== "/") {
|
||||||
|
if ($cwd === null) $cwd = getcwd();
|
||||||
|
$path = "$cwd/$path";
|
||||||
|
}
|
||||||
|
return self::normalize($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir le chemin $path exprimé relativement à $basedir.
|
||||||
|
* si $basedir est relatif, il est exprimé par rapport à $cwd qui vaut par
|
||||||
|
* défaut le chemin courant.
|
||||||
|
*/
|
||||||
|
static final function relpath($path, string $basedir="", ?string $cwd=null): string {
|
||||||
|
$path = self::abspath($path, $cwd);
|
||||||
|
$basedir = self::abspath($basedir, $cwd);
|
||||||
|
if ($path === $basedir) return "";
|
||||||
|
|
||||||
|
$initial_slashes = strpos($path, "//") === 0? 2: 1;
|
||||||
|
$path = substr($path, $initial_slashes);
|
||||||
|
$initial_slashes = strpos($basedir, "//") === 0? 2: 1;
|
||||||
|
$basedir = substr($basedir, $initial_slashes);
|
||||||
|
if ($basedir === "") return $path;
|
||||||
|
|
||||||
|
$pparts = $path? explode("/", $path): [];
|
||||||
|
$pcount = count($pparts);
|
||||||
|
$bparts = explode("/", $basedir);
|
||||||
|
$bcount = count($bparts);
|
||||||
|
|
||||||
|
$i = 0;
|
||||||
|
while ($i < $pcount && $i < $bcount && $pparts[$i] === $bparts[$i]) {
|
||||||
|
$i++;
|
||||||
|
}
|
||||||
|
$ups = array_fill(0, $bcount - $i, "..");
|
||||||
|
$relparts = array_merge($ups, array_slice($pparts, $i));
|
||||||
|
return implode("/", $relparts);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir un chemin visuellement agréable:
|
||||||
|
* - $HOME est remplacé par "~"
|
||||||
|
* - $CWD/ est remplacé par ""
|
||||||
|
*/
|
||||||
|
static final function ppath($path, ?string $cwd=null): string {
|
||||||
|
$path = self::abspath($path, $cwd);
|
||||||
|
$homedir = sh::homedir();
|
||||||
|
$cwd = getcwd()."/";
|
||||||
|
if (str::del_prefix($path, $cwd)) {
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
if (str::starts_with($homedir, $path)) {
|
||||||
|
str::del_prefix($path, $homedir);
|
||||||
|
$path ="~$path";
|
||||||
|
}
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir le chemin absolu canonique correspondant à $path
|
||||||
|
*
|
||||||
|
* @throws IOException si le chemin n'existe pas ou si une autre erreur se
|
||||||
|
* produit
|
||||||
|
*/
|
||||||
|
static final function realpath($path) {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
$path = strval($path);
|
||||||
|
$realpath = realpath($path);
|
||||||
|
if ($realpath === false) throw IOException::last_error();
|
||||||
|
return $realpath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir le chemin parent de $path
|
||||||
|
*
|
||||||
|
* cas particuliers:
|
||||||
|
* dirname("/") === "/";
|
||||||
|
* dirname("filename") === ".";
|
||||||
|
*/
|
||||||
|
static final function dirname($path): ?string {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
else return dirname(strval($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir le nom du fichier sans son chemin */
|
||||||
|
static final function filename($path): ?string {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
$index = strrpos($path, "/");
|
||||||
|
if ($index !== false) $path = substr($path, $index + 1);
|
||||||
|
return $path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir le nom de base du fichier (sans son chemin et sans l'extension) */
|
||||||
|
static final function basename($path): ?string {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
$basename = self::filename($path);
|
||||||
|
$index = strrpos($basename, ".");
|
||||||
|
if ($index !== false) $basename = substr($basename, 0, $index);
|
||||||
|
return $basename;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** obtenir l'extension du fichier. l'extension est retournée avec le '.' */
|
||||||
|
static final function ext($path): ?string {
|
||||||
|
if ($path === null || $path === false) return $path;
|
||||||
|
$ext = self::filename($path);
|
||||||
|
$index = strrpos($ext, ".");
|
||||||
|
if ($index === false) $ext = "";
|
||||||
|
else $ext = substr($ext, $index);
|
||||||
|
return $ext;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** découper le chemin entre sa partie "répertoire" et "fichier" */
|
||||||
|
static final function split($path): array {
|
||||||
|
if ($path === null || $path === false) return [$path, $path];
|
||||||
|
elseif ($path === "") return ["", ""];
|
||||||
|
$index = strrpos($path, "/");
|
||||||
|
if ($index !== false) {
|
||||||
|
if ($index == 0) $dir = "/";
|
||||||
|
else $dir = substr($path, 0, $index);
|
||||||
|
$file = substr($path, $index + 1);
|
||||||
|
} else {
|
||||||
|
$dir = "";
|
||||||
|
$file = $path;
|
||||||
|
}
|
||||||
|
return [$dir, $file];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* joindre les composantes pour faire un seul chemin normalisé. les composantes
|
||||||
|
* sont joints inconditionnellements
|
||||||
|
*/
|
||||||
|
static final function join(...$parts): ?string {
|
||||||
|
$path = null;
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part === null || $part === false) continue;
|
||||||
|
if ($path && substr($path, -1) !== "/"
|
||||||
|
&& substr($part, 0, 1) !== "/") $path .= "/";
|
||||||
|
if ($path === null) $path = "";
|
||||||
|
$path .= $part;
|
||||||
|
}
|
||||||
|
return self::normalize($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* joindre les composantes pour faire un seul chemin normalisé. chaque
|
||||||
|
* composante relative s'ajoute au chemin. chaque composante absolue relance
|
||||||
|
* le calcul de puis le début
|
||||||
|
* e.g reljoin("a", "b", "../c", "d/e") retourne "a/c/d/e"
|
||||||
|
* alors que reljoin("a", "b", "/c", "d/e") retourne "/c/d/e"
|
||||||
|
*/
|
||||||
|
static final function reljoin(...$parts): ?string {
|
||||||
|
$path = null;
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
if ($part === null || $part === false) continue;
|
||||||
|
if (substr($part, 0, 1) == "/") {
|
||||||
|
$path = $part;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($path && substr($path, -1) !== "/") $path .= "/";
|
||||||
|
elseif ($path === null) $path = "";
|
||||||
|
$path .= $part;
|
||||||
|
}
|
||||||
|
return self::normalize($path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tester si $dir est situé dans $basedir.
|
||||||
|
*
|
||||||
|
* par défaut, on considère qu'un chemin $dir est situé dans lui-même sauf si
|
||||||
|
* $strict == true, auquel cas "$dir est dans $basedir" implique $dir !== $basedir
|
||||||
|
*/
|
||||||
|
static final function is_within(string $dir, string $basedir, bool $strict=false): bool {
|
||||||
|
$dir = self::abspath($dir);
|
||||||
|
$basedir = self::abspath($basedir);
|
||||||
|
if ($dir === $basedir) return !$strict;
|
||||||
|
$prefix = $basedir;
|
||||||
|
if ($prefix !== "/") $prefix .= "/";
|
||||||
|
return substr($dir, 0, strlen($prefix)) === $prefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tester si $file a un répertoire, c'est à dire s'il contient le caractère '/'
|
||||||
|
* e.g have_dir('dir/name') est vrai alors que have_dir('name.ext') est faux
|
||||||
|
*/
|
||||||
|
static final function have_dir(string $file): bool {
|
||||||
|
return strpos($file, "/") !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* tester si le chemin est qualifié, c'est à dire s'il est absolu ou s'il est
|
||||||
|
* relatif à '.' ou '..'
|
||||||
|
* e.g is_qualified('./a/b') est vrai alors que is_qualified('a/b') est faux
|
||||||
|
*/
|
||||||
|
static final function is_qualified(string $file): bool {
|
||||||
|
if (strpos($file, "/") === false) return false;
|
||||||
|
if (substr($file, 0, 1) == "/") return true;
|
||||||
|
if (substr($file, 0, 2) == "./") return true;
|
||||||
|
if (substr($file, 0, 3) == "../") return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si $file a une extension */
|
||||||
|
static final function have_ext(string $file): bool {
|
||||||
|
$pos = strrpos($file, "/");
|
||||||
|
if ($pos === false) $pos = 0;
|
||||||
|
return strpos($file, ".", $pos) !== false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function ensure_ext(string $path, string $new_ext, ?string $replace_ext=null): string {
|
||||||
|
[$dir, $filename] = self::split($path);
|
||||||
|
if (self::ext($filename) === $replace_ext) {
|
||||||
|
$filename = self::basename($filename);
|
||||||
|
}
|
||||||
|
$filename .= $new_ext;
|
||||||
|
return self::join($dir, $filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si le fichier, le répertoire ou le lien symbolique existe */
|
||||||
|
static final function exists(string $file): bool {
|
||||||
|
return is_link($file) || file_exists($file);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si $dir est un répertoire (mais pas un lien symbolique) */
|
||||||
|
static final function is_dir(string $dir, bool $allow_link=false): bool {
|
||||||
|
return is_dir($dir) && ($allow_link || !is_link($dir));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** tester si $dir est un fichier (mais pas un lien symbolique) */
|
||||||
|
static final function is_file(string $file, bool $allow_link=false): bool {
|
||||||
|
return is_file($file) && ($allow_link || !is_link($file));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,310 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\os;
|
||||||
|
|
||||||
|
use nulib\cl;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class sh {
|
||||||
|
static final function _quote(string $value): string {
|
||||||
|
if (preg_match('/^[a-zA-Z0-9_.@:,\/+-]+$/', $value)) return $value;
|
||||||
|
return escapeshellarg($value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir la valeur $value quotée pour le shell
|
||||||
|
*
|
||||||
|
* si c'est une valeur scalaire, elle sera quotée sous la forme 'value'
|
||||||
|
* si c'est un tableau séquentiel, elle sera quoté sous la forme ('value'...)
|
||||||
|
* sinon, elle sera quotée sour la forme ([key]=value...)
|
||||||
|
*/
|
||||||
|
static final function quote($value): string {
|
||||||
|
if (is_array($value)) {
|
||||||
|
$parts = [];
|
||||||
|
if (cl::is_list($value)) {
|
||||||
|
foreach ($value as $part) {
|
||||||
|
$parts[] = self::_quote(strval($part));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
foreach ($value as $key => $part) {
|
||||||
|
$key = self::_quote($key);
|
||||||
|
$val = self::_quote($part);
|
||||||
|
$parts[] = "[$key]=$val";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "(".implode(" ", $parts).")";
|
||||||
|
} else {
|
||||||
|
return self::_quote(strval($value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* obtenir une commande shell à partir du tableau des arguments.
|
||||||
|
* à utiliser avec exec()
|
||||||
|
*/
|
||||||
|
static final function join(array $parts): string {
|
||||||
|
$count = count($parts);
|
||||||
|
for($i = 0; $i < $count; $i++) {
|
||||||
|
$parts[$i] = self::_quote(strval($parts[$i]));
|
||||||
|
}
|
||||||
|
return implode(" ", $parts);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
|
||||||
|
if ($redir !== null) {
|
||||||
|
switch ($redir) {
|
||||||
|
case "outonly":
|
||||||
|
case "noerr":
|
||||||
|
$redir = "2>/dev/null";
|
||||||
|
break;
|
||||||
|
case "erronly":
|
||||||
|
case "noout":
|
||||||
|
$redir = "2>&1 >/dev/null";
|
||||||
|
break;
|
||||||
|
case "both":
|
||||||
|
case "err2out":
|
||||||
|
$redir = "2>&1";
|
||||||
|
break;
|
||||||
|
case "none":
|
||||||
|
case "null":
|
||||||
|
$redir = ">/dev/null 2>&1";
|
||||||
|
break;
|
||||||
|
case "default":
|
||||||
|
$redir = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($input !== null) {
|
||||||
|
$redir = $redir !== null? "$redir ": "";
|
||||||
|
$redir .= "<".escapeshellarg($input);
|
||||||
|
}
|
||||||
|
if ($output !== null) {
|
||||||
|
$redir = $redir !== null? "$redir ": "";
|
||||||
|
$redir .= ">".escapeshellarg($output);
|
||||||
|
}
|
||||||
|
if ($redir !== null) $cmd .= " $redir";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corriger la commande $cmd:
|
||||||
|
* - si c'est tableau, joindre les arguments avec {@link join()}
|
||||||
|
* - sinon, mettre les caractères en échappement avec {@link escapeshellarg()}
|
||||||
|
*/
|
||||||
|
static final function fix_cmd(&$cmd, ?string $redir=null, ?string $input=null, ?string $output=null): void {
|
||||||
|
if (is_array($cmd)) $cmd = self::join($cmd);
|
||||||
|
else $cmd = escapeshellcmd(strval($cmd));
|
||||||
|
self::add_redir($cmd, $redir, $input, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lancer la commande spécifiée avec passthru() et retourner le code de retour
|
||||||
|
* dans la variable $retcode. $cmd doit déjà être formaté comme il convient
|
||||||
|
*
|
||||||
|
* voici la différence entre system(), passthru() et exec()
|
||||||
|
* +----------------+-----------------+----------------+----------------+
|
||||||
|
* | Command | Displays Output | Can Get Output | Gets Exit Code |
|
||||||
|
* +----------------+-----------------+----------------+----------------+
|
||||||
|
* | passthru() | Yes (raw) | No | Yes |
|
||||||
|
* | system() | Yes (as text) | Last line only | Yes |
|
||||||
|
* | exec() | No | Yes (array) | Yes |
|
||||||
|
* +----------------+-----------------+----------------+----------------+
|
||||||
|
*
|
||||||
|
* @return bool true si la commande s'est lancée sans erreur, false sinon
|
||||||
|
*/
|
||||||
|
static final function _passthru(string $cmd, int &$retcode=null): bool {
|
||||||
|
passthru($cmd, $retcode);
|
||||||
|
return $retcode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comme {@link _passthru()} mais lancer la commande spécifiée avec system().
|
||||||
|
* cf la doc de {@link _passthru()} pour les autres détails
|
||||||
|
*/
|
||||||
|
static final function _system(string $cmd, string &$output=null, int &$retcode=null): bool {
|
||||||
|
$last_line = system($cmd, $retcode);
|
||||||
|
if ($last_line !== false) $output = $last_line;
|
||||||
|
return $retcode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comme {@link _passthru()} mais lancer la commande spécifiée avec exec().
|
||||||
|
* cf la doc de {@link _passthru()} pour les autres détails
|
||||||
|
*/
|
||||||
|
static final function _exec(string $cmd, array &$output=null, int &$retcode=null): bool {
|
||||||
|
exec($cmd, $output, $retcode);
|
||||||
|
return $retcode == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
|
||||||
|
* fin de son exécution.
|
||||||
|
*
|
||||||
|
* $cmd doit déjà être formaté comme il convient
|
||||||
|
*/
|
||||||
|
static final function _fork_exec(string $cmd, int &$retcode=null): bool {
|
||||||
|
$pid = pcntl_fork();
|
||||||
|
if ($pid == -1) {
|
||||||
|
// parent, impossible de forker
|
||||||
|
throw new RuntimeException("unable to fork");
|
||||||
|
} elseif ($pid) {
|
||||||
|
// parent, fork ok
|
||||||
|
pcntl_waitpid($pid, $status);
|
||||||
|
if (pcntl_wifexited($status)) {
|
||||||
|
$retcode = pcntl_wexitstatus($status);
|
||||||
|
} else {
|
||||||
|
$retcode = 127;
|
||||||
|
}
|
||||||
|
return $retcode == 0;
|
||||||
|
}
|
||||||
|
// child, fork ok
|
||||||
|
pcntl_exec("/bin/sh", ["-c", $cmd]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corriger la commande spécifiée avec {@link fix_cmd()} puis la lancer
|
||||||
|
* avec passthru() et retourner le code de retour dans la variable $retcode
|
||||||
|
*
|
||||||
|
* $redir spécifie le type de redirection demandée:
|
||||||
|
* - "default" | null: $output reçoit STDOUT et STDERR n'est pas redirigé
|
||||||
|
* - "outonly" | "noerr": $output ne reçoit que STDOUT et STDERR est perdu
|
||||||
|
* - "erronly" | "noout": $output ne reçoit que STDERR et STDOUT est perdu
|
||||||
|
* - "both" | "err2out": $output reçoit STDOUT et STDERR
|
||||||
|
* - "none" | "null": STDOUT et STDERR sont perdus
|
||||||
|
* - sinon c'est une redirection spécifique, et la valeur est rajoutée telle
|
||||||
|
* quelle à la ligne de commande
|
||||||
|
*
|
||||||
|
* @return bool true si la commande s'est lancée sans erreur, false sinon
|
||||||
|
*/
|
||||||
|
static final function passthru($cmd, int &$retcode=null, ?string $redir=null): bool {
|
||||||
|
self::fix_cmd($cmd, $redir);
|
||||||
|
return self::_passthru($cmd, $retcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comme {@link passthru()} mais lancer la commande spécifiée avec system().
|
||||||
|
* Cf la doc de {@link passthru()} pour les autres détails
|
||||||
|
*/
|
||||||
|
static final function system($cmd, string &$output=null, int &$retcode=null, ?string $redir=null): bool {
|
||||||
|
self::fix_cmd($cmd, $redir);
|
||||||
|
return self::_system($cmd, $output, $retcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Comme {@link passthru()} mais lancer la commande spécifiée avec exec().
|
||||||
|
* Cf la doc de {@link passthru()} pour les autres détails
|
||||||
|
*/
|
||||||
|
static final function exec($cmd, array &$output=null, int &$retcode=null, ?string $redir=null): bool {
|
||||||
|
self::fix_cmd($cmd, $redir);
|
||||||
|
return self::_exec($cmd, $output, $retcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corriger la commande spécifiée avec {@link fix_cmd()}, la préfixer de
|
||||||
|
* "exec" puis la lancer avec {@link _fork_exec()}
|
||||||
|
*/
|
||||||
|
static final function fork_exec($cmd, int &$retcode=null, ?string $redir=null): bool {
|
||||||
|
self::fix_cmd($cmd, $redir);
|
||||||
|
return self::_fork_exec("exec $cmd", $retcode);
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
|
||||||
|
/** retourner le répertoire $HOME */
|
||||||
|
static final function homedir(): string {
|
||||||
|
$homedir = getenv("HOME");
|
||||||
|
if ($homedir === false) {
|
||||||
|
$homedir = posix_getpwuid(posix_getuid())["dir"];
|
||||||
|
}
|
||||||
|
return path::abspath($homedir);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** s'assurer que le répertoire $dir existe */
|
||||||
|
static final function mkdirp(string $dir): bool {
|
||||||
|
if (is_dir($dir)) return true;
|
||||||
|
return mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** créer le répertoire qui va contenir le fichier $file */
|
||||||
|
static final function mkdirof(string $file): bool {
|
||||||
|
if (file_exists($file)) return true;
|
||||||
|
$dir = path::dirname($file);
|
||||||
|
if (file_exists($dir)) return true;
|
||||||
|
return mkdir($dir, 0777, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* créer un répertoire avec un nom unique. ce répertoire doit être supprimé
|
||||||
|
* manuellement quand il n'est plus utilisé.
|
||||||
|
*
|
||||||
|
* @return string le chemin du répertoire
|
||||||
|
* @throws IOException si une erreur se produit (impossible de créer un
|
||||||
|
* répertoire unique après 2560 essais)
|
||||||
|
*/
|
||||||
|
static final function mktempdir(?string $prefix=null, ?string $basedir=null): string {
|
||||||
|
if ($basedir === null) $basedir = sys_get_temp_dir();
|
||||||
|
if ($prefix !== null) $prefix .= "-";
|
||||||
|
$max = 2560;
|
||||||
|
do {
|
||||||
|
$dir = "$basedir/$prefix".uniqid();
|
||||||
|
$r = @mkdir($dir);
|
||||||
|
$max--;
|
||||||
|
} while ($r === false && $max > 0);
|
||||||
|
if ($r === false) {
|
||||||
|
throw IOException::last_error("$dir: unable to create directory");
|
||||||
|
}
|
||||||
|
return $dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Supprimer un répertoire créé avec mktempdir
|
||||||
|
*
|
||||||
|
* un minimum de vérification est effectué qu'il s'agit bien d'un répertoire
|
||||||
|
* généré par mktempdir
|
||||||
|
*/
|
||||||
|
static final function rmtempdir(string $tmpdir, ?string $prefix=null, ?string $basedir=null): void {
|
||||||
|
if ($basedir === null) $basedir = sys_get_temp_dir();
|
||||||
|
if ($prefix !== null) $prefix .= "-";
|
||||||
|
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
|
||||||
|
if (fnmatch("$basedir/$prefix?????????????", $tmpdir)) {
|
||||||
|
self::exec(["rm", "-rf", $tmpdir]);
|
||||||
|
} else {
|
||||||
|
throw new IOException("$tmpdir: n'est pas un répertoire temporaire");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* supprimer tous les répertoires temporaires qui ont été créés avec le
|
||||||
|
* suffixe spécifié dans le répertoire $basedir
|
||||||
|
*/
|
||||||
|
static final function cleantempdirs(string $prefix, ?string $basedir=null): void {
|
||||||
|
if ($basedir === null) $basedir = sys_get_temp_dir();
|
||||||
|
$prefix .= "-";
|
||||||
|
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
|
||||||
|
$tmpdirs = glob("$basedir/$prefix?????????????", GLOB_ONLYDIR);
|
||||||
|
if ($tmpdirs) {
|
||||||
|
self::exec(["rm", "-rf", ...$tmpdirs]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function is_diff_file(string $f, string $g): bool {
|
||||||
|
if (!is_file($f) || !is_file($g)) return true;
|
||||||
|
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
|
||||||
|
return $retcode !== 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function is_same_file(string $f, string $g): bool {
|
||||||
|
if (!is_file($f) || !is_file($g)) return false;
|
||||||
|
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
|
||||||
|
return $retcode === 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function is_diff_link(string $f, string $g): bool {
|
||||||
|
if (!is_link($f) || !is_link($g)) return true;
|
||||||
|
return @readlink($f) !== @readlink($g);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function is_same_link(string $f, string $g): bool {
|
||||||
|
if (!is_link($f) || !is_link($g)) return false;
|
||||||
|
return @readlink($f) === @readlink($g);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,9 +3,10 @@ namespace nur\sery\output\std;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nur\sery\sys\content;
|
use nur\sery\os\file\Stream;
|
||||||
use nur\sery\sys\IContent;
|
use nur\sery\php\content\content;
|
||||||
use nur\sery\sys\IPrintable;
|
use nur\sery\php\content\IContent;
|
||||||
|
use nur\sery\php\content\IPrintable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
|
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
|
||||||
|
@ -87,6 +88,7 @@ class StdOutput {
|
||||||
$indent = cl::get($params, "indent");
|
$indent = cl::get($params, "indent");
|
||||||
$flush = cl::get($params, "flush");
|
$flush = cl::get($params, "flush");
|
||||||
|
|
||||||
|
if ($output instanceof Stream) $output = $output->getResource();
|
||||||
if ($output !== null) {
|
if ($output !== null) {
|
||||||
if ($output === "php://stdout") {
|
if ($output === "php://stdout") {
|
||||||
$outf = STDOUT;
|
$outf = STDOUT;
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface ICloseable: un objet que l'on peut fermer
|
||||||
|
*/
|
||||||
|
interface ICloseable {
|
||||||
|
/** fermer l'objet et libérer les resources */
|
||||||
|
function close(): void;
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
# nulib\sys
|
# nulib\php
|
||||||
|
|
||||||
Ce package contient des services généraux spécifiques à PHP
|
Ce package contient des services généraux spécifiques à PHP
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface IContent: un objet capable de produire du contenu à afficher. le
|
* Interface IContent: un objet capable de produire du contenu à afficher. le
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface IPrintable: un objet qui écrit du contenu sur la sortie standard
|
* Interface IPrintable: un objet qui écrit du contenu sur la sortie standard
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface IStaticContent: comme {@link IContent} mais la liste retournée est
|
* Interface IStaticContent: comme {@link IContent} mais la liste retournée est
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
|
use nur\sery\php\func;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class content: gestionnaire de contenu (statique ou dynamique)
|
* Class content: gestionnaire de contenu (statique ou dynamique)
|
|
@ -1,5 +1,5 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php;
|
||||||
|
|
||||||
use Closure;
|
use Closure;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
|
@ -0,0 +1,148 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php\iter;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Iterator;
|
||||||
|
use nur\sery\os\EOFException;
|
||||||
|
use nur\sery\php\ICloseable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class AbstractIterator: implémentation de base d'un itérateur
|
||||||
|
*/
|
||||||
|
abstract class AbstractIterator implements Iterator, ICloseable {
|
||||||
|
/**
|
||||||
|
* initialiser les ressources nécessaires à l'itération.
|
||||||
|
*
|
||||||
|
* les exceptions lancées par cette méthode sont ignorées.
|
||||||
|
*/
|
||||||
|
protected function _setup(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lancer un traitement avant de commencer l'itération.
|
||||||
|
*
|
||||||
|
* cette méthode est appelée après {@link _setup()} et l'objet est garanti
|
||||||
|
* d'être dans un état valide.
|
||||||
|
*
|
||||||
|
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||||
|
* doit gérer lui-même les exceptions éventuelles.
|
||||||
|
*/
|
||||||
|
protected function beforeIter() {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* retourner le prochain élément. lancer l'exception {@link EOFException} pour
|
||||||
|
* indiquer que plus aucun élément n'est disponible
|
||||||
|
*
|
||||||
|
* le cas échéant, initialiser $key
|
||||||
|
*
|
||||||
|
* @throws EOFException
|
||||||
|
*/
|
||||||
|
abstract protected function _next(&$key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* modifier l'élement retourné par {@link _next()} avant de le retourner.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||||
|
* doit gérer lui-même les exceptions éventuelles.
|
||||||
|
*/
|
||||||
|
protected function cook(&$item) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* lancer un traitement avant de terminer l'itération et de libérer les
|
||||||
|
* resources
|
||||||
|
*
|
||||||
|
* cette méthode est appelée avant {@link _teardown()} et l'objet est garanti
|
||||||
|
* d'être dans un état valide.
|
||||||
|
*
|
||||||
|
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
|
||||||
|
* doit gérer lui-même les exceptions éventuelles.
|
||||||
|
*/
|
||||||
|
protected function beforeClose(): void {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* libérer les ressources allouées.
|
||||||
|
*
|
||||||
|
* les exceptions lancées par cette méthode sont ignorées.
|
||||||
|
*/
|
||||||
|
protected function _teardown(): void {}
|
||||||
|
|
||||||
|
function close(): void {
|
||||||
|
$this->rewind();
|
||||||
|
}
|
||||||
|
|
||||||
|
#############################################################################
|
||||||
|
# Implémentation par défaut
|
||||||
|
|
||||||
|
private $setup = false;
|
||||||
|
private $valid = false;
|
||||||
|
private $toredown = true;
|
||||||
|
|
||||||
|
private $index = 0;
|
||||||
|
protected $key;
|
||||||
|
protected $item = null;
|
||||||
|
|
||||||
|
function key() {
|
||||||
|
return $this->key;
|
||||||
|
}
|
||||||
|
|
||||||
|
function current() {
|
||||||
|
return $this->item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function next(): void {
|
||||||
|
if ($this->toredown) return;
|
||||||
|
$this->valid = false;
|
||||||
|
try {
|
||||||
|
$item = $this->_next($key);
|
||||||
|
} catch (EOFException $e) {
|
||||||
|
$this->beforeClose();
|
||||||
|
try {
|
||||||
|
$this->_teardown();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
$this->toredown = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->cook($item);
|
||||||
|
$this->item = $item;
|
||||||
|
if ($key !== null) {
|
||||||
|
$this->key = $key;
|
||||||
|
} else {
|
||||||
|
$this->index++;
|
||||||
|
$this->key = $this->index;
|
||||||
|
}
|
||||||
|
$this->valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewind(): void {
|
||||||
|
if ($this->setup) {
|
||||||
|
if (!$this->toredown) {
|
||||||
|
$this->beforeClose();
|
||||||
|
try {
|
||||||
|
$this->_teardown();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->setup = false;
|
||||||
|
$this->valid = false;
|
||||||
|
$this->toredown = true;
|
||||||
|
$this->index = 0;
|
||||||
|
$this->key = null;
|
||||||
|
$this->item = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function valid(): bool {
|
||||||
|
if (!$this->setup) {
|
||||||
|
try {
|
||||||
|
$this->_setup();
|
||||||
|
} catch (Exception $e) {
|
||||||
|
}
|
||||||
|
$this->setup = true;
|
||||||
|
$this->toredown = false;
|
||||||
|
$this->beforeIter();
|
||||||
|
$this->next();
|
||||||
|
}
|
||||||
|
return $this->valid;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\php\json;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class JsonException extends RuntimeException {
|
||||||
|
static final function json_last_error(?string $prefix=null): ?self {
|
||||||
|
$json_last_error = json_last_error();
|
||||||
|
if ($json_last_error === JSON_ERROR_NONE) return null;
|
||||||
|
$message = json_last_error_msg()." ($json_last_error)";
|
||||||
|
if ($prefix) $message = "$prefix: $message";
|
||||||
|
return new static($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function ensure_json_value($value, ?string $prefix=null) {
|
||||||
|
$exception = self::json_last_error($prefix);
|
||||||
|
if ($exception === null) return $value;
|
||||||
|
else throw $exception;
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ namespace nur\sery\values;
|
||||||
|
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\str;
|
use nulib\str;
|
||||||
use nur\sery\sys\func;
|
use nur\sery\php\func;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionException;
|
use ReflectionException;
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,144 @@
|
||||||
|
<?php
|
||||||
|
namespace nur\sery\web;
|
||||||
|
|
||||||
|
use nulib\cv;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class http: des outils pour parler le protocole HTTP dans le cadre de la
|
||||||
|
* génération d'une page
|
||||||
|
*/
|
||||||
|
class http {
|
||||||
|
static final function get_url(): string {
|
||||||
|
$proto = $_SERVER["HTTP_X_FORWARDED_PROTO"];
|
||||||
|
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
|
||||||
|
$uri = $_SERVER["SCRIPT_NAME"];
|
||||||
|
return "$proto://$host$uri";
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function get_baseurl(): string {
|
||||||
|
$proto = $_SERVER["HTTP_X_FORWARDED_PROTO"];
|
||||||
|
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
|
||||||
|
$baseuri = dirname($_SERVER["SCRIPT_NAME"]);
|
||||||
|
return "$proto://$host$baseuri";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ne pas mettre en cache la réponse.
|
||||||
|
*
|
||||||
|
* appeler cette méthode est nécessaire si une page est dynamique
|
||||||
|
* et que les sessions ne sont pas utilisées.
|
||||||
|
*
|
||||||
|
* si un session est déjà démarrée, cette méthode est un NOP, sauf si
|
||||||
|
* $force==true
|
||||||
|
*/
|
||||||
|
static function no_cache(bool $force=false): void {
|
||||||
|
if (session_status() == PHP_SESSION_ACTIVE && !$force) return;
|
||||||
|
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
|
||||||
|
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
|
||||||
|
header("Cache-Control: no-cache, max-age=0, must-revalidate");
|
||||||
|
header("Pragma: public");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** spécifier le type de contenu de la réponse */
|
||||||
|
static function content_type(string $content_type=null, string $charset=null): void {
|
||||||
|
if ($content_type === null) $content_type = "application/octet-stream";
|
||||||
|
if (substr($content_type, 0, 5) == "text/") {
|
||||||
|
if ($charset === null) $charset = "utf-8";
|
||||||
|
if ($charset) $content_type .= "; charset=$charset";
|
||||||
|
}
|
||||||
|
header("Content-Type: $content_type");
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function content_type_text(): void {
|
||||||
|
self::content_type("text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
|
/** indiquer que la réponse doit être téléchargée */
|
||||||
|
static function download_as($filename, string $disposition=null): void {
|
||||||
|
if (cv::nz($filename)) {
|
||||||
|
$filename = basename($filename);
|
||||||
|
if ($disposition === null) $disposition = "attachment";
|
||||||
|
header("Content-Disposition: $disposition; filename=\"$filename\"");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** rediriger vers l'url spécifiée et arrêter le script immédiatement */
|
||||||
|
static function redirect(string $url, bool $exit_now=true): void {
|
||||||
|
header("Location: $url");
|
||||||
|
if ($exit_now) exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** rafraichir vers l'url spécifiée et arrêter le script immédiatement */
|
||||||
|
static function refresh(string $url, int $delay=1, bool $exit_now=true): void {
|
||||||
|
header("Refresh: $delay; url=$url");
|
||||||
|
if ($exit_now) exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** envoyer le status "pas de contenu" et arrêter le script immédiatement */
|
||||||
|
static function send_no_content(bool $exit_now=true): void {
|
||||||
|
header("HTTP/1.1 204 No Content");
|
||||||
|
if ($exit_now) exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Envoyer le fichier spécifié pour téléchargement et arrêter le script
|
||||||
|
* immédiatement. On assume que les méthodes statiques {@link content_type()}
|
||||||
|
* et {@link download_as()} ont déjà été appelées.
|
||||||
|
*
|
||||||
|
* - si $delete==true, supprimer le fichier après téléchargement.
|
||||||
|
* - si $add_content_length==true, ajouter une en-tête qui précise la taille
|
||||||
|
* du fichier à télécharger. attention: la taille du fichier ne devrait pas
|
||||||
|
* changer
|
||||||
|
* - si $close_session==true, la session est fermée avant de lancer le
|
||||||
|
* téléchargement du fichier pour éviter de garder inutilement le verrou.
|
||||||
|
* NB: si le fichier à retourner est d'une certaine taille, il est avisé
|
||||||
|
* de fermer la session pour ne pas bloquer la navigation.
|
||||||
|
*
|
||||||
|
* si une erreur s'est produite, retourner false, même si $exit_now==true
|
||||||
|
*/
|
||||||
|
static function send_file(string $file, bool $delete=false, bool $close_session=true, bool $add_content_length=true, bool $exit_now=true): bool {
|
||||||
|
$fd = fopen($file, "r");
|
||||||
|
if ($fd === false) return false;
|
||||||
|
if ($close_session && session_status() == PHP_SESSION_ACTIVE) session_write_close();
|
||||||
|
if ($add_content_length) {
|
||||||
|
$size = filesize($file);
|
||||||
|
if ($size !== false) header("Content-Length: $size");
|
||||||
|
}
|
||||||
|
if ($delete) unlink($file);
|
||||||
|
$written = fpassthru($fd);
|
||||||
|
# XXX si $written !== $size, il faudrait agir en conséquence...
|
||||||
|
fclose($fd);
|
||||||
|
if ($written === false) return false;
|
||||||
|
if ($exit_now) exit;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function error(string $status, bool $exit=true): void {
|
||||||
|
header("HTTP/1.1 $status");
|
||||||
|
if ($exit) exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function error400(?string $message=null, ?string $body=null, bool $exit=true): void {
|
||||||
|
if ($message === null) $message = "Bad Request";
|
||||||
|
elseif ($body === null) $body = $message;
|
||||||
|
self::error("400 $message", false);
|
||||||
|
if ($body !== null) echo $body;
|
||||||
|
if ($exit) exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function error404(?string $message=null, ?string $body=null, bool $exit=true): void {
|
||||||
|
if ($message === null) $message = "Not Found";
|
||||||
|
elseif ($body === null) $body = $message;
|
||||||
|
self::error("404 $message", false);
|
||||||
|
if ($body !== null) echo $body;
|
||||||
|
if ($exit) exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
static final function error500(?string $message=null, ?string $body=null, bool $exit=true): void {
|
||||||
|
if ($message === null) $message = "Internal Server Error";
|
||||||
|
elseif ($body === null) $body = $message;
|
||||||
|
self::error("500 $message", false);
|
||||||
|
if ($body !== null) echo $body;
|
||||||
|
if ($exit) exit();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys;
|
namespace nur\sery\php\content;
|
||||||
|
|
||||||
use nur\sery\sys\impl\html;
|
use nur\sery\php\content\content;
|
||||||
|
use nur\sery\php\content\impl\html;
|
||||||
use PHPUnit\Framework\TestCase;
|
use PHPUnit\Framework\TestCase;
|
||||||
|
|
||||||
class contentTest extends TestCase {
|
class contentTest extends TestCase {
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
use nur\sery\sys\IContent;
|
use nur\sery\php\content\IContent;
|
||||||
|
|
||||||
class AContent implements IContent {
|
class AContent implements IContent {
|
||||||
function getContent(): iterable {
|
function getContent(): iterable {
|
|
@ -1,7 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
use nur\sery\sys\IPrintable;
|
use nur\sery\php\content\IPrintable;
|
||||||
|
|
||||||
class APrintable implements IPrintable {
|
class APrintable implements IPrintable {
|
||||||
function print(): void {
|
function print(): void {
|
|
@ -1,7 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
use nur\sery\sys\IStaticContent;
|
use nur\sery\php\content\IStaticContent;
|
||||||
|
use nur\sery\php\content\impl\html;
|
||||||
|
|
||||||
class AStaticContent implements IStaticContent {
|
class AStaticContent implements IStaticContent {
|
||||||
function getContent(): iterable {
|
function getContent(): iterable {
|
|
@ -1,8 +1,8 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
use nur\sery\sys\content;
|
use nur\sery\php\content\content;
|
||||||
use nur\sery\sys\IStaticContent;
|
use nur\sery\php\content\IStaticContent;
|
||||||
|
|
||||||
class ATag implements IStaticContent {
|
class ATag implements IStaticContent {
|
||||||
function __construct(string $tag, $contents=null) {
|
function __construct(string $tag, $contents=null) {
|
|
@ -1,5 +1,7 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nur\sery\sys\impl;
|
namespace nur\sery\php\content\impl;
|
||||||
|
|
||||||
|
use nur\sery\php\content\impl\ATag;
|
||||||
|
|
||||||
class html {
|
class html {
|
||||||
const H1 = [self::class, "h1"];
|
const H1 = [self::class, "h1"];
|
|
@ -12,7 +12,7 @@ namespace {
|
||||||
function func_o1v($b=9, ...$c): array { return [$b, ...$c]; }
|
function func_o1v($b=9, ...$c): array { return [$b, ...$c]; }
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace nur\sery\sys {
|
namespace nur\sery\php {
|
||||||
use nulib\tests\TestCase;
|
use nulib\tests\TestCase;
|
||||||
|
|
||||||
class funcTest extends TestCase {
|
class funcTest extends TestCase {
|
Loading…
Reference in New Issue