modifs.mineures sans commentaires
This commit is contained in:
parent
451b2768e6
commit
5460903a9f
|
@ -13,6 +13,8 @@
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"nulib/tests": "7.4",
|
"nulib/tests": "7.4",
|
||||||
|
"ext-posix": "*",
|
||||||
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*"
|
"ext-curl": "*"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
|
@ -20,6 +22,9 @@
|
||||||
"nulib\\": "php/src_base",
|
"nulib\\": "php/src_base",
|
||||||
"nulib\\ref\\": "php/src_ref",
|
"nulib\\ref\\": "php/src_ref",
|
||||||
"nulib\\php\\": "php/src_php",
|
"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\\output\\": "php/src_output",
|
||||||
"nulib\\web\\": "php/src_web"
|
"nulib\\web\\": "php/src_web"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "d0cd1ea89a82b87aa4a078dcea524293",
|
"content-hash": "0f0743123abf677caf20c5e71ece9616",
|
||||||
"packages": [],
|
"packages": [],
|
||||||
"packages-dev": [
|
"packages-dev": [
|
||||||
{
|
{
|
||||||
|
@ -665,16 +665,16 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "phpunit/phpunit",
|
"name": "phpunit/phpunit",
|
||||||
"version": "9.6.18",
|
"version": "9.6.19",
|
||||||
"source": {
|
"source": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
"url": "https://github.com/sebastianbergmann/phpunit.git",
|
||||||
"reference": "32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04"
|
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8"
|
||||||
},
|
},
|
||||||
"dist": {
|
"dist": {
|
||||||
"type": "zip",
|
"type": "zip",
|
||||||
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04",
|
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8",
|
||||||
"reference": "32c2c2d6580b1d8ab3c10b1e9e4dc263cc69bb04",
|
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8",
|
||||||
"shasum": ""
|
"shasum": ""
|
||||||
},
|
},
|
||||||
"require": {
|
"require": {
|
||||||
|
@ -748,7 +748,7 @@
|
||||||
"support": {
|
"support": {
|
||||||
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
|
||||||
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
|
"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": [
|
"funding": [
|
||||||
{
|
{
|
||||||
|
@ -764,7 +764,7 @@
|
||||||
"type": "tidelift"
|
"type": "tidelift"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"time": "2024-03-21T12:07:32+00:00"
|
"time": "2024-04-05T04:35:58+00:00"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "sebastian/cli-parser",
|
"name": "sebastian/cli-parser",
|
||||||
|
@ -1789,6 +1789,8 @@
|
||||||
"php": ">=7.4"
|
"php": ">=7.4"
|
||||||
},
|
},
|
||||||
"platform-dev": {
|
"platform-dev": {
|
||||||
|
"ext-posix": "*",
|
||||||
|
"ext-pcntl": "*",
|
||||||
"ext-curl": "*"
|
"ext-curl": "*"
|
||||||
},
|
},
|
||||||
"plugin-api-version": "2.2.0"
|
"plugin-api-version": "2.2.0"
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
<?php
|
<?php
|
||||||
namespace nulib;
|
namespace nulib;
|
||||||
|
|
||||||
use Exception;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class AccessException: indiquer que la resource ou l'objet auquel on veut
|
* 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
|
* 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)) {
|
} elseif (is_array($value)) {
|
||||||
$values = $value;
|
$values = $value;
|
||||||
$parts = [];
|
$parts = [];
|
||||||
foreach ($values as $value) {
|
$index = 0;
|
||||||
$parts[] = self::value($value);
|
foreach ($values as $key => $value) {
|
||||||
|
if ($key === $index) {
|
||||||
|
$index++;
|
||||||
|
$parts[] = self::value($value);
|
||||||
|
} else {
|
||||||
|
$parts[] = "$key=>".self::value($value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "[" . implode(", ", $parts) . "]";
|
return "[" . implode(", ", $parts) . "]";
|
||||||
} elseif (is_string($value)) {
|
} elseif (is_string($value)) {
|
||||||
|
|
|
@ -141,6 +141,15 @@ class cl {
|
||||||
return $array !== null? array_keys($array): [];
|
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 */
|
/** mettre à jour $dest avec $value si $cond($value) est vrai */
|
||||||
static final function set_if(&$dest, $value, callable $cond) {
|
static final function set_if(&$dest, $value, callable $cond) {
|
||||||
if ($cond($value)) $dest = $value;
|
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
|
rotation des logs
|
||||||
* [ ] lors de la rotation, si l'ouverture du nouveau fichier échoue, continuer
|
* [ ] lors de la rotation, si l'ouverture du nouveau fichier échoue, continuer
|
||||||
à écrire dans l'ancien fichier
|
à é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
|
-*- 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
|
* 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 {
|
class log extends _messenger {
|
||||||
static function set_messenger(IMessenger $log=null) {
|
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
|
* 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 {
|
class msg extends _messenger {
|
||||||
static function set_messenger(IMessenger $say, ?IMessenger $log=null) {
|
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
|
* 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 {
|
class say extends _messenger {
|
||||||
static function set_messenger(IMessenger $say) {
|
static function set_messenger(IMessenger $say) {
|
||||||
|
|
|
@ -150,9 +150,14 @@ class StdMessenger implements _IMessenger {
|
||||||
"color" => $color,
|
"color" => $color,
|
||||||
"indent" => $indent,
|
"indent" => $indent,
|
||||||
];
|
];
|
||||||
if ($output !== null) {
|
if ($this->out === $this->err) {
|
||||||
$this->out->resetParams($params);
|
$this->out->resetParams($params);
|
||||||
} else {
|
} 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->out->resetParams($params);
|
||||||
$this->err->resetParams($params);
|
$this->err->resetParams($params);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,10 @@ namespace nulib\output\std;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\output\IContent;
|
use nulib\os\file\Stream;
|
||||||
use nulib\output\IPrintable;
|
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
|
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
|
||||||
|
@ -86,6 +88,7 @@ class StdOutput {
|
||||||
$indent = cl::get($params, "indent");
|
$indent = cl::get($params, "indent");
|
||||||
$flush = cl::get($params, "flush");
|
$flush = cl::get($params, "flush");
|
||||||
|
|
||||||
|
if ($output instanceof Stream) $output = $output->getResource();
|
||||||
if ($output !== null) {
|
if ($output !== null) {
|
||||||
if ($output === "php://stdout") {
|
if ($output === "php://stdout") {
|
||||||
$outf = STDOUT;
|
$outf = STDOUT;
|
||||||
|
@ -181,37 +184,12 @@ class StdOutput {
|
||||||
return preg_replace('/\x1B\[.*?m/', "", $text);
|
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 {
|
function getIndent(int $indentLevel): string {
|
||||||
return str_repeat($this->indent, $indentLevel);
|
return str_repeat($this->indent, $indentLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getLines(bool $withNl, ...$values): array {
|
function getLines(bool $withNl, ...$values): array {
|
||||||
$values = self::flatten($values);
|
$values = content::flatten($values);
|
||||||
if (!$values) return [];
|
if (!$values) return [];
|
||||||
$text = implode("", $values);
|
$text = implode("", $values);
|
||||||
if ($text === "") return [""];
|
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
|
* 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 {
|
interface IContent {
|
||||||
/** retourner le contenu à afficher */
|
/** retourner le contenu à afficher */
|
||||||
|
|
|
@ -4,7 +4,7 @@ namespace nulib\php;
|
||||||
use Closure;
|
use Closure;
|
||||||
use nulib\cl;
|
use nulib\cl;
|
||||||
use nulib\ValueException;
|
use nulib\ValueException;
|
||||||
use nulib\ref\sys\ref_func;
|
use nulib\ref\php\ref_func;
|
||||||
use nulib\schema\Schema;
|
use nulib\schema\Schema;
|
||||||
use ReflectionClass;
|
use ReflectionClass;
|
||||||
use ReflectionFunction;
|
use ReflectionFunction;
|
||||||
|
@ -300,12 +300,16 @@ class func {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @var Schema */
|
||||||
|
private static $call_all_params_schema;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner la liste des méthodes de $class_or_object qui correspondent au
|
* 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}
|
* filtre $options. le filtre doit respecter le schéme {@link CALL_ALL_PARAMS_SCHEMA}
|
||||||
*/
|
*/
|
||||||
static function get_all($class_or_object, $params=null): array {
|
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)) {
|
if (is_callable($class_or_object, true) && is_array($class_or_object)) {
|
||||||
# callable sous forme de tableau
|
# callable sous forme de tableau
|
||||||
$class_or_object = $class_or_object[0];
|
$class_or_object = $class_or_object[0];
|
||||||
|
|
|
@ -3,7 +3,7 @@ namespace nulib\php\iter;
|
||||||
|
|
||||||
use Exception;
|
use Exception;
|
||||||
use Iterator;
|
use Iterator;
|
||||||
use nulib\DataException;
|
use nulib\NoMoreDataException;
|
||||||
use nulib\php\ICloseable;
|
use nulib\php\ICloseable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,12 +29,13 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
||||||
protected function beforeIter() {}
|
protected function beforeIter() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* retourner le prochain élément. lancer l'exception {@link DataException}
|
* retourner le prochain élément.
|
||||||
* pour indiquer que plus aucun élément n'est disponible
|
* lancer l'exception {@link NoMoreDataException} pour indiquer que plus aucun
|
||||||
|
* élément n'est disponible
|
||||||
*
|
*
|
||||||
* le cas échéant, initialiser $key
|
* le cas échéant, initialiser $key
|
||||||
*
|
*
|
||||||
* @throws DataException
|
* @throws NoMoreDataException
|
||||||
*/
|
*/
|
||||||
abstract protected function _next(&$key);
|
abstract protected function _next(&$key);
|
||||||
|
|
||||||
|
@ -94,7 +95,7 @@ abstract class AbstractIterator implements Iterator, ICloseable {
|
||||||
$this->valid = false;
|
$this->valid = false;
|
||||||
try {
|
try {
|
||||||
$item = $this->_next($key);
|
$item = $this->_next($key);
|
||||||
} catch (DataException $e) {
|
} catch (NoMoreDataException $e) {
|
||||||
$this->beforeClose();
|
$this->beforeClose();
|
||||||
try {
|
try {
|
||||||
$this->_teardown();
|
$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