modifs.mineures sans commentaires
This commit is contained in:
parent
451b2768e6
commit
5460903a9f
|
@ -13,6 +13,8 @@
|
|||
},
|
||||
"require-dev": {
|
||||
"nulib/tests": "7.4",
|
||||
"ext-posix": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-curl": "*"
|
||||
},
|
||||
"autoload": {
|
||||
|
@ -20,6 +22,9 @@
|
|||
"nulib\\": "php/src_base",
|
||||
"nulib\\ref\\": "php/src_ref",
|
||||
"nulib\\php\\": "php/src_php",
|
||||
"nulib\\os\\": "php/src_os",
|
||||
"nulib\\file\\": "php/src_file",
|
||||
"nulib\\values\\": "php/src_values",
|
||||
"nulib\\output\\": "php/src_output",
|
||||
"nulib\\web\\": "php/src_web"
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "d0cd1ea89a82b87aa4a078dcea524293",
|
||||
"content-hash": "0f0743123abf677caf20c5e71ece9616",
|
||||
"packages": [],
|
||||
"packages-dev": [
|
||||
{
|
||||
|
@ -665,16 +665,16 @@
|
|||
},
|
||||
{
|
||||
"name": "phpunit/phpunit",
|
||||
"version": "9.6.18",
|
||||
"version": "9.6.19",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||
"reference": "32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04"
|
||||
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04",
|
||||
"reference": "32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04",
|
||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8",
|
||||
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
|
@ -748,7 +748,7 @@
|
|||
"support": {
|
||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.18"
|
||||
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
|
@ -764,7 +764,7 @@
|
|||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2024-03-21T12:07:32+00:00"
|
||||
"time": "2024-04-05T04:35:58+00:00"
|
||||
},
|
||||
{
|
||||
"name": "sebastian/cli-parser",
|
||||
|
@ -1789,6 +1789,8 @@
|
|||
"php": ">=7.4"
|
||||
},
|
||||
"platform-dev": {
|
||||
"ext-posix": "*",
|
||||
"ext-pcntl": "*",
|
||||
"ext-curl": "*"
|
||||
},
|
||||
"plugin-api-version": "2.2.0"
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use Exception;
|
||||
|
||||
/**
|
||||
* Class AccessException: indiquer que la resource ou l'objet auquel on veut
|
||||
* accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Class NoMoreDataException: indiquer que plus aucune donnée n'est disponible
|
||||
*/
|
||||
class NoMoreDataException extends RuntimeException {
|
||||
}
|
|
@ -11,8 +11,14 @@ class ValueException extends UserException {
|
|||
} elseif (is_array($value)) {
|
||||
$values = $value;
|
||||
$parts = [];
|
||||
foreach ($values as $value) {
|
||||
$parts[] = self::value($value);
|
||||
$index = 0;
|
||||
foreach ($values as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$index++;
|
||||
$parts[] = self::value($value);
|
||||
} else {
|
||||
$parts[] = "$key=>".self::value($value);
|
||||
}
|
||||
}
|
||||
return "[" . implode(", ", $parts) . "]";
|
||||
} elseif (is_string($value)) {
|
||||
|
|
|
@ -141,6 +141,15 @@ class cl {
|
|||
return $array !== null? array_keys($array): [];
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner la première valeur de $array ou $default si le tableau est null
|
||||
* ou vide
|
||||
*/
|
||||
static final function first($array, $default=null) {
|
||||
if (is_array($array)) return $array[array_key_first($array)];
|
||||
return $default;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
/**
|
||||
|
|
|
@ -79,6 +79,15 @@ class cv {
|
|||
|
||||
#############################################################################
|
||||
|
||||
/** échanger les deux valeurs */
|
||||
static final function swap(&$a, &$b): void {
|
||||
$tmp = $a;
|
||||
$a = $b;
|
||||
$b = $tmp;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
|
||||
/** mettre à jour $dest avec $value si $cond($value) est vrai */
|
||||
static final function set_if(&$dest, $value, callable $cond) {
|
||||
if ($cond($value)) $dest = $value;
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\file\base\FileReader;
|
||||
use nulib\file\base\FileWriter;
|
||||
use nulib\file\base\MemoryStream;
|
||||
use nulib\file\base\SharedFile;
|
||||
use nulib\file\base\TempStream;
|
||||
use nulib\file\base\TmpfileWriter;
|
||||
|
||||
/**
|
||||
* Class file: outils pour gérer les fichiers
|
||||
*/
|
||||
class file {
|
||||
static function reader($input, ?callable $func=null): FileReader {
|
||||
$file = new FileReader($input);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function writer($output, ?string $mode=null, ?callable $func=null): FileWriter {
|
||||
$file = new FileWriter($output, $mode);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function shared($file, ?callable $func=null): SharedFile {
|
||||
$file = new SharedFile($file);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file ->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function tmpwriter($destdir=null, ?callable $func=null): TmpfileWriter {
|
||||
$file = new TmpfileWriter($destdir);
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function memory(?callable $func=null): MemoryStream {
|
||||
$file = new MemoryStream();
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
static function temp(?callable $func=null): TempStream {
|
||||
$file = new TempStream();
|
||||
if ($func !== null) {
|
||||
try {
|
||||
$func($file);
|
||||
} finally {
|
||||
$file->close();
|
||||
}
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
namespace nulib\file;
|
||||
|
||||
use nulib\os\EOFException;
|
||||
use nulib\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 nulib\file;
|
||||
|
||||
use nulib\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(): self;
|
||||
|
||||
/** @throws IOException */
|
||||
function ftruncate(int $size): self;
|
||||
|
||||
/** 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): IWriter;
|
||||
|
||||
/** é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,56 @@
|
|||
<?php
|
||||
namespace nulib\file;
|
||||
|
||||
use Iterator;
|
||||
use nulib\os\IOException;
|
||||
use nulib\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 taille du fichier */
|
||||
function getSize(): int;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** comme {@link fseek()} mais retourne self */
|
||||
function seek(int $offset, int $whence=SEEK_SET): self;
|
||||
|
||||
/** fermer le fichier si c'est nécessaire */
|
||||
function close(bool $close=true): void;
|
||||
}
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
/**
|
||||
* Class FileReader: un fichier accédé en lecture
|
||||
*/
|
||||
class FileReader extends _File {
|
||||
/** @var bool *à l'ouverture du fichier*, faut-il ignorer le BOM? */
|
||||
const IGNORE_BOM = true;
|
||||
|
||||
const DEFAULT_MODE = "rb";
|
||||
|
||||
/** @var bool */
|
||||
protected $ignoreBom;
|
||||
|
||||
function __construct($input, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null, ?bool $ignoreBom=null) {
|
||||
if ($ignoreBom === null) $ignoreBom = static::IGNORE_BOM;
|
||||
$this->ignoreBom = $ignoreBom;
|
||||
if ($input === null) {
|
||||
$fd = STDIN;
|
||||
$close = false;
|
||||
} elseif (is_resource($input)) {
|
||||
$fd = $input;
|
||||
$close = false;
|
||||
} else {
|
||||
$file = $input;
|
||||
if ($mode === null) $mode = static::DEFAULT_MODE;
|
||||
$this->file = $file;
|
||||
$this->mode = $mode;
|
||||
$fd = $this->open();
|
||||
$close = true;
|
||||
}
|
||||
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||
}
|
||||
|
||||
/** @return resource */
|
||||
protected function open() {
|
||||
$fd = parent::open();
|
||||
$this->haveBom = false;
|
||||
if ($this->ignoreBom) {
|
||||
$bom = fread($fd, 3);
|
||||
if ($bom === "\xEF\xBB\xBF") $this->seekOffset = 3;
|
||||
else rewind($fd);
|
||||
}
|
||||
return $fd;
|
||||
}
|
||||
|
||||
function haveBom(): bool {
|
||||
return $this->seekOffset !== 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\os\sh;
|
||||
|
||||
/**
|
||||
* Class FileWriter: un fichier accédé en lecture/écriture
|
||||
*/
|
||||
class FileWriter extends _File {
|
||||
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 {
|
||||
$file = $output;
|
||||
if ($mode === null) $mode = static::DEFAULT_MODE;
|
||||
IOException::ensure_valid(sh::mkdirof($file));
|
||||
$this->file = $file;
|
||||
$this->mode = $mode;
|
||||
$fd = $this->open();
|
||||
$close = true;
|
||||
}
|
||||
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
/**
|
||||
* Class MemoryStream: un flux qui peut être lu ou écrit, et qui reste
|
||||
* uniquement en mémoire.
|
||||
*/
|
||||
class MemoryStream extends Stream {
|
||||
protected static function memory_fd() {
|
||||
return fopen("php://memory", "w+b");
|
||||
}
|
||||
|
||||
function __construct(bool $throwOnError=true) {
|
||||
parent::__construct(self::memory_fd(), true, $throwOnError);
|
||||
}
|
||||
|
||||
function getResource() {
|
||||
if ($this->fd === null) $this->fd = self::memory_fd();
|
||||
return parent::getResource();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\ValueException;
|
||||
|
||||
class SharedFile extends FileWriter {
|
||||
const USE_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,400 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\file\csv\csv_flavours;
|
||||
use nulib\file\IReader;
|
||||
use nulib\file\IWriter;
|
||||
use nulib\NoMoreDataException;
|
||||
use nulib\os\EOFException;
|
||||
use nulib\os\IOException;
|
||||
use nulib\php\iter\AbstractIterator;
|
||||
use nulib\ref\os\csv\ref_csv;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
/**
|
||||
* 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 USE_LOCKING = false;
|
||||
|
||||
/** @var resource */
|
||||
protected $fd;
|
||||
|
||||
/** @var bool */
|
||||
protected $close;
|
||||
|
||||
/** @var bool */
|
||||
protected $throwOnError;
|
||||
|
||||
/** @var bool */
|
||||
protected $useLocking;
|
||||
|
||||
/** @var int nombre d'octets à ignorer au début du fichier lors d'un seek */
|
||||
protected $seekOffset = 0;
|
||||
|
||||
/** @var int|null */
|
||||
protected $serial;
|
||||
|
||||
/** @var array */
|
||||
protected $stat;
|
||||
|
||||
function __construct($fd, bool $close=true, bool $throwOnError=true, ?bool $useLocking=null) {
|
||||
if ($fd === null) throw ValueException::null("resource");
|
||||
$this->fd = $fd;
|
||||
$this->close = $close;
|
||||
$this->throwOnError = $throwOnError;
|
||||
if ($useLocking === null) $useLocking = static::USE_LOCKING;
|
||||
$this->useLocking = $useLocking;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# 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_valid(fstat($fd), $this->throwOnError);
|
||||
}
|
||||
return $this->stat;
|
||||
}
|
||||
|
||||
function getSize(?int $seekOffset=null): int {
|
||||
if ($seekOffset === null) $seekOffset = $this->seekOffset;
|
||||
return $this->fstat()["size"] - $seekOffset;
|
||||
}
|
||||
|
||||
/** @throws IOException */
|
||||
function ftell(?int $seekOffset=null): int {
|
||||
$fd = $this->getResource();
|
||||
if ($seekOffset === null) $seekOffset = $this->seekOffset;
|
||||
return IOException::ensure_valid(ftell($fd), $this->throwOnError) - $seekOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int la position après avoir déplacé le pointeur
|
||||
* @throws IOException
|
||||
*/
|
||||
function fseek(int $offset, int $whence=SEEK_SET, ?int $seekOffset=null): int {
|
||||
$fd = $this->getResource();
|
||||
if ($seekOffset === null) $seekOffset = $this->seekOffset;
|
||||
if ($whence === SEEK_SET) $offset += $seekOffset;
|
||||
IOException::ensure_valid(fseek($fd, $offset, $whence), $this->throwOnError, -1);
|
||||
return $this->ftell($seekOffset);
|
||||
}
|
||||
|
||||
function seek(int $offset, int $whence=SEEK_SET): self {
|
||||
$this->fseek($offset, $whence);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** 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;
|
||||
}
|
||||
}
|
||||
|
||||
function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void {
|
||||
$srcr = $this->getResource();
|
||||
$destr = $dest->getResource();
|
||||
if ($srcr !== null && $destr !== null) {
|
||||
while (!feof($srcr)) {
|
||||
fwrite($destr, fread($srcr, 8192));
|
||||
}
|
||||
} else {
|
||||
$dest->fwrite($this->getContents(false));
|
||||
}
|
||||
if ($closeWriter) $dest->close();
|
||||
if ($closeReader) $this->close();
|
||||
}
|
||||
|
||||
const DEFAULT_CSV_FLAVOUR = ref_csv::OO_FLAVOUR;
|
||||
|
||||
/** @var array paramètres pour la lecture et l'écriture de flux au format CSV */
|
||||
protected $csvFlavour;
|
||||
|
||||
function setCsvFlavour(string $flavour): void {
|
||||
$this->csvFlavour = csv_flavours::verifix($flavour);
|
||||
}
|
||||
|
||||
protected function getCsvParams($fd): array {
|
||||
$flavour = $this->csvFlavour;
|
||||
if ($flavour === null) {
|
||||
if ($fd === null) {
|
||||
# utiliser la valeur par défaut
|
||||
$flavour = static::DEFAULT_CSV_FLAVOUR;
|
||||
} else {
|
||||
# il faut déterminer le type de fichier CSV en lisant la première ligne
|
||||
$pos = IOException::ensure_valid(ftell($fd));
|
||||
$line = IOException::ensure_valid(fgets($fd));
|
||||
$line = strpbrk($line, ",;\t");
|
||||
if ($line === false) {
|
||||
# aucun séparateur trouvé, prender la valeur par défaut
|
||||
$flavour = static::DEFAULT_CSV_FLAVOUR;
|
||||
} else {
|
||||
$flavour = substr($line, 0, 1);
|
||||
$flavour = csv_flavours::verifix($flavour);
|
||||
}
|
||||
IOException::ensure_valid(fseek($fd, $pos), true, -1);
|
||||
}
|
||||
$this->csvFlavour = $flavour;
|
||||
}
|
||||
return csv_flavours::get_params($flavour);
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# Reader
|
||||
|
||||
/** @throws IOException */
|
||||
function fread(int $length): string {
|
||||
$fd = $this->getResource();
|
||||
return IOException::ensure_valid(fread($fd, $length), $this->throwOnError);
|
||||
}
|
||||
|
||||
/**
|
||||
* lire la prochaine ligne. la ligne est retournée avec le caractère de fin
|
||||
* de ligne[\r]\n
|
||||
*
|
||||
* @throws EOFException si plus aucune ligne n'est disponible
|
||||
* @throws IOException si une erreur se produit
|
||||
*/
|
||||
function fgets(?int $length=null): string {
|
||||
$fd = $this->getResource();
|
||||
if ($length === null) $r = fgets($fd);
|
||||
else $r = fgets($fd, $length);
|
||||
return EOFException::ensure_not_eof($r, $this->throwOnError);
|
||||
}
|
||||
|
||||
/** @throws IOException */
|
||||
function fpassthru(): int {
|
||||
$fd = $this->getResource();
|
||||
return IOException::ensure_valid(fpassthru($fd), $this->throwOnError);
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner la prochaine ligne au format CSV ou null si le fichier est arrivé
|
||||
* à sa fin
|
||||
*/
|
||||
function fgetcsv(): ?array {
|
||||
$fd = $this->getResource();
|
||||
$params = $this->getCsvParams($fd);
|
||||
$row = fgetcsv($fd, 0, $params[0], $params[1], $params[2]);
|
||||
if ($row === false && feof($fd)) return null;
|
||||
return IOException::ensure_valid($row, $this->throwOnError);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 erreur se produit
|
||||
*/
|
||||
function readLine(): ?string {
|
||||
return str::strip_nl($this->fgets());
|
||||
}
|
||||
|
||||
/** lire et retourner toutes les lignes */
|
||||
function readLines(): array {
|
||||
return iterator_to_array($this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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->useLocking) 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->useLocking && !$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 {
|
||||
$useLocking = $this->useLocking;
|
||||
if ($useLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
|
||||
try {
|
||||
return IOException::ensure_valid(stream_get_contents($this->fd), $this->throwOnError);
|
||||
} finally {
|
||||
if ($useLocking) $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) {
|
||||
try {
|
||||
return $this->fgets();
|
||||
} catch (EOFException $e) {
|
||||
throw new NoMoreDataException();
|
||||
}
|
||||
}
|
||||
|
||||
protected function _teardown(): void {
|
||||
$md = stream_get_meta_data($this->fd);
|
||||
if ($md["seekable"]) $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_valid($r, $this->throwOnError);
|
||||
}
|
||||
|
||||
function fputcsv(array $row): void {
|
||||
$fd = $this->getResource();
|
||||
$params = $this->getCsvParams($fd);
|
||||
IOException::ensure_valid(fputcsv($fd, $row, $params[0], $params[1], $params[2]));
|
||||
}
|
||||
|
||||
/** @throws IOException */
|
||||
function fflush(): self {
|
||||
$fd = $this->getResource();
|
||||
IOException::ensure_valid(fflush($fd), $this->throwOnError);
|
||||
return $this;
|
||||
}
|
||||
|
||||
/** @throws IOException */
|
||||
function ftruncate(int $size): self {
|
||||
$fd = $this->getResource();
|
||||
IOException::ensure_valid(ftruncate($fd, $size), $this->throwOnError);
|
||||
return $this;
|
||||
}
|
||||
|
||||
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->useLocking) 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->useLocking && !$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 {
|
||||
$useLocking = $this->useLocking;
|
||||
if ($useLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
|
||||
try {
|
||||
$this->fwrite($contents);
|
||||
} finally {
|
||||
if ($useLocking) $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,50 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\file\_IFile;
|
||||
use nulib\os\IOException;
|
||||
|
||||
trait TStreamFilter {
|
||||
private $filters = null;
|
||||
|
||||
function appendFilter(string $filterName, ?int $readWrite=null, $params=null): void {
|
||||
$this->filters[] = [$filterName, $readWrite, $params];
|
||||
}
|
||||
|
||||
function prependFilter(string $filterName, ?int $readWrite=null, $params=null): void {
|
||||
if ($this->filters === null) $this->filters = [];
|
||||
array_unshift($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,29 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
/**
|
||||
* Class TempStream: un flux qui peut être lu ou écrit, et qui reste en mémoire,
|
||||
* jusqu'à ce que la taille des données atteint {@link self::MAX_MEMORY} et à
|
||||
* ce moment-là un fichier temporaire est automatiquement créé.
|
||||
*/
|
||||
class TempStream extends Stream {
|
||||
const MAX_MEMORY = 2 * 1024 * 1024;
|
||||
|
||||
function __construct(?int $maxMemory=null, bool $throwOnError=true) {
|
||||
if ($maxMemory === null) $maxMemory = static::MAX_MEMORY;
|
||||
$this->maxMemory = $maxMemory;
|
||||
parent::__construct($this->tempFd(), true, $throwOnError);
|
||||
}
|
||||
|
||||
/** @var int */
|
||||
protected $maxMemory;
|
||||
|
||||
protected function tempFd() {
|
||||
return fopen("php://temp/maxmemory:$this->maxMemory", "w+b");
|
||||
}
|
||||
|
||||
function getResource() {
|
||||
if ($this->fd === null) $this->fd = $this->tempFd();
|
||||
return parent::getResource();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\os\path;
|
||||
|
||||
/**
|
||||
* Class TmpfileWriter: un fichier temporaire accédé en lecture/écriture
|
||||
*/
|
||||
class TmpfileWriter extends FileWriter {
|
||||
const DEFAULT_MODE = "w+b";
|
||||
|
||||
function __construct(?string $destdir=null, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
$tmpDir = sys_get_temp_dir();
|
||||
if ($destdir === null) $destdir = $tmpDir;
|
||||
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;
|
||||
if (!path::is_qualified($file)) $file = path::join($tmpDir, $file);
|
||||
$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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use nulib\os\IOException;
|
||||
use nulib\web\http;
|
||||
|
||||
abstract class _File extends Stream {
|
||||
function __construct($fd, bool $close, bool $throwOnError=true, ?bool $allowLocking=null) {
|
||||
parent::__construct($fd, $close, $throwOnError, $allowLocking);
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $file;
|
||||
|
||||
/** @var string */
|
||||
protected $mode;
|
||||
|
||||
/** @return resource */
|
||||
protected function open() {
|
||||
return IOException::ensure_valid(@fopen($this->file, $this->mode));
|
||||
}
|
||||
|
||||
/** @return resource */
|
||||
function getResource() {
|
||||
if ($this->fd === null && $this->file !== null) $this->fd = $this->open();
|
||||
return parent::getResource();
|
||||
}
|
||||
|
||||
/** 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,46 @@
|
|||
<?php
|
||||
namespace nulib\file\csv;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\ref\os\csv\ref_csv;
|
||||
|
||||
class csv_flavours {
|
||||
const MAP = [
|
||||
"oo" => ref_csv::OO_FLAVOUR,
|
||||
"ooffice" => ref_csv::OO_FLAVOUR,
|
||||
ref_csv::OO_NAME => ref_csv::OO_FLAVOUR,
|
||||
"xl" => ref_csv::XL_FLAVOUR,
|
||||
"excel" => ref_csv::XL_FLAVOUR,
|
||||
ref_csv::XL_NAME => ref_csv::XL_FLAVOUR,
|
||||
];
|
||||
|
||||
const ENCODINGS = [
|
||||
ref_csv::OO_FLAVOUR => ref_csv::OO_ENCODING,
|
||||
ref_csv::XL_FLAVOUR => ref_csv::XL_ENCODING,
|
||||
];
|
||||
|
||||
static final function verifix(string $flavour): string {
|
||||
$lflavour = strtolower($flavour);
|
||||
if (array_key_exists($lflavour, self::MAP)) {
|
||||
$flavour = self::MAP[$lflavour];
|
||||
}
|
||||
if (strlen($flavour) < 1) $flavour .= ",";
|
||||
if (strlen($flavour) < 2) $flavour .= "\"";
|
||||
if (strlen($flavour) < 3) $flavour .= "\\";
|
||||
return $flavour;
|
||||
}
|
||||
|
||||
static final function get_name(string $flavour): string {
|
||||
if ($flavour == ref_csv::OO_FLAVOUR) return ref_csv::OO_NAME;
|
||||
elseif ($flavour == ref_csv::XL_FLAVOUR) return ref_csv::XL_NAME;
|
||||
else return $flavour;
|
||||
}
|
||||
|
||||
static final function get_params(string $flavour): array {
|
||||
return [$flavour[0], $flavour[1], $flavour[2]];
|
||||
}
|
||||
|
||||
static final function get_encoding(string $flavour): ?string {
|
||||
return cl::get(self::ENCODINGS, $flavour);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
namespace nulib\os;
|
||||
|
||||
/**
|
||||
* Class EOFException: indiquer que plus aucune donnée n'est disponible sur un
|
||||
* flux
|
||||
*/
|
||||
class EOFException extends IOException {
|
||||
static final function ensure_not_eof($data, bool $throw=true, $eof=false) {
|
||||
if ($data !== $eof) return $data;
|
||||
elseif (!$throw) return null;
|
||||
else throw new self();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
namespace nulib\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_valid($value, bool $throw=true, $invalid=false) {
|
||||
if ($value !== $invalid) return $value;
|
||||
elseif (!$throw) return null;
|
||||
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,7 @@
|
|||
# nulib\os
|
||||
|
||||
Ce package contient des services permettant d'interagir avec le système d'exploitation
|
||||
|
||||
exemple: args, etc.
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,8 @@
|
|||
# TODO
|
||||
|
||||
* [ ] pour les classes Reader, options pour ignorer automatiquement le BOM
|
||||
important notamment pour un futur CsvReader.
|
||||
les méthodes size(), seek(), etc. tiennent compte de la taille du BOM et
|
||||
corrigent les valeurs en conséquence...
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -0,0 +1,314 @@
|
|||
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
|
||||
namespace nulib\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($path): ?string {
|
||||
if ($path === null || $path === false) return null;
|
||||
$path = strval($path);
|
||||
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: ".";
|
||||
}
|
||||
|
||||
/** obtenir le chemin absolu normalisé correspondant à $path */
|
||||
static final function abspath($path, ?string $cwd=null): ?string {
|
||||
if ($path === null || $path === false) return null;
|
||||
$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): ?string {
|
||||
if ($path === null || $path === false) return null;
|
||||
$path = strval($path);
|
||||
return IOException::ensure_valid(realpath($path));
|
||||
}
|
||||
|
||||
/**
|
||||
* obtenir le chemin parent de $path
|
||||
*
|
||||
* cas particuliers:
|
||||
* dirname("/") === "/";
|
||||
* dirname("filename") === ".";
|
||||
*/
|
||||
static final function dirname($path): ?string {
|
||||
if ($path === null || $path === false) return null;
|
||||
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 null;
|
||||
$path = strval($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 null;
|
||||
$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 null;
|
||||
$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 [null, null];
|
||||
elseif ($path === "") return ["", ""];
|
||||
$path = strval($path);
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* s'assurer que $path a l'extension $new_ext. si $path a l'une des extensions
|
||||
* de $replace_ext, l'enlever avant de rajouter $new_ext.
|
||||
*
|
||||
* si $strict === false alors $new_ext est ajouté à la liste $replace_ext
|
||||
* c'est à dire que si $path a déjà l'extension $new_ext, elle n'est pas
|
||||
* rajoutée de nouveau.
|
||||
*
|
||||
* si $strict === true alors seules les extensions de $replace_ext sont
|
||||
* enlevées le cas échéant, et $new_ext est systématiquement rajouté
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $new_ext
|
||||
* @param string|array|null $replace_ext
|
||||
* @return string
|
||||
*/
|
||||
static final function ensure_ext(string $path, string $new_ext, $replace_ext=null, bool $strict=false): string {
|
||||
[$dir, $filename] = self::split($path);
|
||||
$ext = self::ext($filename);
|
||||
if (is_string($replace_ext)) $replace_ext = [$replace_ext];
|
||||
if (!$strict) $replace_ext[] = $new_ext;
|
||||
if ($ext !== null && $replace_ext !== null) {
|
||||
foreach ($replace_ext as $old_ext) {
|
||||
if ($ext === $old_ext) {
|
||||
$filename = self::basename($filename);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
$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 nulib\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 verifix_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 verifix_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::verifix_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::verifix_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::verifix_cmd($cmd, $redir);
|
||||
return self::_exec($cmd, $output, $retcode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Corriger la commande spécifiée avec {@link verifix_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::verifix_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);
|
||||
}
|
||||
}
|
|
@ -4,5 +4,8 @@
|
|||
rotation des logs
|
||||
* [ ] lors de la rotation, si l'ouverture du nouveau fichier échoue, continuer
|
||||
à écrire dans l'ancien fichier
|
||||
* [ ] dans `StdMessenger::resetParams()`, `[output]` peut être une instance de
|
||||
StdOutput pour mettre à jour $out ET $err, ou un tableau de deux éléments pour
|
||||
mettre à jour séparément $out et $err
|
||||
|
||||
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary
|
|
@ -6,6 +6,9 @@ use nulib\output\std\ProxyMessenger;
|
|||
|
||||
/**
|
||||
* Class log: inscrire un message dans les logs uniquement
|
||||
*
|
||||
* Cette classe (ou la classe parallèle {@link msg} DOIT être initialisée avant
|
||||
* d'être utilisée
|
||||
*/
|
||||
class log extends _messenger {
|
||||
static function set_messenger(IMessenger $log=null) {
|
||||
|
|
|
@ -6,6 +6,10 @@ use nulib\output\std\ProxyMessenger;
|
|||
|
||||
/**
|
||||
* Class msg: inscrire un message dans les logs ET l'afficher sur la console
|
||||
*
|
||||
* Cette classe DOIT être initialisée avec {@link set_messenger()} ou
|
||||
* {@link set_messenger_class()} avant d'être utilisée. Une fois initialisée,
|
||||
* les classes {@link say} et {@link log} sont utilisables aussi
|
||||
*/
|
||||
class msg extends _messenger {
|
||||
static function set_messenger(IMessenger $say, ?IMessenger $log=null) {
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
namespace nulib\output;
|
||||
|
||||
use nulib\output\std\StdOutput;
|
||||
|
||||
/**
|
||||
* Class out: affichage sur la sortie standard
|
||||
*/
|
||||
class out {
|
||||
/** @var StdOutput */
|
||||
private static $out;
|
||||
|
||||
static function get(): StdOutput {
|
||||
return self::$out;
|
||||
}
|
||||
protected static function set(StdOutput $out): StdOutput {
|
||||
return self::$out = $out;
|
||||
}
|
||||
|
||||
/** reparamétrer l'instance */
|
||||
static function reset($output=null, ?array $params=null): StdOutput {
|
||||
if (self::$out === null) return self::set(new StdOutput($output, $params));
|
||||
if ($output !== null) $params["output"] = $output;
|
||||
self::$out->resetParams($params);
|
||||
return self::$out;
|
||||
}
|
||||
|
||||
static function write(...$values): void { self::$out->write(...$values); }
|
||||
static function print(...$values): void { self::$out->print(...$values); }
|
||||
|
||||
static function iwrite(int $indentLevel, ...$values): void { self::$out->iwrite($indentLevel, ...$values); }
|
||||
static function iprint(int $indentLevel, ...$values): void { self::$out->iprint($indentLevel, ...$values); }
|
||||
}
|
||||
out::reset();
|
|
@ -6,6 +6,9 @@ use nulib\output\std\ProxyMessenger;
|
|||
|
||||
/**
|
||||
* Class say: afficher un message sur la console uniquement
|
||||
*
|
||||
* Cette classe (ou la classe parallèle {@link msg} DOIT être initialisée avant
|
||||
* d'être utilisée
|
||||
*/
|
||||
class say extends _messenger {
|
||||
static function set_messenger(IMessenger $say) {
|
||||
|
|
|
@ -150,9 +150,14 @@ class StdMessenger implements _IMessenger {
|
|||
"color" => $color,
|
||||
"indent" => $indent,
|
||||
];
|
||||
if ($output !== null) {
|
||||
if ($this->out === $this->err) {
|
||||
$this->out->resetParams($params);
|
||||
} else {
|
||||
# NB: si initialement [output] était null, et qu'on spécifie une valeur
|
||||
# [output], alors les deux instances $out et $err sont mis à jour
|
||||
# séparément avec la même valeur de output
|
||||
# de plus, on ne peut plus revenir à la situation initiale avec une
|
||||
# destination différente pour $out et $err
|
||||
$this->out->resetParams($params);
|
||||
$this->err->resetParams($params);
|
||||
}
|
||||
|
|
|
@ -3,8 +3,10 @@ namespace nulib\output\std;
|
|||
|
||||
use Exception;
|
||||
use nulib\cl;
|
||||
use nulib\output\IContent;
|
||||
use nulib\output\IPrintable;
|
||||
use nulib\os\file\Stream;
|
||||
use nulib\php\content\content;
|
||||
use nulib\php\content\IContent;
|
||||
use nulib\php\content\IPrintable;
|
||||
|
||||
/**
|
||||
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
|
||||
|
@ -86,6 +88,7 @@ class StdOutput {
|
|||
$indent = cl::get($params, "indent");
|
||||
$flush = cl::get($params, "flush");
|
||||
|
||||
if ($output instanceof Stream) $output = $output->getResource();
|
||||
if ($output !== null) {
|
||||
if ($output === "php://stdout") {
|
||||
$outf = STDOUT;
|
||||
|
@ -181,37 +184,12 @@ class StdOutput {
|
|||
return preg_replace('/\x1B\[.*?m/', "", $text);
|
||||
}
|
||||
|
||||
static function flatten($values, ?array &$dest=null): array {
|
||||
if ($dest === null) $dest = [];
|
||||
if ($values === null) return $dest;
|
||||
if (is_string($values)) {
|
||||
$dest[] = $values;
|
||||
return $dest;
|
||||
} elseif (!is_array($values)) {
|
||||
if ($values instanceof IContent) {
|
||||
$values = $values->getContent();
|
||||
} elseif ($values instanceof IPrintable) {
|
||||
ob_start(null, 0, PHP_OUTPUT_HANDLER_STDFLAGS ^ PHP_OUTPUT_HANDLER_FLUSHABLE);
|
||||
$values->print();
|
||||
$dest[] = ob_get_clean();
|
||||
return $dest;
|
||||
} elseif (!is_iterable($values)) {
|
||||
$dest[] = strval($values);
|
||||
return $dest;
|
||||
}
|
||||
}
|
||||
foreach ($values as $value) {
|
||||
self::flatten($value, $dest);
|
||||
}
|
||||
return $dest;
|
||||
}
|
||||
|
||||
function getIndent(int $indentLevel): string {
|
||||
return str_repeat($this->indent, $indentLevel);
|
||||
}
|
||||
|
||||
function getLines(bool $withNl, ...$values): array {
|
||||
$values = self::flatten($values);
|
||||
$values = content::flatten($values);
|
||||
if (!$values) return [];
|
||||
$text = implode("", $values);
|
||||
if ($text === "") return [""];
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
namespace nulib\php\coll;
|
||||
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class AutoArray: un tableau dont les éléments peuvent être accédés comme des
|
||||
* propriétés
|
||||
*/
|
||||
class AutoArray extends BaseArray {
|
||||
const _AUTO_PROPERTIES = null;
|
||||
static function _AUTO_PROPERTIES(): ?array { return static::_AUTO_PROPERTIES; }
|
||||
|
||||
function __isset($name) {
|
||||
if ($this->has($name)) return true;
|
||||
$properties = self::_AUTO_PROPERTIES();
|
||||
if ($properties === null) return false;
|
||||
return array_key_exists($name, $properties);
|
||||
}
|
||||
function __get($name) {
|
||||
$properties = self::_AUTO_PROPERTIES();
|
||||
if ($this->has($name)) return $this->get($name);
|
||||
$pkey = cl::get($properties, $name, $name);
|
||||
return cl::pget($this->data, $pkey);
|
||||
}
|
||||
function __set($name, $value) {
|
||||
$properties = self::_AUTO_PROPERTIES();
|
||||
if ($this->has($name)) {
|
||||
$this->set($name, $value);
|
||||
} else {
|
||||
$pkey = cl::get($properties, $name, $name);
|
||||
cl::pset($this->data, $pkey, $value);
|
||||
}
|
||||
}
|
||||
function __unset($name) {
|
||||
$properties = self::_AUTO_PROPERTIES();
|
||||
if ($this->has($name)) {
|
||||
$this->del($name);
|
||||
} else {
|
||||
$pkey = cl::get($properties, $name, $name);
|
||||
cl::pdel($this->data, $pkey);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
<?php
|
||||
namespace nulib\php\coll;
|
||||
|
||||
use ArrayAccess;
|
||||
use Countable;
|
||||
use Iterator;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class BaseArray: implémentation de base d'un objet array-like, qui peut aussi
|
||||
* servir comme front-end pour un array
|
||||
*/
|
||||
class BaseArray implements ArrayAccess, Countable, Iterator {
|
||||
function __construct(?array &$data=null) {
|
||||
$this->reset($data);
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $data;
|
||||
|
||||
function __toString(): string { return var_export($this->data, true); }
|
||||
#function __debugInfo() { return $this->data; }
|
||||
function reset(?array &$data): void { $this->data =& $data; }
|
||||
function &array(): ?array { return $this->data; }
|
||||
function count(): int { return $this->data !== null? count($this->data): 0; }
|
||||
function keys(): array { return $this->data !== null? array_keys($this->data): []; }
|
||||
|
||||
#############################################################################
|
||||
# base
|
||||
|
||||
function has($key): bool {
|
||||
return $this->data !== null && array_key_exists($key, $this->data);
|
||||
}
|
||||
function &get($key, $default=null) {
|
||||
if ($this->data !== null && array_key_exists($key, $this->data)) {
|
||||
return $this->data[$key];
|
||||
} else return $default;
|
||||
}
|
||||
function set($key, $value): void {
|
||||
if ($key === null) $this->data[] = $value;
|
||||
else $this->data[$key] = $value;
|
||||
}
|
||||
function del($key): void {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
||||
function offsetExists($offset): bool { return $this->has($offset); }
|
||||
function &offsetGet($offset) { return $this->get($offset); }
|
||||
function offsetSet($offset, $value) { $this->set($offset, $value); }
|
||||
function offsetUnset($offset) { $this->del($offset); }
|
||||
|
||||
function __isset($name) { return $this->has($name); }
|
||||
function &__get($name) { return $this->get($name); }
|
||||
function __set($name, $value) { $this->set($name, $value); }
|
||||
function __unset($name) { $this->del($name); }
|
||||
|
||||
#############################################################################
|
||||
# iterator
|
||||
|
||||
/** @var bool */
|
||||
private $valid = false;
|
||||
|
||||
function rewind() {
|
||||
if ($this->data !== null) {
|
||||
$first = reset($this->data);
|
||||
$this->valid = $first !== false || key($this->data) !== null;
|
||||
} else {
|
||||
$this->valid = false;
|
||||
}
|
||||
}
|
||||
function valid(): bool { return $this->valid; }
|
||||
function key() { return key($this->data); }
|
||||
function current() { return current($this->data); }
|
||||
function next() {
|
||||
$next = next($this->data);
|
||||
$this->valid = $next !== false || key($this->data) !== null;
|
||||
}
|
||||
|
||||
#############################################################################
|
||||
# divers
|
||||
|
||||
function phas($pkey): bool { return cl::phas($this->data, $pkey); }
|
||||
function pget($pkey, $default=null): bool { return cl::pget($this->data, $pkey, $default); }
|
||||
function pset($pkey, $value): void { cl::pset($this->data, $pkey, $value); }
|
||||
function pdel($pkey): void { cl::pdel($this->data, $pkey); }
|
||||
|
||||
function contains($value, bool $strict=false): bool {
|
||||
if ($value === null || $this->data === null) return false;
|
||||
return in_array($value, $this->data, $strict);
|
||||
}
|
||||
|
||||
function add($value, bool $unique=true, bool $strict=false): bool {
|
||||
if ($unique && $this->contains($value, $strict)) return false;
|
||||
$this->set(null, $value);
|
||||
return true;
|
||||
}
|
||||
|
||||
function addAll(?array $values, bool $unique=true, bool $strict=false): void {
|
||||
if ($values === null) return;
|
||||
$index = 0;
|
||||
foreach ($values as $key => $value) {
|
||||
if ($key === $index) {
|
||||
$this->add($value, $unique, $strict);
|
||||
$index++;
|
||||
} else {
|
||||
$this->set($key, $value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function resetAll(?array $values): void {
|
||||
$this->data = null;
|
||||
$this->addAll($values);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,7 @@ namespace nulib\php\content;
|
|||
|
||||
/**
|
||||
* Interface IContent: un objet capable de produire du contenu à afficher. le
|
||||
* contenu retourné doit être pris tel quel,sans plus d'analyse
|
||||
* contenu retourné doit être pris tel quel, sans plus d'analyse
|
||||
*/
|
||||
interface IContent {
|
||||
/** retourner le contenu à afficher */
|
||||
|
|
|
@ -4,7 +4,7 @@ namespace nulib\php;
|
|||
use Closure;
|
||||
use nulib\cl;
|
||||
use nulib\ValueException;
|
||||
use nulib\ref\sys\ref_func;
|
||||
use nulib\ref\php\ref_func;
|
||||
use nulib\schema\Schema;
|
||||
use ReflectionClass;
|
||||
use ReflectionFunction;
|
||||
|
@ -300,12 +300,16 @@ class func {
|
|||
return true;
|
||||
}
|
||||
|
||||
/** @var Schema */
|
||||
private static $call_all_params_schema;
|
||||
|
||||
/**
|
||||
* retourner la liste des méthodes de $class_or_object qui correspondent au
|
||||
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
|
||||
*/
|
||||
static function get_all($class_or_object, $params=null): array {
|
||||
Schema::nv($paramsv, $params, null, $schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
|
||||
Schema::nv($paramsv, $params, null
|
||||
, self::$call_all_params_schema, ref_func::CALL_ALL_PARAMS_SCHEMA);
|
||||
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
|
||||
# callable sous forme de tableau
|
||||
$class_or_object = $class_or_object[0];
|
||||
|
|
|
@ -3,7 +3,7 @@ namespace nulib\php\iter;
|
|||
|
||||
use Exception;
|
||||
use Iterator;
|
||||
use nulib\DataException;
|
||||
use nulib\NoMoreDataException;
|
||||
use nulib\php\ICloseable;
|
||||
|
||||
/**
|
||||
|
@ -29,12 +29,13 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
protected function beforeIter() {}
|
||||
|
||||
/**
|
||||
* retourner le prochain élément. lancer l'exception {@link DataException}
|
||||
* pour indiquer que plus aucun élément n'est disponible
|
||||
* retourner le prochain élément.
|
||||
* lancer l'exception {@link NoMoreDataException} pour indiquer que plus aucun
|
||||
* élément n'est disponible
|
||||
*
|
||||
* le cas échéant, initialiser $key
|
||||
*
|
||||
* @throws DataException
|
||||
* @throws NoMoreDataException
|
||||
*/
|
||||
abstract protected function _next(&$key);
|
||||
|
||||
|
@ -94,7 +95,7 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
|||
$this->valid = false;
|
||||
try {
|
||||
$item = $this->_next($key);
|
||||
} catch (DataException $e) {
|
||||
} catch (NoMoreDataException $e) {
|
||||
$this->beforeClose();
|
||||
try {
|
||||
$this->_teardown();
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeZone;
|
||||
|
||||
/**
|
||||
* Class Date: une date
|
||||
*/
|
||||
class Date extends DateTime {
|
||||
const DEFAULT_FORMAT = "d/m/Y";
|
||||
|
||||
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
||||
parent::__construct($datetime, $timezone);
|
||||
$this->setTime(0, 0, 0, 0);
|
||||
}
|
||||
|
||||
function format($format=self::DEFAULT_FORMAT): string {
|
||||
return \DateTime::format($format);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class DateInterval extends \DateInterval {
|
||||
protected static function to_string(DateInterval $interval) {
|
||||
$string = "P";
|
||||
$y = $interval->y;
|
||||
$m = $interval->m;
|
||||
$d = $interval->d;
|
||||
if ($y > 0) $string .= "${y}Y";
|
||||
if ($m > 0) $string .= "${m}M";
|
||||
if ($d > 0) $string .= "${d}D";
|
||||
$string .= "T";
|
||||
$h = $interval->h;
|
||||
$i = $interval->i;
|
||||
$s = $interval->s;
|
||||
if ($h > 0) $string .= "${h}H";
|
||||
if ($i > 0) $string .= "${i}M";
|
||||
if ($s > 0 || $string == "PT") $string .= "${s}S";
|
||||
if ($interval->invert == 1) $string = "-$string";
|
||||
return $string;
|
||||
}
|
||||
|
||||
function __construct($duration) {
|
||||
if ($duration instanceof \DateInterval) {
|
||||
$this->y = $duration->y;
|
||||
$this->m = $duration->m;
|
||||
$this->d = $duration->d;
|
||||
$this->h = $duration->h;
|
||||
$this->i = $duration->i;
|
||||
$this->s = $duration->s;
|
||||
$this->invert = $duration->invert;
|
||||
$this->days = $duration->days;
|
||||
} elseif (!is_string($duration)) {
|
||||
throw new InvalidArgumentException("duration must be a string");
|
||||
} else {
|
||||
if (substr($duration, 0, 1) == "-") {
|
||||
$duration = substr($duration, 1);
|
||||
$invert = true;
|
||||
} else {
|
||||
$invert = false;
|
||||
}
|
||||
parent::__construct($duration);
|
||||
if ($invert) $this->invert = 1;
|
||||
}
|
||||
}
|
||||
|
||||
function __toString(): string {
|
||||
return self::to_string($this);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
use DateTimeZone;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class DateTime: une date et une heure
|
||||
*
|
||||
* @property-read int $year
|
||||
* @property-read int $month
|
||||
* @property-read int $day
|
||||
* @property-read int $hour
|
||||
* @property-read int $minute
|
||||
* @property-read int $second
|
||||
* @property-read int $wday
|
||||
* @property-read int $wnum
|
||||
* @property-read string $timezone
|
||||
* @property-read string $datetime
|
||||
* @property-read string $date
|
||||
* @property-read string $Ymd
|
||||
* @property-read string $YmdHMS
|
||||
* @property-read string $YmdHMSZ
|
||||
*/
|
||||
class DateTime extends \DateTime {
|
||||
const DMY_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))?$/';
|
||||
const YMD_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})$/';
|
||||
const DMYHIS_PATTERN = '/^(\d+)\/(\d+)(?:\/(\d+))? +(\d+)[h:.](\d+)(?:[:.](\d+))?$/';
|
||||
const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})(\d{2})(\d{2})[tT](\d{2})(\d{2})(\d{2})?([zZ])?$/';
|
||||
|
||||
static function _YmdHMSZ_format(\DateTime $datetime): string {
|
||||
$YmdHMS = $datetime->format("Ymd\\THis");
|
||||
$Z = $datetime->format("P");
|
||||
if ($Z === "+00:00") $Z = "Z";
|
||||
return "$YmdHMS$Z";
|
||||
}
|
||||
|
||||
const DEFAULT_FORMAT = "d/m/Y H:i:s";
|
||||
const INT_FORMATS = [
|
||||
"year" => "Y",
|
||||
"month" => "m",
|
||||
"day" => "d",
|
||||
"hour" => "H",
|
||||
"minute" => "i",
|
||||
"second" => "s",
|
||||
"wday" => "N",
|
||||
"wnum" => "W",
|
||||
];
|
||||
const STRING_FORMATS = [
|
||||
"timezone" => "P",
|
||||
"datetime" => "d/m/Y H:i:s",
|
||||
"date" => "d/m/Y",
|
||||
"Ymd" => "Ymd",
|
||||
"YmdHMS" => "Ymd\\THis",
|
||||
"YmdHMSZ" => [self::class, "_YmdHMSZ_format"],
|
||||
];
|
||||
|
||||
static function clone(DateTimeInterface $dateTime): self {
|
||||
if ($dateTime instanceof static) return clone $dateTime;
|
||||
$clone = new static();
|
||||
$clone->setTimestamp($dateTime->getTimestamp());
|
||||
$clone->setTimezone($dateTime->getTimezone());
|
||||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* corriger une année à deux chiffres qui est située dans le passé et
|
||||
* retourner l'année à 4 chiffres.
|
||||
*
|
||||
* par exemple, si l'année courante est 2019, alors:
|
||||
* - fix_past_year('18') === '2018'
|
||||
* - fix_past_year('19') === '1919'
|
||||
* - fix_past_year('20') === '1920'
|
||||
*/
|
||||
static function fix_past_year(int $year): int {
|
||||
if ($year < 100) {
|
||||
$y = getdate(); $y = $y["year"];
|
||||
$r = $y % 100;
|
||||
$c = $y - $r;
|
||||
if ($year >= $r) $year += $c - 100;
|
||||
else $year += $c;
|
||||
}
|
||||
return $year;
|
||||
}
|
||||
|
||||
/**
|
||||
* corriger une année à deux chiffres et retourner l'année à 4 chiffres.
|
||||
* l'année charnière entre année passée et année future est 70
|
||||
*
|
||||
* par exemple, si l'année courante est 2019, alors:
|
||||
* - fix_past_year('18') === '2018'
|
||||
* - fix_past_year('19') === '2019'
|
||||
* - fix_past_year('20') === '2020'
|
||||
* - fix_past_year('69') === '2069'
|
||||
* - fix_past_year('70') === '1970'
|
||||
* - fix_past_year('71') === '1971'
|
||||
*/
|
||||
static function fix_any_year(int $year): int {
|
||||
if ($year < 100) {
|
||||
$y = intval(date("Y"));
|
||||
$r = $y % 100;
|
||||
$c = $y - $r;
|
||||
if ($year >= 70) $year += $c - 100;
|
||||
else $year += $c;
|
||||
}
|
||||
return $year;
|
||||
}
|
||||
|
||||
function __construct($datetime="now", DateTimeZone $timezone=null) {
|
||||
if ($datetime instanceof \DateTimeInterface) {
|
||||
if ($timezone === null) $timezone = $datetime->getTimezone();
|
||||
parent::__construct();
|
||||
$this->setTimestamp($datetime->getTimestamp());
|
||||
$this->setTimezone($timezone);
|
||||
} elseif (is_int($datetime)) {
|
||||
parent::__construct("now", $timezone);
|
||||
$this->setTimestamp($datetime);
|
||||
} elseif (!is_string($datetime)) {
|
||||
throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface");
|
||||
} else {
|
||||
$Y = $H = $Z = null;
|
||||
if (preg_match(self::DMY_PATTERN, $datetime, $ms)) {
|
||||
$Y = $ms[3] ?? null;
|
||||
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
|
||||
else $Y = intval(date("Y"));
|
||||
$m = intval($ms[2]);
|
||||
$d = intval($ms[1]);
|
||||
} elseif (preg_match(self::YMD_PATTERN, $datetime, $ms)) {
|
||||
$Y = $ms[1];
|
||||
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
|
||||
else $Y = intval($Y);
|
||||
$m = intval($ms[2]);
|
||||
$d = intval($ms[3]);
|
||||
} elseif (preg_match(self::DMYHIS_PATTERN, $datetime, $ms)) {
|
||||
$Y = $ms[3];
|
||||
if ($Y !== null) $Y = self::fix_any_year(intval($Y));
|
||||
else $Y = intval(date("Y"));
|
||||
$m = intval($ms[2]);
|
||||
$d = intval($ms[1]);
|
||||
$H = intval($ms[4]);
|
||||
$M = intval($ms[5]);
|
||||
$S = intval($ms[6] ?? 0);
|
||||
} elseif (preg_match(self::YMDHISZ_PATTERN, $datetime, $ms)) {
|
||||
$Y = $ms[1];
|
||||
if (strlen($Y) == 2) $Y = self::fix_any_year(intval($ms[1]));
|
||||
else $Y = intval($Y);
|
||||
$m = intval($ms[2]);
|
||||
$d = intval($ms[3]);
|
||||
$H = intval($ms[4]);
|
||||
$M = intval($ms[5]);
|
||||
$S = intval($ms[6] ?? 0);
|
||||
$Z = $ms[7] ?? null;
|
||||
}
|
||||
if ($Y !== null) {
|
||||
if ($H === null) $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d);
|
||||
else $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H, $M, $S);
|
||||
if ($Z !== null) $timezone = new DateTimeZone("UTC");
|
||||
}
|
||||
parent::__construct($datetime, $timezone);
|
||||
}
|
||||
}
|
||||
|
||||
function diff($target, $absolute=false): DateInterval {
|
||||
return new DateInterval(parent::diff($target, $absolute));
|
||||
}
|
||||
|
||||
function format($format=self::DEFAULT_FORMAT): string {
|
||||
return \DateTime::format($format);
|
||||
}
|
||||
|
||||
function __toString(): string {
|
||||
return $this->format();
|
||||
}
|
||||
|
||||
function __get($name) {
|
||||
if (array_key_exists($name, self::INT_FORMATS)) {
|
||||
$format = self::INT_FORMATS[$name];
|
||||
if (is_callable($format)) return $format($this);
|
||||
else return intval($this->format($format));
|
||||
} elseif (array_key_exists($name, self::STRING_FORMATS)) {
|
||||
$format = self::STRING_FORMATS[$name];
|
||||
if (is_callable($format)) return $format($this);
|
||||
else return $this->format($format);
|
||||
}
|
||||
throw new InvalidArgumentException("Unknown property $name");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeInterface;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Class Delay: une durée jusqu'à un moment destination. le moment destination
|
||||
* est calculé à la création de l'objet en fonction du moment courant.
|
||||
*
|
||||
* La durée peut-être:
|
||||
* - un nombre, qui est le nombre de secondes jusqu'au moment destination
|
||||
* - une chaine de la forme "x[WDHMS]y" où x et y sont des nombres et la lettre
|
||||
* est l'unité de temps: W représente une semaine, D une journée, H une heure,
|
||||
* M une minute et S une seconde.
|
||||
*
|
||||
* Dans cette dernière forme, le timestamp destination est calculé en ajoutant x
|
||||
* unités. puis l'unité inférieure est ramenée à (0 + y)
|
||||
* par exemple,
|
||||
* - "1D5" signifie le lendemain à 5h (+1 jour, puis ramené à 5h)
|
||||
* - "2H0" signifie au plus deux heures plus tard
|
||||
* - "0W5" signifie à 5 le dimanche de la semaine en cours
|
||||
*
|
||||
* NB: la valeur y pour l'unité S est ignorée
|
||||
*/
|
||||
class Delay {
|
||||
/** valeurs par défaut de x et y pour les unités supportées */
|
||||
const DEFAULTS = [
|
||||
"w" => [0, 5],
|
||||
"d" => [1, 5],
|
||||
"h" => [1, 0],
|
||||
"m" => [1, 0],
|
||||
"s" => [1, 0],
|
||||
];
|
||||
|
||||
static function compute_dest(int $x, string $u, ?int $y, DateTime $from): array {
|
||||
$dest = DateTime::clone($from);
|
||||
switch ($u) {
|
||||
case "w":
|
||||
if ($x > 0) {
|
||||
$x *= 7;
|
||||
$dest->add(new \DateInterval("P${x}D"));
|
||||
}
|
||||
$w = 7 - intval($dest->format("w"));
|
||||
$dest->add(new \DateInterval("P${w}D"));
|
||||
$u = "h";
|
||||
break;
|
||||
case "d":
|
||||
$dest->add(new \DateInterval("P${x}D"));
|
||||
$u = "h";
|
||||
break;
|
||||
case "h":
|
||||
$dest->add(new \DateInterval("PT${x}H"));
|
||||
$u = "m";
|
||||
break;
|
||||
case "m":
|
||||
$dest->add(new \DateInterval("PT${x}M"));
|
||||
$u = "s";
|
||||
break;
|
||||
case "s":
|
||||
$dest->add(new \DateInterval("PT${x}S"));
|
||||
$u = null;
|
||||
break;
|
||||
}
|
||||
if ($y !== null && $u !== null) {
|
||||
$h = intval($dest->format("H"));
|
||||
$m = intval($dest->format("i"));
|
||||
switch ($u) {
|
||||
case "h":
|
||||
$dest->setTime($y, 0, 0, 0);
|
||||
break;
|
||||
case "m":
|
||||
$dest->setTime($h, $y, 0, 0);
|
||||
break;
|
||||
case "s":
|
||||
$dest->setTime($h, $m, $y, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$repr = $y !== null? "$x$y$y": "$x";
|
||||
return [$dest, $repr];
|
||||
}
|
||||
|
||||
function __construct($delay, ?DateTimeInterface $from=null) {
|
||||
if ($from === null) $from = new DateTime();
|
||||
if (is_int($delay)) {
|
||||
[$dest, $repr] = self::compute_dest($delay, "s", null, $from);
|
||||
} elseif (is_string($delay) && preg_match('/^\d+$/', $delay)) {
|
||||
$x = intval($delay);
|
||||
[$dest, $repr] = self::compute_dest($x, "s", null, $from);
|
||||
} elseif (is_string($delay) && preg_match('/^(\d*)([wdhms])(\d*)$/i', $delay, $ms)) {
|
||||
[$x, $u, $y] = [$ms[1], $ms[2], $ms[3]];
|
||||
$u = strtolower($u);
|
||||
$default = self::DEFAULTS[$u];
|
||||
if ($x === "") $x = $default[0];
|
||||
else $x = intval($x);
|
||||
if ($y === "") $y = $default[1];
|
||||
else $y = intval($y);
|
||||
[$dest, $repr] = self::compute_dest($x, $u, $y, $from);
|
||||
} else {
|
||||
throw new InvalidArgumentException("invalid delay");
|
||||
}
|
||||
$this->dest = $dest;
|
||||
$this->repr = $repr;
|
||||
}
|
||||
|
||||
/** @var DateTime */
|
||||
protected $dest;
|
||||
|
||||
function getDest(): DateTime {
|
||||
return $this->dest;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $repr;
|
||||
|
||||
function __toString(): string {
|
||||
return $this->repr;
|
||||
}
|
||||
|
||||
protected function _getDiff(?DateTimeInterface $now=null): \DateInterval {
|
||||
if ($now === null) $now = new DateTime();
|
||||
return $this->dest->diff($now);
|
||||
}
|
||||
|
||||
/** retourner true si le délai imparti est écoulé */
|
||||
function isElapsed(?DateTimeInterface $now=null): bool {
|
||||
return $this->_getDiff($now)->invert == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* retourner l'intervalle entre le moment courant et la destination.
|
||||
*
|
||||
* l'intervalle est négatif si le délai n'est pas écoulé, positif sinon
|
||||
*/
|
||||
function getDiff(?DateTimeInterface $now=null): DateInterval {
|
||||
return new DateInterval($this->_getDiff($now));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
<?php
|
||||
namespace nulib\ref\cli;
|
||||
|
||||
/**
|
||||
* Class ref_args: référence du format des arguments pour une application
|
||||
*/
|
||||
class ref_args {
|
||||
const DEFS_SCHEMA = [
|
||||
"set_defaults" => [null, null, "tableau contenant des paramètres et des options par défaut"],
|
||||
"merge_arrays" => [null, null, "liste de tableaux à merger à celui-ci avant de calculer la liste effective des options"],
|
||||
"merge" => [null, null, "tableau à merger à celui-ci avant de calculer la liste effective des options",
|
||||
# si merge_arrays et merge sont spécifiés tous les deux, "merge" est mergé après "merge_arrays"
|
||||
],
|
||||
"prefix" => [null, null, "texte à afficher avant l'aide générée automatiquement"],
|
||||
"name" => [null, null, "nom du programme, utilisé pour l'affichage de l'aide"],
|
||||
"purpose" => [null, null, "courte description de l'objet de ce programme"],
|
||||
"usage" => [null, null, "exposé textuel des arguments valides du programme",
|
||||
# ce peut être une chaine e.g '[options] SRC DESC'
|
||||
# ou un tableau auquel cas autant de lignes que nécessaire sont affichées
|
||||
],
|
||||
"description" => [null, null, "description longue de l'objet du programme, affiché après usage"],
|
||||
"suffix" => [null, null, "texte à afficher après l'aide générée automatiquement"],
|
||||
"dynamic_command" => [null, null, "fonction indiquant si une commande est valide",
|
||||
# la signature de la fonction est function(string $command):?array
|
||||
# elle doit retourner un tableau au format DEFS_SCHEMA qui définit la
|
||||
# commande spécifiée, ou null si ce n'est pas une commande valide
|
||||
],
|
||||
"sections" => [null, null, "liste de sections permettant de grouper les arguments"],
|
||||
"commandname" => [null, null, "propriété ou clé qui obtient la commande courante",
|
||||
# la valeur par défaut est "command" si ni commandproperty ni commandkey ne sont définis
|
||||
],
|
||||
"commandproperty" => [null, null, "comme commandname mais force l'utilisation d'une propriété"],
|
||||
"commandkey" => [null, null, "comme commandname mais force l'utilisation d'une clé"],
|
||||
"argsname" => [null, null, "propriété ou clé qui obtient les arguments restants",
|
||||
# la valeur par défaut est "args" si ni argsproperty ni argskey ne sont définis
|
||||
],
|
||||
"argsproperty" => [null, null, "comme argsname mais force l'utilisation d'une propriété"],
|
||||
"argskey" => [null, null, "comme argsname mais force l'utilisation d'une clé"],
|
||||
"autohelp" => ["?bool", null, "faut-il ajouter automatiquement le support de l'option --help"],
|
||||
"autoremains" => ["?bool", null, "faut-il ajouter automatiquement la prise en compte des arguments restants"],
|
||||
];
|
||||
|
||||
const SECTION_SCHEMA = [
|
||||
"show" => ["bool", true, "faut-il afficher cette section?"],
|
||||
"title" => [null, null, "titre de la section"],
|
||||
"prefix" => [null, null, "texte à afficher avant l'aide générée automatiquement"],
|
||||
"suffix" => [null, null, "texte à afficher après l'aide générée automatiquement"],
|
||||
|
||||
# ces valeurs sont calculées
|
||||
"defs" => [null, null, "(interne) liste des définitions de cette section"],
|
||||
];
|
||||
|
||||
const DEF_SCHEMA = [
|
||||
"set_defaults" => [null, null, "tableau contenant des paramètres par défaut"],
|
||||
"merge_arrays" => [null, null, "liste de tableaux à merger à celui-ci"],
|
||||
"merge" => [null, null, "tableau à merger à celui-ci",
|
||||
# si merge_arrays et merge sont spécifiés tous les deux, "merge" est mergé après "merge_arrays"
|
||||
],
|
||||
"kind" => [null, null, "type de définition: 'option' ou 'command'"],
|
||||
"arg" => [null, null, "type de l'argument attendu par l'option"],
|
||||
"args" => [null, null, "type des arguments attendus par l'option",
|
||||
# si args est spécifié, arg est ignoré
|
||||
],
|
||||
"argsdesc" => [null, null, "description textuelle des arguments, utilisé pour l'affichage de l'aide"],
|
||||
"type" => [null, null, "types dans lesquels convertir les arguments avant de les fournir à l'utilisateur"],
|
||||
"action" => [null, null, "fonction à appeler quand cette option est utilisée",
|
||||
# la signature de la fonction est ($value, $name, $arg, $dest, $def)
|
||||
],
|
||||
"name" => [null, null, "propriété ou clé à initialiser en réponse à l'utilisation de cette option",
|
||||
# le nom à spécifier est au format under_score, qui est transformée en camelCase si la destination est un objet
|
||||
],
|
||||
"property" => [null, null, "comme name mais force l'utilisation d'une propriété"],
|
||||
"key" => [null, null, "comme name mais force l'utilisation d'une clé"],
|
||||
"inverse" => ["bool", false, "décrémenter la destination au lieu de l'incrémenter pour une option sans argument"],
|
||||
"value" => ["mixed", null, "valeur à forcer au lieu d'incrémenter la destination"],
|
||||
"ensure_array" => [null, null, "forcer la destination à être un tableau"],
|
||||
"help" => [null, null, "description de cette option, utilisé pour l'affichage de l'aide"],
|
||||
"cmd_args" => [null, null, "définition des sous-options pour une commande"],
|
||||
|
||||
# ces valeurs sont calculées
|
||||
"cmd_defs" => [null, null, "(interne) liste des définitions correspondant au paramètre options"],
|
||||
];
|
||||
|
||||
const ARGS_ALLOWED_VALUES = ["value", "path", "dir", "file", "host"];
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
namespace nulib\ref\os\csv;
|
||||
|
||||
/**
|
||||
* Class ref_csv: références des valeurs normalisées pour les fichiers CSV à
|
||||
* destination de Microsoft Excel et de LibreOffice Calc
|
||||
*/
|
||||
class ref_csv {
|
||||
const UTF8 = "utf-8";
|
||||
const CP1252 = "cp1252";
|
||||
const LATIN1 = "iso-8859-1";
|
||||
|
||||
const OO_NAME = "oocalc";
|
||||
const OO_FLAVOUR = ",\"\\";
|
||||
const OO_ENCODING = self::UTF8;
|
||||
|
||||
const XL_NAME = "msexcel";
|
||||
const XL_FLAVOUR = ";\"\\";
|
||||
const XL_ENCODING = self::CP1252;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
namespace nulib\ref\schema;
|
||||
|
||||
class ref_analyze {
|
||||
/** @var int résultat de l'analyse: valeur inexistante */
|
||||
const MISSING = 0;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur non disponible */
|
||||
const UNAVAILABLE = 1;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur nulle */
|
||||
const NULL = 2;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur chaine à parser */
|
||||
const STRING = 3;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur invalide */
|
||||
const INVALID = 4;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur valide mais pas normalisée */
|
||||
const VALID = 5;
|
||||
|
||||
/** @var int résultat de l'analyse: valeur valide normalisée */
|
||||
const NORMALIZED = 6;
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
<?php
|
||||
namespace nulib\ref\schema;
|
||||
|
||||
class ref_schema {
|
||||
/** @var array schéma des natures de schéma */
|
||||
const NATURE_METASCHEMA = [
|
||||
"nature" => ["string", null, "nature du schéma",
|
||||
"pkey" => 0,
|
||||
"allowed_values" => ["assoc", "list", "scalar"],
|
||||
],
|
||||
"title" => ["?string", null, "libellé de la valeur"],
|
||||
"required" => ["bool", false, "la valeur est-elle requise?"],
|
||||
"nullable" => ["?bool", null, "la valeur peut-elle être nulle?"],
|
||||
"desc" => ["?content", null, "description de la valeur"],
|
||||
"name" => ["?key", null, "identifiant de la valeur"],
|
||||
"schema" => ["?array", null, "définition du schéma"],
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature scalaire */
|
||||
const SCALAR_METASCHEMA = [
|
||||
"type" => ["array", null, "types possibles de la valeur", "required" => true],
|
||||
"default" => [null, null, "valeur par défaut si la valeur n'existe pas"],
|
||||
"title" => ["?string", null, "libellé de la valeur"],
|
||||
"required" => ["bool", false, "la valeur est-elle requise?"],
|
||||
"nullable" => ["?bool", null, "la valeur peut-elle être nulle?"],
|
||||
"desc" => ["?content", null, "description de la valeur"],
|
||||
"analyzer_func" => ["?callable", null, "fonction qui analyse une valeur entrante et indique comment la traiter"],
|
||||
"extractor_func" => ["?callable", null, "fonction qui extrait la valeur à analyser dans une chaine de caractère"],
|
||||
"parser_func" => ["?callable", null, "fonction qui analyse une chaine de caractères pour produire la valeur"],
|
||||
"normalizer_func" => ["?callable", null, "fonction qui normalise la valeur"],
|
||||
"messages" => ["?array", null, "messages à afficher en cas d'erreur d'analyse"],
|
||||
"formatter_func" => ["?callable", null, "fonction qui formatte la valeur pour affichage"],
|
||||
"format" => [null, null, "format à utiliser pour l'affichage"],
|
||||
"" => ["array", "scalar", "nature du schéma",
|
||||
"" => ["assoc", "schema" => self::NATURE_METASCHEMA],
|
||||
],
|
||||
"name" => ["?string", null, "identifiant de la valeur"],
|
||||
"pkey" => ["?pkey", null, "chemin de clé de la valeur dans un tableau associatif"],
|
||||
"header" => ["?string", null, "nom de l'en-tête s'il faut présenter cette donnée dans un tableau"],
|
||||
"composite" => ["?bool", null, "ce champ fait-il partie d'une valeur composite?"],
|
||||
];
|
||||
|
||||
const MESSAGES = [
|
||||
"missing" => "{key}: Vous devez spécifier cette valeur",
|
||||
"unavailable" => "{key}: Vous devez spécifier cette valeur",
|
||||
"null" => "{key}: cette valeur ne doit pas être nulle",
|
||||
"empty" => "{key}: cette valeur ne doit pas être vide",
|
||||
"invalid" => "{key}: {orig}: cette valeur est invalide",
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature associative */
|
||||
const ASSOC_METASCHEMA = [
|
||||
];
|
||||
|
||||
/** @var array meta-schema d'un schéma de nature liste */
|
||||
const LIST_METASCHEMA = [
|
||||
];
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
<?php
|
||||
namespace nulib\ref\schema;
|
||||
|
||||
class ref_types {
|
||||
const ALIASES = [
|
||||
"boolean" => "bool",
|
||||
"integer" => "int",
|
||||
"flt" => "float", "double" => "float", "dbl" => "float",
|
||||
];
|
||||
}
|
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\cl;
|
||||
|
||||
/**
|
||||
* Class akey: accéder aux valeurs d'un tableau
|
||||
*
|
||||
* @see valx
|
||||
*/
|
||||
class akey {
|
||||
/** obtenir la valeur d'une clé */
|
||||
static final function get($array, $key, $default=null) {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
if ($array->offsetExists($key)) return $array->offsetGet($key);
|
||||
else return $default;
|
||||
} else {
|
||||
if (!is_array($array)) $array = cl::with($array);
|
||||
return cl::get($array, $key, $default);
|
||||
}
|
||||
}
|
||||
|
||||
/** spécifier la valeur d'une clé */
|
||||
static final function set(&$array, $key, $value) {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
$array->offsetSet($key, $value);
|
||||
} else {
|
||||
cl::set($array, $key, $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** initialiser $dest avec les valeurs de $values */
|
||||
static final function set_values(&$array, ?array $values): void {
|
||||
if ($values === null) return;
|
||||
foreach ($values as $key => $value) {
|
||||
self::set($array, $key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/** incrémenter la valeur de la clé */
|
||||
static final function inc(&$array, $key): int {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
$value = (int)$array->offsetGet($key);
|
||||
$array->offsetSet($key, ++$value);
|
||||
return $value;
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
$value = (int)cl::get($array, $key);
|
||||
return $array[$key] = ++$value;
|
||||
}
|
||||
}
|
||||
|
||||
/** décrémenter la valeur de la clé */
|
||||
static final function dec(&$array, $key, bool $allow_negative=false): int {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
$value = (int)$array->offsetGet($key);
|
||||
if ($allow_negative || $value > 0) $array->offsetSet($key, --$value);
|
||||
return $value;
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
$value = (int)cl::get($array, $key);
|
||||
if ($allow_negative || $value > 0) $array[$key] = --$value;
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* fusionner $merge dans la valeur de la clé, qui est d'abord transformé en
|
||||
* tableau si nécessaire
|
||||
*/
|
||||
static final function merge(&$array, $key, $merge): void {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
$value = $array->offsetGet($key);
|
||||
$value = cl::merge($value, $merge);
|
||||
$array->offsetSet($key, $value);
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
$array[$key] = cl::merge($array[$key], $merge);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ajouter $value à la valeur de la clé, qui est d'abord transformé en
|
||||
* tableau si nécessaire
|
||||
*/
|
||||
static final function append(&$array, $key, $value): void {
|
||||
if ($array instanceof ArrayAccess) {
|
||||
$value = $array->offsetGet($key);
|
||||
cl::set($value, null, $value);
|
||||
$array->offsetSet($key, $value);
|
||||
} else {
|
||||
cl::ensure_array($array);
|
||||
cl::set($array[$key], null, $value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\str;
|
||||
use nulib\php\func;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Class mprop: accéder aux propriétés d'un objet. la différence avec
|
||||
* {@link oprop} est qu'une tentative est effectuée pour accéder d'abord à la
|
||||
* propriété via une méthode normalisée
|
||||
*
|
||||
* @see valx
|
||||
*/
|
||||
class mprop {
|
||||
static function split_prefix_name(string $name): array {
|
||||
preg_match('/^(_*)(.*)/', $name, $ms);
|
||||
return [$ms[1], $ms[2]];
|
||||
}
|
||||
|
||||
static function get_getter_name(string $property, bool $bool=false): string {
|
||||
[$prefix, $name] = self::split_prefix_name($property);
|
||||
$get = $bool? "is": "get";
|
||||
return $prefix.$get.str::upper1(str::us2camel($name));
|
||||
}
|
||||
|
||||
static function get_setter_name(string $property): string {
|
||||
[$prefix, $name] = self::split_prefix_name($property);
|
||||
return $prefix."set".str::upper1(str::us2camel($name));
|
||||
}
|
||||
|
||||
static function get_deletter_name(string $property): string {
|
||||
[$prefix, $name] = self::split_prefix_name($property);
|
||||
return $prefix."del".str::upper1(str::us2camel($name));
|
||||
}
|
||||
|
||||
/** obtenir la valeur d'une propriété */
|
||||
static final function get(object $object, string $property, $default=null, ?string $method=null) {
|
||||
if ($method === null) $method = self::get_getter_name($property);
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$m = $c->getMethod($method);
|
||||
} catch (ReflectionException $e) {
|
||||
return oprop::get($object, $property, $default);
|
||||
}
|
||||
return func::call([$object, $m], $default);
|
||||
}
|
||||
|
||||
/** spécifier la valeur d'une propriété */
|
||||
static final function set(object $object, string $property, $value, ?string $method=null) {
|
||||
$c = new ReflectionClass($object);
|
||||
return self::_set($c, $object, $property, $value, $method);
|
||||
}
|
||||
|
||||
private static final function _set(ReflectionClass $c, object $object, string $property, $value, ?string $method) {
|
||||
if ($method === null) $method = self::get_setter_name($property);
|
||||
try {
|
||||
$m = $c->getMethod($method);
|
||||
} catch (ReflectionException $e) {
|
||||
return oprop::_set($c, $object, $property, $value);
|
||||
}
|
||||
func::call([$object, $m], $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser $dest avec les valeurs de $values
|
||||
*
|
||||
* les noms des clés de $values sont transformées en camelCase pour avoir les
|
||||
* noms des propriétés correspondantes
|
||||
*/
|
||||
static final function set_values(object $object, ?array $values, ?array $keys=null): void {
|
||||
if ($values === null) return;
|
||||
if ($keys === null) $keys = array_keys($values);
|
||||
$c = new ReflectionClass($object);
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $values)) {
|
||||
$property = str::us2camel($key);
|
||||
self::_set($c, $object, $property, $values[$key], null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** incrémenter la valeur d'une propriété */
|
||||
static final function inc(object $object, string $property): int {
|
||||
$value = intval(self::get($object, $property, 0));
|
||||
$value++;
|
||||
self::set($object, $property, $value);
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** décrémenter la valeur d'une propriété */
|
||||
static final function dec(object $object, string $property, bool $allow_negative=false): int {
|
||||
$value = intval(self::get($object, $property, 0));
|
||||
if ($allow_negative || $value > 0) {
|
||||
$value--;
|
||||
self::set($object, $property, $value);
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fusionner la valeur à la propriété qui est transformée en tableau si
|
||||
* nécessaire
|
||||
*/
|
||||
static final function merge(object $object, string $property, $array): void {
|
||||
$values = cl::with(self::get($object, $property));
|
||||
$values = cl::merge($values, cl::with($array));
|
||||
self::set($object, $property, $values);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajouter la valeur à la propriété qui est transformée en tableau si
|
||||
* nécessaire
|
||||
*/
|
||||
static final function append(object $object, string $property, $value): void {
|
||||
$values = cl::with(self::get($object, $property));
|
||||
$values[] = $value;
|
||||
self::set($object, $property, $values);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
|
||||
use nulib\cl;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
|
||||
/**
|
||||
* Class oprop: accéder aux propriétés d'un objet
|
||||
*
|
||||
* @see valx
|
||||
*/
|
||||
class oprop {
|
||||
/** obtenir la valeur d'une propriété */
|
||||
static final function get(object $object, string $property, $default=null) {
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
return $p->getValue($object);
|
||||
} catch (ReflectionException $e) {
|
||||
if (property_exists($object, $property)) return $object->$property;
|
||||
else return $default;
|
||||
}
|
||||
}
|
||||
|
||||
static final function _set(ReflectionClass $c, object $object, string $property, $value) {
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
$p->setValue($object, $value);
|
||||
} catch (ReflectionException $e) {
|
||||
$object->$property = $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** spécifier la valeur d'une propriété */
|
||||
static final function set(object $object, string $property, $value) {
|
||||
$c = new ReflectionClass($object);
|
||||
return self::_set($c, $object, $property, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser $dest avec les valeurs de $values
|
||||
*
|
||||
* les noms des clés de $values sont transformées en camelCase pour avoir les
|
||||
* noms des propriétés correspondantes
|
||||
*/
|
||||
static final function set_values(object $object, ?array $values, ?array $keys=null): void {
|
||||
if ($values === null) return;
|
||||
if ($keys === null) $keys = array_keys($values);
|
||||
$c = new ReflectionClass($object);
|
||||
foreach ($keys as $key) {
|
||||
if (array_key_exists($key, $values)) {
|
||||
$property = str::us2camel($key);
|
||||
self::_set($c, $object, $property, $values[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** incrémenter la valeur d'une propriété */
|
||||
static final function inc(object $object, string $property): int {
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
$value = (int)$p->getValue($object);
|
||||
$value++;
|
||||
$p->setValue($object, $value);
|
||||
} catch (ReflectionException $e) {
|
||||
if (property_exists($object, $property)) {
|
||||
$value = (int)$object->$property;
|
||||
$value++;
|
||||
} else {
|
||||
$value = 1;
|
||||
}
|
||||
$object->$property = $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/** décrémenter la valeur d'une propriété */
|
||||
static final function dec(object $object, string $property, bool $allow_negative=false): int {
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
$value = (int)$p->getValue($object);
|
||||
if ($allow_negative || $value > 0) {
|
||||
$value --;
|
||||
$p->setValue($object, $value);
|
||||
}
|
||||
} catch (ReflectionException $e) {
|
||||
if (property_exists($object, $property)) {
|
||||
$value = (int)$object->$property;
|
||||
} else {
|
||||
$value = 0;
|
||||
}
|
||||
if ($allow_negative || $value > 0) $value--;
|
||||
$object->$property = $value;
|
||||
}
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fusionner la valeur à la propriété qui est transformée en tableau si
|
||||
* nécessaire
|
||||
*/
|
||||
static final function merge(object $object, string $property, $array): void {
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
$values = cl::with($p->getValue($object));
|
||||
$values = cl::merge($values, cl::with($array));
|
||||
$p->setValue($object, $values);
|
||||
} catch (ReflectionException $e) {
|
||||
if (property_exists($object, $property)) {
|
||||
$values = cl::with($object->$property);
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
$values = cl::merge($values, cl::with($array));
|
||||
$object->$property = $values;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajouter la valeur à la propriété qui est transformée en tableau si
|
||||
* nécessaire
|
||||
*/
|
||||
static final function append(object $object, string $property, $value): void {
|
||||
$c = new ReflectionClass($object);
|
||||
try {
|
||||
$p = $c->getProperty($property);
|
||||
$p->setAccessible(true);
|
||||
$values = cl::with($p->getValue($object));
|
||||
$values[] = $value;
|
||||
$p->setValue($object, $values);
|
||||
} catch (ReflectionException $e) {
|
||||
if (property_exists($object, $property)) {
|
||||
$values = cl::with($object->$property);
|
||||
} else {
|
||||
$values = [];
|
||||
}
|
||||
$values[] = $value;
|
||||
$object->$property = $values;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* Class valm: frontend pour accéder à une propriété d'un objet (par une méthode
|
||||
* normalisée ou en direct) ou à la clé d'un tableau. cette classe utilise
|
||||
* {@link mprop} ou {@link akey} en fonction de la nature de la cible
|
||||
*
|
||||
* si la cible est un objet, les noms des clés sont transformés en camelCase
|
||||
* pour avoir les propriétés correspondantes
|
||||
*/
|
||||
class valm {
|
||||
static final function get($src, $name, $default=null) {
|
||||
if (is_object($src) && !($src instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return mprop::get($src, $name, $default);
|
||||
} else {
|
||||
return akey::get($src, $name, $default);
|
||||
}
|
||||
}
|
||||
|
||||
static final function set(&$dest, $name, $value) {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return mprop::set($dest, $name, $value);
|
||||
} else {
|
||||
return akey::set($dest, $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser $dest avec les valeurs de $values
|
||||
*
|
||||
* si $dest est un objet, les noms des clés sont transformées en camelCase
|
||||
* pour avoir les propriétés correspondantes
|
||||
*/
|
||||
static final function set_values(&$dest, ?array $values): void {
|
||||
if ($values === null) return;
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
mprop::set_values($dest, $values);
|
||||
} else {
|
||||
akey::set_values($dest, $values);
|
||||
}
|
||||
}
|
||||
|
||||
static final function inc(&$dest, $name): int {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return mprop::inc($dest, $name);
|
||||
} else {
|
||||
return akey::inc($dest, $name);
|
||||
}
|
||||
}
|
||||
|
||||
static final function dec(&$dest, $name): int {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return mprop::dec($dest, $name);
|
||||
} else {
|
||||
return akey::dec($dest, $name);
|
||||
}
|
||||
}
|
||||
|
||||
static final function merge(&$dest, $name, $merge): void {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
mprop::merge($dest, $name, $merge);
|
||||
} else {
|
||||
akey::merge($dest, $name, $merge);
|
||||
}
|
||||
}
|
||||
|
||||
static final function append(&$dest, $name, $value): void {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
mprop::append($dest, $name, $value);
|
||||
} else {
|
||||
akey::append($dest, $name, $value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
namespace nulib\values;
|
||||
|
||||
use ArrayAccess;
|
||||
use nulib\str;
|
||||
|
||||
/**
|
||||
* Class valx: frontend pour accéder à une propriété d'un objet ou à la clé
|
||||
* d'un tableau. cette classe utilise {@link oprop} ou {@link akey} en fonction
|
||||
* de la nature de la cible
|
||||
*
|
||||
* si la cible est un objet, les noms des clés sont transformés en camelCase
|
||||
* pour avoir les propriétés correspondantes
|
||||
*/
|
||||
class valx {
|
||||
static final function get($src, $name, $default=null) {
|
||||
if (is_object($src) && !($src instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return oprop::get($src, $name, $default);
|
||||
} else {
|
||||
return akey::get($src, $name, $default);
|
||||
}
|
||||
}
|
||||
|
||||
static final function set(&$dest, $name, $value) {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return oprop::set($dest, $name, $value);
|
||||
} else {
|
||||
return akey::set($dest, $name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* initialiser $dest avec les valeurs de $values
|
||||
*
|
||||
* si $dest est un objet, les noms des clés sont transformées en camelCase
|
||||
* pour avoir les propriétés correspondantes
|
||||
*/
|
||||
static final function set_values(&$dest, ?array $values): void {
|
||||
if ($values === null) return;
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
oprop::set_values($dest, $values);
|
||||
} else {
|
||||
akey::set_values($dest, $values);
|
||||
}
|
||||
}
|
||||
|
||||
static final function inc(&$dest, $name): int {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return oprop::inc($dest, $name);
|
||||
} else {
|
||||
return akey::inc($dest, $name);
|
||||
}
|
||||
}
|
||||
|
||||
static final function dec(&$dest, $name): int {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
return oprop::dec($dest, $name);
|
||||
} else {
|
||||
return akey::dec($dest, $name);
|
||||
}
|
||||
}
|
||||
|
||||
static final function merge(&$dest, $name, $merge): void {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
oprop::merge($dest, $name, $merge);
|
||||
} else {
|
||||
akey::merge($dest, $name, $merge);
|
||||
}
|
||||
}
|
||||
|
||||
static final function append(&$dest, $name, $value): void {
|
||||
if (is_object($dest) && !($dest instanceof ArrayAccess)) {
|
||||
$name = str::us2camel($name);
|
||||
oprop::append($dest, $name, $value);
|
||||
} else {
|
||||
akey::append($dest, $name, $value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
<?php
|
||||
namespace nulib\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();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
<?php
|
||||
namespace nulib\file\base;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class FileReaderTest extends TestCase {
|
||||
function testIgnoreBom() {
|
||||
# la lecture avec et sans BOM doit être identique
|
||||
## sans BOM
|
||||
$reader = new FileReader(__DIR__ . '/impl/sans_bom.txt');
|
||||
self::assertSame("0123456789", $reader->fread(10));
|
||||
self::assertSame(10, $reader->ftell());
|
||||
$reader->seek(30);
|
||||
self::assertSame("abcdefghij", $reader->fread(10));
|
||||
self::assertSame(40, $reader->ftell());
|
||||
$reader->seek(10);
|
||||
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||
self::assertSame(20, $reader->ftell());
|
||||
$reader->seek(40);
|
||||
self::assertSame("0123456789\n", $reader->getContents());
|
||||
$reader->close();
|
||||
## avec BOM
|
||||
$reader = new FileReader(__DIR__ . '/impl/avec_bom.txt');
|
||||
self::assertSame("0123456789", $reader->fread(10));
|
||||
self::assertSame(10, $reader->ftell());
|
||||
$reader->seek(30);
|
||||
self::assertSame("abcdefghij", $reader->fread(10));
|
||||
self::assertSame(40, $reader->ftell());
|
||||
$reader->seek(10);
|
||||
self::assertSame("ABCDEFGHIJ", $reader->fread(10));
|
||||
self::assertSame(20, $reader->ftell());
|
||||
$reader->seek(40);
|
||||
self::assertSame("0123456789\n", $reader->getContents());
|
||||
$reader->close();
|
||||
}
|
||||
|
||||
function testCsvAutoParams() {
|
||||
$reader = new FileReader(__DIR__ . '/impl/msexcel.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/ooffice.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/weird.tsv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
|
||||
$reader = new FileReader(__DIR__ . '/impl/avec_bom.csv');
|
||||
self::assertSame(["nom", "prenom", "age"], $reader->fgetcsv());
|
||||
self::assertSame(["clain", "jephte", "50"], $reader->fgetcsv());
|
||||
self::assertNull($reader->fgetcsv());
|
||||
$reader->close();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
nom,prenom,age
|
||||
clain,jephte,50
|
|
|
@ -0,0 +1 @@
|
|||
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789
|
|
@ -0,0 +1,2 @@
|
|||
nom;prenom;age
|
||||
clain;jephte;50
|
|
|
@ -0,0 +1,2 @@
|
|||
nom,prenom,age
|
||||
clain,jephte,50
|
|
|
@ -0,0 +1 @@
|
|||
0123456789ABCDEFGHIJ0123456789abcdefghij0123456789
|
|
@ -0,0 +1,2 @@
|
|||
nom prenom age
|
||||
clain jephte 50
|
|
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeZone;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class DateTest extends TestCase {
|
||||
protected static function dt(string $datetime): Date {
|
||||
return new Date($datetime, new DateTimeZone("Indian/Reunion"));
|
||||
}
|
||||
|
||||
function testDate() {
|
||||
$date = self::dt("2024-04-05 09:15:23");
|
||||
|
||||
self::assertSame("05/04/2024", $date->format());
|
||||
self::assertSame("05/04/2024", strval($date));
|
||||
self::assertSame(2024, $date->year);
|
||||
self::assertSame(4, $date->month);
|
||||
self::assertSame(5, $date->day);
|
||||
self::assertSame(0, $date->hour);
|
||||
self::assertSame(0, $date->minute);
|
||||
self::assertSame(0, $date->second);
|
||||
self::assertSame(5, $date->wday);
|
||||
self::assertSame(14, $date->wnum);
|
||||
self::assertSame("+04:00", $date->timezone);
|
||||
self::assertSame("05/04/2024 00:00:00", $date->datetime);
|
||||
self::assertSame("05/04/2024", $date->date);
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
$date = self::dt("now");
|
||||
$clone = Date::clone($date);
|
||||
self::assertInstanceOf(DateTime::class, $clone);
|
||||
}
|
||||
|
||||
function testConstruct() {
|
||||
$y = date("Y");
|
||||
self::assertSame("05/04/$y", strval(new Date("5/4")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/24")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024")));
|
||||
self::assertSame("05/04/2024", strval(new Date("05/04/2024")));
|
||||
self::assertSame("05/04/2024", strval(new Date("20240405")));
|
||||
self::assertSame("05/04/2024", strval(new Date("240405")));
|
||||
self::assertSame("05/04/2024", strval(new Date("20240405T091523")));
|
||||
self::assertSame("05/04/2024", strval(new Date("20240405T091523Z")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 9:15:23")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 9.15.23")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 9:15")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 9.15")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 9h15")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 09:15:23")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 09:15")));
|
||||
self::assertSame("05/04/2024", strval(new Date("5/4/2024 09h15")));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeZone;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class DateTimeTest extends TestCase {
|
||||
protected static function dt(string $datetime): DateTime {
|
||||
return new DateTime($datetime, new DateTimeZone("Indian/Reunion"));
|
||||
}
|
||||
|
||||
function testDateTime() {
|
||||
$date = self::dt("2024-04-05 09:15:23");
|
||||
|
||||
self::assertEquals("05/04/2024 09:15:23", $date->format());
|
||||
self::assertEquals("05/04/2024 09:15:23", strval($date));
|
||||
self::assertSame(2024, $date->year);
|
||||
self::assertSame(4, $date->month);
|
||||
self::assertSame(5, $date->day);
|
||||
self::assertSame(9, $date->hour);
|
||||
self::assertSame(15, $date->minute);
|
||||
self::assertSame(23, $date->second);
|
||||
self::assertSame(5, $date->wday);
|
||||
self::assertSame(14, $date->wnum);
|
||||
self::assertEquals("+04:00", $date->timezone);
|
||||
self::assertSame("05/04/2024 09:15:23", $date->datetime);
|
||||
self::assertSame("05/04/2024", $date->date);
|
||||
self::assertSame("20240405", $date->Ymd);
|
||||
self::assertSame("20240405T091523", $date->YmdHMS);
|
||||
self::assertSame("20240405T091523+04:00", $date->YmdHMSZ);
|
||||
}
|
||||
|
||||
function testDateTimeZ() {
|
||||
$date = new DateTime("20240405T091523Z");
|
||||
self::assertSame("20240405T091523", $date->YmdHMS);
|
||||
self::assertSame("20240405T091523Z", $date->YmdHMSZ);
|
||||
}
|
||||
|
||||
function testClone() {
|
||||
$date = self::dt("now");
|
||||
$clone = DateTime::clone($date);
|
||||
self::assertInstanceOf(DateTime::class, $clone);
|
||||
}
|
||||
|
||||
function testConstruct() {
|
||||
$y = date("Y");
|
||||
self::assertSame("05/04/$y 00:00:00", strval(new DateTime("5/4")));
|
||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/24")));
|
||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("5/4/2024")));
|
||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("05/04/2024")));
|
||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("20240405")));
|
||||
self::assertSame("05/04/2024 00:00:00", strval(new DateTime("240405")));
|
||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("20240405T091523")));
|
||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("20240405T091523Z")));
|
||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9:15:23")));
|
||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 9.15.23")));
|
||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 9:15")));
|
||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 9.15")));
|
||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 9h15")));
|
||||
self::assertSame("05/04/2024 09:15:23", strval(new DateTime("5/4/2024 09:15:23")));
|
||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 09:15")));
|
||||
self::assertSame("05/04/2024 09:15:00", strval(new DateTime("5/4/2024 09h15")));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
<?php
|
||||
namespace nulib\php\time;
|
||||
|
||||
use DateTimeZone;
|
||||
use nulib\tests\TestCase;
|
||||
|
||||
class DelayTest extends TestCase {
|
||||
protected static function dt(string $datetime): DateTime {
|
||||
return new DateTime($datetime, new DateTimeZone("Indian/Reunion"));
|
||||
}
|
||||
|
||||
function testDelay() {
|
||||
$from = self::dt("2024-04-05 09:15:23");
|
||||
|
||||
$delay = new Delay(10, $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("10", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("10s", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:15:33"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("s", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:15:24"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5m", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5m0", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:20:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5m2", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:20:02"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("m", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 09:16:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5h", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5h0", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 14:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5h2", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 14:02:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("h", $from);
|
||||
self::assertEquals(self::dt("2024-04-05 10:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5d", $from);
|
||||
self::assertEquals(self::dt("2024-04-10 05:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5d2", $from);
|
||||
self::assertEquals(self::dt("2024-04-10 02:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("5d0", $from);
|
||||
self::assertEquals(self::dt("2024-04-10 00:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("d", $from);
|
||||
self::assertEquals(self::dt("2024-04-06 05:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("2w", $from);
|
||||
self::assertEquals(self::dt("2024-04-21 05:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("2w2", $from);
|
||||
self::assertEquals(self::dt("2024-04-21 02:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("2w0", $from);
|
||||
self::assertEquals(self::dt("2024-04-21 00:00:00"), $delay->getDest());
|
||||
|
||||
$delay = new Delay("w", $from);
|
||||
self::assertEquals(self::dt("2024-04-07 05:00:00"), $delay->getDest());
|
||||
}
|
||||
|
||||
function testElapsed() {
|
||||
$delay = new Delay(5);
|
||||
sleep(2);
|
||||
self::assertFalse($delay->isElapsed());
|
||||
sleep(5);
|
||||
self::assertTrue($delay->isElapsed());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue