modifs.mineures sans commentaires

This commit is contained in:
Jephté Clain 2023-12-31 18:54:10 +04:00
parent 77c90f02a4
commit bc115a3e6c
33 changed files with 1689 additions and 23 deletions

View File

@ -3,6 +3,7 @@ namespace nur\ref;
use nur\data\types\Metadata; use nur\data\types\Metadata;
use nur\md; use nur\md;
use nur\sery\php\content\content;
/** /**
* Class ref_type: référence des types utilisables dans les schémas * Class ref_type: référence des types utilisables dans les schémas

18
src/os/EOFException.php Normal file
View File

@ -0,0 +1,18 @@
<?php
namespace nur\sery\os;
/**
* Class EOFException: indiquer que plus aucune donnée n'est disponible sur un
* flux
*/
class EOFException extends IOException {
static final function no_more_data(): self {
return new self("no more data");
}
static final function ensure_not_eof($data, bool $throw=true, $eof=false) {
if (!$throw) return null;
elseif ($data !== $eof) return $data;
else throw self::no_more_data();
}
}

42
src/os/IOException.php Normal file
View File

@ -0,0 +1,42 @@
<?php
namespace nur\sery\os;
use RuntimeException;
/**
* Class IOException: erreur sur un flux ou un système de fichiers
*/
class IOException extends RuntimeException {
static final function last_error(?string $prefix=null): ?self {
$error = error_get_last();
if ($error === null) return null;
$message = $error["message"];
if ($prefix) $message = "$prefix: $message";
return new static($message);
}
static final function generic_error(?string $message=null): self {
if ($message === null) $message = "generic error";
return new static($message);
}
static final function error(?string $prefix=null): self {
$exception = self::last_error($prefix);
if ($exception !== null) throw $exception;
else throw self::generic_error($prefix);
}
static final function ensure_value($value, bool $throw=true, $invalid=false) {
if (!$throw) return null;
elseif ($value !== $invalid) return $value;
else throw self::error();
}
static final function already_closed(): self {
return new static("already closed");
}
static final function ensure_open(bool $closed): void {
if ($closed) throw self::already_closed();
}
}

View File

@ -0,0 +1,41 @@
<?php
namespace nur\sery\os\file;
use nur\sery\os\IOException;
/**
* Class FileReader: un fichier accédé en lecture
*/
class FileReader extends Stream {
const DEFAULT_MODE = "rb";
function __construct($input, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
if ($input === null) {
$fd = STDIN;
$close = false;
} elseif (is_resource($input)) {
$fd = $input;
$close = false;
} else {
if ($mode === null) $mode = static::DEFAULT_MODE;
$this->file = $input;
$this->mode = $mode;
$fd = null;
$close = true;
}
parent::__construct($fd, $close, $throwOnError, $allowLocking);
}
/** @var string */
protected $file;
/** @var string */
protected $mode;
function getResource() {
if ($this->fd === null && $this->file !== null) {
$this->fd = IOException::ensure_value(@fopen($this->file, $this->mode));
}
return parent::getResource();
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace nur\sery\os\file;
use nur\sery\os\IOException;
use nur\sery\os\sh;
/**
* Class FileWriter: un fichier accédé en lecture/écriture
*/
class FileWriter extends Stream {
const DEFAULT_MODE = "a+b";
function __construct($output, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
if ($output === null) {
$fd = STDOUT;
$close = false;
} elseif (is_resource($output)) {
$fd = $output;
$close = false;
} else {
if ($mode === null) $mode = static::DEFAULT_MODE;
$this->file = $output;
$this->mode = $mode;
$fd = null;
$close = true;
}
parent::__construct($fd, $close, $throwOnError, $allowLocking);
}
/** @var string */
protected $file;
/** @var string */
protected $mode;
function getResource() {
if ($this->fd === null && $this->file !== null) {
IOException::ensure_value(sh::mkdirof($this->file));
$this->fd = IOException::ensure_value(@fopen($this->file, $this->mode));
}
return parent::getResource();
}
}

52
src/os/file/IReader.php Normal file
View File

@ -0,0 +1,52 @@
<?php
namespace nur\sery\os\file;
use nur\sery\os\EOFException;
use nur\sery\os\IOException;
/**
* Interface IReader: un objet depuis lequel on peut lire des données
*/
interface IReader extends _IFile {
/** @throws IOException */
function fread(int $length): string;
/** @throws IOException */
function fgets(): string;
/** @throws IOException */
function fpassthru(): int;
/**
* lire la prochaine ligne. la ligne est retournée *sans* le caractère de fin
* de ligne [\r]\n
*
* @throws EOFException si plus aucune ligne n'est disponible
* @throws IOException si une autre erreur se produit
*/
function readLine(): ?string;
/**
* essayer de verrouiller le fichier en lecture. retourner true si l'opération
* réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument
* true
*/
function canRead(): bool;
/**
* verrouiller en mode partagé puis retourner un objet permettant de lire le
* fichier.
*/
function getReader(bool $lockedByCanRead=false): IReader;
/**
* lire tout le contenu du fichier en une seule fois, puis, si $close==true,
* le fermer
*
* @throws IOException si une erreur se produit
*/
function getContents(bool $close=true, bool $lockedByCanRead=false): string;
/** désérialiser le contenu du fichier, puis, si $close===true, le fermer */
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false);
}

40
src/os/file/IWriter.php Normal file
View File

@ -0,0 +1,40 @@
<?php
namespace nur\sery\os\file;
use nur\sery\os\IOException;
/**
* Interface IWriter: un objet dans lequel on peut écrire des données
*/
interface IWriter extends _IFile {
/** @throws IOException */
function fwrite(string $data, int $length=0): int;
/** @throws IOException */
function fflush(): void;
/** @throws IOException */
function ftruncate(int $size): void;
/** afficher les lignes */
function writeLines(?iterable $lines): self;
/**
* essayer de verrouiller le fichier en écriture. retourner true si l'opération
* réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument
* true
*/
function canWrite(): bool;
/**
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
* dans le fichier
*/
function getWriter(bool $lockedByCanWrite=false): self;
/** écrire le contenu spécifié dans le fichier */
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void;
/** sérialiser l'objet dans la destination */
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void;
}

View File

@ -0,0 +1,15 @@
<?php
namespace nur\sery\os\file;
use nulib\ValueException;
class SharedFile extends FileWriter {
const ALLOW_LOCKING = true;
const DEFAULT_MODE = "c+b";
function __construct($file, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
if ($file === null) throw ValueException::null("file");
parent::__construct($file, $mode, $throwOnError, $allowLocking);
}
}

286
src/os/file/Stream.php Normal file
View File

@ -0,0 +1,286 @@
<?php
namespace nur\sery\os\file;
use nulib\str;
use nulib\ValueException;
use nur\sery\os\EOFException;
use nur\sery\os\IOException;
use nur\sery\php\iter\AbstractIterator;
/**
* Class Stream: lecture/écriture générique dans un flux
*/
class Stream extends AbstractIterator implements IReader, IWriter {
use TStreamFilter;
/** @var bool les opérations de verrouillages sont-elle activées? */
const ALLOW_LOCKING = false;
/** @var resource */
protected $fd;
/** @var bool */
protected $close;
/** @var bool */
protected $throwOnError;
/** @var bool */
protected $allowLocking;
/** @var int|null */
protected $serial;
/** @var array */
protected $stat;
function __construct($fd, bool $close=true, bool $throwOnError=true, ?bool $allowLocking=null) {
if ($fd === null) throw ValueException::null("resource");
$this->fd = $fd;
$this->close = $close;
$this->throwOnError = $throwOnError;
if ($allowLocking === null) $allowLocking = static::ALLOW_LOCKING;
$this->allowLocking = $allowLocking;
}
#############################################################################
# File
/**
* @return resource|null retourner la resource associée à ce fichier si cela
* a du sens
*/
function getResource() {
IOException::ensure_open($this->fd === null);
$this->_streamAppendFilters($this->fd);
return $this->fd;
}
protected function lock(int $operation, ?int &$wouldBlock=null): bool {
$locked = flock($this->getResource(), $operation, $wouldBlock);
if ($locked) return true;
if ($operation & LOCK_NB) return false;
else throw IOException::error();
}
protected function unlock(bool $close=false): void {
if ($this->fd !== null) {
flock($this->fd, LOCK_UN);
if ($close) $this->close();
}
}
function isatty(): bool {
return stream_isatty($this->getResource());
}
/** obtenir des informations sur le fichier */
function fstat(bool $reload=false): array {
if ($this->stat === null || $reload) {
$fd = $this->getResource();
$this->stat = IOException::ensure_value(fstat($fd), $this->throwOnError);
}
return $this->stat;
}
/** @throws IOException */
function ftell(): int {
$fd = $this->getResource();
return IOException::ensure_value(ftell($fd), $this->throwOnError);
}
/**
* @return int la position après avoir déplacé le pointeur
* @throws IOException
*/
function fseek(int $offset, int $whence=SEEK_SET): int {
$fd = $this->getResource();
IOException::ensure_value(fseek($fd, $offset, $whence), $this->throwOnError, -1);
return $this->ftell();
}
/** fermer le fichier si c'est nécessaire */
function close(bool $close=true, ?int $ifSerial=null): void {
AbstractIterator::rewind();
if ($this->fd !== null && $close && $this->close && ($ifSerial === null || $this->serial === $ifSerial)) {
fclose($this->fd);
$this->fd = null;
}
}
#############################################################################
# Reader
/** @throws IOException */
function fread(int $length): string {
$fd = $this->getResource();
return IOException::ensure_value(fread($fd, $length), $this->throwOnError);
}
/** @throws IOException */
function fgets(?int $length=null): string {
$fd = $this->getResource();
return EOFException::ensure_not_eof(fgets($fd, $length), $this->throwOnError);
}
/** @throws IOException */
function fpassthru(): int {
$fd = $this->getResource();
return IOException::ensure_value(fpassthru($fd), $this->throwOnError);
}
function readLine(): ?string {
return str::strip_nl($this->fgets());
}
/**
* essayer de verrouiller le fichier en lecture. retourner true si l'opération
* réussit. dans ce cas, il faut appeler {@link getReader()} avec l'argument
* true
*/
function canRead(): bool {
if ($this->allowLocking) return $this->lock(LOCK_SH + LOCK_NB);
else return true;
}
/**
* verrouiller en mode partagé puis retourner un objet permettant de lire le
* fichier.
*/
function getReader(bool $lockedByCanRead=false): IReader {
if ($this->allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
return new class($this->fd, ++$this->serial, $this) extends Stream {
function __construct($fd, int $serial, Stream $parent) {
$this->parent = $parent;
parent::__construct($fd);
}
/** @var Stream */
private $parent;
function close(bool $close=true): void {
if ($this->parent !== null && $close) {
$this->parent->close(true, $this->serial);
$this->fd = null;
$this->parent = null;
}
}
};
}
/** retourner le contenu du fichier sous forme de chaine */
function getContents(bool $close=true, bool $lockedByCanRead=false): string {
$allowLocking = $this->allowLocking;
if ($allowLocking && !$lockedByCanRead) $this->lock(LOCK_SH);
try {
return IOException::ensure_value(stream_get_contents($this->fd), $this->throwOnError);
} finally {
if ($allowLocking) $this->unlock($close);
elseif ($close) $this->close();
}
}
function unserialize(?array $options=null, bool $close=true, bool $lockedByCanRead=false) {
$args = [$this->getContents($lockedByCanRead)];
if ($options !== null) $args[] = $options;
return unserialize(...$args);
}
#############################################################################
# Iterator
protected function _setup(): void {
}
protected function _next(&$key) {
return $this->fgets();
}
protected function _teardown(): void {
$this->fseek(0);
}
#############################################################################
# Writer
/** @throws IOException */
function fwrite(string $data, ?int $length=null): int {
$fd = $this->getResource();
if ($length === null) $r = fwrite($fd, $data);
else $r = fwrite($fd, $data, $length);
return IOException::ensure_value($r, $this->throwOnError);
}
/** @throws IOException */
function fflush(): void {
$fd = $this->getResource();
IOException::ensure_value(fflush($fd), $this->throwOnError);
}
/** @throws IOException */
function ftruncate(int $size): void {
$fd = $this->getResource();
IOException::ensure_value(ftruncate($fd, $size), $this->throwOnError);
}
function writeLines(?iterable $lines): IWriter {
if ($lines !== null) {
foreach ($lines as $line) {
$this->fwrite($line);
$this->fwrite("\n");
}
}
return $this;
}
/**
* essayer de verrouiller le fichier en écriture. retourner true si l'opération
* réussit. dans ce cas, il faut appeler {@link getWriter()} avec l'argument
* true
*/
function canWrite(): bool {
if ($this->allowLocking) return $this->lock(LOCK_EX + LOCK_NB);
else return true;
}
/**
* verrouiller en mode exclusif puis retourner un objet permettant d'écrire
* dans le fichier
*/
function getWriter(bool $lockedByCanWrite=false): IWriter {
if ($this->allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
return new class($this->fd, ++$this->serial, $this) extends Stream {
function __construct($fd, int $serial, Stream $parent) {
$this->parent = $parent;
$this->serial = $serial;
parent::__construct($fd);
}
/** @var Stream */
private $parent;
function close(bool $close=true): void {
if ($this->parent !== null && $close) {
$this->parent->close(true, $this->serial);
$this->fd = null;
$this->parent = null;
}
}
};
}
function putContents(string $contents, bool $close=true, bool $lockedByCanWrite=false): void {
$allowLocking = $this->allowLocking;
if ($allowLocking && !$lockedByCanWrite) $this->lock(LOCK_EX);
try {
$this->fwrite($contents);
} finally {
if ($allowLocking) $this->unlock($close);
elseif ($close) $this->close();
}
}
function serialize($object, bool $close=true, bool $lockedByCanWrite=false): void {
$this->putContents(serialize($object), $lockedByCanWrite);
}
}

View File

@ -0,0 +1,49 @@
<?php
namespace nur\sery\os\file;
use nur\A;
use nur\b\io\IOException;
trait TStreamFilter {
private $filters = null;
function appendFilter(string $filterName, ?int $readWrite=null, $params=null): void {
A::append($this->filters, [$filterName, $readWrite, $params]);
}
function prependFilter(string $filterName, ?int $readWrite=null, $params=null): void {
A::prepend($this->filters, [$filterName, $readWrite, $params]);
}
function setEncodingFilter(string $from, string $to): void {
if ($to !== $from) {
$this->appendFilter("convert.iconv.$from.$to");
}
}
/**
* @param $fd resource
* @throws IOException
*/
protected function _streamAppendFilters($fd): void {
if ($this->filters !== null) {
foreach ($this->filters as [$filterName, $readWrite, $params]) {
if (stream_filter_append($fd, $filterName, $readWrite, $params) === false) {
throw new IOException("unable to add filter $filterName");
}
}
$this->filters = null;
}
}
/**
* @param $file _IFile
*/
protected function _appendFilters($file): void {
if ($this->filters !== null) {
foreach ($this->filters as [$filterName, $readWrite, $params]) {
$file->appendFilter($filterName, $readWrite, $params);
}
}
}
}

View File

@ -0,0 +1,95 @@
<?php
namespace nur\sery\os\file;
use nur\sery\os\IOException;
use nur\sery\web\http;
class TmpfileWriter extends FileWriter {
const DEFAULT_MODE = "w+b";
function __construct(?string $destdir=null, ?string $mode=null, bool $throwOnError=true, ?bool $allowLocking=null) {
if ($destdir === null) $destdir = sys_get_temp_dir();
if (is_dir($destdir)) {
# si on spécifie un répertoire, créer un fichier temporaire dedans
$file = tempnam($destdir, "tmp_nulib_");
$this->delete = true;
} elseif (is_file($destdir)) {
# si on spécifie un fichier qui existe le prendre comme "fichier
# temporaire" mais ne pas le supprimer automatiquement
$file = $destdir;
$this->delete = false;
} else {
# un chemin qui n'existe pas: ne le sélectionner que si le répertoire
# existe. dans ce cas, le fichier sera créé automatiquement, mais pas
# supprimé
if (!is_dir(dirname($destdir))) {
throw new IOException("$destdir: no such file or directory");
}
$file = $destdir;
$this->delete = false;
}
parent::__construct($file, $mode, $throwOnError, $allowLocking);
}
/** @var bool */
protected $delete;
function __destruct() {
$this->close();
if ($this->delete) $this->delete();
}
/** supprimer le fichier. NB: le flux **n'est pas** fermé au préalable */
function delete(): self {
if (file_exists($this->file)) unlink($this->file);
return $this;
}
/**
* renommer le fichier. le flux est fermé d'abord
*
* @param int|null $defaultMode mode par défaut si le fichier destination
* n'existe pas. sinon, changer le mode du fichier temporaire à la valeur du
* fichier destination après renommage
* @param bool $setOwner si le propriétaire et/ou le groupe du fichier
* temporaire ne sont pas les mêmes que le fichier destination, tenter de
* changer le propriétaire et le groupe du fichier temporaire à la valeur
* du fichier destination après le renommage (nécessite les droits de root)
* @throws IOException
*/
function rename(string $dest, ?int $defaultMode=0644, bool $setOwner=true): void {
$this->close();
$file = $this->file;
if (file_exists($dest)) {
$mode = fileperms($dest);
if ($setOwner) {
$tmpowner = fileowner($file);
$owner = fileowner($dest);
$tmpgroup = filegroup($file);
$group = filegroup($dest);
} else {
$owner = $group = null;
}
} else {
$mode = $defaultMode;
$owner = $group = null;
}
if (!rename($file, $dest)) {
throw new IOException("$file: unable to rename to $dest");
}
$this->file = $dest;
if ($mode !== null) chmod($dest, $mode);
if ($owner !== null) {
if ($owner !== $tmpowner) chown($dest, $owner);
if ($group !== $tmpgroup) chgrp($dest, $group);
}
if ($mode !== null || $owner !== null) clearstatcache(true, $file);
}
/** streamer le contenu du fichier en sortie */
function readfile(?string $contentType=null, ?string $charset=null, ?string $filename=null, string $disposition=null): bool {
if ($contentType !== null) http::content_type($contentType, $charset);
if ($filename !== null) http::download_as($filename, $disposition);
return readfile($this->file) !== false;
}
}

50
src/os/file/_IFile.php Normal file
View File

@ -0,0 +1,50 @@
<?php
namespace nur\sery\os\file;
use Iterator;
use nur\sery\os\IOException;
use nur\sery\php\ICloseable;
/**
* Interface _IFile: méthodes communes entre {@link IReader} et {@link IWriter}
*/
interface _IFile extends Iterator, ICloseable {
/**
* @return resource|null retourner la resource associée à ce fichier si cela
* a du sens
*/
function getResource();
/** si ce fichier est basé sur une resource, ajouter un filtre. */
function appendFilter(string $filterName, ?int $readWrite=null, $params=null): void;
/** si ce fichier est basé sur une resource, ajouter un filtre. */
function prependFilter(string $filterName, ?int $readWrite=null, $params=null): void;
/** si ce fichier est basé sur une resource, spécifier l'encoding. */
function setEncodingFilter(string $from, string $to): void;
/** vérifier si la resource est associée à un terminal */
function isatty(): bool;
/** obtenir des informations sur le fichier */
function fstat(): array;
/**
* retourner la position actuelle de lecture/écriture
*
* @throws IOException
*/
function ftell(): int;
/**
* modifier la position actuelle de lecture/écriture
*
* @return int la position après avoir déplacé le pointeur
* @throws IOException
*/
function fseek(int $offset, int $whence=SEEK_SET): int;
/** fermer le fichier si c'est nécessaire */
function close(bool $close=true): void;
}

295
src/os/path.php Normal file
View File

@ -0,0 +1,295 @@
<?php # -*- coding: utf-8 mode: php -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
namespace nur\sery\os;
use nulib\str;
/**
* Class path: manipulation de chemins du système de fichiers
*/
class path {
/**
* Normaliser le chemin spécifié:
* - supprimer si possible les ocurrences de ./ et ../
* - supprimer les slash multiples, sauf s'il y en a exactement 2 au début de
* la chaine
* - "~/path" est transformé en "$HOME/path"
*
* cas particuliers:
* - retourner la valeur null inchangée
* - retourner '.' pour un chemin vide
*/
static final function normalize(?string $path): ?string {
if ($path === null) return null;
if ($path !== "") {
if (substr($path, 0, 2) == "~/") {
$path = sh::homedir().substr($path, 1);
}
$initial_slashes = strpos($path, "/") === 0;
if ($initial_slashes &&
strpos($path, "//") === 0 &&
strpos($path, "///") === false) {
$initial_slashes = 2;
}
$initial_slashes = intval($initial_slashes);
$comps = explode("/", $path);
$new_comps = array();
foreach ($comps as $comp) {
if ($comp === "" || $comp === ".") continue;
if ($comp != ".." ||
(!$initial_slashes && !$new_comps) ||
($new_comps && end($new_comps) == "..")) {
array_push($new_comps, $comp);
} elseif ($new_comps) {
array_pop($new_comps);
}
}
$comps = $new_comps;
$path = implode("/", $comps);
if ($initial_slashes) $path = str_repeat("/", $initial_slashes) . $path;
}
return $path !== ""? $path: ".";
}
/** Comme normalize() mais retourner inchangée la valeur false */
static final function with($path) {
if ($path === null || $path === false) return $path;
else return self::normalize(strval($path));
}
/** obtenir le chemin absolu normalisé correspondant à $path */
static final function abspath($path, ?string $cwd=null) {
if ($path === null || $path === false) return $path;
$path = strval($path);
if (substr($path, 0, 1) !== "/") {
if ($cwd === null) $cwd = getcwd();
$path = "$cwd/$path";
}
return self::normalize($path);
}
/**
* obtenir le chemin $path exprimé relativement à $basedir.
* si $basedir est relatif, il est exprimé par rapport à $cwd qui vaut par
* défaut le chemin courant.
*/
static final function relpath($path, string $basedir="", ?string $cwd=null): string {
$path = self::abspath($path, $cwd);
$basedir = self::abspath($basedir, $cwd);
if ($path === $basedir) return "";
$initial_slashes = strpos($path, "//") === 0? 2: 1;
$path = substr($path, $initial_slashes);
$initial_slashes = strpos($basedir, "//") === 0? 2: 1;
$basedir = substr($basedir, $initial_slashes);
if ($basedir === "") return $path;
$pparts = $path? explode("/", $path): [];
$pcount = count($pparts);
$bparts = explode("/", $basedir);
$bcount = count($bparts);
$i = 0;
while ($i < $pcount && $i < $bcount && $pparts[$i] === $bparts[$i]) {
$i++;
}
$ups = array_fill(0, $bcount - $i, "..");
$relparts = array_merge($ups, array_slice($pparts, $i));
return implode("/", $relparts);
}
/**
* obtenir un chemin visuellement agréable:
* - $HOME est remplacé par "~"
* - $CWD/ est remplacé par ""
*/
static final function ppath($path, ?string $cwd=null): string {
$path = self::abspath($path, $cwd);
$homedir = sh::homedir();
$cwd = getcwd()."/";
if (str::del_prefix($path, $cwd)) {
return $path;
}
if (str::starts_with($homedir, $path)) {
str::del_prefix($path, $homedir);
$path ="~$path";
}
return $path;
}
/**
* obtenir le chemin absolu canonique correspondant à $path
*
* @throws IOException si le chemin n'existe pas ou si une autre erreur se
* produit
*/
static final function realpath($path) {
if ($path === null || $path === false) return $path;
$path = strval($path);
$realpath = realpath($path);
if ($realpath === false) throw IOException::last_error();
return $realpath;
}
/**
* obtenir le chemin parent de $path
*
* cas particuliers:
* dirname("/") === "/";
* dirname("filename") === ".";
*/
static final function dirname($path): ?string {
if ($path === null || $path === false) return $path;
else return dirname(strval($path));
}
/** obtenir le nom du fichier sans son chemin */
static final function filename($path): ?string {
if ($path === null || $path === false) return $path;
$index = strrpos($path, "/");
if ($index !== false) $path = substr($path, $index + 1);
return $path;
}
/** obtenir le nom de base du fichier (sans son chemin et sans l'extension) */
static final function basename($path): ?string {
if ($path === null || $path === false) return $path;
$basename = self::filename($path);
$index = strrpos($basename, ".");
if ($index !== false) $basename = substr($basename, 0, $index);
return $basename;
}
/** obtenir l'extension du fichier. l'extension est retournée avec le '.' */
static final function ext($path): ?string {
if ($path === null || $path === false) return $path;
$ext = self::filename($path);
$index = strrpos($ext, ".");
if ($index === false) $ext = "";
else $ext = substr($ext, $index);
return $ext;
}
/** découper le chemin entre sa partie "répertoire" et "fichier" */
static final function split($path): array {
if ($path === null || $path === false) return [$path, $path];
elseif ($path === "") return ["", ""];
$index = strrpos($path, "/");
if ($index !== false) {
if ($index == 0) $dir = "/";
else $dir = substr($path, 0, $index);
$file = substr($path, $index + 1);
} else {
$dir = "";
$file = $path;
}
return [$dir, $file];
}
/**
* joindre les composantes pour faire un seul chemin normalisé. les composantes
* sont joints inconditionnellements
*/
static final function join(...$parts): ?string {
$path = null;
foreach ($parts as $part) {
if ($part === null || $part === false) continue;
if ($path && substr($path, -1) !== "/"
&& substr($part, 0, 1) !== "/") $path .= "/";
if ($path === null) $path = "";
$path .= $part;
}
return self::normalize($path);
}
/**
* joindre les composantes pour faire un seul chemin normalisé. chaque
* composante relative s'ajoute au chemin. chaque composante absolue relance
* le calcul de puis le début
* e.g reljoin("a", "b", "../c", "d/e") retourne "a/c/d/e"
* alors que reljoin("a", "b", "/c", "d/e") retourne "/c/d/e"
*/
static final function reljoin(...$parts): ?string {
$path = null;
foreach ($parts as $part) {
if ($part === null || $part === false) continue;
if (substr($part, 0, 1) == "/") {
$path = $part;
continue;
}
if ($path && substr($path, -1) !== "/") $path .= "/";
elseif ($path === null) $path = "";
$path .= $part;
}
return self::normalize($path);
}
/**
* tester si $dir est situé dans $basedir.
*
* par défaut, on considère qu'un chemin $dir est situé dans lui-même sauf si
* $strict == true, auquel cas "$dir est dans $basedir" implique $dir !== $basedir
*/
static final function is_within(string $dir, string $basedir, bool $strict=false): bool {
$dir = self::abspath($dir);
$basedir = self::abspath($basedir);
if ($dir === $basedir) return !$strict;
$prefix = $basedir;
if ($prefix !== "/") $prefix .= "/";
return substr($dir, 0, strlen($prefix)) === $prefix;
}
/**
* tester si $file a un répertoire, c'est à dire s'il contient le caractère '/'
* e.g have_dir('dir/name') est vrai alors que have_dir('name.ext') est faux
*/
static final function have_dir(string $file): bool {
return strpos($file, "/") !== false;
}
/**
* tester si le chemin est qualifié, c'est à dire s'il est absolu ou s'il est
* relatif à '.' ou '..'
* e.g is_qualified('./a/b') est vrai alors que is_qualified('a/b') est faux
*/
static final function is_qualified(string $file): bool {
if (strpos($file, "/") === false) return false;
if (substr($file, 0, 1) == "/") return true;
if (substr($file, 0, 2) == "./") return true;
if (substr($file, 0, 3) == "../") return true;
return false;
}
/** tester si $file a une extension */
static final function have_ext(string $file): bool {
$pos = strrpos($file, "/");
if ($pos === false) $pos = 0;
return strpos($file, ".", $pos) !== false;
}
static final function ensure_ext(string $path, string $new_ext, ?string $replace_ext=null): string {
[$dir, $filename] = self::split($path);
if (self::ext($filename) === $replace_ext) {
$filename = self::basename($filename);
}
$filename .= $new_ext;
return self::join($dir, $filename);
}
/** tester si le fichier, le répertoire ou le lien symbolique existe */
static final function exists(string $file): bool {
return is_link($file) || file_exists($file);
}
/** tester si $dir est un répertoire (mais pas un lien symbolique) */
static final function is_dir(string $dir, bool $allow_link=false): bool {
return is_dir($dir) && ($allow_link || !is_link($dir));
}
/** tester si $dir est un fichier (mais pas un lien symbolique) */
static final function is_file(string $file, bool $allow_link=false): bool {
return is_file($file) && ($allow_link || !is_link($file));
}
}

310
src/os/sh.php Normal file
View File

@ -0,0 +1,310 @@
<?php
namespace nur\sery\os;
use nulib\cl;
use RuntimeException;
class sh {
static final function _quote(string $value): string {
if (preg_match('/^[a-zA-Z0-9_.@:,\/+-]+$/', $value)) return $value;
return escapeshellarg($value);
}
/**
* obtenir la valeur $value quotée pour le shell
*
* si c'est une valeur scalaire, elle sera quotée sous la forme 'value'
* si c'est un tableau séquentiel, elle sera quoté sous la forme ('value'...)
* sinon, elle sera quotée sour la forme ([key]=value...)
*/
static final function quote($value): string {
if (is_array($value)) {
$parts = [];
if (cl::is_list($value)) {
foreach ($value as $part) {
$parts[] = self::_quote(strval($part));
}
} else {
foreach ($value as $key => $part) {
$key = self::_quote($key);
$val = self::_quote($part);
$parts[] = "[$key]=$val";
}
}
return "(".implode(" ", $parts).")";
} else {
return self::_quote(strval($value));
}
}
/**
* obtenir une commande shell à partir du tableau des arguments.
* à utiliser avec exec()
*/
static final function join(array $parts): string {
$count = count($parts);
for($i = 0; $i < $count; $i++) {
$parts[$i] = self::_quote(strval($parts[$i]));
}
return implode(" ", $parts);
}
private static final function add_redir(string &$cmd, ?string $redir, ?string $input, ?string $output): void {
if ($redir !== null) {
switch ($redir) {
case "outonly":
case "noerr":
$redir = "2>/dev/null";
break;
case "erronly":
case "noout":
$redir = "2>&1 >/dev/null";
break;
case "both":
case "err2out":
$redir = "2>&1";
break;
case "none":
case "null":
$redir = ">/dev/null 2>&1";
break;
case "default":
$redir = null;
break;
}
}
if ($input !== null) {
$redir = $redir !== null? "$redir ": "";
$redir .= "<".escapeshellarg($input);
}
if ($output !== null) {
$redir = $redir !== null? "$redir ": "";
$redir .= ">".escapeshellarg($output);
}
if ($redir !== null) $cmd .= " $redir";
}
/**
* Corriger la commande $cmd:
* - si c'est tableau, joindre les arguments avec {@link join()}
* - sinon, mettre les caractères en échappement avec {@link escapeshellarg()}
*/
static final function fix_cmd(&$cmd, ?string $redir=null, ?string $input=null, ?string $output=null): void {
if (is_array($cmd)) $cmd = self::join($cmd);
else $cmd = escapeshellcmd(strval($cmd));
self::add_redir($cmd, $redir, $input, $output);
}
/**
* Lancer la commande spécifiée avec passthru() et retourner le code de retour
* dans la variable $retcode. $cmd doit déjà être formaté comme il convient
*
* voici la différence entre system(), passthru() et exec()
* +----------------+-----------------+----------------+----------------+
* | Command | Displays Output | Can Get Output | Gets Exit Code |
* +----------------+-----------------+----------------+----------------+
* | passthru() | Yes (raw) | No | Yes |
* | system() | Yes (as text) | Last line only | Yes |
* | exec() | No | Yes (array) | Yes |
* +----------------+-----------------+----------------+----------------+
*
* @return bool true si la commande s'est lancée sans erreur, false sinon
*/
static final function _passthru(string $cmd, int &$retcode=null): bool {
passthru($cmd, $retcode);
return $retcode == 0;
}
/**
* Comme {@link _passthru()} mais lancer la commande spécifiée avec system().
* cf la doc de {@link _passthru()} pour les autres détails
*/
static final function _system(string $cmd, string &$output=null, int &$retcode=null): bool {
$last_line = system($cmd, $retcode);
if ($last_line !== false) $output = $last_line;
return $retcode == 0;
}
/**
* Comme {@link _passthru()} mais lancer la commande spécifiée avec exec().
* cf la doc de {@link _passthru()} pour les autres détails
*/
static final function _exec(string $cmd, array &$output=null, int &$retcode=null): bool {
exec($cmd, $output, $retcode);
return $retcode == 0;
}
/**
* Lancer la commande $cmd dans un processus fils via un shell et attendre la
* fin de son exécution.
*
* $cmd doit déjà être formaté comme il convient
*/
static final function _fork_exec(string $cmd, int &$retcode=null): bool {
$pid = pcntl_fork();
if ($pid == -1) {
// parent, impossible de forker
throw new RuntimeException("unable to fork");
} elseif ($pid) {
// parent, fork ok
pcntl_waitpid($pid, $status);
if (pcntl_wifexited($status)) {
$retcode = pcntl_wexitstatus($status);
} else {
$retcode = 127;
}
return $retcode == 0;
}
// child, fork ok
pcntl_exec("/bin/sh", ["-c", $cmd]);
return false;
}
/**
* Corriger la commande spécifiée avec {@link fix_cmd()} puis la lancer
* avec passthru() et retourner le code de retour dans la variable $retcode
*
* $redir spécifie le type de redirection demandée:
* - "default" | null: $output reçoit STDOUT et STDERR n'est pas redirigé
* - "outonly" | "noerr": $output ne reçoit que STDOUT et STDERR est perdu
* - "erronly" | "noout": $output ne reçoit que STDERR et STDOUT est perdu
* - "both" | "err2out": $output reçoit STDOUT et STDERR
* - "none" | "null": STDOUT et STDERR sont perdus
* - sinon c'est une redirection spécifique, et la valeur est rajoutée telle
* quelle à la ligne de commande
*
* @return bool true si la commande s'est lancée sans erreur, false sinon
*/
static final function passthru($cmd, int &$retcode=null, ?string $redir=null): bool {
self::fix_cmd($cmd, $redir);
return self::_passthru($cmd, $retcode);
}
/**
* Comme {@link passthru()} mais lancer la commande spécifiée avec system().
* Cf la doc de {@link passthru()} pour les autres détails
*/
static final function system($cmd, string &$output=null, int &$retcode=null, ?string $redir=null): bool {
self::fix_cmd($cmd, $redir);
return self::_system($cmd, $output, $retcode);
}
/**
* Comme {@link passthru()} mais lancer la commande spécifiée avec exec().
* Cf la doc de {@link passthru()} pour les autres détails
*/
static final function exec($cmd, array &$output=null, int &$retcode=null, ?string $redir=null): bool {
self::fix_cmd($cmd, $redir);
return self::_exec($cmd, $output, $retcode);
}
/**
* Corriger la commande spécifiée avec {@link fix_cmd()}, la préfixer de
* "exec" puis la lancer avec {@link _fork_exec()}
*/
static final function fork_exec($cmd, int &$retcode=null, ?string $redir=null): bool {
self::fix_cmd($cmd, $redir);
return self::_fork_exec("exec $cmd", $retcode);
}
#############################################################################
/** retourner le répertoire $HOME */
static final function homedir(): string {
$homedir = getenv("HOME");
if ($homedir === false) {
$homedir = posix_getpwuid(posix_getuid())["dir"];
}
return path::abspath($homedir);
}
/** s'assurer que le répertoire $dir existe */
static final function mkdirp(string $dir): bool {
if (is_dir($dir)) return true;
return mkdir($dir, 0777, true);
}
/** créer le répertoire qui va contenir le fichier $file */
static final function mkdirof(string $file): bool {
if (file_exists($file)) return true;
$dir = path::dirname($file);
if (file_exists($dir)) return true;
return mkdir($dir, 0777, true);
}
/**
* créer un répertoire avec un nom unique. ce répertoire doit être supprimé
* manuellement quand il n'est plus utilisé.
*
* @return string le chemin du répertoire
* @throws IOException si une erreur se produit (impossible de créer un
* répertoire unique après 2560 essais)
*/
static final function mktempdir(?string $prefix=null, ?string $basedir=null): string {
if ($basedir === null) $basedir = sys_get_temp_dir();
if ($prefix !== null) $prefix .= "-";
$max = 2560;
do {
$dir = "$basedir/$prefix".uniqid();
$r = @mkdir($dir);
$max--;
} while ($r === false && $max > 0);
if ($r === false) {
throw IOException::last_error("$dir: unable to create directory");
}
return $dir;
}
/**
* Supprimer un répertoire créé avec mktempdir
*
* un minimum de vérification est effectué qu'il s'agit bien d'un répertoire
* généré par mktempdir
*/
static final function rmtempdir(string $tmpdir, ?string $prefix=null, ?string $basedir=null): void {
if ($basedir === null) $basedir = sys_get_temp_dir();
if ($prefix !== null) $prefix .= "-";
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
if (fnmatch("$basedir/$prefix?????????????", $tmpdir)) {
self::exec(["rm", "-rf", $tmpdir]);
} else {
throw new IOException("$tmpdir: n'est pas un répertoire temporaire");
}
}
/**
* supprimer tous les répertoires temporaires qui ont été créés avec le
* suffixe spécifié dans le répertoire $basedir
*/
static final function cleantempdirs(string $prefix, ?string $basedir=null): void {
if ($basedir === null) $basedir = sys_get_temp_dir();
$prefix .= "-";
// 13 '?' parce que c'est la taille d'une chaine générée par uniqid()
$tmpdirs = glob("$basedir/$prefix?????????????", GLOB_ONLYDIR);
if ($tmpdirs) {
self::exec(["rm", "-rf", ...$tmpdirs]);
}
}
static final function is_diff_file(string $f, string $g): bool {
if (!is_file($f) || !is_file($g)) return true;
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
return $retcode !== 0;
}
static final function is_same_file(string $f, string $g): bool {
if (!is_file($f) || !is_file($g)) return false;
self::exec(array("diff", "-q", $f, $g), $output, $retcode);
return $retcode === 0;
}
static final function is_diff_link(string $f, string $g): bool {
if (!is_link($f) || !is_link($g)) return true;
return @readlink($f) !== @readlink($g);
}
static final function is_same_link(string $f, string $g): bool {
if (!is_link($f) || !is_link($g)) return false;
return @readlink($f) === @readlink($g);
}
}

View File

@ -3,9 +3,10 @@ namespace nur\sery\output\std;
use Exception; use Exception;
use nulib\cl; use nulib\cl;
use nur\sery\sys\content; use nur\sery\os\file\Stream;
use nur\sery\sys\IContent; use nur\sery\php\content\content;
use nur\sery\sys\IPrintable; use nur\sery\php\content\IContent;
use nur\sery\php\content\IPrintable;
/** /**
* Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque * Class StdOutput: affichage sur STDOUT, STDERR ou dans un fichier quelconque
@ -87,6 +88,7 @@ class StdOutput {
$indent = cl::get($params, "indent"); $indent = cl::get($params, "indent");
$flush = cl::get($params, "flush"); $flush = cl::get($params, "flush");
if ($output instanceof Stream) $output = $output->getResource();
if ($output !== null) { if ($output !== null) {
if ($output === "php://stdout") { if ($output === "php://stdout") {
$outf = STDOUT; $outf = STDOUT;

10
src/php/ICloseable.php Normal file
View File

@ -0,0 +1,10 @@
<?php
namespace nur\sery\php;
/**
* Interface ICloseable: un objet que l'on peut fermer
*/
interface ICloseable {
/** fermer l'objet et libérer les resources */
function close(): void;
}

View File

@ -1,4 +1,4 @@
# nulib\sys # nulib\php
Ce package contient des services généraux spécifiques à PHP Ce package contient des services généraux spécifiques à PHP

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php\content;
/** /**
* Interface IContent: un objet capable de produire du contenu à afficher. le * Interface IContent: un objet capable de produire du contenu à afficher. le

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php\content;
/** /**
* Interface IPrintable: un objet qui écrit du contenu sur la sortie standard * Interface IPrintable: un objet qui écrit du contenu sur la sortie standard

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php\content;
/** /**
* Interface IStaticContent: comme {@link IContent} mais la liste retournée est * Interface IStaticContent: comme {@link IContent} mais la liste retournée est

View File

@ -1,7 +1,8 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php\content;
use nulib\cl; use nulib\cl;
use nur\sery\php\func;
/** /**
* Class content: gestionnaire de contenu (statique ou dynamique) * Class content: gestionnaire de contenu (statique ou dynamique)

View File

@ -1,5 +1,5 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php;
use Closure; use Closure;
use nulib\cl; use nulib\cl;

View File

@ -0,0 +1,148 @@
<?php
namespace nur\sery\php\iter;
use Exception;
use Iterator;
use nur\sery\os\EOFException;
use nur\sery\php\ICloseable;
/**
* Class AbstractIterator: implémentation de base d'un itérateur
*/
abstract class AbstractIterator implements Iterator, ICloseable {
/**
* initialiser les ressources nécessaires à l'itération.
*
* les exceptions lancées par cette méthode sont ignorées.
*/
protected function _setup(): void {}
/**
* lancer un traitement avant de commencer l'itération.
*
* cette méthode est appelée après {@link _setup()} et l'objet est garanti
* d'être dans un état valide.
*
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles.
*/
protected function beforeIter() {}
/**
* retourner le prochain élément. lancer l'exception {@link EOFException} pour
* indiquer que plus aucun élément n'est disponible
*
* le cas échéant, initialiser $key
*
* @throws EOFException
*/
abstract protected function _next(&$key);
/**
* modifier l'élement retourné par {@link _next()} avant de le retourner.
*
*
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles.
*/
protected function cook(&$item) {}
/**
* lancer un traitement avant de terminer l'itération et de libérer les
* resources
*
* cette méthode est appelée avant {@link _teardown()} et l'objet est garanti
* d'être dans un état valide.
*
* cette méthode est prévue pour être surchargée par l'utilisateur, mais il
* doit gérer lui-même les exceptions éventuelles.
*/
protected function beforeClose(): void {}
/**
* libérer les ressources allouées.
*
* les exceptions lancées par cette méthode sont ignorées.
*/
protected function _teardown(): void {}
function close(): void {
$this->rewind();
}
#############################################################################
# Implémentation par défaut
private $setup = false;
private $valid = false;
private $toredown = true;
private $index = 0;
protected $key;
protected $item = null;
function key() {
return $this->key;
}
function current() {
return $this->item;
}
function next(): void {
if ($this->toredown) return;
$this->valid = false;
try {
$item = $this->_next($key);
} catch (EOFException $e) {
$this->beforeClose();
try {
$this->_teardown();
} catch (Exception $e) {
}
$this->toredown = true;
return;
}
$this->cook($item);
$this->item = $item;
if ($key !== null) {
$this->key = $key;
} else {
$this->index++;
$this->key = $this->index;
}
$this->valid = true;
}
function rewind(): void {
if ($this->setup) {
if (!$this->toredown) {
$this->beforeClose();
try {
$this->_teardown();
} catch (Exception $e) {
}
}
$this->setup = false;
$this->valid = false;
$this->toredown = true;
$this->index = 0;
$this->key = null;
$this->item = null;
}
}
function valid(): bool {
if (!$this->setup) {
try {
$this->_setup();
} catch (Exception $e) {
}
$this->setup = true;
$this->toredown = false;
$this->beforeIter();
$this->next();
}
return $this->valid;
}
}

View File

@ -0,0 +1,20 @@
<?php
namespace nur\sery\php\json;
use RuntimeException;
class JsonException extends RuntimeException {
static final function json_last_error(?string $prefix=null): ?self {
$json_last_error = json_last_error();
if ($json_last_error === JSON_ERROR_NONE) return null;
$message = json_last_error_msg()." ($json_last_error)";
if ($prefix) $message = "$prefix: $message";
return new static($message);
}
static final function ensure_json_value($value, ?string $prefix=null) {
$exception = self::json_last_error($prefix);
if ($exception === null) return $value;
else throw $exception;
}
}

View File

@ -3,7 +3,7 @@ namespace nur\sery\values;
use nulib\cl; use nulib\cl;
use nulib\str; use nulib\str;
use nur\sery\sys\func; use nur\sery\php\func;
use ReflectionClass; use ReflectionClass;
use ReflectionException; use ReflectionException;

144
src/web/http.php Normal file
View File

@ -0,0 +1,144 @@
<?php
namespace nur\sery\web;
use nulib\cv;
/**
* Class http: des outils pour parler le protocole HTTP dans le cadre de la
* génération d'une page
*/
class http {
static final function get_url(): string {
$proto = $_SERVER["HTTP_X_FORWARDED_PROTO"];
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
$uri = $_SERVER["SCRIPT_NAME"];
return "$proto://$host$uri";
}
static final function get_baseurl(): string {
$proto = $_SERVER["HTTP_X_FORWARDED_PROTO"];
$host = $_SERVER["HTTP_X_FORWARDED_HOST"];
$baseuri = dirname($_SERVER["SCRIPT_NAME"]);
return "$proto://$host$baseuri";
}
/**
* ne pas mettre en cache la réponse.
*
* appeler cette méthode est nécessaire si une page est dynamique
* et que les sessions ne sont pas utilisées.
*
* si un session est déjà démarrée, cette méthode est un NOP, sauf si
* $force==true
*/
static function no_cache(bool $force=false): void {
if (session_status() == PHP_SESSION_ACTIVE && !$force) return;
header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
header("Cache-Control: no-cache, max-age=0, must-revalidate");
header("Pragma: public");
}
/** spécifier le type de contenu de la réponse */
static function content_type(string $content_type=null, string $charset=null): void {
if ($content_type === null) $content_type = "application/octet-stream";
if (substr($content_type, 0, 5) == "text/") {
if ($charset === null) $charset = "utf-8";
if ($charset) $content_type .= "; charset=$charset";
}
header("Content-Type: $content_type");
}
static final function content_type_text(): void {
self::content_type("text/plain");
}
/** indiquer que la réponse doit être téléchargée */
static function download_as($filename, string $disposition=null): void {
if (cv::nz($filename)) {
$filename = basename($filename);
if ($disposition === null) $disposition = "attachment";
header("Content-Disposition: $disposition; filename=\"$filename\"");
}
}
/** rediriger vers l'url spécifiée et arrêter le script immédiatement */
static function redirect(string $url, bool $exit_now=true): void {
header("Location: $url");
if ($exit_now) exit;
}
/** rafraichir vers l'url spécifiée et arrêter le script immédiatement */
static function refresh(string $url, int $delay=1, bool $exit_now=true): void {
header("Refresh: $delay; url=$url");
if ($exit_now) exit;
}
/** envoyer le status "pas de contenu" et arrêter le script immédiatement */
static function send_no_content(bool $exit_now=true): void {
header("HTTP/1.1 204 No Content");
if ($exit_now) exit;
}
/**
* Envoyer le fichier spécifié pour téléchargement et arrêter le script
* immédiatement. On assume que les méthodes statiques {@link content_type()}
* et {@link download_as()} ont déjà été appelées.
*
* - si $delete==true, supprimer le fichier après téléchargement.
* - si $add_content_length==true, ajouter une en-tête qui précise la taille
* du fichier à télécharger. attention: la taille du fichier ne devrait pas
* changer
* - si $close_session==true, la session est fermée avant de lancer le
* téléchargement du fichier pour éviter de garder inutilement le verrou.
* NB: si le fichier à retourner est d'une certaine taille, il est avisé
* de fermer la session pour ne pas bloquer la navigation.
*
* si une erreur s'est produite, retourner false, même si $exit_now==true
*/
static function send_file(string $file, bool $delete=false, bool $close_session=true, bool $add_content_length=true, bool $exit_now=true): bool {
$fd = fopen($file, "r");
if ($fd === false) return false;
if ($close_session && session_status() == PHP_SESSION_ACTIVE) session_write_close();
if ($add_content_length) {
$size = filesize($file);
if ($size !== false) header("Content-Length: $size");
}
if ($delete) unlink($file);
$written = fpassthru($fd);
# XXX si $written !== $size, il faudrait agir en conséquence...
fclose($fd);
if ($written === false) return false;
if ($exit_now) exit;
return true;
}
static final function error(string $status, bool $exit=true): void {
header("HTTP/1.1 $status");
if ($exit) exit();
}
static final function error400(?string $message=null, ?string $body=null, bool $exit=true): void {
if ($message === null) $message = "Bad Request";
elseif ($body === null) $body = $message;
self::error("400 $message", false);
if ($body !== null) echo $body;
if ($exit) exit();
}
static final function error404(?string $message=null, ?string $body=null, bool $exit=true): void {
if ($message === null) $message = "Not Found";
elseif ($body === null) $body = $message;
self::error("404 $message", false);
if ($body !== null) echo $body;
if ($exit) exit();
}
static final function error500(?string $message=null, ?string $body=null, bool $exit=true): void {
if ($message === null) $message = "Internal Server Error";
elseif ($body === null) $body = $message;
self::error("500 $message", false);
if ($body !== null) echo $body;
if ($exit) exit();
}
}

View File

@ -1,7 +1,8 @@
<?php <?php
namespace nur\sery\sys; namespace nur\sery\php\content;
use nur\sery\sys\impl\html; use nur\sery\php\content\content;
use nur\sery\php\content\impl\html;
use PHPUnit\Framework\TestCase; use PHPUnit\Framework\TestCase;
class contentTest extends TestCase { class contentTest extends TestCase {

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\sys\impl; namespace nur\sery\php\content\impl;
use nur\sery\sys\IContent; use nur\sery\php\content\IContent;
class AContent implements IContent { class AContent implements IContent {
function getContent(): iterable { function getContent(): iterable {

View File

@ -1,7 +1,7 @@
<?php <?php
namespace nur\sery\sys\impl; namespace nur\sery\php\content\impl;
use nur\sery\sys\IPrintable; use nur\sery\php\content\IPrintable;
class APrintable implements IPrintable { class APrintable implements IPrintable {
function print(): void { function print(): void {

View File

@ -1,7 +1,8 @@
<?php <?php
namespace nur\sery\sys\impl; namespace nur\sery\php\content\impl;
use nur\sery\sys\IStaticContent; use nur\sery\php\content\IStaticContent;
use nur\sery\php\content\impl\html;
class AStaticContent implements IStaticContent { class AStaticContent implements IStaticContent {
function getContent(): iterable { function getContent(): iterable {

View File

@ -1,8 +1,8 @@
<?php <?php
namespace nur\sery\sys\impl; namespace nur\sery\php\content\impl;
use nur\sery\sys\content; use nur\sery\php\content\content;
use nur\sery\sys\IStaticContent; use nur\sery\php\content\IStaticContent;
class ATag implements IStaticContent { class ATag implements IStaticContent {
function __construct(string $tag, $contents=null) { function __construct(string $tag, $contents=null) {

View File

@ -1,5 +1,7 @@
<?php <?php
namespace nur\sery\sys\impl; namespace nur\sery\php\content\impl;
use nur\sery\php\content\impl\ATag;
class html { class html {
const H1 = [self::class, "h1"]; const H1 = [self::class, "h1"];

View File

@ -12,7 +12,7 @@ namespace {
function func_o1v($b=9, ...$c): array { return [$b, ...$c]; } function func_o1v($b=9, ...$c): array { return [$b, ...$c]; }
} }
namespace nur\sery\sys { namespace nur\sery\php {
use nulib\tests\TestCase; use nulib\tests\TestCase;
class funcTest extends TestCase { class funcTest extends TestCase {