<pman>Intégration de la branche dev74
This commit is contained in:
		
						commit
						b20613f3b7
					
				
							
								
								
									
										14
									
								
								bin/._pman-composer_local_deps.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										14
									
								
								bin/._pman-composer_local_deps.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,14 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__ . "/../php/vendor/autoload.php"; | ||||
| 
 | ||||
| use nulib\tools\pman\ComposerFile; | ||||
| use nulib\tools\pman\ComposerPmanFile; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| $composer = new ComposerFile(); | ||||
| 
 | ||||
| $deps = $composer->getLocalDeps(); | ||||
| foreach ($deps as $dep => $path) { | ||||
|   echo "$path\n"; | ||||
| } | ||||
							
								
								
									
										22
									
								
								bin/._pman-composer_select_profile.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										22
									
								
								bin/._pman-composer_select_profile.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,22 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__ . "/../php/vendor/autoload.php"; | ||||
| 
 | ||||
| use nulib\tools\pman\ComposerFile; | ||||
| use nulib\tools\pman\ComposerPmanFile; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| $composer = new ComposerFile(); | ||||
| $config = new ComposerPmanFile(); | ||||
| 
 | ||||
| if ($argc <= 1) { | ||||
|   throw new ValueException("Il faut spécifier le profil à sélectionner"); | ||||
| } | ||||
| $profile = $argv[1]; | ||||
| 
 | ||||
| $composer->selectProfile($profile, $config); | ||||
| if (getenv("PMAN_COMPOSER_DEBUG")) { | ||||
|   $composer->print(); | ||||
| } else { | ||||
|   $composer->write(); | ||||
| } | ||||
							
								
								
									
										10
									
								
								bin/_merge82
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								bin/_merge82
									
									
									
									
									
								
							| @ -1,4 +1,12 @@ | ||||
| #!/bin/bash | ||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||
| source "$(dirname -- "$0")/../load.sh" || exit 1 | ||||
| 
 | ||||
| exec "$(dirname -- "$0")/pmer" --tech-merge -Bdev82 dev74 -a "git checkout dev74" "$@" | ||||
| dev74=1 | ||||
| args=( | ||||
|     "merger la branche dev74 dans la branche dev82" | ||||
|     -n,--no-dev74 dev74= "ne pas basculer sur la branche dev74 après la fusion" | ||||
| ) | ||||
| parse_args "$@"; set -- "${args[@]}" | ||||
| 
 | ||||
| exec "$MYDIR/pmer" --tech-merge -Bdev82 dev74 ${dev74:+-a "git checkout dev74"} "$@" | ||||
|  | ||||
| @ -1,14 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__ . "/../php/vendor/autoload.php"; | ||||
| 
 | ||||
| use nulib\tools\pman\ComposerFile; | ||||
| use nulib\tools\pman\ComposerPmanFile; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| $composer = new ComposerFile(); | ||||
| 
 | ||||
| $deps = $composer->getLocalDeps(); | ||||
| foreach ($deps as $dep => $path) { | ||||
|   echo "$path\n"; | ||||
| } | ||||
							
								
								
									
										1
									
								
								bin/_pman-composer_local_deps.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/_pman-composer_local_deps.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| runphp | ||||
| @ -1,22 +0,0 @@ | ||||
| #!/usr/bin/php
 | ||||
| <?php | ||||
| require __DIR__ . "/../php/vendor/autoload.php"; | ||||
| 
 | ||||
| use nulib\tools\pman\ComposerFile; | ||||
| use nulib\tools\pman\ComposerPmanFile; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| $composer = new ComposerFile(); | ||||
| $config = new ComposerPmanFile(); | ||||
| 
 | ||||
| if ($argc <= 1) { | ||||
|   throw new ValueException("Il faut spécifier le profil à sélectionner"); | ||||
| } | ||||
| $profile = $argv[1]; | ||||
| 
 | ||||
| $composer->selectProfile($profile, $config); | ||||
| if (getenv("PMAN_COMPOSER_DEBUG")) { | ||||
|   $composer->print(); | ||||
| } else { | ||||
|   $composer->write(); | ||||
| } | ||||
							
								
								
									
										1
									
								
								bin/_pman-composer_select_profile.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/_pman-composer_select_profile.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | ||||
| runphp | ||||
							
								
								
									
										24
									
								
								bin/jsondiff
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										24
									
								
								bin/jsondiff
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,24 @@ | ||||
| #!/bin/bash | ||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||
| source "$(dirname -- "$0")/../load.sh" || exit 1 | ||||
| 
 | ||||
| args=( | ||||
|     "comparer deux fichiers JSON" | ||||
|     "first.json second.json" | ||||
| ) | ||||
| parse_args "$@"; set -- "${args[@]}" | ||||
| 
 | ||||
| first="$1"; shift | ||||
| [ -n "$first" ] || die "vous devez spécifier le premier fichier" | ||||
| second="$1"; shift | ||||
| [ -n "$second" ] || die "vous devez spécifier le deuxième fichier" | ||||
| 
 | ||||
| if [ $# -gt 0 ]; then | ||||
|     options=("$@") | ||||
| else | ||||
|     options=(-u) | ||||
| fi | ||||
| 
 | ||||
| diff "${options[@]}" \ | ||||
|      <(jq . <"$first") \ | ||||
|      <(jq . <"$second") | ||||
							
								
								
									
										21
									
								
								bin/runphp
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								bin/runphp
									
									
									
									
									
								
							| @ -20,14 +20,25 @@ while true; do | ||||
|     cd .. | ||||
| done | ||||
| 
 | ||||
| if [ -z "$PROJDIR" ]; then | ||||
|     # s'il n'y a pas de projet, --bs est l'action par défaut | ||||
|     [ $# -gt 0 ] || set -- --bs --ue | ||||
| elif [ "$MYNAME" == composer ]; then | ||||
| export RUNPHP_MOUNT= | ||||
| if [ "$MYNAME" == composer ]; then | ||||
|     set -- composer "$@" | ||||
| elif [[ "$MYNAME" == *.php ]]; then | ||||
|     # frontend pour une commande php | ||||
|     set -- php "$MYDIR/.$MYNAME" "$@" | ||||
|     # s'assurer que NULIBDIR est monté | ||||
|     RUNPHP_MOUNT="$NULIBDIR" | ||||
| elif [ $# -eq 0 ]; then | ||||
|     # s'il n'y a pas de projet, --bs est l'action par défaut | ||||
|     [ -n "$PROJDIR" ] || set -- --bs --ue | ||||
| else | ||||
|     case "$1" in | ||||
|     *.php|*.phar) set -- php "$@";; | ||||
|     *.php|*.phar) | ||||
|         set -- php "$@" | ||||
|         # s'assurer que le répertoire du script est monté | ||||
|         setx RUNPHP_MOUNT=dirname "$1" | ||||
|         setx RUNPHP_MOUNT=abspath "$RUNPHP_MOUNT" | ||||
|         ;; | ||||
|     esac | ||||
| fi | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										14
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										14
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							| @ -4,7 +4,7 @@ | ||||
|         "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", | ||||
|         "This file is @generated automatically" | ||||
|     ], | ||||
|     "content-hash": "2d630ab5ff0ffe6139447cf93c362ed7", | ||||
|     "content-hash": "4569957a35f86d8a4964d01c7358935c", | ||||
|     "packages": [ | ||||
|         { | ||||
|             "name": "symfony/deprecation-contracts", | ||||
| @ -361,16 +361,16 @@ | ||||
|         }, | ||||
|         { | ||||
|             "name": "nikic/php-parser", | ||||
|             "version": "v5.4.0", | ||||
|             "version": "v5.5.0", | ||||
|             "source": { | ||||
|                 "type": "git", | ||||
|                 "url": "https://github.com/nikic/PHP-Parser.git", | ||||
|                 "reference": "447a020a1f875a434d62f2a401f53b82a396e494" | ||||
|                 "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" | ||||
|             }, | ||||
|             "dist": { | ||||
|                 "type": "zip", | ||||
|                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", | ||||
|                 "reference": "447a020a1f875a434d62f2a401f53b82a396e494", | ||||
|                 "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", | ||||
|                 "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", | ||||
|                 "shasum": "" | ||||
|             }, | ||||
|             "require": { | ||||
| @ -413,9 +413,9 @@ | ||||
|             ], | ||||
|             "support": { | ||||
|                 "issues": "https://github.com/nikic/PHP-Parser/issues", | ||||
|                 "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" | ||||
|                 "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" | ||||
|             }, | ||||
|             "time": "2024-12-30T11:07:19+00:00" | ||||
|             "time": "2025-05-31T08:24:38+00:00" | ||||
|         }, | ||||
|         { | ||||
|             "name": "nulib/tests", | ||||
|  | ||||
| @ -143,6 +143,19 @@ class Capacitor implements ITransactor { | ||||
|     return $this->storage->_charge($this->channel, $item, $func, $args, $row); | ||||
|   } | ||||
| 
 | ||||
|   function chargeAll(?iterable $items, $func=null, ?array $args=null): int { | ||||
|     $count = 0; | ||||
|     if ($items !== null) { | ||||
|       if ($func !== null) { | ||||
|         $func = func::with($func, $args)->bind($this->channel); | ||||
|       } | ||||
|       foreach ($items as $item) { | ||||
|         $count += $this->charge($item, $func); | ||||
|       } | ||||
|     } | ||||
|     return $count; | ||||
|   } | ||||
| 
 | ||||
|   function discharge(bool $reset=true): Traversable { | ||||
|     return $this->storage->_discharge($this->channel, $reset); | ||||
|   } | ||||
|  | ||||
| @ -214,8 +214,18 @@ class CapacitorChannel implements ITransactor { | ||||
| 
 | ||||
|   protected ?array $migration; | ||||
| 
 | ||||
|   function getMigration(): ?array { | ||||
|     return $this->migration; | ||||
|   function getMigration(?string $prefix=null): ?array { | ||||
|     if ($prefix === null || $this->migration === null) return $this->migration; | ||||
|     $migration = null; | ||||
|     str::add_suffix($prefix, ":"); | ||||
|     foreach ($this->migration as $mkey => $mdef) { | ||||
|       if (str::starts_with($prefix, $mkey)) { | ||||
|         $migration[$mkey] = $mdef; | ||||
|       } elseif (strpos($mkey, ":") === false) { | ||||
|         $migration[$mkey] = $mdef; | ||||
|       } | ||||
|     } | ||||
|     return $migration; | ||||
|   } | ||||
| 
 | ||||
|   protected ?array $primaryKeys; | ||||
| @ -444,27 +454,57 @@ class CapacitorChannel implements ITransactor { | ||||
|     return $this->capacitor->charge($item, $func, $args, $row); | ||||
|   } | ||||
| 
 | ||||
|   function chargeAll(?iterable $items, $func=null, ?array $args=null): int { | ||||
|     return $this->capacitor->chargeAll($items, $func, $args); | ||||
|   } | ||||
| 
 | ||||
|   function discharge(bool $reset=true): Traversable { | ||||
|     return $this->capacitor->discharge($reset); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * retourner le filtre de base: les filtres de toutes les fonctions ci-dessous | ||||
|    * sont fusionnées avec le filtre de base | ||||
|    * | ||||
|    * cela permet de limiter toutes les opérations à un sous-ensemble des données | ||||
|    * du canal | ||||
|    */ | ||||
|   function getBaseFilter(): ?array { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
|   protected function verifixFilter(&$filter): void { | ||||
|     if ($filter !== null && !is_array($filter)) { | ||||
|       $primaryKeys = $this->primaryKeys ?? ["id_"]; | ||||
|       $id = $filter; | ||||
|       $this->verifixId($id); | ||||
|       $filter = [$primaryKeys[0] => $id]; | ||||
|     } | ||||
|     $filter = cl::merge($this->getBaseFilter(), $filter); | ||||
|   } | ||||
| 
 | ||||
|   function count($filter=null): int { | ||||
|     $this->verifixFilter($filter); | ||||
|     return $this->capacitor->count($filter); | ||||
|   } | ||||
| 
 | ||||
|   function one($filter, ?array $mergeQuery=null): ?array { | ||||
|     $this->verifixFilter($filter); | ||||
|     return $this->capacitor->one($filter, $mergeQuery); | ||||
|   } | ||||
| 
 | ||||
|   function all($filter, ?array $mergeQuery=null): Traversable { | ||||
|     $this->verifixFilter($filter); | ||||
|     return $this->capacitor->all($filter, $mergeQuery); | ||||
|   } | ||||
| 
 | ||||
|   function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int { | ||||
|     $this->verifixFilter($filter); | ||||
|     return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated); | ||||
|   } | ||||
| 
 | ||||
|   function delete($filter, $func=null, ?array $args=null): int { | ||||
|     $this->verifixFilter($filter); | ||||
|     return $this->capacitor->delete($filter, $func, $args); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -45,7 +45,7 @@ abstract class CapacitorStorage { | ||||
|   const GENLIC_DEFINITION = "varchar(80)"; | ||||
|   const GENLIB_DEFINITION = "varchar(255)"; | ||||
|   const GENTEXT_DEFINITION = "mediumtext"; | ||||
|   const GENBOOL_DEFINITION = "integer(1)"; | ||||
|   const GENBOOL_DEFINITION = "integer(1) default 0"; | ||||
|   const GENUUID_DEFINITION = "varchar(36)"; | ||||
| 
 | ||||
|   protected static function gencol($def): string { | ||||
| @ -122,7 +122,7 @@ abstract class CapacitorStorage { | ||||
|   } | ||||
| 
 | ||||
|   protected function getMigration(CapacitorChannel $channel): ?array { | ||||
|     return $channel->getMigration(); | ||||
|     return $channel->getMigration($this->db()->getPrefix()); | ||||
|   } | ||||
| 
 | ||||
|   /** sérialiser les valeurs qui doivent l'être dans $row */ | ||||
| @ -434,7 +434,7 @@ abstract class CapacitorStorage { | ||||
|     } | ||||
| 
 | ||||
|     if ($func !== null) { | ||||
|       $updates = func::with($func) | ||||
|       $updates = func::with($func, $args) | ||||
|         ->prependArgs([$item, $row, $prow]) | ||||
|         ->bind($channel) | ||||
|         ->invoke(); | ||||
|  | ||||
| @ -2,6 +2,12 @@ | ||||
| namespace nulib\db; | ||||
| 
 | ||||
| interface IDatabase extends ITransactor { | ||||
|   /** | ||||
|    * retourner le type de la base de données (mysql, pgsql, sqlite, ...) | ||||
|    * ce préfixe peut servir à qualifier les migrations | ||||
|    */ | ||||
|   function getPrefix(): ?string; | ||||
| 
 | ||||
|   /** obtenir la requête SQL correspondant à $query */ | ||||
|   function getSql($query, ?array $params=null): string; | ||||
| 
 | ||||
|  | ||||
| @ -4,6 +4,8 @@ namespace nulib\db\mysql; | ||||
| use nulib\db\pdo\Pdo; | ||||
| 
 | ||||
| class Mysql extends Pdo { | ||||
|   const PREFIX = "mysql"; | ||||
| 
 | ||||
|   function getDbname(): ?string { | ||||
|     $url = $this->dbconn["name"] ?? null; | ||||
|     if ($url !== null && preg_match('/^mysql(?::|.*;)dbname=([^;]+)/i', $url, $ms)) { | ||||
|  | ||||
| @ -39,7 +39,7 @@ class MysqlStorage extends CapacitorStorage { | ||||
|   function _getMigration(CapacitorChannel $channel): _mysqlMigration { | ||||
|     $migrations = cl::merge([ | ||||
|       "0init" => [$this->_createSql($channel)], | ||||
|     ], $channel->getMigration()); | ||||
|     ], $channel->getMigration($this->db->getPrefix())); | ||||
|     return new _mysqlMigration($migrations, $channel->getName()); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -12,6 +12,12 @@ use nulib\ValueException; | ||||
| class Pdo implements IDatabase { | ||||
|   use Tvalues; | ||||
| 
 | ||||
|   const PREFIX = null; | ||||
| 
 | ||||
|   function getPrefix(): ?string { | ||||
|     return static::PREFIX; | ||||
|   } | ||||
| 
 | ||||
|   static function with($pdo, ?array $params=null): self { | ||||
|     if ($pdo instanceof static) { | ||||
|       return $pdo; | ||||
|  | ||||
| @ -12,6 +12,12 @@ use nulib\ValueException; | ||||
| class Pgsql implements IDatabase { | ||||
|   use Tvalues; | ||||
| 
 | ||||
|   const PREFIX = "pgsql"; | ||||
| 
 | ||||
|   function getPrefix(): ?string { | ||||
|     return self::PREFIX; | ||||
|   } | ||||
| 
 | ||||
|   static function with($pgsql, ?array $params=null): self { | ||||
|     if ($pgsql instanceof static) { | ||||
|       return $pgsql; | ||||
|  | ||||
| @ -11,7 +11,7 @@ class PgsqlStorage extends CapacitorStorage { | ||||
|   const SERTS_DEFINITION = "timestamp"; | ||||
|   const GENSERIAL_DEFINITION = "serial primary key"; | ||||
|   const GENTEXT_DEFINITION = "text"; | ||||
|   const GENBOOL_DEFINITION = "boolean"; | ||||
|   const GENBOOL_DEFINITION = "boolean default false"; | ||||
|   const GENUUID_DEFINITION = "uuid"; | ||||
| 
 | ||||
|   function __construct($pgsql) { | ||||
| @ -44,7 +44,7 @@ class PgsqlStorage extends CapacitorStorage { | ||||
|   function _getMigration(CapacitorChannel $channel): _pgsqlMigration { | ||||
|     $migrations = cl::merge([ | ||||
|       "0init" => [$this->_createSql($channel)], | ||||
|     ], $channel->getMigration()); | ||||
|     ], $channel->getMigration($this->db->getPrefix())); | ||||
|     return new _pgsqlMigration($migrations, $channel->getName()); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -19,6 +19,12 @@ use SQLite3Stmt; | ||||
| class Sqlite implements IDatabase { | ||||
|   use Tvalues; | ||||
| 
 | ||||
|   const PREFIX = "sqlite"; | ||||
| 
 | ||||
|   function getPrefix(): ?string { | ||||
|     return self::PREFIX; | ||||
|   } | ||||
| 
 | ||||
|   static function with($sqlite, ?array $params=null): self { | ||||
|     if ($sqlite instanceof static) { | ||||
|       return $sqlite; | ||||
|  | ||||
| @ -34,7 +34,7 @@ class SqliteStorage extends CapacitorStorage { | ||||
|   function _getMigration(CapacitorChannel $channel): _sqliteMigration { | ||||
|     $migrations = cl::merge([ | ||||
|       "0init" => [$this->_createSql($channel)], | ||||
|     ], $channel->getMigration()); | ||||
|     ], $channel->getMigration($this->db->getPrefix())); | ||||
|     return new _sqliteMigration($migrations, $channel->getName()); | ||||
|   } | ||||
| 
 | ||||
|  | ||||
| @ -26,7 +26,7 @@ class CsvBuilder extends AbstractBuilder { | ||||
|   } | ||||
| 
 | ||||
|   protected function _checkOk(): bool { | ||||
|     $size = $this->ftell(); | ||||
|     $this->size = $size = $this->ftell(); | ||||
|     if ($size === 0) return false; | ||||
|     $this->rewind(); | ||||
|     return true; | ||||
|  | ||||
| @ -118,18 +118,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { | ||||
|     if ($unsetRows) $this->rows = null; | ||||
|   } | ||||
| 
 | ||||
|   abstract protected function _sendContentType(): void; | ||||
| 
 | ||||
|   protected bool $sentHeaders = false; | ||||
| 
 | ||||
|   function sendHeaders(): void { | ||||
|     if ($this->sentHeaders) return; | ||||
|     $this->_sendContentType(); | ||||
|     $output = $this->output; | ||||
|     if ($output !== null) { | ||||
|       http::download_as(path::filename($output)); | ||||
|     } | ||||
|     $this->sentHeaders = true; | ||||
|   function getCount(): int { | ||||
|     return $this->index; | ||||
|   } | ||||
| 
 | ||||
|   protected function _build(?iterable $rows=null): void { | ||||
| @ -141,6 +131,8 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { | ||||
| 
 | ||||
|   protected bool $built = false, $closed = false; | ||||
| 
 | ||||
|   protected ?int $size = null; | ||||
| 
 | ||||
|   function build(?iterable $rows=null, bool $close=true): bool { | ||||
|     $ok = true; | ||||
|     if (!$this->built) { | ||||
| @ -154,6 +146,24 @@ abstract class AbstractBuilder extends TempStream implements IBuilder { | ||||
|     return $ok; | ||||
|   } | ||||
| 
 | ||||
|   abstract protected function _sendContentType(): void; | ||||
| 
 | ||||
|   protected bool $sentHeaders = false; | ||||
| 
 | ||||
|   function sendHeaders(): void { | ||||
|     if ($this->sentHeaders) return; | ||||
|     $this->_sendContentType(); | ||||
|     $output = $this->output; | ||||
|     if ($output !== null) { | ||||
|       http::download_as(path::filename($output)); | ||||
|     } | ||||
|     $size = $this->size; | ||||
|     if ($size !== null) { | ||||
|       header("Content-Length: $size"); | ||||
|     } | ||||
|     $this->sentHeaders = true; | ||||
|   } | ||||
| 
 | ||||
|   function sendFile(?iterable $rows=null): int { | ||||
|     if (!$this->built) { | ||||
|       $this->_build($rows); | ||||
|  | ||||
| @ -1,6 +1,8 @@ | ||||
| <?php | ||||
| namespace nulib\file\tab; | ||||
| 
 | ||||
| use nulib\file\IWriter; | ||||
| 
 | ||||
| interface IBuilder extends \nulib\file\IReader { | ||||
|   function writeHeaders(?array $headers=null): void; | ||||
| 
 | ||||
| @ -8,7 +10,23 @@ interface IBuilder extends \nulib\file\IReader { | ||||
| 
 | ||||
|   function writeAll(?iterable $rows=null): void; | ||||
| 
 | ||||
|   /** | ||||
|    * @return int le nombre de ligne qui ont été écrites (sans compte l'en-tête) | ||||
|    */ | ||||
|   function getCount(): int; | ||||
| 
 | ||||
|   /** préparer le fichier (sans l'envoyer) */ | ||||
|   function build(?iterable $rows=null, bool $close=true): bool; | ||||
| 
 | ||||
|   /** enregistrer le fichier préparé avec {@link build()} */ | ||||
|   function copyTo(IWriter $dest, bool $closeWriter=false, bool $closeReader=true): void; | ||||
| 
 | ||||
|   /** envoyer les en-têtes */ | ||||
|   function sendHeaders(): void; | ||||
| 
 | ||||
|   /** | ||||
|    * envoyer le fichier pour téléchargement (la méthode {@link sendHeaders()} | ||||
|    * est automatiquement appelée le cas échéant | ||||
|    */ | ||||
|   function sendFile(?iterable $rows=null): int; | ||||
| } | ||||
|  | ||||
| @ -15,6 +15,6 @@ class Date extends DateTime { | ||||
|   } | ||||
| 
 | ||||
|   function format($format=self::DEFAULT_FORMAT): string { | ||||
|     return \DateTime::format($format); | ||||
|     return parent::format($format); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -4,6 +4,7 @@ namespace nulib\php\time; | ||||
| use DateTimeInterface; | ||||
| use DateTimeZone; | ||||
| use InvalidArgumentException; | ||||
| use nulib\str; | ||||
| 
 | ||||
| /** | ||||
|  * Class DateTime: une date et une heure | ||||
| @ -29,10 +30,20 @@ class DateTime extends \DateTime { | ||||
|     else return new static($datetime); | ||||
|   } | ||||
| 
 | ||||
|   static function withn($datetime): ?self { | ||||
|     if ($datetime === null) return null; | ||||
|     elseif ($datetime instanceof static) return $datetime; | ||||
|     else return new static($datetime); | ||||
|   } | ||||
| 
 | ||||
|   static function ensure(&$datetime): void { | ||||
|     $datetime = static::withn($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])?$/'; | ||||
|   const YMDHISZ_PATTERN = '/^((?:\d{2})?\d{2})-?(\d{2})-?(\d{2})[tT](\d{2}):?(\d{2}):?(\d{2})?([zZ]|\+\d{2}:?\d{2})?$/'; | ||||
| 
 | ||||
| 
 | ||||
|   protected static function get_value(array $datetime, ?string $key, ?string $k, ?int $index) { | ||||
| @ -206,16 +217,34 @@ class DateTime extends \DateTime { | ||||
|     return $year; | ||||
|   } | ||||
| 
 | ||||
|   function __construct($datetime="now", DateTimeZone $timezone=null) { | ||||
|   static function fix_z(?string $Z): ?string { | ||||
|     $Z = strtoupper($Z); | ||||
|     str::del_prefix($Z, "+"); | ||||
|     if (preg_match('/^\d{4}$/', $Z)) { | ||||
|       $Z = substr($Z, 0, 2).":".substr($Z, 2); | ||||
|     } | ||||
|     if ($Z === "Z" || $Z === "UTC" || $Z === "00:00") return "UTC"; | ||||
|     return "GMT+$Z"; | ||||
|   } | ||||
| 
 | ||||
|   function __construct($datetime="now", DateTimeZone $timezone=null, ?bool $forceLocalTimezone=null) { | ||||
|     $forceLocalTimezone ??= $timezone === null; | ||||
|     if ($forceLocalTimezone) { | ||||
|       $setTimezone = $timezone; | ||||
|       $timezone = null; | ||||
|     } | ||||
| 
 | ||||
|     $datetime ??= "now"; | ||||
|     if ($datetime instanceof \DateTimeInterface) { | ||||
|       if ($timezone === null) $timezone = $datetime->getTimezone(); | ||||
|       $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)) { | ||||
|       $Y = $H = $Z = null; | ||||
|       if (preg_match(self::DMY_PATTERN, $datetime, $ms)) { | ||||
| @ -253,22 +282,30 @@ class DateTime extends \DateTime { | ||||
|       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"); | ||||
|         if ($Z !== null) $timezone = new DateTimeZone(self::fix_z($Z)); | ||||
|       } | ||||
|       parent::__construct($datetime, $timezone); | ||||
| 
 | ||||
|     } elseif (is_array($datetime) && ($datetime = self::parse_array($datetime)) !== null) { | ||||
|       [$Y, $m, $d, $H, $M, $S, $tz] = $datetime; | ||||
|       $setTimezone = $timezone; | ||||
|       $timezone = null; | ||||
|       [$Y, $m, $d, $H, $M, $S, $Z] = $datetime; | ||||
|       if ($H === null && $M === null && $S === null) { | ||||
|         $datetime = sprintf("%04d-%02d-%02d", $Y, $m, $d); | ||||
|       } else { | ||||
|         $datetime = sprintf("%04d-%02d-%02d %02d:%02d:%02d", $Y, $m, $d, $H ?? 0, $M ?? 0, $S ?? 0); | ||||
|       } | ||||
|       if ($tz === "Z" || $tz === "z") $tz = "UTC"; | ||||
|       $timezone = $tz !== null? new DateTimeZone($tz): null; | ||||
|       if ($Z !== null) $timezone = new DateTimeZone(self::fix_z($Z)); | ||||
|       parent::__construct($datetime, $timezone); | ||||
| 
 | ||||
|     } else { | ||||
|       throw new InvalidArgumentException("datetime must be a string or an instance of DateTimeInterface"); | ||||
|     } | ||||
| 
 | ||||
|     if ($forceLocalTimezone) { | ||||
|       $setTimezone ??= new DateTimeZone(date_default_timezone_get()); | ||||
|       $this->setTimezone($setTimezone); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function diff($target, $absolute=false): DateInterval { | ||||
| @ -276,7 +313,13 @@ class DateTime extends \DateTime { | ||||
|   } | ||||
| 
 | ||||
|   function format($format=self::DEFAULT_FORMAT): string { | ||||
|     return \DateTime::format($format); | ||||
|     if (array_key_exists($format, self::INT_FORMATS)) { | ||||
|       $format = self::INT_FORMATS[$format]; | ||||
|     } elseif (array_key_exists($format, self::STRING_FORMATS)) { | ||||
|       $format = self::STRING_FORMATS[$format]; | ||||
|     } | ||||
|     if (is_callable($format)) return $format($this); | ||||
|     else return \DateTime::format($format); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|  | ||||
| @ -5,6 +5,7 @@ use nulib\db\Capacitor; | ||||
| use nulib\db\sqlite\impl\MyChannel; | ||||
| use nulib\db\sqlite\impl\MyChannelV2; | ||||
| use nulib\db\sqlite\impl\MyChannelV3; | ||||
| use nulib\db\sqlite\impl\MyIndexChannel; | ||||
| use nulib\output\msg; | ||||
| use nulib\output\std\StdMessenger; | ||||
| use nulib\php\time\DateTime; | ||||
| @ -69,6 +70,42 @@ alter table my add column date_mod datetime; | ||||
| -- infos | ||||
| alter table my add column age integer; | ||||
| 
 | ||||
| EOT; | ||||
|     self::assertSame($expected, $sql); | ||||
|   } | ||||
| 
 | ||||
|   function testMigrationIndex() { | ||||
|     $storage = new SqliteStorage(__DIR__.'/capacitor.db'); | ||||
|     $data = [ | ||||
|       ["un", "premier", "first"], | ||||
|       ["deux", "deuxieme", "second"], | ||||
|     ]; | ||||
| 
 | ||||
|     new Capacitor($storage, $channel = new MyIndexChannel()); | ||||
|     $channel->reset(true); | ||||
|     $channel->chargeAll($data); | ||||
| 
 | ||||
|     $sql = $channel->getCapacitor()->getCreateSql(); | ||||
|     $class = MyIndexChannel::class; | ||||
|     $expected = <<<EOT | ||||
| -- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||
| -- autogénéré à partir de $class | ||||
| 
 | ||||
| -- 0init | ||||
| create table  if not exists my_index ( | ||||
|   name varchar not null primary key | ||||
| , first varchar | ||||
| , second varchar | ||||
| , item__ mediumtext | ||||
| , item__sum_ varchar(40) | ||||
| , created_ datetime | ||||
| , modified_ datetime | ||||
| ); | ||||
| 
 | ||||
| -- index | ||||
| create index my_index_first on my_index(first); | ||||
| create index my_index_second on my_index(second); | ||||
| 
 | ||||
| EOT; | ||||
|     self::assertSame($expected, $sql); | ||||
|   } | ||||
|  | ||||
							
								
								
									
										29
									
								
								php/tests/db/sqlite/impl/MyIndexChannel.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								php/tests/db/sqlite/impl/MyIndexChannel.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| <?php | ||||
| namespace nulib\db\sqlite\impl; | ||||
| 
 | ||||
| use nulib\cl; | ||||
| use nulib\db\CapacitorChannel; | ||||
| 
 | ||||
| class MyIndexChannel extends CapacitorChannel { | ||||
|   const NAME = "my_index"; | ||||
|   const TABLE_NAME = "my_index"; | ||||
|   const COLUMN_DEFINITIONS = [ | ||||
|     "name" => "varchar not null primary key", | ||||
|     "first" => "varchar", | ||||
|     "second" => "varchar", | ||||
|   ]; | ||||
|   const MIGRATION = [ | ||||
|     "index" => [ | ||||
|       "create index my_index_first on my_index(first)", | ||||
|       "create index my_index_second on my_index(second)", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   function getItemValues($item): ?array { | ||||
|     return cl::select($item, [ | ||||
|       "name" => 0, | ||||
|       "first" => 1, | ||||
|       "second" => 2, | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
| @ -467,9 +467,11 @@ OPTIONS | ||||
| 
 | ||||
| function host_docker_run() { | ||||
|     # lancer une commande avec docker | ||||
|     if [ "$1" == composer ]; then | ||||
|         : # pas d'analyse d'argument pour composer | ||||
|     else | ||||
|     case "$1" in | ||||
|     ci|cu|composer) | ||||
|         : # pas d'analyse d'argument pour les commandes composer | ||||
|         ;; | ||||
|     *) | ||||
|         SOPTS=+w: | ||||
|         LOPTS=help,chdir:,no-use-rslave | ||||
|         args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" | ||||
| @ -513,7 +515,7 @@ OPTIONS | ||||
|             esac | ||||
|             shift | ||||
|         done | ||||
|     fi | ||||
|     esac | ||||
| 
 | ||||
|     args=( | ||||
|         run -it --rm | ||||
| @ -535,26 +537,34 @@ OPTIONS | ||||
| 
 | ||||
|     # monter le répertoire qui contient $PROJDIR | ||||
|     mount_composer= | ||||
|     mount_runphp=1 | ||||
|     mount_standalone=1 | ||||
|     mount_mount=1 | ||||
|     if [ -z "$PROJDIR" -o "${PROJDIR#$HOME/}" != "$PROJDIR" -o "$PROJDIR" == "$HOME" ]; then | ||||
|         # bind mount $HOME | ||||
|         args+=(-v "$HOME:$HOME${UseRslave:+:rslave}") | ||||
|         [ -n "$RUNPHP_STANDALONE" ] && | ||||
|             [ "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ] && | ||||
|             mount_runphp= | ||||
|         if [ -n "$RUNPHP_STANDALONE" -a "${RUNPHP_STANDALONE#$HOME/}" != "$RUNPHP_STANDALONE" ]; then | ||||
|             mount_standalone= | ||||
|         fi | ||||
|         if [ -n "$RUNPHP_MOUNT" -a "${RUNPHP_MOUNT#$HOME/}" != "$RUNPHP_MOUNT" ]; then | ||||
|             mount_mount= | ||||
|         fi | ||||
|     elif [ -n "$PROJDIR" ]; then | ||||
|         # bind mount uniquement le répertoire du projet | ||||
|         args+=(-v "$PROJDIR:$PROJDIR${UseRslave:+:rslave}") | ||||
|         mount_composer=1 | ||||
|         [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_runphp= | ||||
|         [ "$RUNPHP_STANDALONE" == "$PROJDIR" ] && mount_standalone= | ||||
|         [ "$RUNPHP_MOUNT" == "$PROJDIR" ] && mount_mount= | ||||
|     fi | ||||
|     if [ -n "$mount_composer" -a -d "$HOME/.composer" ]; then | ||||
|         # monter la configuration de composer | ||||
|         args+=(-v "$HOME/.composer:$HOME/.composer") | ||||
|     fi | ||||
|     if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_runphp" ]; then | ||||
|     if [ -n "$RUNPHP_STANDALONE" -a -n "$mount_standalone" ]; then | ||||
|         args+=(-v "$RUNPHP_STANDALONE:$RUNPHP_STANDALONE") | ||||
|     fi | ||||
|     if [ -n "$RUNPHP_MOUNT" -a -n "$mount_mount" ]; then | ||||
|         args+=(-v "$RUNPHP_MOUNT:$RUNPHP_MOUNT") | ||||
|     fi | ||||
|     args+=(-w "$(pwd)") | ||||
| 
 | ||||
|     # lancer avec l'utilisateur courant | ||||
| @ -589,9 +599,22 @@ function container_exec() { | ||||
|         fi | ||||
|     fi | ||||
| 
 | ||||
|     if [ "$1" == composer ]; then | ||||
|         : # pas d'analyse d'argument pour composer | ||||
|     else | ||||
|     # pour les commandes suivantes, pas d'analyse d'argument | ||||
|     case "$1" in | ||||
|     ci) | ||||
|         eecho "== installing composer dependencies" | ||||
|         shift | ||||
|         composer i "$@" | ||||
|         ;; | ||||
|     cu) | ||||
|         eecho "== upgrading composer dependencies" | ||||
|         shift | ||||
|         composer u "$@" | ||||
|         ;; | ||||
|     composer) | ||||
|         "$@" | ||||
|         ;; | ||||
|     *) | ||||
|         SOPTS=+w: | ||||
|         LOPTS=chdir: | ||||
|         args="$(getopt -n "$MYNAME" -o "$SOPTS" -l "$LOPTS" -- "$@")" || exit 1; eval "set -- $args" | ||||
| @ -606,26 +629,14 @@ function container_exec() { | ||||
|             esac | ||||
|             shift | ||||
|         done | ||||
|     fi | ||||
| 
 | ||||
|     if [ $# -eq 0 ]; then | ||||
|         die "no command specified" | ||||
|     elif [ "$1" == ci ]; then | ||||
|         eecho "== installing composer dependencies" | ||||
|         shift | ||||
|         composer i "$@" | ||||
|     elif [ "$1" == cu ]; then | ||||
|         eecho "== upgrading composer dependencies" | ||||
|         shift | ||||
|         composer u "$@" | ||||
|     elif [ "$1" == composer ]; then | ||||
|         "$@" | ||||
|     else | ||||
|         [ $# -gt 0 ] || die "no command specified" | ||||
|         if [ -n "$chdir" ]; then | ||||
|             cd "$chdir" || exit 1 | ||||
|         fi | ||||
|         exec "$@" | ||||
|     fi | ||||
|         ;; | ||||
|     esac | ||||
| } | ||||
| 
 | ||||
| ################################################################################ | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user