<pman>Intégration de la branche dev74
This commit is contained in:
		
						commit
						982e313342
					
				
							
								
								
									
										1
									
								
								.idea/nulib-base.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1
									
								
								.idea/nulib-base.iml
									
									
									
										generated
									
									
									
								
							| @ -4,6 +4,7 @@ | ||||
|     <content url="file://$MODULE_DIR$"> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" /> | ||||
|       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> | ||||
|       <excludeFolder url="file://$MODULE_DIR$/php/vendor" /> | ||||
|     </content> | ||||
|     <orderEntry type="inheritedJdk" /> | ||||
|  | ||||
							
								
								
									
										14
									
								
								.idea/php-test-framework.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								.idea/php-test-framework.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,14 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="PhpTestFrameworkVersionCache"> | ||||
|     <tools_cache> | ||||
|       <tool tool_name="PHPUnit"> | ||||
|         <cache> | ||||
|           <versions> | ||||
|             <info id="Local/php/vendor/autoload.php" version="9.6.23" /> | ||||
|           </versions> | ||||
|         </cache> | ||||
|       </tool> | ||||
|     </tools_cache> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										12
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										12
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							| @ -1,5 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="MessDetector"> | ||||
|     <phpmd_settings> | ||||
|       <phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" /> | ||||
|     </phpmd_settings> | ||||
|   </component> | ||||
|   <component name="MessDetectorOptionsConfiguration"> | ||||
|     <option name="transferred" value="true" /> | ||||
|   </component> | ||||
| @ -10,6 +15,11 @@ | ||||
|     <option name="highlightLevel" value="WARNING" /> | ||||
|     <option name="transferred" value="true" /> | ||||
|   </component> | ||||
|   <component name="PhpCodeSniffer"> | ||||
|     <phpcs_settings> | ||||
|       <phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" /> | ||||
|     </phpcs_settings> | ||||
|   </component> | ||||
|   <component name="PhpIncludePathManager"> | ||||
|     <include_path> | ||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" /> | ||||
| @ -55,7 +65,7 @@ | ||||
|   </component> | ||||
|   <component name="PhpUnit"> | ||||
|     <phpunit_settings> | ||||
|       <PhpUnitSettings configuration_file_path="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator/phpunit.xml" custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" use_configuration_file="true" /> | ||||
|       <PhpUnitSettings custom_loader_path="$PROJECT_DIR$/php/vendor/autoload.php" phpunit_phar_path="" /> | ||||
|     </phpunit_settings> | ||||
|   </component> | ||||
|   <component name="PsalmOptionsConfiguration"> | ||||
|  | ||||
							
								
								
									
										10
									
								
								.idea/phpunit.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.idea/phpunit.xml
									
									
									
										generated
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project version="4"> | ||||
|   <component name="PHPUnit"> | ||||
|     <option name="directories"> | ||||
|       <list> | ||||
|         <option value="$PROJECT_DIR$/tests" /> | ||||
|       </list> | ||||
|     </option> | ||||
|   </component> | ||||
| </project> | ||||
							
								
								
									
										2
									
								
								.udir
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								.udir
									
									
									
									
									
								
							| @ -9,7 +9,7 @@ uinc_options=() | ||||
| uinc_args=() | ||||
| preconfig_scripts=() | ||||
| configure_variables=(dest) | ||||
| configure_dest_for=(lib/profile.d/nulib-base) | ||||
| configure_dest_for=(lib/profile.d/nulib) | ||||
| config_scripts=(lib/uinst/conf) | ||||
| install_profiles=true | ||||
| profiledir=lib/profile.d | ||||
|  | ||||
| @ -38,6 +38,8 @@ echo "commit=$commit" | ||||
| # reprendre la valeur affichée par la précédente commande | ||||
| commit=XXX | ||||
| 
 | ||||
| pu | ||||
| 
 | ||||
| git checkout dev74 | ||||
| 
 | ||||
| git cherry-pick "$commit" | ||||
|  | ||||
| @ -138,9 +138,9 @@ class Capacitor implements ITransactor { | ||||
|     $this->storage->_reset($this->channel, $recreate); | ||||
|   } | ||||
| 
 | ||||
|   function charge($item, $func=null, ?array $args=null, ?array &$values=null): int { | ||||
|   function charge($item, $func=null, ?array $args=null, ?array &$row=null): int { | ||||
|     if ($this->subChannels !== null) $this->beginTransaction(); | ||||
|     return $this->storage->_charge($this->channel, $item, $func, $args, $values); | ||||
|     return $this->storage->_charge($this->channel, $item, $func, $args, $row); | ||||
|   } | ||||
| 
 | ||||
|   function discharge(bool $reset=true): Traversable { | ||||
|  | ||||
| @ -25,8 +25,6 @@ class CapacitorChannel implements ITransactor { | ||||
| 
 | ||||
|   const EACH_COMMIT_THRESHOLD = 100; | ||||
| 
 | ||||
|   const USE_CACHE = false; | ||||
| 
 | ||||
|   static function verifix_name(?string &$name, ?string &$tableName=null): void { | ||||
|     if ($name !== null) { | ||||
|       $name = strtolower($name); | ||||
| @ -60,7 +58,6 @@ class CapacitorChannel implements ITransactor { | ||||
|     $this->tableName = $tableName; | ||||
|     $this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS; | ||||
|     $this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold); | ||||
|     $this->useCache = static::USE_CACHE; | ||||
|     $this->setup = false; | ||||
|     $this->created = false; | ||||
|     $columnDefinitions = $this->COLUMN_DEFINITIONS(); | ||||
| @ -111,6 +108,8 @@ class CapacitorChannel implements ITransactor { | ||||
|           $def = strval($def); | ||||
|           if (preg_match('/\bprimary\s+key\b/i', $def)) { | ||||
|             $primaryKeys[] = $col; | ||||
|           } elseif ($def === "genserial") { | ||||
|             $primaryKeys[] = $col; | ||||
|           } | ||||
|         } | ||||
|       } | ||||
| @ -166,23 +165,6 @@ class CapacitorChannel implements ITransactor { | ||||
|     return $this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @var bool faut-il passer par le cache pour les requêtes de all(), each() | ||||
|    * et delete()? | ||||
|    * ça peut être nécessaire avec MySQL/MariaDB si on utilise les requêtes non | ||||
|    * bufférisées, et que la fonction manipule la base de données | ||||
|    */ | ||||
|   protected bool $useCache; | ||||
| 
 | ||||
|   function isUseCache(): bool { | ||||
|     return $this->useCache; | ||||
|   } | ||||
| 
 | ||||
|   function setUseCache(bool $useCache=true): self { | ||||
|     $this->useCache = $useCache; | ||||
|     return $this; | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * initialiser ce channel avant sa première utilisation. | ||||
|    */ | ||||
| @ -255,6 +237,10 @@ class CapacitorChannel implements ITransactor { | ||||
|    * Retourner la clé primaire par cette méthode est l'unique moyen de | ||||
|    * déclencher une mise à jour plutôt qu'une nouvelle création. | ||||
|    * | ||||
|    * Bien que ce ne soit pas prévu à la base, si on veut modifier $item dans | ||||
|    * cette méthode pour des raisons pratiques, il suffit de retournerla valeur | ||||
|    * modifiée avec la clé "item" | ||||
|    * | ||||
|    * Retourner [false] pour annuler le chargement | ||||
|    */ | ||||
|   function getItemValues($item): ?array { | ||||
| @ -277,8 +263,8 @@ class CapacitorChannel implements ITransactor { | ||||
|    * | ||||
|    * cette méthode doit être utilisée dans {@link self::onUpdate()} | ||||
|    */ | ||||
|   function wasRowModified(array $values, array $pvalues): bool { | ||||
|     return $values["item__sum_"] !== $pvalues["item__sum_"]; | ||||
|   function wasRowModified(array $row, array $prow): bool { | ||||
|     return $row["item__sum_"] !== $prow["item__sum_"]; | ||||
|   } | ||||
| 
 | ||||
|   final function serialize($item): ?string { | ||||
| @ -309,17 +295,17 @@ class CapacitorChannel implements ITransactor { | ||||
|     return array_combine($sumCols, [$serial, $sum]); | ||||
|   } | ||||
| 
 | ||||
|   function wasSumModified(string $key, $value, array $pvalues): bool { | ||||
|   function wasSumModified(string $key, $value, array $prow): bool { | ||||
|     $sumCol = $this->getSumCols($key)[1]; | ||||
|     $sum = $this->sum(null, $value); | ||||
|     $psum = $pvalues[$sumCol] ?? $this->sum(null, $pvalues[$key] ?? null); | ||||
|     $psum = $prow[$sumCol] ?? $this->sum(null, $prow[$key] ?? null); | ||||
|     return $sum !== $psum; | ||||
|   } | ||||
| 
 | ||||
|   function _wasSumModified(string $key, array $row, array $prow): bool { | ||||
|   function _wasSumModified(string $key, array $raw, array $praw): bool { | ||||
|     $sumCol = $this->getSumCols($key)[1]; | ||||
|     $sum = $row[$sumCol] ?? null; | ||||
|     $psum = $prow[$sumCol] ?? null; | ||||
|     $sum = $raw[$sumCol] ?? null; | ||||
|     $psum = $praw[$sumCol] ?? null; | ||||
|     return $sum !== $psum; | ||||
|   } | ||||
| 
 | ||||
| @ -328,21 +314,21 @@ class CapacitorChannel implements ITransactor { | ||||
|    * créer un nouvel élément | ||||
|    * | ||||
|    * @param mixed $item l'élément à charger | ||||
|    * @param array $values la ligne à créer, calculée à partir de $item et des | ||||
|    * @param array $row la ligne à créer, calculée à partir de $item et des | ||||
|    * valeurs retournées par {@link getItemValues()} | ||||
|    * @return ?array le cas échéant, un tableau non null à merger dans $values et | ||||
|    * @return ?array le cas échéant, un tableau non null à merger dans $row et | ||||
|    * utilisé pour provisionner la ligne nouvellement créée. | ||||
|    * Retourner [false] pour annuler le chargement (la ligne n'est pas créée) | ||||
|    * | ||||
|    * Si $item est modifié dans cette méthode, il est possible de le retourner | ||||
|    * avec la clé "item" pour mettre à jour la ligne correspondante. | ||||
|    * avec la clé "item" pour mettre à jour la colonne correspondante. | ||||
|    * | ||||
|    * la création ou la mise à jour est uniquement décidée en fonction des | ||||
|    * valeurs calculées par {@link self::getItemValues()}. Bien que cette méthode | ||||
|    * peut techniquement retourner de nouvelles valeurs pour la clé primaire, ça | ||||
|    * risque de créer des doublons | ||||
|    */ | ||||
|   function onCreate($item, array $values, ?array $alwaysNull): ?array { | ||||
|   function onCreate($item, array $row, ?array $alwaysNull): ?array { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
| @ -351,12 +337,12 @@ class CapacitorChannel implements ITransactor { | ||||
|    * mettre à jour un élément existant | ||||
|    * | ||||
|    * @param mixed $item l'élément à charger | ||||
|    * @param array $values la nouvelle ligne, calculée à partir de $item et | ||||
|    * @param array $row la nouvelle ligne, calculée à partir de $item et | ||||
|    * des valeurs retournées par {@link getItemValues()} | ||||
|    * @param array $pvalues la précédente ligne, chargée depuis la base de | ||||
|    * @param array $prow la précédente ligne, chargée depuis la base de | ||||
|    * données | ||||
|    * @return ?array null s'il ne faut pas mettre à jour la ligne. sinon, ce | ||||
|    * tableau est mergé dans $values puis utilisé pour mettre à jour la ligne | ||||
|    * tableau est mergé dans $row puis utilisé pour mettre à jour la ligne | ||||
|    * existante | ||||
|    * Retourner [false] pour annuler le chargement (la ligne n'est pas mise à | ||||
|    * jour) | ||||
| @ -365,7 +351,7 @@ class CapacitorChannel implements ITransactor { | ||||
|    * - La clé primaire (il s'agit généralement de "id_") ne peut pas être | ||||
|    * modifiée. si elle est retournée, elle est ignorée | ||||
|    */ | ||||
|   function onUpdate($item, array $values, array $pvalues): ?array { | ||||
|   function onUpdate($item, array $row, array $prow): ?array { | ||||
|     return null; | ||||
|   } | ||||
| 
 | ||||
| @ -373,8 +359,8 @@ class CapacitorChannel implements ITransactor { | ||||
|    * méthode appelée lors du parcours des éléments avec | ||||
|    * {@link Capacitor::each()} | ||||
|    * | ||||
|    * @param mixed $item l'élément courant | ||||
|    * @param ?array $values la ligne courante | ||||
|    * @param ?array $row la ligne courante. l'élément courant est accessible via | ||||
|    * $row["item"] | ||||
|    * @return ?array le cas échéant, un tableau non null utilisé pour mettre à | ||||
|    * jour la ligne courante | ||||
|    * | ||||
| @ -382,7 +368,7 @@ class CapacitorChannel implements ITransactor { | ||||
|    * - La clé primaire (il s'agit généralement de "id_") ne peut pas être | ||||
|    * modifiée. si elle est retournée, elle est ignorée | ||||
|    */ | ||||
|   function onEach($item, array $values): ?array { | ||||
|   function onEach(array $row): ?array { | ||||
|     return null; | ||||
|   } | ||||
|   const onEach = "->".[self::class, "onEach"][1]; | ||||
| @ -391,11 +377,11 @@ class CapacitorChannel implements ITransactor { | ||||
|    * méthode appelée lors du parcours des éléments avec | ||||
|    * {@link Capacitor::delete()} | ||||
|    * | ||||
|    * @param mixed $item l'élément courant | ||||
|    * @param ?array $values la ligne courante | ||||
|    * @param ?array $row la ligne courante. l'élément courant est accessible via | ||||
|    * $row["item"] | ||||
|    * @return bool true s'il faut supprimer la ligne, false sinon | ||||
|    */ | ||||
|   function onDelete($item, array $values): bool { | ||||
|   function onDelete(array $row): bool { | ||||
|     return true; | ||||
|   } | ||||
|   const onDelete = "->".[self::class, "onDelete"][1]; | ||||
| @ -454,8 +440,8 @@ class CapacitorChannel implements ITransactor { | ||||
|     $this->capacitor->reset($recreate); | ||||
|   } | ||||
| 
 | ||||
|   function charge($item, $func=null, ?array $args=null, ?array &$values=null): int { | ||||
|     return $this->capacitor->charge($item, $func, $args, $values); | ||||
|   function charge($item, $func=null, ?array $args=null, ?array &$row=null): int { | ||||
|     return $this->capacitor->charge($item, $func, $args, $row); | ||||
|   } | ||||
| 
 | ||||
|   function discharge(bool $reset=true): Traversable { | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| <?php | ||||
| namespace nulib\db; | ||||
| 
 | ||||
| use nulib\A; | ||||
| use nulib\cl; | ||||
| use nulib\db\_private\_migration; | ||||
| use nulib\php\func; | ||||
| @ -32,21 +33,43 @@ abstract class CapacitorStorage { | ||||
|     return $channel; | ||||
|   } | ||||
| 
 | ||||
|   /** DOIT être défini dans les classes dérivées */ | ||||
|   const PRIMARY_KEY_DEFINITION = null; | ||||
|   const PRIMARY_KEY_DEFINITION = [ | ||||
|     "id_" => "genserial", | ||||
|   ]; | ||||
| 
 | ||||
|   # les définitions sont par défaut pour MariaDB/MySQL
 | ||||
|   const SERDATA_DEFINITION = "mediumtext"; | ||||
|   const SERSUM_DEFINITION = "varchar(40)"; | ||||
|   const SERTS_DEFINITION = "datetime"; | ||||
|   const GENSERIAL_DEFINITION = "integer primary key auto_increment"; | ||||
|   const GENLIC_DEFINITION = "varchar(80)"; | ||||
|   const GENLIB_DEFINITION = "varchar(255)"; | ||||
|   const GENTEXT_DEFINITION = "mediumtext"; | ||||
|   const GENBOOL_DEFINITION = "integer(1)"; | ||||
|   const GENUUID_DEFINITION = "varchar(36)"; | ||||
| 
 | ||||
|   protected static function sercol($def): string { | ||||
|   protected static function gencol($def): string { | ||||
|     if (!is_string($def)) $def = strval($def); | ||||
|     $def = trim($def); | ||||
|     $parts = preg_split('/\s+/', $def, 2); | ||||
|     if (count($parts) == 2) { | ||||
|       $def = $parts[0]; | ||||
|       $rest = " $parts[1]"; | ||||
|     } else { | ||||
|       $rest = null; | ||||
|     } | ||||
|     switch ($def) { | ||||
|     case "serdata": $def = static::SERDATA_DEFINITION; break; | ||||
|     case "sersum": $def = static::SERSUM_DEFINITION; break; | ||||
|     case "serts": $def = static::SERTS_DEFINITION; break; | ||||
|     case "genserial": $def = static::GENSERIAL_DEFINITION; break; | ||||
|     case "genlic": $def = static::GENLIC_DEFINITION; break; | ||||
|     case "genlib": $def = static::GENLIB_DEFINITION; break; | ||||
|     case "gentext": $def = static::GENTEXT_DEFINITION; break; | ||||
|     case "genbool": $def = static::GENBOOL_DEFINITION; break; | ||||
|     case "genuuid": $def = static::GENUUID_DEFINITION; break; | ||||
|     } | ||||
|     return $def; | ||||
|     return "$def$rest"; | ||||
|   } | ||||
| 
 | ||||
|   const COLUMN_DEFINITIONS = [ | ||||
| @ -81,7 +104,7 @@ abstract class CapacitorStorage { | ||||
|                 $mindex++; | ||||
|               } else { | ||||
|                 if ($mdef) { | ||||
|                   $definitions[$mcol] = self::sercol($mdef); | ||||
|                   $definitions[$mcol] = self::gencol($mdef); | ||||
|                 } else { | ||||
|                   unset($definitions[$mcol]); | ||||
|                 } | ||||
| @ -92,7 +115,7 @@ abstract class CapacitorStorage { | ||||
|           $constraints[] = $def; | ||||
|         } | ||||
|       } else { | ||||
|         $definitions[$col] = self::sercol($def); | ||||
|         $definitions[$col] = self::gencol($def); | ||||
|       } | ||||
|     } | ||||
|     return cl::merge($definitions, $constraints); | ||||
| @ -102,9 +125,35 @@ abstract class CapacitorStorage { | ||||
|     return $channel->getMigration(); | ||||
|   } | ||||
| 
 | ||||
|   /** sérialiser les valeurs qui doivent l'être dans $values */ | ||||
|   protected function serialize(CapacitorChannel $channel, ?array $values): ?array { | ||||
|     if ($values === null) return null; | ||||
|   /** sérialiser les valeurs qui doivent l'être dans $row */ | ||||
|   protected function serialize(CapacitorChannel $channel, ?array $row): ?array { | ||||
|     if ($row === null) return null; | ||||
|     $cols = $this->ColumnDefinitions($channel); | ||||
|     $index = 0; | ||||
|     $raw = []; | ||||
|     foreach (array_keys($cols) as $col) { | ||||
|       $key = $col; | ||||
|       if ($key === $index) { | ||||
|         $index++; | ||||
|       } elseif ($channel->isSerialCol($key)) { | ||||
|         [$serialCol, $sumCol] = $channel->getSumCols($key); | ||||
|         if (array_key_exists($key, $row)) { | ||||
|           $sum = $channel->getSum($key, $row[$key]); | ||||
|           $raw[$serialCol] = $sum[$serialCol]; | ||||
|           if (array_key_exists($sumCol, $cols)) { | ||||
|             $raw[$sumCol] = $sum[$sumCol]; | ||||
|           } | ||||
|         } | ||||
|       } elseif (array_key_exists($key, $row)) { | ||||
|         $raw[$col] = $row[$key]; | ||||
|       } | ||||
|     } | ||||
|     return $raw; | ||||
|   } | ||||
| 
 | ||||
|   /** désérialiser les valeurs qui doivent l'être dans $values */ | ||||
|   protected function unserialize(CapacitorChannel $channel, ?array $raw): ?array { | ||||
|     if ($raw === null) return null; | ||||
|     $cols = $this->ColumnDefinitions($channel); | ||||
|     $index = 0; | ||||
|     $row = []; | ||||
| @ -112,44 +161,18 @@ abstract class CapacitorStorage { | ||||
|       $key = $col; | ||||
|       if ($key === $index) { | ||||
|         $index++; | ||||
|       } elseif (!array_key_exists($col, $raw)) { | ||||
|       } elseif ($channel->isSerialCol($key)) { | ||||
|         [$serialCol, $sumCol] = $channel->getSumCols($key); | ||||
|         if (array_key_exists($key, $values)) { | ||||
|           $sum = $channel->getSum($key, $values[$key]); | ||||
|           $row[$serialCol] = $sum[$serialCol]; | ||||
|           if (array_key_exists($sumCol, $cols)) { | ||||
|             $row[$sumCol] = $sum[$sumCol]; | ||||
|           } | ||||
|         } | ||||
|       } elseif (array_key_exists($key, $values)) { | ||||
|         $row[$col] = $values[$key]; | ||||
|         $value = $raw[$col]; | ||||
|         if ($value !== null) $value = $channel->unserialize($value); | ||||
|         $row[$key] = $value; | ||||
|       } else { | ||||
|         $row[$key] = $raw[$col]; | ||||
|       } | ||||
|     } | ||||
|     return $row; | ||||
|   } | ||||
| 
 | ||||
|   /** désérialiser les valeurs qui doivent l'être dans $values */ | ||||
|   protected function unserialize(CapacitorChannel $channel, ?array $row): ?array { | ||||
|     if ($row === null) return null; | ||||
|     $cols = $this->ColumnDefinitions($channel); | ||||
|     $index = 0; | ||||
|     $values = []; | ||||
|     foreach (array_keys($cols) as $col) { | ||||
|       $key = $col; | ||||
|       if ($key === $index) { | ||||
|         $index++; | ||||
|       } elseif (!array_key_exists($col, $row)) { | ||||
|       } elseif ($channel->isSerialCol($key)) { | ||||
|         $value = $row[$col]; | ||||
|         if ($value !== null) $value = $channel->unserialize($value); | ||||
|         $values[$key] = $value; | ||||
|       } else { | ||||
|         $values[$key] = $row[$col]; | ||||
|       } | ||||
|     } | ||||
|     return $values; | ||||
|   } | ||||
| 
 | ||||
|   function getPrimaryKeys(CapacitorChannel $channel): array { | ||||
|     $primaryKeys = $channel->getPrimaryKeys(); | ||||
|     if ($primaryKeys === null) $primaryKeys = ["id_"]; | ||||
| @ -215,6 +238,22 @@ abstract class CapacitorStorage { | ||||
|     "class_name" => "varchar", | ||||
|   ]; | ||||
| 
 | ||||
|   function channelExists(string $name, ?array &$raw=null): bool { | ||||
|     $raw = $this->db()->one([ | ||||
|       "select", | ||||
|       "from" => static::CHANNELS_TABLE, | ||||
|       "where" => ["name" => $name], | ||||
|     ]); | ||||
|     return $raw !== null; | ||||
|   } | ||||
| 
 | ||||
|   function getChannels(): iterable { | ||||
|     return $this->db()->all([ | ||||
|       "select", | ||||
|       "from" => static::CHANNELS_TABLE, | ||||
|     ]); | ||||
|   } | ||||
| 
 | ||||
|   protected function _createChannelsSql(): array { | ||||
|     return [ | ||||
|       "create table if not exists", | ||||
| @ -316,7 +355,7 @@ abstract class CapacitorStorage { | ||||
|    * en fonction du type d'opération: création ou mise à jour | ||||
|    * | ||||
|    * Dans les deux cas, si la fonction retourne un tableau, il est utilisé pour | ||||
|    * modifier les valeurs insérées/mises à jour. De plus, $values obtient la | ||||
|    * modifier les valeurs insérées/mises à jour. De plus, $row obtient la | ||||
|    * valeur finale des données insérées/mises à jour | ||||
|    * | ||||
|    * Si $args est renseigné, il est ajouté aux arguments utilisés pour appeler | ||||
| @ -327,23 +366,27 @@ abstract class CapacitorStorage { | ||||
|    * @return int 1 si l'objet a été chargé ou mis à jour, 0 s'il existait | ||||
|    * déjà à l'identique dans le canal | ||||
|    */ | ||||
|   function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$values=null): int { | ||||
|   function _charge(CapacitorChannel $channel, $item, $func, ?array $args, ?array &$row=null): int { | ||||
|     $this->_create($channel); | ||||
|     $tableName = $channel->getTableName(); | ||||
|     $db = $this->db(); | ||||
|     $args ??= []; | ||||
| 
 | ||||
|     $values = func::call([$channel, "getItemValues"], $item, ...$args); | ||||
|     if ($values === [false]) return 0; | ||||
|     $row = func::call([$channel, "getItemValues"], $item, ...$args); | ||||
|     if ($row === [false]) return 0; | ||||
| 
 | ||||
|     $row = cl::merge( | ||||
|     if ($row !== null && array_key_exists("item", $row)) { | ||||
|       $item = A::pop($row, "item"); | ||||
|     } | ||||
| 
 | ||||
|     $raw = cl::merge( | ||||
|       $channel->getSum("item", $item), | ||||
|       $this->serialize($channel, $values)); | ||||
|     $prow = null; | ||||
|     $rowIds = $this->getRowIds($channel, $row, $primaryKeys); | ||||
|       $this->serialize($channel, $row)); | ||||
|     $praw = null; | ||||
|     $rowIds = $this->getRowIds($channel, $raw, $primaryKeys); | ||||
|     if ($rowIds !== null) { | ||||
|       # modification
 | ||||
|       $prow = $db->one([ | ||||
|       $praw = $db->one([ | ||||
|         "select", | ||||
|         "from" => $tableName, | ||||
|         "where" => $rowIds, | ||||
| @ -352,47 +395,47 @@ abstract class CapacitorStorage { | ||||
| 
 | ||||
|     $now = date("Y-m-d H:i:s"); | ||||
|     $insert = null; | ||||
|     if ($prow === null) { | ||||
|     if ($praw === null) { | ||||
|       # création
 | ||||
|       $row = cl::merge($row, [ | ||||
|       $raw = cl::merge($raw, [ | ||||
|         "created_" => $now, | ||||
|         "modified_" => $now, | ||||
|       ]); | ||||
|       $insert = true; | ||||
|       $initFunc = func::with([$channel, "onCreate"], $args); | ||||
|       $values = $this->unserialize($channel, $row); | ||||
|       $pvalues = null; | ||||
|       $row = $this->unserialize($channel, $raw); | ||||
|       $prow = null; | ||||
|     } else { | ||||
|       # modification
 | ||||
|       # intégrer autant que possible les valeurs de prow dans row, de façon que
 | ||||
|       # intégrer autant que possible les valeurs de praw dans raw, de façon que
 | ||||
|       # l'utilisateur puisse voir clairement ce qui a été modifié
 | ||||
|       if ($channel->_wasSumModified("item", $row, $prow)) { | ||||
|       if ($channel->_wasSumModified("item", $raw, $praw)) { | ||||
|         $insert = false; | ||||
|         $row = cl::merge($prow, $row, [ | ||||
|         $raw = cl::merge($praw, $raw, [ | ||||
|           "modified_" => $now, | ||||
|         ]); | ||||
|       } else { | ||||
|         $row = cl::merge($prow, $row); | ||||
|         $raw = cl::merge($praw, $raw); | ||||
|       } | ||||
|       $initFunc = func::with([$channel, "onUpdate"], $args); | ||||
|       $values = $this->unserialize($channel, $row); | ||||
|       $pvalues = $this->unserialize($channel, $prow); | ||||
|       $row = $this->unserialize($channel, $raw); | ||||
|       $prow = $this->unserialize($channel, $praw); | ||||
|     } | ||||
| 
 | ||||
|     $updates = $initFunc->prependArgs([$item, $values, $pvalues])->invoke(); | ||||
|     $updates = $initFunc->prependArgs([$item, $row, $prow])->invoke(); | ||||
|     if ($updates === [false]) return 0; | ||||
|     if (is_array($updates) && $updates) { | ||||
|       if ($insert === null) $insert = false; | ||||
|       if (!array_key_exists("modified_", $updates)) { | ||||
|         $updates["modified_"] = $now; | ||||
|       } | ||||
|       $values = cl::merge($values, $updates); | ||||
|       $row = cl::merge($row, $this->serialize($channel, $updates)); | ||||
|       $row = cl::merge($row, $updates); | ||||
|       $raw = cl::merge($raw, $this->serialize($channel, $updates)); | ||||
|     } | ||||
| 
 | ||||
|     if ($func !== null) { | ||||
|       $updates = func::with($func) | ||||
|         ->prependArgs([$item, $values, $pvalues]) | ||||
|         ->prependArgs([$item, $row, $prow]) | ||||
|         ->bind($channel) | ||||
|         ->invoke(); | ||||
|       if ($updates === [false]) return 0; | ||||
| @ -401,8 +444,8 @@ abstract class CapacitorStorage { | ||||
|         if (!array_key_exists("modified_", $updates)) { | ||||
|           $updates["modified_"] = $now; | ||||
|         } | ||||
|         $values = cl::merge($values, $updates); | ||||
|         $row = cl::merge($row, $this->serialize($channel, $updates)); | ||||
|         $row = cl::merge($row, $updates); | ||||
|         $raw = cl::merge($raw, $this->serialize($channel, $updates)); | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
| @ -421,25 +464,23 @@ abstract class CapacitorStorage { | ||||
|         $id = $db->exec([ | ||||
|           "insert", | ||||
|           "into" => $tableName, | ||||
|           "values" => $row, | ||||
|           "values" => $raw, | ||||
|         ]); | ||||
|         if (count($primaryKeys) == 1 && $rowIds === null) { | ||||
|           # mettre à jour avec l'id généré
 | ||||
|           $values[$primaryKeys[0]] = $id; | ||||
|           $row[$primaryKeys[0]] = $id; | ||||
|         } | ||||
|         $nbModified = 1; | ||||
|       } else { | ||||
|         # calculer ce qui a changé pour ne mettre à jour que le nécessaire
 | ||||
|         $updates = []; | ||||
|         foreach ($row as $col => $value) { | ||||
|         foreach ($raw as $col => $value) { | ||||
|           if (array_key_exists($col, $rowIds)) { | ||||
|             # ne jamais mettre à jour la clé primaire
 | ||||
|             continue; | ||||
|           } | ||||
|           $pvalue = $prow[$col] ?? null; | ||||
|           if ($value !== ($pvalue)) { | ||||
|             $updates[$col] = $value; | ||||
|           } | ||||
|           $pvalue = $praw[$col] ?? null; | ||||
|           if ($value !== $pvalue) $updates[$col] = $value; | ||||
|         } | ||||
|         if (count($updates) == 1 && array_key_first($updates) == "modified_") { | ||||
|           # si l'unique modification porte sur la date de modification, alors
 | ||||
| @ -467,19 +508,22 @@ abstract class CapacitorStorage { | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$values=null): int { | ||||
|     return $this->_charge($this->getChannel($channel), $item, $func, $args, $values); | ||||
|   function charge(?string $channel, $item, $func=null, ?array $args=null, ?array &$row=null): int { | ||||
|     return $this->_charge($this->getChannel($channel), $item, $func, $args, $row); | ||||
|   } | ||||
| 
 | ||||
|   /** décharger les données du canal spécifié */ | ||||
|   /** | ||||
|    * décharger les données du canal spécifié. seul la valeur de $item est | ||||
|    * fournie | ||||
|    */ | ||||
|   function _discharge(CapacitorChannel $channel, bool $reset=true): Traversable { | ||||
|     $this->_create($channel); | ||||
|     $rows = $this->db()->all([ | ||||
|     $raws = $this->db()->all([ | ||||
|       "select item__", | ||||
|       "from" => $channel->getTableName(), | ||||
|     ]); | ||||
|     foreach ($rows as $row) { | ||||
|       yield unserialize($row['item__']); | ||||
|     foreach ($raws as $raw) { | ||||
|       yield unserialize($raw['item__']); | ||||
|     } | ||||
|     if ($reset) $this->_reset($channel); | ||||
|   } | ||||
| @ -548,45 +592,34 @@ abstract class CapacitorStorage { | ||||
|     if ($filter === null) throw ValueException::null("filter"); | ||||
|     $this->_create($channel); | ||||
|     $this->verifixFilter($channel, $filter); | ||||
|     $row = $this->db()->one(cl::merge([ | ||||
|     $raw = $this->db()->one(cl::merge([ | ||||
|       "select", | ||||
|       "from" => $channel->getTableName(), | ||||
|       "where" => $filter, | ||||
|     ], $mergeQuery)); | ||||
|     return $this->unserialize($channel, $row); | ||||
|     return $this->unserialize($channel, $raw); | ||||
|   } | ||||
| 
 | ||||
|   function one(?string $channel, $filter, ?array $mergeQuery=null): ?array { | ||||
|     return $this->_one($this->getChannel($channel), $filter, $mergeQuery); | ||||
|   } | ||||
| 
 | ||||
|   private function _allCached(string $id, CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable { | ||||
|     $this->_create($channel); | ||||
|     $this->verifixFilter($channel, $filter); | ||||
|     $rows = $this->db()->all(cl::merge([ | ||||
|       "select", | ||||
|       "from" => $channel->getTableName(), | ||||
|       "where" => $filter, | ||||
|     ], $mergeQuery), null, $this->getPrimaryKeys($channel)); | ||||
|     if ($channel->isUseCache()) { | ||||
|       $cacheIds = [$id, get_class($channel)]; | ||||
|       cache::get()->resetCached($cacheIds); | ||||
|       $rows = cache::new(null, $cacheIds, function() use ($rows) { | ||||
|         yield from $rows; | ||||
|       }); | ||||
|     } | ||||
|     foreach ($rows as $key => $row) { | ||||
|       yield $key => $this->unserialize($channel, $row); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * obtenir les lignes correspondant au filtre sur le canal spécifié | ||||
|    * | ||||
|    * si $filter n'est pas un tableau, il est transformé en ["id_" => $filter] | ||||
|    */ | ||||
|   function _all(CapacitorChannel $channel, $filter, ?array $mergeQuery=null): Traversable { | ||||
|     return $this->_allCached("all", $channel, $filter, $mergeQuery); | ||||
|     $this->_create($channel); | ||||
|     $this->verifixFilter($channel, $filter); | ||||
|     $raws = $this->db()->all(cl::merge([ | ||||
|       "select", | ||||
|       "from" => $channel->getTableName(), | ||||
|       "where" => $filter, | ||||
|     ], $mergeQuery), null, $this->getPrimaryKeys($channel)); | ||||
|     foreach ($raws as $key => $raw) { | ||||
|       yield $key => $this->unserialize($channel, $raw); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function all(?string $channel, $filter, $mergeQuery=null): Traversable { | ||||
| @ -622,10 +655,10 @@ abstract class CapacitorStorage { | ||||
|     $tableName = $channel->getTableName(); | ||||
|     try { | ||||
|       $args ??= []; | ||||
|       $all = $this->_allCached("each", $channel, $filter, $mergeQuery); | ||||
|       foreach ($all as $values) { | ||||
|         $rowIds = $this->getRowIds($channel, $values); | ||||
|         $updates = $onEach->invoke([$values["item"], $values, ...$args]); | ||||
|       $rows = $this->_all($channel, $filter, $mergeQuery); | ||||
|       foreach ($rows as $row) { | ||||
|         $rowIds = $this->getRowIds($channel, $row); | ||||
|         $updates = $onEach->invoke([$row, ...$args]); | ||||
|         if (is_array($updates) && $updates) { | ||||
|           if (!array_key_exists("modified_", $updates)) { | ||||
|             $updates["modified_"] = date("Y-m-d H:i:s"); | ||||
| @ -689,10 +722,10 @@ abstract class CapacitorStorage { | ||||
|     $tableName = $channel->getTableName(); | ||||
|     try { | ||||
|       $args ??= []; | ||||
|       $all = $this->_allCached("delete", $channel, $filter); | ||||
|       foreach ($all as $values) { | ||||
|         $rowIds = $this->getRowIds($channel, $values); | ||||
|         $shouldDelete = boolval($onDelete->invoke([$values["item"], $values, ...$args])); | ||||
|       $rows = $this->_all($channel, $filter); | ||||
|       foreach ($rows as $row) { | ||||
|         $rowIds = $this->getRowIds($channel, $row); | ||||
|         $shouldDelete = boolval($onDelete->invoke([$row, ...$args])); | ||||
|         if ($shouldDelete) { | ||||
|           $db->exec([ | ||||
|             "delete", | ||||
|  | ||||
| @ -6,7 +6,6 @@ class _update extends _common { | ||||
|     "prefix" => "?string", | ||||
|     "table" => "?string", | ||||
|     "schema" => "?array", | ||||
|     "cols" => "?array", | ||||
|     "values" => "?array", | ||||
|     "where" => "?array", | ||||
|     "suffix" => "?string", | ||||
|  | ||||
| @ -19,10 +19,6 @@ class MysqlStorage extends CapacitorStorage { | ||||
|     return $this->db; | ||||
|   } | ||||
| 
 | ||||
|   const PRIMARY_KEY_DEFINITION = [ | ||||
|     "id_" => "integer primary key auto_increment", | ||||
|   ]; | ||||
| 
 | ||||
|   protected function tableExists(string $tableName): bool { | ||||
|     $db = $this->db; | ||||
|     $found = $db->get([ | ||||
|  | ||||
| @ -9,6 +9,10 @@ class PgsqlStorage extends CapacitorStorage { | ||||
|   const SERDATA_DEFINITION = "text"; | ||||
|   const SERSUM_DEFINITION = "varchar(40)"; | ||||
|   const SERTS_DEFINITION = "timestamp"; | ||||
|   const GENSERIAL_DEFINITION = "serial primary key"; | ||||
|   const GENTEXT_DEFINITION = "text"; | ||||
|   const GENBOOL_DEFINITION = "boolean"; | ||||
|   const GENUUID_DEFINITION = "uuid"; | ||||
| 
 | ||||
|   function __construct($pgsql) { | ||||
|     $this->db = Pgsql::with($pgsql); | ||||
| @ -20,10 +24,6 @@ class PgsqlStorage extends CapacitorStorage { | ||||
|     return $this->db; | ||||
|   } | ||||
| 
 | ||||
|   const PRIMARY_KEY_DEFINITION = [ | ||||
|     "id_" => "serial primary key", | ||||
|   ]; | ||||
| 
 | ||||
|   protected function tableExists(string $tableName): bool { | ||||
|     if (($index = strpos($tableName, ".")) !== false) { | ||||
|       $schemaName = substr($tableName, 0, $index); | ||||
|  | ||||
| @ -9,6 +9,8 @@ use nulib\db\CapacitorStorage; | ||||
|  * Class SqliteStorage | ||||
|  */ | ||||
| class SqliteStorage extends CapacitorStorage { | ||||
|   const GENSERIAL_DEFINITION = "integer primary key autoincrement"; | ||||
| 
 | ||||
|   function __construct($sqlite) { | ||||
|     $this->db = Sqlite::with($sqlite); | ||||
|   } | ||||
| @ -19,10 +21,6 @@ class SqliteStorage extends CapacitorStorage { | ||||
|     return $this->db; | ||||
|   } | ||||
| 
 | ||||
|   const PRIMARY_KEY_DEFINITION = [ | ||||
|     "id_" => "integer primary key autoincrement", | ||||
|   ]; | ||||
| 
 | ||||
|   protected function tableExists(string $tableName): bool { | ||||
|     $found = $this->db->get([ | ||||
|       # depuis la version 3.33.0 le nom officiel de la table est sqlite_schema,
 | ||||
| @ -40,22 +38,6 @@ class SqliteStorage extends CapacitorStorage { | ||||
|     return new _sqliteMigration($migrations, $channel->getName()); | ||||
|   } | ||||
| 
 | ||||
|   function channelExists(string $name, ?array &$row=null): bool { | ||||
|     $row = $this->db->one([ | ||||
|       "select", | ||||
|       "from" => static::CHANNELS_TABLE, | ||||
|       "where" => ["name" => $name], | ||||
|     ]); | ||||
|     return $row !== null; | ||||
|   } | ||||
| 
 | ||||
|   function getChannels(): iterable { | ||||
|     return $this->db->all([ | ||||
|       "select", | ||||
|       "from" => static::CHANNELS_TABLE, | ||||
|     ]); | ||||
|   } | ||||
| 
 | ||||
|   protected function _addToChannelsSql(CapacitorChannel $channel): array { | ||||
|     $sql = parent::_addToChannelsSql($channel); | ||||
|     $sql[0] = "insert or ignore"; | ||||
|  | ||||
| @ -117,4 +117,18 @@ class file { | ||||
|     } | ||||
|     return $file; | ||||
|   } | ||||
| 
 | ||||
|   static function string_reader(string $content, ?callable $func=null): MemoryStream { | ||||
|     $file = new MemoryStream(); | ||||
|     $file->fwrite($content); | ||||
|     $file->rewind(); | ||||
|     if ($func !== null) { | ||||
|       try { | ||||
|         $func($file); | ||||
|       } finally { | ||||
|         $file ->close(); | ||||
|       } | ||||
|     } | ||||
|     return $file; | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -52,6 +52,7 @@ abstract class AbstractReader implements IReader { | ||||
|     $this->headers = $params["headers"] ?? static::HEADERS; | ||||
|     $this->useHeaders = $params["use_headers"] ?? static::USE_HEADERS; | ||||
|     $this->input = $params["input"] ?? static::INPUT; | ||||
|     $this->skip = $params["skip"] ?? 0; | ||||
|     $this->trim = boolval($params["trim"] ?? static::TRIM); | ||||
|     $this->emptyAsNull = boolval($params["empty_as_null"] ?? static::EMPTY_AS_NULL); | ||||
|     $this->parseNone = boolval($params["parse_none"] ?? static::PARSE_NONE); | ||||
| @ -67,6 +68,8 @@ abstract class AbstractReader implements IReader { | ||||
| 
 | ||||
|   protected $input; | ||||
| 
 | ||||
|   protected int $skip; | ||||
| 
 | ||||
|   protected bool $trim; | ||||
| 
 | ||||
|   protected bool $emptyAsNull; | ||||
| @ -81,15 +84,16 @@ abstract class AbstractReader implements IReader { | ||||
| 
 | ||||
|   protected function cookRow(array &$row): bool { | ||||
|     if (!$this->useHeaders) return true; | ||||
|     if ($this->isrc == 0) { | ||||
|     if ($this->skip > 0) { | ||||
|       $this->skip--; | ||||
|       return false; | ||||
|     } | ||||
|     if ($this->headers === null) { | ||||
|       # ligne d'en-tête
 | ||||
|       $headers = $this->headers; | ||||
|       if ($headers === null) { | ||||
|         if ($this->schema === null) $headers = null; | ||||
|         else $headers = array_keys($this->schema); | ||||
|         if ($headers === null) $headers = $row; | ||||
|         $this->headers = $headers; | ||||
|       } | ||||
|       if ($this->schema === null) $headers = null; | ||||
|       else $headers = array_keys($this->schema); | ||||
|       if ($headers === null) $headers = $row; | ||||
|       $this->headers = $headers; | ||||
|       return false; | ||||
|     } | ||||
|     A::ensure_size($row, count($this->headers)); | ||||
|  | ||||
| @ -1,124 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\tools; | ||||
| 
 | ||||
| use nulib\app; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\app\RunFile; | ||||
| use nulib\ExitError; | ||||
| use nulib\ext\yaml; | ||||
| use nulib\os\path; | ||||
| use nulib\os\proc\Cmd; | ||||
| use nulib\os\sh; | ||||
| use nulib\output\msg; | ||||
| 
 | ||||
| class BgLauncherApp extends Application { | ||||
|   const ACTION_INFOS = 0, ACTION_START = 1, ACTION_STOP = 2; | ||||
|   const ARGS = [ | ||||
|     "purpose" => "lancer un script en tâche de fond", | ||||
|     "usage" => "ApplicationClass args...", | ||||
| 
 | ||||
|     "sections" => [ | ||||
|       parent::VERBOSITY_SECTION, | ||||
|     ], | ||||
| 
 | ||||
|     ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, | ||||
|       "help" => "Afficher des informations sur la tâche", | ||||
|     ], | ||||
|     ["-s", "--start", "name" => "action", "value" => self::ACTION_START, | ||||
|       "help" => "Démarrer la tâche", | ||||
|     ], | ||||
|     ["-k", "--stop", "name" => "action", "value" => self::ACTION_STOP, | ||||
|       "help" => "Arrêter la tâche", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   protected int $action = self::ACTION_START; | ||||
| 
 | ||||
|   protected ?array $args = null; | ||||
| 
 | ||||
|   static function show_infos(RunFile $runfile, ?int $level=null): void { | ||||
|     msg::print($runfile->getDesc(), $level); | ||||
|     msg::print(yaml::with(["data" => $runfile->read()]), ($level ?? 0) - 1); | ||||
|   } | ||||
| 
 | ||||
|   function main() { | ||||
|     $args = $this->args; | ||||
| 
 | ||||
|     $appClass = $args[0] ?? null; | ||||
|     if ($appClass === null) { | ||||
|       self::die("Vous devez spécifier la classe de l'application"); | ||||
|     } | ||||
|     $appClass = $args[0] = str_replace("/", "\\", $appClass); | ||||
|     if (!class_exists($appClass)) { | ||||
|       self::die("$appClass: classe non trouvée"); | ||||
|     } | ||||
| 
 | ||||
|     $useRunfile = constant("$appClass::USE_RUNFILE"); | ||||
|     if (!$useRunfile) { | ||||
|       self::die("Cette application ne supporte le lancement en tâche de fond"); | ||||
|     } | ||||
| 
 | ||||
|     $runfile = app::with($appClass)->getRunfile(); | ||||
|     switch ($this->action) { | ||||
|     case self::ACTION_START: | ||||
|       $argc = count($args); | ||||
|       $appClass::_manage_runfile($argc, $args, $runfile); | ||||
|       if ($runfile->warnIfLocked()) self::exit(app::EC_LOCKED); | ||||
|       array_splice($args, 0, 0, [ | ||||
|         PHP_BINARY, | ||||
|         path::abspath(NULIB_APP_app_launcher), | ||||
|       ]); | ||||
|       app::params_putenv(); | ||||
|       self::_start($args, $runfile); | ||||
|       break; | ||||
|     case self::ACTION_STOP: | ||||
|       self::_stop($runfile); | ||||
|       self::show_infos($runfile, -1); | ||||
|       break; | ||||
|     case self::ACTION_INFOS: | ||||
|       self::show_infos($runfile); | ||||
|       break; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static function _start(array $args, Runfile $runfile): void { | ||||
|     $pid = pcntl_fork(); | ||||
|     if ($pid == -1) { | ||||
|       # parent, impossible de forker
 | ||||
|       throw new ExitError(app::EC_FORK_PARENT, "Unable to fork"); | ||||
|     } elseif (!$pid) { | ||||
|       # child, fork ok
 | ||||
|       $runfile->wfPrepare($pid); | ||||
|       $outfile = $runfile->getOutfile() ?? "/tmp/NULIB_APP_app_console.out"; | ||||
|       $exitcode = app::EC_FORK_CHILD; | ||||
|       try { | ||||
|         # rediriger STDIN, STDOUT et STDERR
 | ||||
|         fclose(fopen($outfile, "wb")); // vider le fichier
 | ||||
|         fclose(STDIN); $in = fopen("/dev/null", "rb"); | ||||
|         fclose(STDOUT); $out = fopen($outfile, "ab"); | ||||
|         fclose(STDERR); $err = fopen($outfile, "ab"); | ||||
|         # puis lancer la commande
 | ||||
|         $cmd = new Cmd($args); | ||||
|         $cmd->addSource("/g/init.env"); | ||||
|         $cmd->addRedir("both", $outfile, true); | ||||
|         $cmd->fork_exec($exitcode, false); | ||||
|         sh::_waitpid(-$pid, $exitcode); | ||||
|       } finally { | ||||
|         $runfile->wfReaped($exitcode); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   public static function _stop(Runfile $runfile): bool { | ||||
|     $data = $runfile->read(); | ||||
|     $pid = $runfile->_getCid($data); | ||||
|     msg::action("stop $pid"); | ||||
|     if ($runfile->wfKill($reason)) { | ||||
|       msg::asuccess(); | ||||
|       return true; | ||||
|     } else { | ||||
|       msg::afailure($reason); | ||||
|       return false; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| @ -1,53 +0,0 @@ | ||||
| <?php | ||||
| namespace nulib\tools; | ||||
| 
 | ||||
| use nulib\app; | ||||
| use nulib\app\cli\Application; | ||||
| use nulib\output\msg; | ||||
| use nulib\php\time\DateTime; | ||||
| use nulib\text\words; | ||||
| 
 | ||||
| class SteamTrainApp extends Application { | ||||
|   const PROJDIR = __DIR__.'/../..'; | ||||
|   const TITLE = "Train à vapeur"; | ||||
|   const USE_LOGFILE = true; | ||||
|   const USE_RUNFILE = true; | ||||
|   const USE_RUNLOCK = true; | ||||
| 
 | ||||
|   const ARGS = [ | ||||
|     "purpose" => self::TITLE, | ||||
|     "description" => <<<EOT | ||||
| Cette application peut être utilisée pour tester le lancement des tâches de fond | ||||
| EOT, | ||||
| 
 | ||||
|     ["-c", "--count", "args" => 1, | ||||
|       "help" => "spécifier le nombre d'étapes", | ||||
|     ], | ||||
|     ["-f", "--force-enabled", "value" => true, | ||||
|       "help" => "lancer la commande même si les tâches planifiées sont désactivées", | ||||
|     ], | ||||
|     ["-n", "--no-install-signal-handler", "value" => false, | ||||
|       "help" => "ne pas installer le gestionnaire de signaux", | ||||
|     ], | ||||
|   ]; | ||||
| 
 | ||||
|   protected $count = 100; | ||||
| 
 | ||||
|   protected bool $forceEnabled = false; | ||||
| 
 | ||||
|   protected bool $installSignalHandler = true; | ||||
| 
 | ||||
|   function main() { | ||||
|     app::check_bgapplication_enabled($this->forceEnabled); | ||||
|     if ($this->installSignalHandler) app::install_signal_handler(); | ||||
|     $count = intval($this->count); | ||||
|     msg::info("Starting train for ".words::q($count, "step#s")); | ||||
|     app::action("Running train...", $count); | ||||
|     for ($i = 1; $i <= $count; $i++) { | ||||
|       msg::print("Tchou-tchou! x $i"); | ||||
|       app::step(); | ||||
|       sleep(1); | ||||
|     } | ||||
|     msg::info("Stopping train at ".new DateTime()); | ||||
|   } | ||||
| } | ||||
| @ -1,39 +0,0 @@ | ||||
| #!/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 | ||||
| 
 | ||||
| declare -A DESTDIRS=( | ||||
|     [template-_bg_launcher.php]=sbin | ||||
|     [template-.launcher.php]=_cli | ||||
|     [template-_wrapper.sh]=_cli | ||||
| ) | ||||
| declare -A MODES=( | ||||
|     [template-_bg_launcher.php]=+x | ||||
|     [template-.launcher.php]= | ||||
|     [template-_wrapper.sh]=+x | ||||
| ) | ||||
| 
 | ||||
| projdir= | ||||
| args=( | ||||
|     "copier les templates dans le projet en cours" | ||||
|     #"usage" | ||||
|     -d:,--projdir: . | ||||
| ) | ||||
| parse_args "$@"; set -- "${args[@]}" | ||||
| 
 | ||||
| if [ -n "$projdir" ]; then | ||||
|     cd "$projdir" || die | ||||
| fi | ||||
| 
 | ||||
| [ -f composer.json ] || die "$(basename "$(dirname "$(pwd)")"): n'est pas un projet composer" | ||||
| 
 | ||||
| setx -a templates=ls_files "$MYDIR" "template-*" | ||||
| for template in "${templates[@]}"; do | ||||
|     destdir="${DESTDIRS[$template]}" | ||||
|     [ -n "$destdir" ] || die "$template: la destination n'est pas configurée" | ||||
|     mode="${MODES[$template]}" | ||||
|     destname="${template#template-}" | ||||
| 
 | ||||
|     tail -n+4 "$MYDIR/$template" >"$destdir/$destname" | ||||
|     [ -n "$mode" ] && chmod "$mode" "$destdir/$destname" | ||||
| done | ||||
| @ -7,6 +7,12 @@ use nulib\db\Capacitor; | ||||
| use nulib\db\CapacitorChannel; | ||||
| 
 | ||||
| class SqliteStorageTest extends TestCase { | ||||
|   static function Txx(...$values): void { | ||||
|     foreach ($values as $value) { | ||||
|       var_export($value); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   function _testChargeStrings(SqliteStorage $storage, ?string $channel) { | ||||
|     $storage->reset($channel); | ||||
|     $storage->charge($channel, "first"); | ||||
| @ -67,7 +73,8 @@ class SqliteStorageTest extends TestCase { | ||||
|     $capacitor->charge(["name" => "third", "age" => 15]); | ||||
|     $capacitor->charge(["name" => "fourth", "age" => 20]); | ||||
| 
 | ||||
|     $setDone = function ($item, $row, $suffix=null) { | ||||
|     $setDone = function ($row, $suffix=null) { | ||||
|       $item = $row["item"]; | ||||
|       $updates = ["done" => 1]; | ||||
|       if ($suffix !== null) { | ||||
|         $item["name"] .= $suffix; | ||||
| @ -76,9 +83,9 @@ class SqliteStorageTest extends TestCase { | ||||
|       return $updates; | ||||
|     }; | ||||
|     $capacitor->each(["age" => [">", 10]], $setDone, ["++"]); | ||||
|     $capacitor->each(["done" => 0], $setDone, null); | ||||
|     $capacitor->each(["done" => 0], $setDone); | ||||
| 
 | ||||
|     Txx(cl::all($capacitor->discharge(false))); | ||||
|     self::Txx(cl::all($capacitor->discharge(false))); | ||||
|     $capacitor->close(); | ||||
|     self::assertTrue(true); | ||||
|   } | ||||
| @ -133,16 +140,16 @@ class SqliteStorageTest extends TestCase { | ||||
|     $capacitor->charge(["a" => null, "b" => null]); | ||||
|     $capacitor->charge(["a" => "first", "b" => "second"]); | ||||
| 
 | ||||
|     Txx("=== all"); | ||||
|     self::Txx("=== all"); | ||||
|     /** @var Sqlite $sqlite */ | ||||
|     $sqlite = $capacitor->getStorage()->db(); | ||||
|     Txx(cl::all($sqlite->all([ | ||||
|     self::Txx(cl::all($sqlite->all([ | ||||
|       "select", | ||||
|       "from" => $capacitor->getChannel()->getTableName(), | ||||
|     ]))); | ||||
|     Txx("=== each"); | ||||
|     $capacitor->each(null, function ($item, $values) { | ||||
|       Txx($values); | ||||
|     self::Txx("=== each"); | ||||
|     $capacitor->each(null, function ($row) { | ||||
|       self::Txx($row); | ||||
|     }); | ||||
| 
 | ||||
|     $capacitor->close(); | ||||
| @ -170,99 +177,100 @@ class SqliteStorageTest extends TestCase { | ||||
|     }); | ||||
| 
 | ||||
|     $capacitor->reset(); | ||||
|     $capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $values, ?array $pvalues) { | ||||
|     $capacitor->charge(["name" => "first", "age" => 5], function($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame("first", $item["name"]); | ||||
|       self::assertSame(5, $item["age"]); | ||||
|       self::assertnotnull($values); | ||||
|       self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($values)); | ||||
|       self::assertnotnull($row); | ||||
|       self::assertSame(["name", "age", "item", "item__sum_", "created_", "modified_"], array_keys($row)); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 5, | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "item"])); | ||||
|       self::assertNull($pvalues); | ||||
|       ], cl::select($row, ["name", "age", "item"])); | ||||
|       self::assertNull($prow); | ||||
|     }); | ||||
|     $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) { | ||||
|     $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame("first", $item["name"]); | ||||
|       self::assertSame(10, $item["age"]); | ||||
|       self::assertnotnull($values); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values)); | ||||
|       self::assertnotnull($row); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row)); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 10, | ||||
|         "done" => 0, | ||||
|         "notes" => null, | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($pvalues); | ||||
|       ], cl::select($row, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($prow); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 5, | ||||
|         "done" => 0, | ||||
|         "notes" => null, | ||||
|         "item" => ["name" => "first", "age" => 5], | ||||
|       ], cl::select($pvalues, ["name", "age", "done", "notes", "item"])); | ||||
|       ], cl::select($prow, ["name", "age", "done", "notes", "item"])); | ||||
|     }); | ||||
| 
 | ||||
|     $capacitor->each(null, function($item, ?array $values) { | ||||
|     $capacitor->each(null, function(array $row) { | ||||
|       $item = $row["item"]; | ||||
|       self::assertSame("first", $item["name"]); | ||||
|       self::assertSame(10, $item["age"]); | ||||
|       self::assertnotnull($values); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values)); | ||||
|       self::assertnotnull($row); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row)); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 10, | ||||
|         "done" => 0, | ||||
|         "notes" => null, | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "done", "notes", "item"])); | ||||
|       ], cl::select($row, ["name", "age", "done", "notes", "item"])); | ||||
|       return [ | ||||
|         "done" => 1, | ||||
|         "notes" => "modified", | ||||
|       ]; | ||||
|     }); | ||||
|     $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $values, ?array $pvalues) { | ||||
|     $capacitor->charge(["name" => "first", "age" => 10], function($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame("first", $item["name"]); | ||||
|       self::assertSame(10, $item["age"]); | ||||
|       self::assertnotnull($values); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values)); | ||||
|       self::assertnotnull($row); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row)); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 10, | ||||
|         "done" => 1, | ||||
|         "notes" => "modified", | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($pvalues); | ||||
|       ], cl::select($row, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($prow); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 10, | ||||
|         "done" => 1, | ||||
|         "notes" => "modified", | ||||
|         "item" => $item, | ||||
|       ], cl::select($pvalues, ["name", "age", "done", "notes", "item"])); | ||||
|       ], cl::select($prow, ["name", "age", "done", "notes", "item"])); | ||||
|     }); | ||||
| 
 | ||||
|     $capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $values, ?array $pvalues) { | ||||
|     $capacitor->charge(["name" => "first", "age" => 20], function($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame("first", $item["name"]); | ||||
|       self::assertSame(20, $item["age"]); | ||||
|       self::assertnotnull($values); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($values)); | ||||
|       self::assertnotnull($row); | ||||
|       self::assertSame(["name", "age", "done", "notes", "item", "item__sum_", "created_", "modified_"], array_keys($row)); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 20, | ||||
|         "done" => 1, | ||||
|         "notes" => "modified", | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($pvalues); | ||||
|       ], cl::select($row, ["name", "age", "done", "notes", "item"])); | ||||
|       self::assertNotNull($prow); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", | ||||
|         "age" => 10, | ||||
|         "done" => 1, | ||||
|         "notes" => "modified", | ||||
|         "item" => ["name" => "first", "age" => 10], | ||||
|       ], cl::select($pvalues, ["name", "age", "done", "notes", "item"])); | ||||
|       ], cl::select($prow, ["name", "age", "done", "notes", "item"])); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
| @ -287,55 +295,55 @@ class SqliteStorageTest extends TestCase { | ||||
|     }); | ||||
| 
 | ||||
|     $capacitor->reset(); | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $values, ?array $pvalues) { | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 5], function ($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 5, | ||||
|         "item" => $item, | ||||
|       ], cl::select($values, ["name", "age", "item"])); | ||||
|       ], cl::select($row, ["name", "age", "item"])); | ||||
|       return ["item" => null]; | ||||
|     }); | ||||
|     self::assertSame(1, $nbModified); | ||||
|     sleep(1); | ||||
|     # nb: on met des sleep() pour que la date de modification soit systématiquement différente
 | ||||
| 
 | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) { | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 10, | ||||
|         "item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc", | ||||
|       ], cl::select($values, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($row, ["name", "age", "item", "item__sum_"])); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 5, | ||||
|         "item" => null, "item__sum_" => null, | ||||
|       ], cl::select($pvalues, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($prow, ["name", "age", "item", "item__sum_"])); | ||||
|       return ["item" => null]; | ||||
|     }); | ||||
|     self::assertSame(1, $nbModified); | ||||
|     sleep(1); | ||||
| 
 | ||||
|     # pas de modification ici
 | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $values, ?array $pvalues) { | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 10], function ($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 10, | ||||
|         "item" => $item, "item__sum_" => "9181336dfca20c86313d6065d89aa2ad5070b0fc", | ||||
|       ], cl::select($values, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($row, ["name", "age", "item", "item__sum_"])); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 10, | ||||
|         "item" => null, "item__sum_" => null, | ||||
|       ], cl::select($pvalues, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($prow, ["name", "age", "item", "item__sum_"])); | ||||
|       return ["item" => null]; | ||||
|     }); | ||||
|     self::assertSame(0, $nbModified); | ||||
|     sleep(1); | ||||
| 
 | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $values, ?array $pvalues) { | ||||
|     $nbModified = $capacitor->charge(["name" => "first", "age" => 20], function ($item, ?array $row, ?array $prow) { | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 20, | ||||
|         "item" => $item, "item__sum_" => "001b91982b4e0883b75428c0eb28573a5dc5f7a5", | ||||
|       ], cl::select($values, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($row, ["name", "age", "item", "item__sum_"])); | ||||
|       self::assertSame([ | ||||
|         "name" => "first", "age" => 10, | ||||
|         "item" => null, "item__sum_" => null, | ||||
|       ], cl::select($pvalues, ["name", "age", "item", "item__sum_"])); | ||||
|       ], cl::select($prow, ["name", "age", "item", "item__sum_"])); | ||||
|       return ["item" => null]; | ||||
|     }); | ||||
|     self::assertSame(1, $nbModified); | ||||
|  | ||||
| @ -3,6 +3,7 @@ namespace nulib\db\sqlite; | ||||
| 
 | ||||
| use Exception; | ||||
| use nulib\tests\TestCase; | ||||
| use nulib\ValueException; | ||||
| 
 | ||||
| class SqliteTest extends TestCase { | ||||
|   const CREATE_PERSON = "create table person(nom varchar, prenom varchar, age integer)"; | ||||
| @ -12,8 +13,8 @@ class SqliteTest extends TestCase { | ||||
|   function testMigration() { | ||||
|     $sqlite = new Sqlite(":memory:", [ | ||||
|       "migration" => [ | ||||
|         self::CREATE_PERSON, | ||||
|         self::INSERT_JEPHTE, | ||||
|         "create" => self::CREATE_PERSON, | ||||
|         "insert" => self::INSERT_JEPHTE, | ||||
|       ], | ||||
|     ]); | ||||
|     self::assertSame("clain", $sqlite->get("select nom, age from person")); | ||||
| @ -30,15 +31,15 @@ class SqliteTest extends TestCase { | ||||
|     ], $sqlite->get("select nom, age from person where nom = 'payet'", null, true)); | ||||
| 
 | ||||
|     self::assertSame([ | ||||
|       ["key" => "0", "value" => self::CREATE_PERSON, "done" => 1], | ||||
|       ["key" => "1", "value" => self::INSERT_JEPHTE, "done" => 1], | ||||
|     ], iterator_to_array($sqlite->all("select key, value, done from _migration"))); | ||||
|       ["channel" => "", "name" => "create", "done" => 1], | ||||
|       ["channel" => "", "name" => "insert", "done" => 1], | ||||
|     ], iterator_to_array($sqlite->all("select channel, name, done from _migration"))); | ||||
|   } | ||||
| 
 | ||||
|   function testException() { | ||||
|     $sqlite = new Sqlite(":memory:"); | ||||
|     self::assertException(Exception::class, [$sqlite, "exec"], "prout"); | ||||
|     self::assertException(SqliteException::class, [$sqlite, "exec"], ["prout"]); | ||||
|     self::assertException(ValueException::class, [$sqlite, "exec"], ["prout"]); | ||||
|   } | ||||
| 
 | ||||
|   protected function assertInserted(Sqlite $sqlite, array $row, array $query): void { | ||||
| @ -141,6 +142,10 @@ class SqliteTest extends TestCase { | ||||
| 
 | ||||
|     self::assertSame([ | ||||
|       ["count" => 2], | ||||
|     ], iterator_to_array($sqlite->all(["select count(name) as count from user", "group by" => ["amount"], "having" => ["count(name) = 2"]]))); | ||||
|     ], iterator_to_array($sqlite->all([ | ||||
|       "select count(name) as count from user", | ||||
|       "group by" => ["amount"], | ||||
|       "having" => ["count(name) = 2"], | ||||
|     ]))); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @ -74,7 +74,7 @@ function build_check_env() { | ||||
|             if template_copy_missing "$PROJDIR/$file"; then | ||||
|                 updated=1 | ||||
|                 setx name=basename -- "$file" | ||||
|                 name="${name#.}"; name="${name%.}" | ||||
|                 name="${name#.}"; name="${name%.*}" | ||||
|                 setx file=dirname -- "$file" | ||||
|                 file="$file/$name" | ||||
|                 updatedfiles+=("$file") | ||||
| @ -86,7 +86,7 @@ function build_check_env() { | ||||
|                 if template_copy_missing "$file"; then | ||||
|                     updated=1 | ||||
|                     setx name=basename -- "$file" | ||||
|                     name="${name#.}"; name="${name%.}" | ||||
|                     name="${name#.}"; name="${name%.*}" | ||||
|                     setx file=dirname -- "$file" | ||||
|                     file="$file/$name" | ||||
|                     updatedfiles+=("${file#$PROJDIR/}") | ||||
|  | ||||
| @ -1,12 +1,12 @@ | ||||
| # TODO Faire une copie de ce script dans un répertoire de l'application web
 | ||||
| # (dans le répertoire _cli/ par défaut) et modifier les paramètres si nécessaire
 | ||||
| # (dans le répertoire cli_config/ par défaut) et modifier les paramètres si nécessaire
 | ||||
| #-------------------------------------------------------------------------------
 | ||||
| <?php | ||||
| require __DIR__ . '/../vendor/autoload.php'; | ||||
| require __DIR__.'/../vendor/autoload.php'; | ||||
| # Lancer une application en ligne de commande
 | ||||
| 
 | ||||
| const NULIB_APP_app_params = [ | ||||
|   "projdir" => __DIR__ . '/..', | ||||
|   "projdir" => __DIR__.'/..', | ||||
|   "appcode" => \app\config\bootstrap::APPCODE, | ||||
| ]; | ||||
| require __DIR__.'/../vendor/nulib/base/php/src/app/cli/include-launcher.php'; | ||||
| @ -6,10 +6,10 @@ require __DIR__.'/../vendor/autoload.php'; | ||||
| # Lancer une application en tâche de fond
 | ||||
| 
 | ||||
| use nulib\app; | ||||
| use nulib\tools\BgLauncherApp; | ||||
| use nulib\cli\BgLauncherApp; | ||||
| 
 | ||||
| # chemin vers le lanceur PHP
 | ||||
| const NULIB_APP_app_launcher = __DIR__.'/../_cli/.launcher.php'; | ||||
| const NULIB_APP_app_launcher = __DIR__.'/../@@CLI@@/.launcher.php'; | ||||
| 
 | ||||
| app::init([ | ||||
|   "projdir" => __DIR__.'/..', | ||||
| @ -1,5 +1,5 @@ | ||||
| # TODO Faire une copie de ce script dans un répertoire de l'application web | ||||
| # (dans le répertoire _cli/ par défaut) et modifier les paramétres si nécessaire | ||||
| # (dans le répertoire cli_config/ par défaut) et modifier les paramétres si nécessaire | ||||
| #------------------------------------------------------------------------------- | ||||
| #!/bin/bash | ||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||
| @ -57,7 +57,7 @@ Vérification des liens..." | ||||
|         for i in *.php*; do | ||||
|             [ -f "$i" ] || continue | ||||
|             name="bin/${i%.*}.php" | ||||
|             dest="../_cli/_wrapper.sh" | ||||
|             dest="../@@CLI@@/_wrapper.sh" | ||||
|             link="../bin/${i%.*}.php" | ||||
|             if [ -L "$link" ]; then | ||||
|                 echo "* $name OK" | ||||
| @ -40,11 +40,31 @@ p == 1 { | ||||
|     ac_clean "$conf0" | ||||
| } | ||||
| 
 | ||||
| declare -A PHPWRAPPER_DESTDIRS=( | ||||
|     [_bg_launcher.php]=@@SBIN@@ | ||||
|     [.launcher.php]=@@CLI@@ | ||||
|     [_wrapper.sh]=@@CLI@@ | ||||
| ) | ||||
| declare -A PHPWRAPPER_MODES=( | ||||
|     [_bg_launcher.php]=+x | ||||
|     [.launcher.php]= | ||||
|     [_wrapper.sh]=+x | ||||
| ) | ||||
| 
 | ||||
| projdir= | ||||
| install_phpwrappers=auto | ||||
| args=( | ||||
|     "Mettre à jour le script runphp" | ||||
|     "[path/to/runphp]" | ||||
|     -d:,--projdir:PROJDIR . "Copier les fichiers pour un projet de l'université de la Réunion" | ||||
|     -d:,--projdir:PROJDIR . "\ | ||||
| Copier les fichiers pour un projet de l'université de la Réunion: | ||||
| - BUILDENV0 et BUILDENV sont fixés à ..env.dist et .env | ||||
| - les fichiers ..env.dist et .runphp.conf sont créés le cas échéant | ||||
| - le script build est mis à jour | ||||
| - les wrappers PHP pour la gestion des tâches de fond sont mis à jour le cas | ||||
|   échéant" | ||||
|     -p,--phpwrappers-install install_phpwrappers=1 "forcer l'installation des wrappers PHP" | ||||
|     --np,--no-phpwrappers-install install_phpwrappers= "ne pas installer les wrappers PHP" | ||||
| ) | ||||
| parse_args "$@"; set -- "${args[@]}" | ||||
| 
 | ||||
| @ -93,6 +113,7 @@ else | ||||
| fi | ||||
| 
 | ||||
| # (Re)construire le fichier destination | ||||
| estep "$(relpath "$runphp")" | ||||
| ( | ||||
|     cat "$preamble" | ||||
|     echo | ||||
| @ -102,24 +123,81 @@ fi | ||||
| ) >"$runphp" | ||||
| [ -x "$runphp" ] || chmod +x "$runphp" | ||||
| 
 | ||||
| eval "$( | ||||
| vars=(PROJDIR COMPOSERDIR COMPOSERPHAR VENDORDIR BUILDENV0 BUILDENV BUILD_FLAVOUR DIST IMAGENAME) | ||||
| arrays=(BUILD_IMAGES DISTFILES TEMPLATEFILES VARFILES) | ||||
| for var in "${vars[@]}"; do eval "$var="; done | ||||
| for array in "${arrays[@]}"; do eval "$array=()"; done | ||||
| source "$runphp" | ||||
| for var in "${vars[@]}"; do echo_setv2 "$var"; done | ||||
| for array in "${arrays[@]}"; do echo_seta2 "$array"; done | ||||
| )" | ||||
| 
 | ||||
| estep "$(relpath "$rundir/Dockerfile.runphp")" | ||||
| rsync -lpt "$MYDIR/Dockerfile.runphp" "$rundir/" | ||||
| 
 | ||||
| if [ -n "$projdir" ]; then | ||||
|     if testdiff "$rundir/build" "$MYDIR/build"; then | ||||
|         estep "$(relpath "$rundir/build")" | ||||
|         cp "$MYDIR/build" "$rundir/build" | ||||
|         chmod +x "$rundir/build" | ||||
|     fi | ||||
|     if [ ! -f "$projdir/..env.dist" ]; then | ||||
|         estep "$(relpath "$projdir/..env.dist")" | ||||
|         sed <"$MYDIR/dot-build.env.dist" >"$projdir/..env.dist" ' | ||||
| /^IMAGENAME=/s/=.*\//='"$(basename -- "$projdir")"'\// | ||||
| ' | ||||
|         initial_config=1 | ||||
|     fi | ||||
|     if [ ! -f "$projdir/.runphp.conf" ]; then | ||||
|         estep "$(relpath "$projdir/.runphp.conf")" | ||||
|         sed <"$MYDIR/dot-runphp.conf" >"$projdir/.runphp.conf" ' | ||||
| /^RUNPHP=/s/=.*/=sbin\/runphp/ | ||||
| ' | ||||
|     fi | ||||
| 
 | ||||
|     sbin_path=sbin | ||||
|     cli_path=cli_config | ||||
|     if [ "$install_phpwrappers" == auto ]; then | ||||
|         if [ ! -f "$PROJDIR/$COMPOSERDIR/composer.json" ]; then | ||||
|             # ce doit être un projet PHP | ||||
|             install_phpwrappers= | ||||
|         elif [ -d "$projdir/cli_config" ]; then | ||||
|             install_phpwrappers=1 | ||||
|             sbin_path=sbin | ||||
|             cli_path=cli_config | ||||
|         elif [ -d "$projdir/_cli" ]; then | ||||
|             install_phpwrappers=1 | ||||
|             sbin_path=sbin | ||||
|             cli_path=_cli | ||||
|         else | ||||
|             install_phpwrappers= | ||||
|         fi | ||||
|     fi | ||||
| 
 | ||||
|     if [ -n "$install_phpwrappers" ]; then | ||||
|         setx -a phpwrappers=ls_files "$MYDIR" "phpwrapper-*" | ||||
|         for phpwrapper in "${phpwrappers[@]}"; do | ||||
|             destname="${phpwrapper#phpwrapper-}" | ||||
|             destdir="${PHPWRAPPER_DESTDIRS[$destname]}" | ||||
|             [ -n "$destdir" ] || die "$phpwrapper: la destination n'est pas configurée" | ||||
|             mode="${PHPWRAPPER_MODES[$destname]}" | ||||
| 
 | ||||
|             case "$destdir" in | ||||
|             @@SBIN@@) destdir="$PROJDIR/$sbin_path";; | ||||
|             @@CLI@@) destdir="$PROJDIR/$cli_path";; | ||||
|             *) destdir="$PROJDIR/$destdir";; | ||||
|             esac | ||||
| 
 | ||||
|             estep "$(relpath "$destdir/$destname")" | ||||
|             mkdir -p "$destdir" | ||||
|             tail -n+4 "$MYDIR/$phpwrapper" | sed " | ||||
| s|/@@SBIN@@/|/$sbin_path/| | ||||
| s|/@@CLI@@/|/$cli_path/| | ||||
| " >"$destdir/$destname" | ||||
|             [ -n "$mode" ] && chmod "$mode" "$destdir/$destname" | ||||
|         done | ||||
|     fi | ||||
| fi | ||||
| 
 | ||||
| [ -n "$initial_config" ] | ||||
|  | ||||
							
								
								
									
										21
									
								
								wip/pman.md
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								wip/pman.md
									
									
									
									
									
								
							| @ -6,9 +6,22 @@ outil pour gérer les projets PHP | ||||
|   projets dépendants du projet courant | ||||
| * pver: gestion des versions. | ||||
|   calculer la prochaine version en respectant semver | ||||
| * pmer: gérer les branches de features et hotfixes. | ||||
| * prel: faire une release. | ||||
|   ces outils peuvent agir sur les projets dépendants: faire une release sur un | ||||
|   projet downstream, ou synchroniser la version depuis un projet upstream | ||||
| 
 | ||||
| ## scripts de gestion de projet | ||||
| 
 | ||||
| définir précisément le rôle des scripts | ||||
| * pdist: créer la branche DIST, basculer dessus, merger MAIN dans DIST | ||||
| * pmain: initialiser la	branche MAIN (si nouveau dépôt), basculer dessus, merger DEVELOP dans MAIN | ||||
|   (s'occupe aussi de la configuration pman.conf) | ||||
| * pdev: créer la branche DEVELOP, basculer dessus | ||||
| * pwip: créer une branche WIP, basculer dessus si unique (ou laisser le choix), merger WIP dans DEVELOP | ||||
| * PEUT-ETRE: pfix: créer une branche HOTFIX, basculer dessus si unique (ou laisser le choix), merger HOTFIX dans MAIN | ||||
| * prel: faire une release de DEVELOP dans MAIN. à terme, support des branches de hotfix | ||||
|   * il s'agit d'une spécialisation de pmain et/ou pfix pour la gestion des releases | ||||
|   * à terme, gestion en cascade des projets dépendants: release sur un projet downstream, ou synchroniser la version depuis un projet upstream | ||||
| 
 | ||||
| il faudra supprimer | ||||
| * pman: fonctionnalités réparties dans les autres scripts spécialisés | ||||
| * pmer: fonctionnalités réperties dans les autres scripts spécialisés | ||||
| 
 | ||||
| -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user