<pman>Intégration de la branche dev74
This commit is contained in:
		
						commit
						7420704880
					
				
							
								
								
									
										2
									
								
								.idea/nulib-base.iml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								.idea/nulib-base.iml
									
									
									
										generated
									
									
									
								
							| @ -4,7 +4,7 @@ | |||||||
|     <content url="file://$MODULE_DIR$"> |     <content url="file://$MODULE_DIR$"> | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" /> |       <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$/php/tests" isTestSource="true" packagePrefix="nulib\" /> | ||||||
|       <sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" /> |       <sourceFolder url="file://$MODULE_DIR$/php/cli" isTestSource="false" packagePrefix="cli\" /> | ||||||
|       <excludeFolder url="file://$MODULE_DIR$/php/vendor" /> |       <excludeFolder url="file://$MODULE_DIR$/php/vendor" /> | ||||||
|     </content> |     </content> | ||||||
|     <orderEntry type="inheritedJdk" /> |     <orderEntry type="inheritedJdk" /> | ||||||
|  | |||||||
							
								
								
									
										30
									
								
								.idea/php-docker-settings.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										30
									
								
								.idea/php-docker-settings.xml
									
									
									
										generated
									
									
									
								
							| @ -17,6 +17,36 @@ | |||||||
|             </DockerContainerSettings> |             </DockerContainerSettings> | ||||||
|           </value> |           </value> | ||||||
|         </entry> |         </entry> | ||||||
|  |         <entry key="385aa179-8c50-45e2-9ad7-4d9bfba298a3"> | ||||||
|  |           <value> | ||||||
|  |             <DockerContainerSettings> | ||||||
|  |               <option name="version" value="1" /> | ||||||
|  |               <option name="volumeBindings"> | ||||||
|  |                 <list> | ||||||
|  |                   <DockerVolumeBindingImpl> | ||||||
|  |                     <option name="containerPath" value="/opt/project" /> | ||||||
|  |                     <option name="hostPath" value="$PROJECT_DIR$" /> | ||||||
|  |                   </DockerVolumeBindingImpl> | ||||||
|  |                 </list> | ||||||
|  |               </option> | ||||||
|  |             </DockerContainerSettings> | ||||||
|  |           </value> | ||||||
|  |         </entry> | ||||||
|  |         <entry key="38915385-b3ff-4f4b-8a9a-d5f3ecae559e"> | ||||||
|  |           <value> | ||||||
|  |             <DockerContainerSettings> | ||||||
|  |               <option name="version" value="1" /> | ||||||
|  |               <option name="volumeBindings"> | ||||||
|  |                 <list> | ||||||
|  |                   <DockerVolumeBindingImpl> | ||||||
|  |                     <option name="containerPath" value="/opt/project" /> | ||||||
|  |                     <option name="hostPath" value="$PROJECT_DIR$" /> | ||||||
|  |                   </DockerVolumeBindingImpl> | ||||||
|  |                 </list> | ||||||
|  |               </option> | ||||||
|  |             </DockerContainerSettings> | ||||||
|  |           </value> | ||||||
|  |         </entry> | ||||||
|       </map> |       </map> | ||||||
|     </list> |     </list> | ||||||
|   </component> |   </component> | ||||||
|  | |||||||
							
								
								
									
										77
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										77
									
								
								.idea/php.xml
									
									
									
										generated
									
									
									
								
							| @ -2,7 +2,7 @@ | |||||||
| <project version="4"> | <project version="4"> | ||||||
|   <component name="MessDetector"> |   <component name="MessDetector"> | ||||||
|     <phpmd_settings> |     <phpmd_settings> | ||||||
|       <phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" /> |       <phpmd_by_interpreter asDefaultInterpreter="true" interpreter_id="38915385-b3ff-4f4b-8a9a-d5f3ecae559e" timeout="30000" /> | ||||||
|     </phpmd_settings> |     </phpmd_settings> | ||||||
|   </component> |   </component> | ||||||
|   <component name="MessDetectorOptionsConfiguration"> |   <component name="MessDetectorOptionsConfiguration"> | ||||||
| @ -17,44 +17,61 @@ | |||||||
|   </component> |   </component> | ||||||
|   <component name="PhpCodeSniffer"> |   <component name="PhpCodeSniffer"> | ||||||
|     <phpcs_settings> |     <phpcs_settings> | ||||||
|       <phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="846389f7-9fb5-4173-a868-1dc6b8fbb3fa" timeout="30000" /> |       <phpcs_by_interpreter asDefaultInterpreter="true" interpreter_id="38915385-b3ff-4f4b-8a9a-d5f3ecae559e" timeout="30000" /> | ||||||
|     </phpcs_settings> |     </phpcs_settings> | ||||||
|   </component> |   </component> | ||||||
|   <component name="PhpIncludePathManager"> |   <component name="PhpIncludePathManager"> | ||||||
|     <include_path> |     <include_path> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" /> |       <path value="$PROJECT_DIR$/php/vendor/composer" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" /> |       <path value="$PROJECT_DIR$/php/vendor/dflydev/dot-access-data" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" /> |       <path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/symfony/yaml" /> |       <path value="$PROJECT_DIR$/php/vendor/league/commonmark" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" /> |       <path value="$PROJECT_DIR$/php/vendor/league/config" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" /> |       <path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-timer" /> |       <path value="$PROJECT_DIR$/php/vendor/nette/schema" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/nette/utils" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/nikic/php-parser" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/nulib/tests" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/phar-io/manifest" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/phar-io/version" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/phpmailer/phpmailer" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-code-coverage" /> |       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-code-coverage" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/type" /> |       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator" /> |       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/version" /> |       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/global-state" /> |       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-timer" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/phpunit/phpunit" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/psr/cache" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/psr/container" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/psr/event-dispatcher" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/psr/log" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/cli-parser" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit-reverse-lookup" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/comparator" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/complexity" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/complexity" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/diff" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/environment" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/environment" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/exporter" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/global-state" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/lines-of-code" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator" /> | ||||||
|  |       <path value="$PROJECT_DIR$/php/vendor/sebastian/object-reflector" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/recursion-context" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/recursion-context" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/resource-operations" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/resource-operations" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/diff" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/type" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/cli-parser" /> |       <path value="$PROJECT_DIR$/php/vendor/sebastian/version" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/cache" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/comparator" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/cache-contracts" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/lines-of-code" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit-reverse-lookup" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/expression-language" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/object-reflector" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php73" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/sebastian/exporter" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php80" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/phpunit" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/service-contracts" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/var-exporter" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phar-io/version" /> |       <path value="$PROJECT_DIR$/php/vendor/symfony/yaml" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/phar-io/manifest" /> |       <path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" /> | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/nulib/tests" /> |  | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" /> |  | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/nikic/php-parser" /> |  | ||||||
|       <path value="$PROJECT_DIR$/php/vendor/composer" /> |  | ||||||
|     </include_path> |     </include_path> | ||||||
|   </component> |   </component> | ||||||
|   <component name="PhpProjectSharedConfiguration" php_language_level="7.4"> |   <component name="PhpProjectSharedConfiguration" php_language_level="7.4"> | ||||||
|  | |||||||
| @ -4,7 +4,7 @@ UPSTREAM=dev74 | |||||||
| DEVELOP=dev82 | DEVELOP=dev82 | ||||||
| FEATURE=wip82/ | FEATURE=wip82/ | ||||||
| RELEASE=rel82- | RELEASE=rel82- | ||||||
| MAIN=dist82 | MAIN=main82 | ||||||
| TAG_SUFFIX=p82 | TAG_SUFFIX=p82 | ||||||
| HOTFIX=hotf82- | HOTFIX=hotf82- | ||||||
| DIST= | DIST= | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								TODO.md
									
									
									
									
									
								
							
							
						
						
									
										4
									
								
								TODO.md
									
									
									
									
									
								
							| @ -1,3 +1,7 @@ | |||||||
|  | # nulib | ||||||
|  | 
 | ||||||
|  | * [wip](wip/TODO.md) | ||||||
|  | 
 | ||||||
| # nulib/bash | # nulib/bash | ||||||
| 
 | 
 | ||||||
| * [nulib/bash](bash/TODO.md) | * [nulib/bash](bash/TODO.md) | ||||||
|  | |||||||
| @ -6,7 +6,8 @@ function __esection() { | |||||||
|     local length="${COLUMNS:-80}" |     local length="${COLUMNS:-80}" | ||||||
|     setx lsep=__complete "$prefix" "$length" - |     setx lsep=__complete "$prefix" "$length" - | ||||||
| 
 | 
 | ||||||
|     recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE" |     recho " | ||||||
|  | $COULEUR_BLEUE$lsep$COULEUR_NORMALE" | ||||||
|     [ -n "$*" ] || return 0 |     [ -n "$*" ] || return 0 | ||||||
|     length=$((length - 1)) |     length=$((length - 1)) | ||||||
|     setx -a lines=echo "$1" |     setx -a lines=echo "$1" | ||||||
|  | |||||||
| @ -6,7 +6,8 @@ function __esection() { | |||||||
|     local length="${COLUMNS:-80}" |     local length="${COLUMNS:-80}" | ||||||
|     setx lsep=__complete "$prefix" "$length" - |     setx lsep=__complete "$prefix" "$length" - | ||||||
| 
 | 
 | ||||||
|     recho "$lsep" |     recho " | ||||||
|  | $lsep" | ||||||
|     [ -n "$*" ] || return 0 |     [ -n "$*" ] || return 0 | ||||||
|     length=$((length - 1)) |     length=$((length - 1)) | ||||||
|     setx -a lines=echo "$1" |     setx -a lines=echo "$1" | ||||||
|  | |||||||
| @ -184,7 +184,7 @@ function __nulib_args_parse_args() { | |||||||
|         *) die "Invalid arg definition: expected option, got '$1'" || return;; |         *) die "Invalid arg definition: expected option, got '$1'" || return;; | ||||||
|         esac |         esac | ||||||
|         # est-ce que l'option prend un argument? |         # est-ce que l'option prend un argument? | ||||||
|         local __def __longdef __witharg __valdesc |         local __def __longdef __witharg __valdesc __defvaldesc | ||||||
|         __witharg= |         __witharg= | ||||||
|         for __def in "${__defs[@]}"; do |         for __def in "${__defs[@]}"; do | ||||||
|             if [ "${__def%::*}" != "$__def" ]; then |             if [ "${__def%::*}" != "$__def" ]; then | ||||||
| @ -346,16 +346,19 @@ $prefix$usage" | |||||||
|             fi |             fi | ||||||
|             # est-ce que l'option prend un argument? |             # est-ce que l'option prend un argument? | ||||||
|             __witharg= |             __witharg= | ||||||
|             __valdesc=value |             __valdesc= | ||||||
|  |             __defvaldesc=value | ||||||
|             for __def in "${__defs[@]}"; do |             for __def in "${__defs[@]}"; do | ||||||
|                 if [ "${__def%::*}" != "$__def" ]; then |                 if [ "${__def%::*}" != "$__def" ]; then | ||||||
|                     [ "$__witharg" != : ] && __witharg=:: |                     [ "$__witharg" != : ] && __witharg=:: | ||||||
|                     [ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]" |                     [ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]" | ||||||
|  |                     __defvaldesc="[value]" | ||||||
|                 elif [ "${__def%:*}" != "$__def" ]; then |                 elif [ "${__def%:*}" != "$__def" ]; then | ||||||
|                     __witharg=: |                     __witharg=: | ||||||
|                     [ -n "${__def#*:}" ] && __valdesc="${__def#*:}" |                     [ -n "${__def#*:}" ] && __valdesc="${__def#*:}" | ||||||
|                 fi |                 fi | ||||||
|             done |             done | ||||||
|  |             [ -n "$__valdesc" ] || __valdesc="$__defvaldesc" | ||||||
|             # description de l'option |             # description de l'option | ||||||
|             local first=1 thelp tdesc |             local first=1 thelp tdesc | ||||||
|             for __def in "${__defs[@]}"; do |             for __def in "${__defs[@]}"; do | ||||||
|  | |||||||
| @ -21,6 +21,13 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then | |||||||
|     fi |     fi | ||||||
|     [ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR" |     [ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR" | ||||||
| 
 | 
 | ||||||
|  |     # Si le script courant est un lien, calculer le répertoire destination | ||||||
|  |     if [ -n "$MYREALSELF" -a -n "$MYSELF" ]; then | ||||||
|  |         MYREALSELF="$(readlink -f "$MYSELF")" | ||||||
|  |         MYREALDIR="$(dirname -- "$MYREALSELF")" | ||||||
|  |         MYREALNAME="$(basename -- "$MYREALSELF")" | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|     # Repertoire temporaire |     # Repertoire temporaire | ||||||
|     [ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp" |     [ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp" | ||||||
|     [ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}" |     [ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}" | ||||||
|  | |||||||
							
								
								
									
										147
									
								
								bash/src/install.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								bash/src/install.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | |||||||
|  | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
|  | ##@cooked nocomments | ||||||
|  | module: install "Outils de haut niveau pour installer des fichiers de configuration" | ||||||
|  | require: base | ||||||
|  | 
 | ||||||
|  | if [ -z "$NULIB_INSTALL_CONFIGURED" ]; then | ||||||
|  |     # Faut-il afficher le nom des fichiers copié/créés? | ||||||
|  |     export NULIB_INSTALL_VERBOSE=1 | ||||||
|  |     # Faut-il afficher les destinations avec ppath? | ||||||
|  |     export NULIB_INSTALL_USES_PPATH= | ||||||
|  | fi | ||||||
|  | export NULIB_INSTALL_CONFIGURED=1 | ||||||
|  | 
 | ||||||
|  | function ensure_exists() { | ||||||
|  |     # Créer le fichier vide "$1" s'il n'existe pas déjà, avec les permissions | ||||||
|  |     # $2(=644). retourner vrai si le fichier a été créé sans erreur | ||||||
|  |     [ -f "$1" ] || { | ||||||
|  |         if [ -n "$NULIB_INSTALL_VERBOSE" ]; then | ||||||
|  |             if [ -n "$NULIB_INSTALL_USES_PPATH" ]; then | ||||||
|  |                 estep "$(ppath "$1")" | ||||||
|  |             else | ||||||
|  |                 estep "$1" | ||||||
|  |             fi | ||||||
|  |         fi | ||||||
|  |         mkdirof "$1" | ||||||
|  |         local r=0 | ||||||
|  |         touch "$1" && chmod "${2:-644}" "$1" || r=$? | ||||||
|  |         return $r | ||||||
|  |     } | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function __nulib_install_show_args() { | ||||||
|  |     if [ -z "$NULIB_INSTALL_VERBOSE" ]; then | ||||||
|  |         : | ||||||
|  |     elif [ -n "$NULIB_INSTALL_USES_PPATH" ]; then | ||||||
|  |         estep "$1 --> $(ppath "$2")${3:+/}" | ||||||
|  |     else | ||||||
|  |         estep "$1 --> $2${3:+/}" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function copy_replace() { | ||||||
|  |     # Copier de façon inconditionnelle le fichier $1 vers le fichier $2, en | ||||||
|  |     # réinitialisation les permissions à la valeur $3 | ||||||
|  |     local src="$1" dest="$2" | ||||||
|  |     local srcname="$(basename -- "$src")" | ||||||
|  | 
 | ||||||
|  |     [ -d "$dest" ] && dest="$dest/$srcname" | ||||||
|  |     mkdirof "$dest" || return 1 | ||||||
|  | 
 | ||||||
|  |     if [ -n "$NULIB_INSTALL_VERBOSE" ]; then | ||||||
|  |         local destarg destname slash | ||||||
|  |         destarg="$(dirname -- "$dest")" | ||||||
|  |         destname="$(basename -- "$dest")" | ||||||
|  |         if [ "$srcname" == "$destname" ]; then | ||||||
|  |             slash=1 | ||||||
|  |         else | ||||||
|  |             destarg="$destarg/$destname" | ||||||
|  |         fi | ||||||
|  |         __nulib_install_show_args "$srcname" "$destarg" "$slash" | ||||||
|  |     fi | ||||||
|  |     local r=0 | ||||||
|  |     if cp "$src" "$dest"; then | ||||||
|  |         if [ -n "$3" ]; then | ||||||
|  |             chmod "$3" "$dest" || r=$? | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |     return $r | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function copy_new() { | ||||||
|  |     # Copier le fichier "$1" vers le fichier "$2", avec les permissions $3(=644) | ||||||
|  |     # Ne pas écraser le fichier destination s'il existe déjà | ||||||
|  |     # Retourner vrai si le fichier a été copié sans erreur | ||||||
|  |     local src="$1" dest="$2" | ||||||
|  | 
 | ||||||
|  |     [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" | ||||||
|  |     mkdirof "$dest" || return 1 | ||||||
|  | 
 | ||||||
|  |     if [ ! -e "$dest" ]; then | ||||||
|  |         copy_replace "$src" "$dest" "$3" | ||||||
|  |     else | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function copy_update() { | ||||||
|  |     # Copier le fichier "$1" vers le fichier "$2", si $2 n'existe pas, ou si $1 | ||||||
|  |     # a été modifié par rapport à $2. Réinitialiser le cas échéant les | ||||||
|  |     # permissions à la valeur $3 | ||||||
|  |     # Retourner vrai si le fichier a été copié sans erreur. | ||||||
|  |     local src="$1" dest="$2" | ||||||
|  | 
 | ||||||
|  |     [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" | ||||||
|  |     mkdirof "$dest" || return 1 | ||||||
|  | 
 | ||||||
|  |     if [ ! -e "$dest" ]; then | ||||||
|  |         copy_replace "$src" "$dest" "$3" | ||||||
|  |     elif testdiff "$src" "$dest"; then | ||||||
|  |         copy_replace "$src" "$dest" "$3" | ||||||
|  |     else | ||||||
|  |         return 1 | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | COPY_UPDATE_ASK_DEFAULT= | ||||||
|  | function copy_update_ask() { | ||||||
|  |     # Copier ou mettre à jour le fichier $1 vers le fichier $2. | ||||||
|  |     # Si le fichier existe déjà, la différence est affichée, et une confirmation | ||||||
|  |     # est demandée pour l'écrasement du fichier. | ||||||
|  |     # si $1 commence par '-' alors on s'en sert comme option pour configurer le | ||||||
|  |     # niveau d'interaction pour demander la confirmation. les paramètres sont | ||||||
|  |     # alors décalés | ||||||
|  |     # Retourner vrai si le fichier a été copié sans erreur. | ||||||
|  |     local interopt=-c | ||||||
|  |     if [[ "$1" == -* ]]; then | ||||||
|  |         interopt="$1" | ||||||
|  |         shift | ||||||
|  |     fi | ||||||
|  |     local src="$1" dest="$2" | ||||||
|  | 
 | ||||||
|  |     [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" | ||||||
|  |     mkdirof "$dest" || return 1 | ||||||
|  | 
 | ||||||
|  |     [ -f "$dest" ] || copy_replace "$src" "$dest" | ||||||
|  |     if testdiff "$src" "$dest"; then | ||||||
|  |         check_interaction "$interopt" && diff -u "$dest" "$src" | ||||||
|  |         if ask_yesno "$interopt" "Voulez-vous remplacer $(ppath "$dest") par la nouvelle version?" "${COPY_UPDATE_ASK_DEFAULT:-C}"; then | ||||||
|  |             copy_replace "$src" "$dest" "$3" | ||||||
|  |             return $? | ||||||
|  |         elif ! check_interaction "$interopt"; then | ||||||
|  |             ewarn "Les mises à jours suivantes sont disponibles:" | ||||||
|  |             diff -u "$dest" "$src" | ||||||
|  |             ewarn "Le fichier $(ppath "$dest") n'a pas été mis à jour" | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  |     return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function link_new() { | ||||||
|  |     # Si $2 n'existe pas, créer le lien symbolique $2 pointant vers $1 | ||||||
|  |     [ -e "$2" ] && return 0 | ||||||
|  | 
 | ||||||
|  |     __nulib_install_show_args "$(basename -- "$2")" "$(dirname -- "$1")" | ||||||
|  |     ln -s "$1" "$2" | ||||||
|  | } | ||||||
| @ -1,7 +1,5 @@ | |||||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
| 
 | 
 | ||||||
| ## configuration par défaut |  | ||||||
| 
 |  | ||||||
| UPSTREAM= | UPSTREAM= | ||||||
| DEVELOP=develop | DEVELOP=develop | ||||||
| FEATURE=wip/ | FEATURE=wip/ | ||||||
|  | |||||||
							
								
								
									
										428
									
								
								bash/src/pman.sh
									
									
									
									
									
								
							
							
						
						
									
										428
									
								
								bash/src/pman.sh
									
									
									
									
									
								
							| @ -25,9 +25,146 @@ DIST= | |||||||
| NOAUTO= | NOAUTO= | ||||||
| 
 | 
 | ||||||
| CONFIG_VARS=( | CONFIG_VARS=( | ||||||
|     UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO |     UPSTREAM DEVELOP FEATURE RELEASE MAIN HOTFIX DIST | ||||||
|  |     TAG_PREFIX TAG_SUFFIX NOAUTO | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
|  | PMAN_BRANCHES=(UPSTREAM DEVELOP FEATURE MAIN DIST) | ||||||
|  | PMAN_TOOL_PDEV=DEVELOP | ||||||
|  | PMAN_TOOL_PWIP=FEATURE | ||||||
|  | PMAN_TOOL_PMAIN=MAIN | ||||||
|  | PMAN_TOOL_PDIST=DIST | ||||||
|  | UPSTREAM_BASE=DEVELOP ; UPSTREAM_MERGE_FROM=       ; UPSTREAM_MERGE_TO=DEVELOP ; UPSTREAM_PREL=    ; UPSTREAM_DELETE= | ||||||
|  | DEVELOP_BASE=MAIN     ; DEVELOP_MERGE_FROM=FEATURE ; DEVELOP_MERGE_TO=MAIN     ; DEVELOP_PREL=from ; DEVELOP_DELETE=to | ||||||
|  | MAIN_BASE=DEVELOP     ; MAIN_MERGE_FROM=DEVELOP    ; MAIN_MERGE_TO=DIST        ; MAIN_PREL=to      ; MAIN_DELETE= | ||||||
|  | DIST_BASE=MAIN        ; DIST_MERGE_FROM=MAIN       ; DIST_MERGE_TO=            ; DIST_PREL=        ; DIST_DELETE= | ||||||
|  | FEATURE_BASE=DEVELOP  ; FEATURE_MERGE_FROM=        ; FEATURE_MERGE_TO=DEVELOP  ; FEATURE_PREL=     ; FEATURE_DELETE=from | ||||||
|  | 
 | ||||||
|  | UPSTREAM_CREATE_FUNCTION=_create_upstream_action | ||||||
|  | 
 | ||||||
|  | function get_base_branch() { | ||||||
|  |     # afficher la branche depuis laquelle créer la branche $1 | ||||||
|  |     # retourner 1 en cas d'erreur (pas de branche source) | ||||||
|  |     local branch="$1" infos | ||||||
|  |     [ -n "$branch" ] || return 1 | ||||||
|  |     infos="${branch^^}_BASE"; branch="${!infos}" | ||||||
|  |     [ -n "$branch" ] && echo "$branch" || return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function get_merge_from_branch() { | ||||||
|  |     # afficher la branche depuis laquelle la branche $1 doit merger | ||||||
|  |     # retourner 1 en cas d'erreur (pas de branche source) | ||||||
|  |     local branch="$1" infos | ||||||
|  |     [ -n "$branch" ] || return 1 | ||||||
|  |     infos="${branch^^}_MERGE_FROM"; branch="${!infos}" | ||||||
|  |     [ -n "$branch" ] && echo "$branch" || return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function get_merge_to_branch() { | ||||||
|  |     # afficher la branche dans laquelle la branche $1 doit merger | ||||||
|  |     # retourner 1 en cas d'erreur (pas de branche destination) | ||||||
|  |     local branch="$1" infos | ||||||
|  |     [ -n "$branch" ] || return 1 | ||||||
|  |     infos="${branch^^}_MERGE_TO"; branch="${!infos}" | ||||||
|  |     [ -n "$branch" ] && echo "$branch" || return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function should_prel_merge() { | ||||||
|  |     # tester si la branche $1 doit être mergée avec prel dans la direction | ||||||
|  |     # $2(=to) | ||||||
|  |     local branch="$1" merge_dir="${2:-to}" infos | ||||||
|  |     [ -n "$branch" ] || return 1 | ||||||
|  |     infos="${branch^^}_PREL" | ||||||
|  |     [ "${!infos}" == "$merge_dir" ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function should_delete_merged() { | ||||||
|  |     # tester si la branche $1 doit être supprimée après avoir été mergée dans la | ||||||
|  |     # direction $2(=to) | ||||||
|  |     local branch="$1" merge_dir="${2:-to}" infos | ||||||
|  |     [ -n "$branch" ] || return 1 | ||||||
|  |     infos="${branch^^}_DELETE" | ||||||
|  |     [ "${!infos}" == "$merge_dir" ] | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | : " | ||||||
|  | # description des variables # | ||||||
|  | 
 | ||||||
|  | * REF_BRANCH -- code de la branche de référence basé sur le nom de l'outil | ||||||
|  | * RefBranch -- nom effectif de la branche si elle est définie dans | ||||||
|  |   .pman.conf, vide sinon | ||||||
|  | * IfRefBranch -- nom effectif de la branche *si elle existe*, vide sinon | ||||||
|  | 
 | ||||||
|  | * REF_UNIQUE -- si la branche de référence est unique. est vide pour les | ||||||
|  |   codes de branches multiples, telle que FEATURE | ||||||
|  | 
 | ||||||
|  | * BASE_BRANCH -- branche de base à partir de laquelle créer la branche | ||||||
|  |   de référence | ||||||
|  | * BaseBranch -- nom effectif de la branche de base si elle est définie | ||||||
|  |   dans .pman.conf, vide sinon | ||||||
|  | * IfBaseBranch -- nom effectif de la branche de base *si elle existe*, vide | ||||||
|  |   sinon | ||||||
|  | 
 | ||||||
|  | * MERGE_FROM -- code de la branche source à partir de laquelle la fusion | ||||||
|  |   est faite dans REF_BRANCH. vide si la branche n'a pas de source | ||||||
|  | * MERGE_TO -- code de la branche destination dans laquelle la fusion est | ||||||
|  |   faite depuis REF_BRANCH. vide si la branche n'a pas de destination | ||||||
|  | * MERGE_DIR -- direction de la fusion: | ||||||
|  |   'from' si on fait REF_BRANCH --> MERGE_TO | ||||||
|  |   'to' si on fait MERGE_FROM --> REF_BRANCH | ||||||
|  | * PREL_MERGE -- si la fusion devrait se faire avec prel | ||||||
|  | * DELETE_MERGED -- s'il faut supprimer la branche source après la fusion | ||||||
|  | 
 | ||||||
|  | * MERGE_SRC -- code de la branche source pour la fusion, ou vide si la | ||||||
|  |   fusion n'est pas possible | ||||||
|  | * MergeSrc -- nom effectif de la branche source si elle est définie | ||||||
|  |   dans .pman.conf | ||||||
|  | * IfMergeSrc -- nom effectif de la branche source *si elle existe*, vide | ||||||
|  |   sinon | ||||||
|  | 
 | ||||||
|  | * MERGE_DEST -- code de la branche destination pour la fusion, ou vide si | ||||||
|  |   la fusion n'est pas possible | ||||||
|  | * MergeDest -- nom effectif de la branche destination si elle est | ||||||
|  |   définie dans .pman.conf | ||||||
|  | * IfMergeDest -- nom effectif de la branche source *si elle existe*, vide | ||||||
|  |   sinon" | ||||||
|  | 
 | ||||||
|  | function set_pman_vars() { | ||||||
|  |     RefBranch="${!REF_BRANCH}" | ||||||
|  |     case "$REF_BRANCH" in | ||||||
|  |     FEATURE|RELEASE|HOTFIX) REF_UNIQUE=;; | ||||||
|  |     *) REF_UNIQUE=1;; | ||||||
|  |     esac | ||||||
|  | 
 | ||||||
|  |     BASE_BRANCH=$(get_base_branch "$REF_BRANCH") | ||||||
|  |     [ -n "$BASE_BRANCH" ] && BaseBranch="${!BASE_BRANCH}" || BaseBranch= | ||||||
|  | 
 | ||||||
|  |     MERGE_FROM=$(get_merge_from_branch "$REF_BRANCH") | ||||||
|  |     MERGE_TO=$(get_merge_to_branch "$REF_BRANCH") | ||||||
|  |     if [ -n "$1" ]; then MERGE_DIR="$1" | ||||||
|  |     else MERGE_DIR=from | ||||||
|  |     fi | ||||||
|  |     PREL_MERGE=$(should_prel_merge "$REF_BRANCH" "$MERGE_DIR" && echo 1) | ||||||
|  |     DELETE_MERGED=$(should_delete_merged "$REF_BRANCH" "$MERGE_DIR" && echo 1) | ||||||
|  |     case "$MERGE_DIR" in | ||||||
|  |     from) | ||||||
|  |         MERGE_SRC="$REF_BRANCH" | ||||||
|  |         MERGE_DEST="$MERGE_TO" | ||||||
|  |         ;; | ||||||
|  |     to) | ||||||
|  |         MERGE_SRC="$MERGE_FROM" | ||||||
|  |         MERGE_DEST="$REF_BRANCH" | ||||||
|  |         ;; | ||||||
|  |     esac | ||||||
|  | 
 | ||||||
|  |     [ -n "$MERGE_SRC" ] && MergeSrc="${!MERGE_SRC}" || MergeSrc= | ||||||
|  |     [ -n "$MERGE_DEST" ] && MergeDest="${!MERGE_DEST}" || MergeDest= | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
| function _init_changelog() { | function _init_changelog() { | ||||||
|     setx date=date +%d/%m/%Y-%H:%M |     setx date=date +%d/%m/%Y-%H:%M | ||||||
|     ac_set_tmpfile changelog |     ac_set_tmpfile changelog | ||||||
| @ -77,7 +214,7 @@ $1 == "|" { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function _list_commits() { | function _list_commits() { | ||||||
|     local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase |     local source="${1:-$MergeSrc}" dest="${2:-$MergeDest}" mergebase | ||||||
|     setx mergebase=git merge-base "$dest" "$source" |     setx mergebase=git merge-base "$dest" "$source" | ||||||
|     git log --oneline --graph --no-decorate "$mergebase..$source" | |     git log --oneline --graph --no-decorate "$mergebase..$source" | | ||||||
|         grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' | |         grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' | | ||||||
| @ -85,7 +222,7 @@ function _list_commits() { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function _show_diff() { | function _show_diff() { | ||||||
|     local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase |     local source="${1:-$MergeSrc}" dest="${2:-$MergeDest}" mergebase | ||||||
|     setx mergebase=git merge-base "$dest" "$source" |     setx mergebase=git merge-base "$dest" "$source" | ||||||
|     git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source" |     git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source" | ||||||
| } | } | ||||||
| @ -147,22 +284,27 @@ EOF | |||||||
| ################################################################################ | ################################################################################ | ||||||
| # Config | # Config | ||||||
| 
 | 
 | ||||||
| function ensure_gitdir() { | function check_gitdir() { | ||||||
|     # commencer dans le répertoire indiqué |     # commencer dans le répertoire indiqué | ||||||
|     local chdir="$1" |     local chdir="$1" | ||||||
|     if [ -n "$chdir" ]; then |     if [ -n "$chdir" ]; then | ||||||
|         cd "$chdir" || die || return |         cd "$chdir" || return 1 | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     # se mettre à la racine du dépôt git |     # se mettre à la racine du dépôt git | ||||||
|     local gitdir |     local gitdir | ||||||
|     git_ensure_gitvcs |     git_check_gitvcs || return 1 | ||||||
|     setx gitdir=git_get_toplevel |     setx gitdir=git_get_toplevel | ||||||
|     cd "$gitdir" || die || return |     cd "$gitdir" || return 1 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function ensure_gitdir() { | ||||||
|  |     # commencer dans le répertoire indiqué | ||||||
|  |     check_gitdir "$@" || die || return 1 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function load_branches() { | function load_branches() { | ||||||
|     local what="${1:-all}"; shift |     local branch what="${1:-all}"; shift | ||||||
|     case "$what" in |     case "$what" in | ||||||
|     all) |     all) | ||||||
|         [ -n "$Origin" ] || Origin=origin |         [ -n "$Origin" ] || Origin=origin | ||||||
| @ -172,30 +314,6 @@ function load_branches() { | |||||||
|         setx -a AllBranches=git_list_pbranches "$Origin" |         setx -a AllBranches=git_list_pbranches "$Origin" | ||||||
|         ;; |         ;; | ||||||
|     current) |     current) | ||||||
|         SrcBranch="$1" |  | ||||||
|         [ -n "$SrcBranch" ] || SrcBranch="$CurrentBranch" |  | ||||||
|         case "$SrcBranch" in |  | ||||||
|         "$UPSTREAM") SrcType=upstream; DestBranch="$DEVELOP";; |  | ||||||
|         "$FEATURE"*) SrcType=feature; DestBranch="$DEVELOP";; |  | ||||||
|         "$DEVELOP") SrcType=develop; DestBranch="$MAIN";; |  | ||||||
|         "$RELEASE"*) SrcType=release; DestBranch="$MAIN";; |  | ||||||
|         "$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";; |  | ||||||
|         "$MAIN") SrcType=main; DestBranch="$DIST";; |  | ||||||
|         "$DIST") SrcType=dist; DestBranch=;; |  | ||||||
|         *) SrcType=; DestBranch=;; |  | ||||||
|         esac |  | ||||||
|         case "$DestBranch" in |  | ||||||
|         "$UPSTREAM") DestType=upstream;; |  | ||||||
|         "$FEATURE"*) DestType=feature;; |  | ||||||
|         "$DEVELOP") DestType=develop;; |  | ||||||
|         "$RELEASE"*) DestType=release;; |  | ||||||
|         "$HOTFIX"*) DestType=hotfix;; |  | ||||||
|         "$MAIN") DestType=main;; |  | ||||||
|         "$DIST") DestType=dist;; |  | ||||||
|         *) DestType=;; |  | ||||||
|         esac |  | ||||||
| 
 |  | ||||||
|         local branch |  | ||||||
|         UpstreamBranch= |         UpstreamBranch= | ||||||
|         FeatureBranches=() |         FeatureBranches=() | ||||||
|         DevelopBranch= |         DevelopBranch= | ||||||
| @ -203,23 +321,32 @@ function load_branches() { | |||||||
|         HotfixBranch= |         HotfixBranch= | ||||||
|         MainBranch= |         MainBranch= | ||||||
|         DistBranch= |         DistBranch= | ||||||
|  |         IfRefBranch= | ||||||
|  |         IfBaseBranch= | ||||||
|  |         IfMergeSrc= | ||||||
|  |         IfMergeDest= | ||||||
|         for branch in "${LocalBranches[@]}"; do |         for branch in "${LocalBranches[@]}"; do | ||||||
|             if [ "$branch" == "$UPSTREAM" ]; then |             if [ "$branch" == "$UPSTREAM" ]; then | ||||||
|                 UpstreamBranch="$branch" |                 UpstreamBranch="$branch" | ||||||
|             elif [[ "$branch" == "$FEATURE"* ]]; then |             elif [ -n "$FEATURE" ] && [[ "$branch" == "$FEATURE"* ]]; then | ||||||
|                 FeatureBranches+=("$branch") |                 FeatureBranches+=("$branch") | ||||||
|             elif [ "$branch" == "$DEVELOP" ]; then |             elif [ -n "$DEVELOP" -a "$branch" == "$DEVELOP" ]; then | ||||||
|                 DevelopBranch="$branch" |                 DevelopBranch="$branch" | ||||||
|             elif [[ "$branch" == "$RELEASE"* ]]; then |             elif [ -n "$RELEASE" ] && [[ "$branch" == "$RELEASE"* ]]; then | ||||||
|                 ReleaseBranch="$branch" |                 ReleaseBranch="$branch" | ||||||
|             elif [[ "$branch" == "$HOTFIX"* ]]; then |             elif [ -n "$HOTFIX" ] && [[ "$branch" == "$HOTFIX"* ]]; then | ||||||
|                 HotfixBranch="$branch" |                 HotfixBranch="$branch" | ||||||
|             elif [ "$branch" == "$MAIN" ]; then |             elif [ -n "$MAIN" -a "$branch" == "$MAIN" ]; then | ||||||
|                 MainBranch="$branch" |                 MainBranch="$branch" | ||||||
|             elif [ "$branch" == "$DIST" ]; then |             elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then | ||||||
|                 DistBranch="$branch" |                 DistBranch="$branch" | ||||||
|             fi |             fi | ||||||
|  |             [ -n "$RefBranch" -a "$branch" == "$RefBranch" ] && IfRefBranch="$branch" | ||||||
|  |             [ -n "$BaseBranch" -a "$branch" == "$BaseBranch" ] && IfBaseBranch="$branch" | ||||||
|  |             [ -n "$MergeSrc" -a "$branch" == "$MergeSrc" ] && IfMergeSrc="$branch" | ||||||
|  |             [ -n "$MergeDest" -a "$branch" == "$MergeDest" ] && IfMergeDest="$branch" | ||||||
|         done |         done | ||||||
|  |         [ -n "$IfMergeSrc" -a "$IfMergeDest" ] && IfCanMerge=1 || IfCanMerge= | ||||||
|         ;; |         ;; | ||||||
|     esac |     esac | ||||||
| } | } | ||||||
| @ -244,9 +371,6 @@ function load_config() { | |||||||
|     elif [ -f .pman.conf ]; then |     elif [ -f .pman.conf ]; then | ||||||
|         ConfigFile="$(pwd)/.pman.conf" |         ConfigFile="$(pwd)/.pman.conf" | ||||||
|         source "$ConfigFile" |         source "$ConfigFile" | ||||||
|     elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then |  | ||||||
|         ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh" |  | ||||||
|         source "$ConfigFile" |  | ||||||
|     else |     else | ||||||
|         ConfigFile="$NULIBDIR/bash/src/pman.conf.sh" |         ConfigFile="$NULIBDIR/bash/src/pman.conf.sh" | ||||||
|     fi |     fi | ||||||
| @ -319,10 +443,8 @@ function _mscript_start() { | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| $(qvals source "$NULIBDIR/load.sh") || exit 1 | $(qvals source "$NULIBDIR/load.sh") || exit 1 | ||||||
| 
 | 
 | ||||||
| $(echo_setv SrcBranch="$SrcBranch") | $(echo_setv MergeSrc="$MergeSrc") | ||||||
| $(echo_setv SrcType="$SrcType") | $(echo_setv MergeDest="$MergeDest") | ||||||
| $(echo_setv DestBranch="$DestBranch") |  | ||||||
| $(echo_setv DestType="$DestType") |  | ||||||
| 
 | 
 | ||||||
| merge= | merge= | ||||||
| delete= | delete= | ||||||
| @ -342,32 +464,32 @@ function _mscript_merge_branch() { | |||||||
|     local msg |     local msg | ||||||
| 
 | 
 | ||||||
|     # basculer sur la branche |     # basculer sur la branche | ||||||
|     _scripta "switch to branch $DestBranch" <<EOF |     _scripta "switch to branch $MergeDest" <<EOF | ||||||
| $comment$(qvals git checkout "$DestBranch")$or_die | $comment$(qvals git checkout "$MergeDest")$or_die | ||||||
| EOF | EOF | ||||||
| 
 | 
 | ||||||
|     if [ -n "$SquashMsg" ]; then |     if [ -n "$SquashMsg" ]; then | ||||||
|         msg="$SquashMsg" |         msg="$SquashMsg" | ||||||
|         [ -n "$TechMerge" ] && msg="<pman>$msg" |         [ -n "$TechMerge" ] && msg="<pman>$msg" | ||||||
|         _scripta "squash merge $SrcBranch" <<EOF |         _scripta "squash merge $MergeSrc" <<EOF | ||||||
| $comment$(qvals git merge "$SrcBranch" --squash)$or_die | $comment$(qvals git merge "$MergeSrc" --squash)$or_die | ||||||
| $comment$(qvals git commit -m "$msg")$or_die | $comment$(qvals git commit -m "$msg")$or_die | ||||||
| EOF | EOF | ||||||
|     else |     else | ||||||
|         msg="Intégration de la branche $SrcBranch" |         msg="Intégration de la branche $MergeSrc" | ||||||
|         [ -n "$TechMerge" ] && msg="<pman>$msg" |         [ -n "$TechMerge" ] && msg="<pman>$msg" | ||||||
|         _scripta "merge $SrcBranch" <<EOF |         _scripta "merge $MergeSrc" <<EOF | ||||||
| $comment$(qvals git merge "$SrcBranch" --no-ff -m "$msg")$or_die | $comment$(qvals git merge "$MergeSrc" --no-ff -m "$msg")$or_die | ||||||
| EOF | EOF | ||||||
|     fi |     fi | ||||||
|     array_addu push_branches "$DestBranch" |     array_addu push_branches "$MergeDest" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function _mscript_delete_branch() { | function _mscript_delete_branch() { | ||||||
|     _scripta "delete branch $SrcBranch" <<EOF |     _scripta "delete branch $MergeSrc" <<EOF | ||||||
| $comment$(qvals git branch -D "$SrcBranch")$or_die | $comment$(qvals git branch -D "$MergeSrc")$or_die | ||||||
| EOF | EOF | ||||||
|     array_addu delete_branches ":$SrcBranch" |     array_addu delete_branches ":$MergeSrc" | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ################################################################################ | ################################################################################ | ||||||
| @ -379,13 +501,11 @@ function _rscript_start() { | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| $(qvals source "$NULIBDIR/load.sh") || exit 1 | $(qvals source "$NULIBDIR/load.sh") || exit 1 | ||||||
| 
 | 
 | ||||||
| $(echo_setv SrcBranch="$SrcBranch") | $(echo_setv MergeSrc="$MergeSrc") | ||||||
| $(echo_setv SrcType="$SrcType") |  | ||||||
| $(echo_setv Version="$Version") | $(echo_setv Version="$Version") | ||||||
| $(echo_setv Tag="$Tag") | $(echo_setv Tag="$Tag") | ||||||
| $(echo_setv ReleaseBranch="$ReleaseBranch") | $(echo_setv ReleaseBranch="$ReleaseBranch") | ||||||
| $(echo_setv DestBranch="$DestBranch") | $(echo_setv MergeDest="$MergeDest") | ||||||
| $(echo_setv DestType="$DestType") |  | ||||||
| 
 | 
 | ||||||
| create= | create= | ||||||
| merge= | merge= | ||||||
| @ -409,25 +529,28 @@ function _rscript_create_release_branch() { | |||||||
| ## Release $Tag du $date | ## Release $Tag du $date | ||||||
| " | " | ||||||
|     _list_commits | _filter_changes | _format_md >>"$changelog" |     _list_commits | _filter_changes | _format_md >>"$changelog" | ||||||
|     if [ -s CHANGES.md ]; then |  | ||||||
|         echo >>"$changelog" |  | ||||||
|         cat CHANGES.md >>"$changelog" |  | ||||||
|     fi |  | ||||||
|     "${EDITOR:-nano}" +7 "$changelog" |     "${EDITOR:-nano}" +7 "$changelog" | ||||||
|     [ -s "$changelog" ] || exit_with ewarn "Création de la release annulée" |     [ -s "$changelog" ] || exit_with ewarn "Création de la release annulée" | ||||||
| 
 | 
 | ||||||
|     # créer la branche de release et basculer dessus |     # créer la branche de release et basculer dessus | ||||||
|     _scripta "create branch $ReleaseBranch" <<EOF |     _scripta "create branch $ReleaseBranch" <<EOF | ||||||
| $(qvals git checkout -b "$ReleaseBranch" "$SrcBranch")$or_die | $(qvals git checkout -b "$ReleaseBranch" "$MergeSrc")$or_die | ||||||
| EOF | EOF | ||||||
| 
 | 
 | ||||||
|     # créer le changelog |     # créer le changelog | ||||||
|     _scripta "update CHANGES.md" <<EOF |     _scripta "update CHANGES.md" <<EOF | ||||||
|  | tmpchanges=/tmp/pman_CHANGES.$$.md | ||||||
| $(qvals echo "$(awk <"$changelog" ' | $(qvals echo "$(awk <"$changelog" ' | ||||||
| BEGIN { p = 0 } | BEGIN { p = 0 } | ||||||
| p == 0 && $0 == "" { p = 1; next } | p == 0 && $0 == "" { p = 1; next } | ||||||
| p == 1 { print } | p == 1 { print } | ||||||
| ')") >CHANGES.md | ')") >"\$tmpchanges" | ||||||
|  | if [ -s CHANGES.md ]; then | ||||||
|  |   echo >>"\$tmpchanges" | ||||||
|  |   cat CHANGES.md >>"\$tmpchanges" | ||||||
|  | fi | ||||||
|  | cat "\$tmpchanges" >CHANGES.md | ||||||
|  | rm -f "\$tmpchanges" | ||||||
| git add CHANGES.md | git add CHANGES.md | ||||||
| EOF | EOF | ||||||
| 
 | 
 | ||||||
| @ -471,3 +594,176 @@ function _rscript_delete_release_branch() { | |||||||
| $comment$(qvals git branch -D "$ReleaseBranch")$or_die | $comment$(qvals git branch -D "$ReleaseBranch")$or_die | ||||||
| EOF | EOF | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | # Outils | ||||||
|  | 
 | ||||||
|  | function dump_action() { | ||||||
|  |     enote "Valeurs des variables: | ||||||
|  | REF_BRANCH=$REF_BRANCH${RefBranch:+ RefBranch=$RefBranch IfRefBranch=$IfRefBranch} | ||||||
|  | BASE_BRANCH=$BASE_BRANCH${BaseBranch:+ BaseBranch=$BaseBranch IfBaseBranch=$IfBaseBranch} | ||||||
|  | MERGE_FROM=$MERGE_FROM | ||||||
|  | MERGE_TO=$MERGE_TO | ||||||
|  | MERGE_DIR=$MERGE_DIR | ||||||
|  | PREL_MERGE=$PREL_MERGE | ||||||
|  | DELETE_MERGED=$DELETE_MERGED | ||||||
|  | MERGE_SRC=$MERGE_SRC${MergeSrc:+ MergeSrc=$MergeSrc IfMergeSrc=$IfMergeSrc} | ||||||
|  | MERGE_DEST=$MERGE_DEST${MergeDest:+ MergeDest=$MergeDest IfMergeDest=$IfMergeDest} | ||||||
|  | 
 | ||||||
|  | CurrentBranch=$CurrentBranch | ||||||
|  | LocalBranches=${LocalBranches[*]} | ||||||
|  | RemoteBranches=${RemoteBranches[*]} | ||||||
|  | AllBranches=${AllBranches[*]} | ||||||
|  | 
 | ||||||
|  | UpstreamBranch=$UpstreamBranch | ||||||
|  | FeatureBranches=${FeatureBranches[*]} | ||||||
|  | DevelopBranch=$DevelopBranch | ||||||
|  | ReleaseBranch=$ReleaseBranch | ||||||
|  | HotfixBranch=$HotfixBranch | ||||||
|  | MainBranch=$MainBranch | ||||||
|  | DistBranch=$DistBranch | ||||||
|  | " | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function resolve_unique_branch() { | ||||||
|  |     if [ "$REF_BRANCH" == FEATURE ]; then | ||||||
|  |         if [ $# -gt 0 ]; then | ||||||
|  |             RefBranch="$FEATURE${1#$FEATURE}" | ||||||
|  |         elif [[ "$CurrentBranch" == "$FEATURE"* ]]; then | ||||||
|  |             RefBranch="$CurrentBranch" | ||||||
|  |         elif [ ${#FeatureBranches[*]} -eq 0 ]; then | ||||||
|  |             die "Vous devez spécifier la branche de feature" | ||||||
|  |         elif [ ${#FeatureBranches[*]} -eq 1 ]; then | ||||||
|  |             RefBranch="${FeatureBranches[0]}" | ||||||
|  |         else | ||||||
|  |             simple_menu \ | ||||||
|  |                 RefBranch FeatureBranches \ | ||||||
|  |                 -t "Branches de feature" \ | ||||||
|  |                 -m "Veuillez choisir la branche de feature" \ | ||||||
|  |                 -d "${FeatureBranches[0]}" | ||||||
|  |         fi | ||||||
|  |     else | ||||||
|  |         die "resolve_unique_branch: $REF_BRANCH: non implémenté" | ||||||
|  |     fi | ||||||
|  |     if [ "$MERGE_DIR" == from ]; then | ||||||
|  |         MergeSrc="$RefBranch" | ||||||
|  |     elif [ "$MERGE_DIR" == to ]; then | ||||||
|  |         MergeDest="$RefBranch" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _ensure_ref_branch() { | ||||||
|  |     [ -n "$RefBranch" ] || die "\ | ||||||
|  | La branche $REF_BRANCH n'a pas été définie. | ||||||
|  | Veuillez éditer le fichier .pman.conf" | ||||||
|  |     [ "$1" == init -o -n "$IfRefBranch" ] || die "$RefBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _ensure_base_branch() { | ||||||
|  |     [ -n "$BaseBranch" ] || die "\ | ||||||
|  | La branche $BASE_BRANCH n'a pas été définie. | ||||||
|  | Veuillez éditer le fichier .pman.conf" | ||||||
|  |     [ "$1" == init -o -n "$IfBaseBranch" ] || die "$BaseBranch: cette branche n'existe pas (le dépôt a-t-il été initialisé?)" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _create_default_action() { | ||||||
|  |     enote "Vous allez créer la branche ${COULEUR_BLEUE}$RefBranch${COULEUR_NORMALE} <-- ${COULEUR_ROUGE}$BaseBranch${COULEUR_NORMALE}" | ||||||
|  |     ask_yesno "Voulez-vous continuer?" O || die | ||||||
|  | 
 | ||||||
|  |     einfo "Création de la branche $RefBranch" | ||||||
|  |     git checkout -b "$RefBranch" "$BaseBranch" || die | ||||||
|  |     push_branches+=("$RefBranch") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _create_upstream_action() { | ||||||
|  |     enote "Vous allez créer la branche ${COULEUR_BLEUE}$RefBranch${COULEUR_NORMALE}" | ||||||
|  |     ask_yesno "Voulez-vous continuer?" O || die | ||||||
|  | 
 | ||||||
|  |     # faire une copie de la configuration actuelle | ||||||
|  |     local config; ac_set_tmpfile config | ||||||
|  |     set -x; ls -l "$ConfigFile" #XXX | ||||||
|  |     cp "$ConfigFile" "$config" | ||||||
|  |     set +x #XXX | ||||||
|  | 
 | ||||||
|  |     einfo "Création de la branche $RefBranch" | ||||||
|  |     git checkout --orphan "$RefBranch" || die | ||||||
|  |     git rm -rf . | ||||||
|  |     cp "$config" .pman.conf | ||||||
|  |     git add .pman.conf | ||||||
|  |     git commit -m "commit initial" | ||||||
|  |     push_branches+=("$RefBranch") | ||||||
|  | 
 | ||||||
|  |     einfo "Fusion dans $DevelopBranch" | ||||||
|  |     git checkout "$DevelopBranch" | ||||||
|  |     git merge \ | ||||||
|  |         --no-ff -m "<pman>Intégration initiale de la branche $RefBranch" \ | ||||||
|  |         -srecursive -Xours --allow-unrelated-histories \ | ||||||
|  |         "$RefBranch" | ||||||
|  |     push_branches+=("$DevelopBranch") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function checkout_action() { | ||||||
|  |     local -a push_branches | ||||||
|  | 
 | ||||||
|  |     [ -n "$REF_UNIQUE" ] || resolve_unique_branch "$@" | ||||||
|  |     _ensure_ref_branch init | ||||||
|  | 
 | ||||||
|  |     if array_contains LocalBranches "$RefBranch"; then | ||||||
|  |         git checkout "$RefBranch" | ||||||
|  |     elif array_contains AllBranches "$RefBranch"; then | ||||||
|  |         enote "$RefBranch: une branche du même nom existe dans l'origine" | ||||||
|  |         ask_yesno "Voulez-vous basculer sur cette branche?" O || die | ||||||
|  |         git checkout "$RefBranch" | ||||||
|  |     else | ||||||
|  |         _ensure_base_branch | ||||||
|  |         resolve_should_push | ||||||
|  | 
 | ||||||
|  |         local create_function | ||||||
|  |         create_function="${REF_BRANCH}_CREATE_FUNCTION"; create_function="${!create_function}" | ||||||
|  |         [ -n "$create_function" ] || create_function=_create_default_action | ||||||
|  |         "$create_function" | ||||||
|  | 
 | ||||||
|  |         _push_branches | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function ensure_merge_branches() { | ||||||
|  |     [ -n "$MergeSrc" ] || die "\ | ||||||
|  | $RefBranch: configuration de fusion non trouvée: la branche $MERGE_SRC n'a pas été définie. | ||||||
|  | Veuillez éditer le fichier .pman.conf" | ||||||
|  |     [ -n "$MergeDest" ] || die "\ | ||||||
|  | $RefBranch: configuration de fusion non trouvée: la branche $MERGE_DEST n'a pas été définie. | ||||||
|  | Veuillez éditer le fichier .pman.conf" | ||||||
|  | 
 | ||||||
|  |     local branches | ||||||
|  |     [ "$1" == -a ] && branches=AllBranches || branches=LocalBranches | ||||||
|  |     array_contains "$branches" "$MergeSrc" || die "$MergeSrc: branche source introuvable" | ||||||
|  |     array_contains "$branches" "$MergeDest" || die "$MergeDest: branche destination introuvable" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _show_action() { | ||||||
|  |     local commits | ||||||
|  |     setx commits=_list_commits "$MergeSrc" "$MergeDest" | ||||||
|  |     if [ -n "$commits" ]; then | ||||||
|  |         if [ $ShowLevel -ge 2 ]; then | ||||||
|  |             { | ||||||
|  |                 echo "\ | ||||||
|  | # Commits à fusionner $MergeSrc --> $MergeDest | ||||||
|  | 
 | ||||||
|  | $commits | ||||||
|  | " | ||||||
|  |                 _sd_COLOR=always _show_diff | ||||||
|  |             } | less -eRF | ||||||
|  |         else | ||||||
|  |             einfo "Commits à fusionner $MergeSrc --> $MergeDest" | ||||||
|  |             eecho "$commits" | ||||||
|  |         fi | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function show_action() { | ||||||
|  |     git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" | ||||||
|  |     [ -n "$REF_UNIQUE" ] || resolve_unique_branch "$@" | ||||||
|  |     ensure_merge_branches | ||||||
|  |     _show_action "$@" | ||||||
|  | } | ||||||
|  | |||||||
| @ -1,14 +1,10 @@ | |||||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
| 
 | 
 | ||||||
| ## configuration de la branche 7.4 d'un projet PHP multiversion |  | ||||||
| # il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les |  | ||||||
| # modifications de la 7.4 étant incluses dans la branche 8.2 |  | ||||||
| 
 |  | ||||||
| UPSTREAM= | UPSTREAM= | ||||||
| DEVELOP=dev74 | DEVELOP=dev74 | ||||||
| FEATURE=wip74/ | FEATURE=wip74/ | ||||||
| RELEASE=rel74- | RELEASE=rel74- | ||||||
| MAIN=dist74 | MAIN=main74 | ||||||
| TAG_PREFIX= | TAG_PREFIX= | ||||||
| TAG_SUFFIX=p74 | TAG_SUFFIX=p74 | ||||||
| HOTFIX=hotf74- | HOTFIX=hotf74- | ||||||
|  | |||||||
| @ -1,14 +1,10 @@ | |||||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
| 
 | 
 | ||||||
| ## configuration de la branche 8.2 d'un projet PHP multiversion |  | ||||||
| # il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les |  | ||||||
| # modifications de la 7.4 étant incluses dans la branche 8.2 |  | ||||||
| 
 |  | ||||||
| UPSTREAM=dev74 | UPSTREAM=dev74 | ||||||
| DEVELOP=dev82 | DEVELOP=dev82 | ||||||
| FEATURE=wip82/ | FEATURE=wip82/ | ||||||
| RELEASE=rel82- | RELEASE=rel82- | ||||||
| MAIN=dist82 | MAIN=main82 | ||||||
| TAG_PREFIX= | TAG_PREFIX= | ||||||
| TAG_SUFFIX=p82 | TAG_SUFFIX=p82 | ||||||
| HOTFIX=hotf82- | HOTFIX=hotf82- | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								bash/src/pman84.conf.sh
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								bash/src/pman84.conf.sh
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | |||||||
|  | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
|  | 
 | ||||||
|  | UPSTREAM=dev74 | ||||||
|  | DEVELOP=dev84 | ||||||
|  | FEATURE=wip84/ | ||||||
|  | RELEASE=rel84- | ||||||
|  | MAIN=main84 | ||||||
|  | TAG_PREFIX= | ||||||
|  | TAG_SUFFIX=p84 | ||||||
|  | HOTFIX=hotf84- | ||||||
|  | DIST= | ||||||
|  | NOAUTO= | ||||||
| @ -22,7 +22,12 @@ et \$2 vaudra alors 'file' | |||||||
| si un fichier \${2#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à | si un fichier \${2#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à | ||||||
| la place comme source | la place comme source | ||||||
| 
 | 
 | ||||||
| Ajouter file au tableau userfiles" | Ajouter file au tableau userfiles | ||||||
|  | 
 | ||||||
|  | retourner: | ||||||
|  | - 0 en cas de copie avec succès | ||||||
|  | - 2 si la source n'existe pas | ||||||
|  | - 3 si une erreur I/O s'est produite lors de la copie" | ||||||
| function template_copy_replace() { | function template_copy_replace() { | ||||||
|     local src="$1" dest="$2" |     local src="$1" dest="$2" | ||||||
|     local srcdir srcname lsrcname |     local srcdir srcname lsrcname | ||||||
| @ -37,8 +42,28 @@ function template_copy_replace() { | |||||||
|     lsrcname="${srcname#.}.local" |     lsrcname="${srcname#.}.local" | ||||||
|     [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" |     [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" | ||||||
| 
 | 
 | ||||||
|  |     [ -e "$src" ] || return 2 | ||||||
|  | 
 | ||||||
|     userfiles+=("$dest") |     userfiles+=("$dest") | ||||||
|     cp -P "$src" "$dest" |     local have_backup | ||||||
|  |     if [ -e "$dest" ]; then | ||||||
|  |         # copie de sauvegarde avant | ||||||
|  |         if ! cp -P --preserve=all "$dest" "$dest.bck.$$"; then | ||||||
|  |             rm "$dest.bck.$$" | ||||||
|  |             return 3 | ||||||
|  |         fi | ||||||
|  |         have_backup=1 | ||||||
|  |     fi | ||||||
|  |     if ! cp -P "$src" "$dest"; then | ||||||
|  |         rm "$dest" | ||||||
|  |         if [ -n "$have_backup" ]; then | ||||||
|  |             # restaurer la sauvegarde en cas d'erreur | ||||||
|  |             cp -P --preserve=all "$dest.bck.$$" "$dest" && | ||||||
|  |                 rm "$dest.bck.$$" | ||||||
|  |         fi | ||||||
|  |         return 3 | ||||||
|  |     fi | ||||||
|  |     [ -n "$have_backup" ] && rm "$dest.bck.$$" | ||||||
|     return 0 |     return 0 | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -51,7 +76,13 @@ et \$2 vaudra alors 'file' | |||||||
| si un fichier \${1#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à | si un fichier \${1#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à | ||||||
| la place comme source | la place comme source | ||||||
| 
 | 
 | ||||||
| Ajouter file au tableau userfiles" | Ajouter file au tableau userfiles | ||||||
|  | 
 | ||||||
|  | retourner: | ||||||
|  | - 0 en cas de copie avec succès | ||||||
|  | - 1 si le fichier existait déjà | ||||||
|  | - 2 si la source n'existe pas | ||||||
|  | - 3 si une erreur I/O s'est produite lors de la copie" | ||||||
| function template_copy_missing() { | function template_copy_missing() { | ||||||
|     local src="$1" dest="$2" |     local src="$1" dest="$2" | ||||||
|     local srcdir srcname lsrcname |     local srcdir srcname lsrcname | ||||||
| @ -63,15 +94,33 @@ function template_copy_missing() { | |||||||
|         dest="$srcdir/$dest" |         dest="$srcdir/$dest" | ||||||
|     fi |     fi | ||||||
| 
 | 
 | ||||||
|     userfiles+=("$dest") |     lsrcname="${srcname#.}.local" | ||||||
|     if [ ! -e "$dest" ]; then |     [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" | ||||||
|         lsrcname="${srcname#.}.local" |  | ||||||
|         [ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname" |  | ||||||
| 
 | 
 | ||||||
|         cp -P "$src" "$dest" |     [ -e "$src" ] || return 2 | ||||||
|         return 0 | 
 | ||||||
|  |     userfiles+=("$dest") | ||||||
|  |     [ -e "$dest" ] && return 1 | ||||||
|  | 
 | ||||||
|  |     if ! cp -P "$src" "$dest"; then | ||||||
|  |         # ne pas garder le fichier en cas d'erreur de copie | ||||||
|  |         rm "$dest" | ||||||
|  |         return 3 | ||||||
|     fi |     fi | ||||||
|     return 1 |     return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function: template_ioerror "\ | ||||||
|  | tester si une erreur de copie s'est produite lors de l'appel à | ||||||
|  | template_copy_missing() ou template_copy_replace(), par exemple en cas de | ||||||
|  | dépassement de capacité du disque ou si le fichier source n'existe pas | ||||||
|  | 
 | ||||||
|  | il faut appeler cette fonction avec la valeur de retour de ces fonctions, e.g | ||||||
|  |     template_copy_missing file | ||||||
|  |     template_ioerror $? && die" | ||||||
|  | function template_ioerror() { | ||||||
|  |     local r="${1:-$?}" | ||||||
|  |     [ $r -ge 2 ] | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function: template_dump_vars "\ | function: template_dump_vars "\ | ||||||
| @ -219,8 +268,13 @@ function _template_can_process() { | |||||||
|     esac |     esac | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | function: template_process_userfiles "\ | ||||||
|  | retourner: | ||||||
|  | - 0 en cas de traitement avec succès des fichiers | ||||||
|  | - 3 si une erreur I/O s'est produite lors du traitement d'un des fichiers" | ||||||
| function template_process_userfiles() { | function template_process_userfiles() { | ||||||
|     local awkscript sedscript workfile userfile |     local awkscript sedscript workfile userfile | ||||||
|  |     local have_backup | ||||||
|     ac_set_tmpfile awkscript |     ac_set_tmpfile awkscript | ||||||
|     ac_set_tmpfile sedscript |     ac_set_tmpfile sedscript | ||||||
|     template_generate_scripts "$awkscript" "$sedscript" "$@" |     template_generate_scripts "$awkscript" "$sedscript" "$@" | ||||||
| @ -231,10 +285,28 @@ function template_process_userfiles() { | |||||||
|         if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then |         if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then | ||||||
|             if testdiff "$workfile" "$userfile"; then |             if testdiff "$workfile" "$userfile"; then | ||||||
|                 # n'écrire le fichier que s'il a changé |                 # n'écrire le fichier que s'il a changé | ||||||
|                 cat "$workfile" >"$userfile" |                 if [ -e "$userfile" ]; then | ||||||
|  |                     # copie de sauvegarde avant | ||||||
|  |                     if ! cp -P --preserve=all "$userfile" "$userfile.bck.$$"; then | ||||||
|  |                         rm "$userfile.bck.$$" | ||||||
|  |                         return 3 | ||||||
|  |                     fi | ||||||
|  |                     have_backup=1 | ||||||
|  |                 fi | ||||||
|  |                 if ! cat "$workfile" >"$userfile"; then | ||||||
|  |                     rm "$userfile" | ||||||
|  |                     if [ -n "$have_backup" ]; then | ||||||
|  |                         # restaurer la sauvegarde en cas d'erreur | ||||||
|  |                         cp -P --preserve=all "$userfile.bck.$$" "$userfile" && | ||||||
|  |                             rm "$userfile.bck.$$" | ||||||
|  |                     fi | ||||||
|  |                     return 3 | ||||||
|  |                 fi | ||||||
|  |                 [ -n "$have_backup" ] && rm "$userfile.bck.$$" | ||||||
|             fi |             fi | ||||||
|         fi |         fi | ||||||
|     done |     done | ||||||
| 
 | 
 | ||||||
|     ac_clean "$awkscript" "$sedscript" "$workfile" |     ac_clean "$awkscript" "$sedscript" "$workfile" | ||||||
|  |     return 0 | ||||||
| } | } | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								bin/.cachectl.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.cachectl.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/cachectl.php | ||||||
							
								
								
									
										1
									
								
								bin/.dumpser.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.dumpser.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/dumpser.php | ||||||
							
								
								
									
										1
									
								
								bin/.json2yml.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.json2yml.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/json2yml.php | ||||||
							
								
								
									
										1
									
								
								bin/.mysql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.mysql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/mysql.capacitor.php | ||||||
| @ -2,9 +2,7 @@ | |||||||
| <?php | <?php | ||||||
| require __DIR__ . "/../php/vendor/autoload.php"; | require __DIR__ . "/../php/vendor/autoload.php"; | ||||||
| 
 | 
 | ||||||
| use nulib\tools\pman\ComposerFile; | use cli\pman\ComposerFile; | ||||||
| use nulib\tools\pman\ComposerPmanFile; |  | ||||||
| use nulib\ValueException; |  | ||||||
| 
 | 
 | ||||||
| $composer = new ComposerFile(); | $composer = new ComposerFile(); | ||||||
| 
 | 
 | ||||||
| @ -2,8 +2,8 @@ | |||||||
| <?php | <?php | ||||||
| require __DIR__ . "/../php/vendor/autoload.php"; | require __DIR__ . "/../php/vendor/autoload.php"; | ||||||
| 
 | 
 | ||||||
| use nulib\tools\pman\ComposerFile; | use cli\pman\ComposerFile; | ||||||
| use nulib\tools\pman\ComposerPmanFile; | use cli\pman\ComposerPmanFile; | ||||||
| use nulib\ValueException; | use nulib\ValueException; | ||||||
| 
 | 
 | ||||||
| $composer = new ComposerFile(); | $composer = new ComposerFile(); | ||||||
							
								
								
									
										1
									
								
								bin/.pgsql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.pgsql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/pgsql.capacitor.php | ||||||
							
								
								
									
										1
									
								
								bin/.sqlite.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.sqlite.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/sqlite.capacitor.php | ||||||
							
								
								
									
										1
									
								
								bin/.yml2json.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/.yml2json.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | ../php/bin/yml2json.php | ||||||
| @ -9,4 +9,4 @@ args=( | |||||||
| ) | ) | ||||||
| parse_args "$@"; set -- "${args[@]}" | parse_args "$@"; set -- "${args[@]}" | ||||||
| 
 | 
 | ||||||
| exec "$MYDIR/pmer" --tech-merge -Bdev82 dev74 ${dev74:+-a "git checkout dev74"} "$@" | exec "$MYDIR/ptool" -fupstream -Bdev82 -m --tech-merge ${dev74:+-a "git checkout dev74"} "$@" | ||||||
|  | |||||||
| @ -19,7 +19,7 @@ export RUNPHP_BUILD_FLAVOUR= | |||||||
| runphp=("$MYDIR/../runphp/runphp" --bs) | runphp=("$MYDIR/../runphp/runphp" --bs) | ||||||
| [ -z "$force" ] && runphp+=(--ue) | [ -z "$force" ] && runphp+=(--ue) | ||||||
| 
 | 
 | ||||||
| for RUNPHP_DIST in d12 d11; do | for RUNPHP_DIST in d13 d12 d11; do | ||||||
|     for RUNPHP_BUILD_FLAVOUR in +ic none; do |     for RUNPHP_BUILD_FLAVOUR in +ic none; do | ||||||
|         flavour="$RUNPHP_BUILD_FLAVOUR" |         flavour="$RUNPHP_BUILD_FLAVOUR" | ||||||
|         [ "$flavour" == none ] && flavour= |         [ "$flavour" == none ] && flavour= | ||||||
|  | |||||||
							
								
								
									
										85
									
								
								bin/ff
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										85
									
								
								bin/ff
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,85 @@ | |||||||
|  | #!/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 | ||||||
|  | 
 | ||||||
|  | function filter_arg() { | ||||||
|  |     local inverse="$1"; shift | ||||||
|  |     while read arg; do | ||||||
|  |         if [ "$inverse" == 0 ]; then | ||||||
|  |             [ $1 "$arg" ] && echo "$arg" | ||||||
|  |         else | ||||||
|  |             [ $1 "$arg" ] || echo "$arg" | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  | } | ||||||
|  | function filter_broken() { | ||||||
|  |     local inverse="$1"; shift | ||||||
|  |     while read arg; do | ||||||
|  |         [ -L "$arg" ] || continue | ||||||
|  |         if [ "$inverse" == 0 ]; then | ||||||
|  |             [ -e "$arg" ] || echo "$arg" | ||||||
|  |         else | ||||||
|  |             [ -e "$arg" ] && echo "$arg" | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | chdir= | ||||||
|  | lsdirs= | ||||||
|  | lsfiles= | ||||||
|  | lsnames= | ||||||
|  | dir= | ||||||
|  | file= | ||||||
|  | link= | ||||||
|  | broken= | ||||||
|  | exists= | ||||||
|  | nonzero= | ||||||
|  | inverse=0 | ||||||
|  | args=( | ||||||
|  |     "filtrer une liste de fichiers" | ||||||
|  |     "[filter]" | ||||||
|  |     -C:,--chdir chdir= "changer le répertoire courant avant de lister les fichiers" | ||||||
|  |     --lsdirs . "n'afficher que les répertoires du répertoire courant" | ||||||
|  |     --lsfiles . "n'afficher que les fichiers du répertoire courant" | ||||||
|  |     --lsnames . "n'afficher que les noms de fichier" | ||||||
|  |     -d,--dir . "garder uniquement les répertoires" | ||||||
|  |     -f,--file . "garder uniquement les fichiers" | ||||||
|  |     -L,--link . "garder uniquement les liens symboliques" | ||||||
|  |     -b,--broken . "garder uniquement les liens symboliques cassés" | ||||||
|  |     -e,--exists . "garder uniquement les fichiers/répertoires qui existent" | ||||||
|  |     -s,--nonzero . "garder uniquement les fichiers ayant une taille non nulle" | ||||||
|  |     -n,--inverse inverse=1 "inverser le sens du filtre" | ||||||
|  | ) | ||||||
|  | parse_args "$@"; set -- "${args[@]}" | ||||||
|  | 
 | ||||||
|  | if [ -n "$chdir" ]; then | ||||||
|  |     cd "$chdir" || die | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if in_isatty; then | ||||||
|  |     # lister les fichiers | ||||||
|  |     setx cwd=pwd | ||||||
|  |     [ -n "$lsnames" ] && withpath= || withpath=1 | ||||||
|  |     if [ -n "$lsdirs" -a -n "$lsfiles" ]; then | ||||||
|  |         cmd="{ | ||||||
|  | $(qvals ls_dirs ${withpath:+-p} "$cwd" "$@") | ||||||
|  | $(qvals ls_files ${withpath:+-p} "$cwd" "$@") | ||||||
|  | }" | ||||||
|  |     elif [ -n "$lsdirs" ]; then | ||||||
|  |         cmd="$(qvals ls_dirs ${withpath:+-p} "$cwd" "$@")" | ||||||
|  |     elif [ -n "$lsfiles" ]; then | ||||||
|  |         cmd="$(qvals ls_files ${withpath:+-p} "$cwd" "$@")" | ||||||
|  |     else | ||||||
|  |         cmd="$(qvals ls_all ${withpath:+-p} "$cwd" "$@")" | ||||||
|  |     fi | ||||||
|  | else | ||||||
|  |     cmd="cat" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | [ -n "$dir" ] && cmd="$cmd | filter_arg $inverse -d" | ||||||
|  | [ -n "$file" ] && cmd="$cmd | filter_arg $inverse -f" | ||||||
|  | [ -n "$link" ] && cmd="$cmd | filter_arg $inverse -L" | ||||||
|  | [ -n "$broken" ] && cmd="$cmd | filter_broken $inverse" | ||||||
|  | [ -n "$exists" ] && cmd="$cmd | filter_arg $inverse -e" | ||||||
|  | [ -n "$nonzero" ] && cmd="$cmd | filter_arg $inverse -s" | ||||||
|  | eval "$cmd" | ||||||
							
								
								
									
										1
									
								
								bin/json2yml.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/json2yml.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
							
								
								
									
										1
									
								
								bin/mysql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/mysql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
| @ -20,8 +20,8 @@ fi | |||||||
| [ -f /etc/profile ] && source /etc/profile | [ -f /etc/profile ] && source /etc/profile | ||||||
| [ -f ~/.bash_profile ] && source ~/.bash_profile | [ -f ~/.bash_profile ] && source ~/.bash_profile | ||||||
| 
 | 
 | ||||||
| # Modifier le PATH. Ajouter aussi le chemin vers les uapps python | # Modifier le PATH | ||||||
| PATH=$(qval "$NULIBDIR/bin:$PATH") | PATH=$(qval "$NULIBDIR/wip:$NULIBDIR/bin:$PATH") | ||||||
| 
 | 
 | ||||||
| if [ -n '$DEFAULT_PS1' ]; then | if [ -n '$DEFAULT_PS1' ]; then | ||||||
|   DEFAULT_PS1=$(qval "[nlshell] $DEFAULT_PS1") |   DEFAULT_PS1=$(qval "[nlshell] $DEFAULT_PS1") | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								bin/pcomp-local_deps.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/pcomp-local_deps.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
							
								
								
									
										1
									
								
								bin/pcomp-select_profile.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/pcomp-select_profile.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
							
								
								
									
										1
									
								
								bin/pgsql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/pgsql.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
							
								
								
									
										192
									
								
								bin/pinit
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										192
									
								
								bin/pinit
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,192 @@ | |||||||
|  | #!/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 | ||||||
|  | require: git pman pman.conf | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | # Informations | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
|  | SHOW_VARS=( | ||||||
|  |     --Configuration | ||||||
|  |     "${CONFIG_VARS[@]}" | ||||||
|  |     --Paramètres | ||||||
|  |     CurrentBranch | ||||||
|  |     CurrentType=SrcType | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | function show_action() { | ||||||
|  |     local var src | ||||||
|  |     echo_setv ConfigBranch="$ConfigBranch" | ||||||
|  |     echo_setv ConfigFile="$(ppath "$ConfigFile")" | ||||||
|  |     for var in "${SHOW_VARS[@]}"; do | ||||||
|  |         if [ "${var#--}" != "$var" ]; then | ||||||
|  |             estep "${var#--}" | ||||||
|  |         else | ||||||
|  |             splitfsep "$var" = var src | ||||||
|  |             [ -n "$src" ] || src="$var" | ||||||
|  |             echo_setv "$var=${!src}" | ||||||
|  |         fi | ||||||
|  |     done | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | # Initialisation | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
|  | function _init_config() { | ||||||
|  |     if [ ! -f .pman.conf -o -n "$ForceCreate" ]; then | ||||||
|  |         ac_set_tmpfile config | ||||||
|  |         cp "$ConfigFile" "$config" | ||||||
|  |         "${EDITOR:-nano}" "$config" | ||||||
|  |         [ -s "$config" ] || return 1 | ||||||
|  | 
 | ||||||
|  |         cp "$config" .pman.conf | ||||||
|  |         if testdiff .pman.conf "$ConfigFile"; then | ||||||
|  |             ConfigFile="$(pwd)/.pman.conf" | ||||||
|  |             load_config | ||||||
|  |             load_branches current "$SrcBranch" | ||||||
|  |         fi | ||||||
|  |         git add .pman.conf | ||||||
|  |     fi | ||||||
|  |     if [ ! -f ".gitignore" ]; then | ||||||
|  |         echo >.gitignore "\ | ||||||
|  | .~lock*# | ||||||
|  | .*.swp" | ||||||
|  |         git add .gitignore | ||||||
|  |     fi | ||||||
|  |     return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function init_repo_action() { | ||||||
|  |     local -a push_branches; local config | ||||||
|  | 
 | ||||||
|  |     [ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé" | ||||||
|  | 
 | ||||||
|  |     _init_config || exit_with ewarn "Initialisation du dépôt annulée" | ||||||
|  | 
 | ||||||
|  |     einfo "Création de la branche $MAIN" | ||||||
|  |     git symbolic-ref HEAD "refs/heads/$MAIN" | ||||||
|  |     git commit -m "commit initial" | ||||||
|  |     push_branches+=("$MAIN") | ||||||
|  | 
 | ||||||
|  |     einfo "Création de la branche $DEVELOP" | ||||||
|  |     git checkout -b "$DEVELOP" | ||||||
|  |     push_branches+=("$DEVELOP") | ||||||
|  | 
 | ||||||
|  |     _push_branches | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function init_config_action() { | ||||||
|  |     local -a push_branches; local config | ||||||
|  | 
 | ||||||
|  |     [ -f .pman.conf -a -z "$ForceCreate" ] && die "La configuration pman a déjà été initialisée" | ||||||
|  | 
 | ||||||
|  |     resolve_should_push | ||||||
|  | 
 | ||||||
|  |     _init_config || exit_with ewarn "Initialisation de la configuration annulée" | ||||||
|  |     git commit -m "configuration pman" | ||||||
|  |     push_branches+=("$CurrentBranch") | ||||||
|  | 
 | ||||||
|  |     _push_branches | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function _init_composer() { | ||||||
|  |     if [ ! -f .composer.pman.yml -o -n "$ForceCreate" ]; then | ||||||
|  |         ac_set_tmpfile config | ||||||
|  |         cat >"$config" <<EOF | ||||||
|  | # -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8 | ||||||
|  | 
 | ||||||
|  | composer: | ||||||
|  |   match_prefix: | ||||||
|  |   match_prefix-dev: | ||||||
|  |   profiles: [ dev, dist ] | ||||||
|  |   dev: | ||||||
|  |     link: true | ||||||
|  |     require: | ||||||
|  |     reqire-dev: | ||||||
|  |   dist: | ||||||
|  |     link: false | ||||||
|  |     require: | ||||||
|  |     reqire-dev: | ||||||
|  | EOF | ||||||
|  |         "${EDITOR:-nano}" "$config" | ||||||
|  |         [ -s "$config" ] || return 1 | ||||||
|  | 
 | ||||||
|  |         cp "$config" .composer.pman.yml | ||||||
|  |         git add .composer.pman.yml | ||||||
|  |     fi | ||||||
|  |     return 0 | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function init_composer_action() { | ||||||
|  |     local -a push_branches; local config | ||||||
|  | 
 | ||||||
|  |     [ -f .composer.pman.yml -a -z "$ForceCreate" ] && die "La configuration pman composer a déjà été initialisée" | ||||||
|  | 
 | ||||||
|  |     resolve_should_push | ||||||
|  | 
 | ||||||
|  |     _init_composer || exit_with ewarn "Initialisation de la configuration annulée" | ||||||
|  |     git commit -m "configuration pman composer" | ||||||
|  |     push_branches+=("$CurrentBranch") | ||||||
|  | 
 | ||||||
|  |     _push_branches | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function init_action() { | ||||||
|  |     local what="${1:-repo}"; shift | ||||||
|  |     case "$what" in | ||||||
|  |     repo|r) init_repo_action "$@";; | ||||||
|  |     config|c) init_config_action "$@";; | ||||||
|  |     composer|o) init_composer_action "$@";; | ||||||
|  |     *) die "$what: destination non implémentée" | ||||||
|  |     esac | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | # Programme principal | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
|  | chdir= | ||||||
|  | ConfigFile= | ||||||
|  | action=init | ||||||
|  | Origin= | ||||||
|  | Push=1 | ||||||
|  | ForceCreate= | ||||||
|  | args=( | ||||||
|  |     "initialiser un dépôt git" | ||||||
|  |     "repo|config|composer|all" | ||||||
|  |     -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" | ||||||
|  |     -c:,--config-file:CONFIG ConfigFile= "++\ | ||||||
|  | fichier de configuration des branches. cette option est prioritaire sur --config-branch | ||||||
|  | par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" | ||||||
|  |     -w,--show-config action=show "++\ | ||||||
|  | afficher la configuration chargée" | ||||||
|  |     -O:,--origin Origin= "++\ | ||||||
|  | origine vers laquelle pousser les branches" | ||||||
|  |     -n,--no-push Push= "\ | ||||||
|  | ne pas pousser les branches vers leur origine après leur création" | ||||||
|  |     --push Push=1 "++\ | ||||||
|  | pousser les branches vers leur origine après leur création. | ||||||
|  | c'est l'option par défaut" | ||||||
|  |     -f,--force-create ForceCreate=1 "\ | ||||||
|  | Forcer la (re)création des fichiers de configuration (notamment .pman.conf, | ||||||
|  | .composer.pman.yml, etc.)" | ||||||
|  | ) | ||||||
|  | parse_args "$@"; set -- "${args[@]}" | ||||||
|  | 
 | ||||||
|  | # charger la configuration | ||||||
|  | ensure_gitdir "$chdir" | ||||||
|  | load_branches all | ||||||
|  | load_config | ||||||
|  | load_branches current | ||||||
|  | 
 | ||||||
|  | # puis faire l'action que l'on nous demande | ||||||
|  | case "$action" in | ||||||
|  | show) show_action "$@";; | ||||||
|  | init) | ||||||
|  |     git_ensure_cleancheckout | ||||||
|  |     init_action "$@" | ||||||
|  |     ;; | ||||||
|  | *) die "$action: action non implémentée";; | ||||||
|  | esac | ||||||
							
								
								
									
										71
									
								
								bin/prel
									
									
									
									
									
								
							
							
						
						
									
										71
									
								
								bin/prel
									
									
									
									
									
								
							| @ -7,33 +7,7 @@ git_cleancheckout_DIRTY="\ | |||||||
| Vous avez des modifications locales. | Vous avez des modifications locales. | ||||||
| Enregistrez ces modifications avant de créer une release" | Enregistrez ces modifications avant de créer une release" | ||||||
| 
 | 
 | ||||||
| function show_action() { | function ensure_rel_infos() { | ||||||
|     local commits |  | ||||||
|     setx commits=_list_commits |  | ||||||
|     if [ -n "$commits" ]; then |  | ||||||
|         if [ $ShowLevel -ge 2 ]; then |  | ||||||
|             { |  | ||||||
|                 echo "\ |  | ||||||
| # Commits à fusionner $SrcBranch --> $DestBranch |  | ||||||
| 
 |  | ||||||
| $commits |  | ||||||
| " |  | ||||||
|                 _sd_COLOR=always _show_diff |  | ||||||
|             } | less -eRF |  | ||||||
|         else |  | ||||||
|             einfo "Commits à fusionner $SrcBranch --> $DestBranch" |  | ||||||
|             eecho "$commits" |  | ||||||
|         fi |  | ||||||
|     fi |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function ensure_branches() { |  | ||||||
|    [ -n "$SrcBranch" -a -n "$DestBranch" ] || |  | ||||||
|         die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche" |  | ||||||
| 
 |  | ||||||
|    array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable" |  | ||||||
|    array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable" |  | ||||||
| 
 |  | ||||||
|    Tag="$TAG_PREFIX$Version$TAG_SUFFIX" |    Tag="$TAG_PREFIX$Version$TAG_SUFFIX" | ||||||
|    local -a tags |    local -a tags | ||||||
|    setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" |    setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}" | ||||||
| @ -71,14 +45,14 @@ L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine" | |||||||
|     if [ -n "$Merge" ]; then |     if [ -n "$Merge" ]; then | ||||||
|         enote "\ |         enote "\ | ||||||
| Ce script va: | Ce script va: | ||||||
| - créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} | - créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MergeSrc${COULEUR_NORMALE} | ||||||
| - la provisionner avec une description des changements | - la provisionner avec une description des changements | ||||||
| - la fusionner dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+ | - la fusionner dans la branche destination ${COULEUR_ROUGE}$MergeDest${COULEUR_NORMALE}${Push:+ | ||||||
| - pousser les branches modifiées}" | - pousser les branches modifiées}" | ||||||
|     else |     else | ||||||
|         enote "\ |         enote "\ | ||||||
| Ce script va: | Ce script va: | ||||||
| - créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} | - créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MergeSrc${COULEUR_NORMALE} | ||||||
| - la provisionner avec une description des changements | - la provisionner avec une description des changements | ||||||
| Vous devrez: | Vous devrez: | ||||||
| - mettre à jour les informations de release puis relancer ce script" | - mettre à jour les informations de release puis relancer ce script" | ||||||
| @ -123,8 +97,8 @@ EOF | |||||||
| $BEFORE_MERGE_RELEASE | $BEFORE_MERGE_RELEASE | ||||||
| )$or_die | )$or_die | ||||||
| EOF | EOF | ||||||
|     _rscript_merge_release_branch "$DestBranch" "$Tag" |     _rscript_merge_release_branch "$MergeDest" "$Tag" | ||||||
|     _rscript_merge_release_branch "$SrcBranch" |     _rscript_merge_release_branch "$MergeSrc" | ||||||
|     _rscript_delete_release_branch |     _rscript_delete_release_branch | ||||||
|     [ -n "$AFTER_MERGE_RELEASE" ] && _scripta <<EOF |     [ -n "$AFTER_MERGE_RELEASE" ] && _scripta <<EOF | ||||||
| ( | ( | ||||||
| @ -183,14 +157,14 @@ function merge_release_action() { | |||||||
|     enote "\ |     enote "\ | ||||||
| Vous allez: | Vous allez: | ||||||
| - fusionner la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} | - fusionner la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} | ||||||
|   dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}" |   dans la branche destination ${COULEUR_ROUGE}$MergeDest${COULEUR_NORMALE}" | ||||||
|     ask_yesno "Voulez-vous continuer?" O || die |     ask_yesno "Voulez-vous continuer?" O || die | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function merge_hotfix_action() { | function merge_hotfix_action() { | ||||||
|     enote "\ |     enote "\ | ||||||
| Vous allez intégrer la branche de hotfix ${COULEUR_JAUNE}$HotfixBranch${COULEUR_NORMALE} | Vous allez intégrer la branche de hotfix ${COULEUR_JAUNE}$HotfixBranch${COULEUR_NORMALE} | ||||||
| dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}" | dans la branche destination ${COULEUR_ROUGE}$MergeDest${COULEUR_NORMALE}" | ||||||
|     ask_yesno "Voulez-vous continuer?" O || die |     ask_yesno "Voulez-vous continuer?" O || die | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -206,14 +180,14 @@ _Fake= | |||||||
| _KeepScript= | _KeepScript= | ||||||
| action=release | action=release | ||||||
| ShowLevel=0 | ShowLevel=0 | ||||||
| [ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge= | Merge=1 | ||||||
| [ -z "$PMAN_NO_PUSH" ] && Push=1 || Push= | Push=1 | ||||||
| Version= | Version= | ||||||
| CurrentVersion= | CurrentVersion= | ||||||
| ForceCreate= | ForceCreate= | ||||||
| args=( | args=( | ||||||
|     "faire une nouvelle release à partir de la branche source" |     "faire une nouvelle release" | ||||||
|     " -v VERSION [source] |     " -v VERSION | ||||||
| 
 | 
 | ||||||
| CONFIGURATION | CONFIGURATION | ||||||
| Le fichier .pman.conf contient la configuration des branches. Les variables | Le fichier .pman.conf contient la configuration des branches. Les variables | ||||||
| @ -261,8 +235,10 @@ parse_args "$@"; set -- "${args[@]}" | |||||||
| # charger la configuration | # charger la configuration | ||||||
| ensure_gitdir "$chdir" | ensure_gitdir "$chdir" | ||||||
| load_branches all | load_branches all | ||||||
| load_config "$MYNAME" | load_config | ||||||
| load_branches current "$1"; shift | REF_BRANCH=DEVELOP | ||||||
|  | set_pman_vars | ||||||
|  | load_branches current | ||||||
| 
 | 
 | ||||||
| [ -n "$Merge" -a -n "$NOAUTO" ] && ManualRelease=1 || ManualRelease= | [ -n "$Merge" -a -n "$NOAUTO" ] && ManualRelease=1 || ManualRelease= | ||||||
| [ -n "$ManualRelease" ] && Merge= | [ -n "$ManualRelease" ] && Merge= | ||||||
| @ -272,19 +248,12 @@ resolve_should_push quiet | |||||||
| 
 | 
 | ||||||
| # puis faire l'action que l'on nous demande | # puis faire l'action que l'on nous demande | ||||||
| case "$action" in | case "$action" in | ||||||
| show) | show) show_action "$@";; | ||||||
|     git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY" |  | ||||||
|     ensure_branches |  | ||||||
|     show_action "$@" |  | ||||||
|     ;; |  | ||||||
| release) | release) | ||||||
|     [ -z "$_Fake" ] && git_ensure_cleancheckout |     [ -z "$_Fake" ] && git_ensure_cleancheckout | ||||||
|     ensure_branches |     ensure_merge_branches | ||||||
|     case "$SrcType" in |     ensure_rel_infos | ||||||
|     release) merge_release_action "$@";; |     create_release_action "$@" | ||||||
|     hotfix) merge_hotfix_action "$@";; |  | ||||||
|     *) create_release_action "$@";; |  | ||||||
|     esac |  | ||||||
|     ;; |     ;; | ||||||
| *) | *) | ||||||
|     die "$action: action non implémentée" |     die "$action: action non implémentée" | ||||||
|  | |||||||
							
								
								
									
										383
									
								
								bin/ptool
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										383
									
								
								bin/ptool
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,383 @@ | |||||||
|  | #!/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 | ||||||
|  | require: git pman pman.conf | ||||||
|  | 
 | ||||||
|  | git_cleancheckout_DIRTY="\ | ||||||
|  | Vous avez des modifications locales. | ||||||
|  | Enregistrez ces modifications avant de fusionner la branche" | ||||||
|  | 
 | ||||||
|  | function _merge_action() { | ||||||
|  |     enote "\ | ||||||
|  | Ce script va | ||||||
|  | - fusionner la branche ${COULEUR_BLEUE}$MergeSrc${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$MergeDest${COULEUR_NORMALE}${Push:+ | ||||||
|  | - pousser les branches modifiées}" | ||||||
|  |     ask_yesno "Voulez-vous continuer?" O || die | ||||||
|  | 
 | ||||||
|  |     local script=".git/pman-merge.sh" | ||||||
|  |     local -a push_branches delete_branches | ||||||
|  |     local hook | ||||||
|  |     local comment= | ||||||
|  |     local or_die=" || exit 1" | ||||||
|  | 
 | ||||||
|  |     _mscript_start | ||||||
|  |     _scripta <<EOF | ||||||
|  | ################################################################################ | ||||||
|  | # merge | ||||||
|  | if [ -n "\$merge" ]; then | ||||||
|  | esection "Fusionner la branche" | ||||||
|  | EOF | ||||||
|  |     hook="BEFORE_MERGE_$MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF | ||||||
|  | ( | ||||||
|  | ${!hook} | ||||||
|  | )$or_die | ||||||
|  | EOF | ||||||
|  |     _mscript_merge_branch | ||||||
|  |     hook="AFTER_MERGE_$MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF | ||||||
|  | ( | ||||||
|  | ${!hook} | ||||||
|  | )$or_die | ||||||
|  | EOF | ||||||
|  |     _scripta <<EOF | ||||||
|  | fi | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  |     if [ -n "$ShouldDelete" ]; then | ||||||
|  |         _scripta <<EOF | ||||||
|  | ################################################################################ | ||||||
|  | # delete | ||||||
|  | if [ -n "\$delete" ]; then | ||||||
|  | esection "Supprimer la branche" | ||||||
|  | EOF | ||||||
|  |         _mscript_delete_branch | ||||||
|  |         hook="AFTER_DELETE_$MERGE_SRC"; [ -n "${!hook}" ] && _scripta <<EOF | ||||||
|  | ( | ||||||
|  | ${!hook} | ||||||
|  | )$or_die | ||||||
|  | EOF | ||||||
|  |         _scripta <<EOF | ||||||
|  | fi | ||||||
|  | EOF | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     _scripta <<EOF | ||||||
|  | ################################################################################ | ||||||
|  | # push | ||||||
|  | if [ -n "\$push" ]; then | ||||||
|  | esection "Pousser les branches" | ||||||
|  | EOF | ||||||
|  |     hook="BEFORE_PUSH_$MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF | ||||||
|  | ( | ||||||
|  | ${!hook} | ||||||
|  | )$or_die | ||||||
|  | EOF | ||||||
|  |     _script_push_branches | ||||||
|  |     if [ ${#delete_branches[*]} -gt 0 ]; then | ||||||
|  |         _scripta <<<"if [ -n \"\$delete\" ]; then" | ||||||
|  |         push_branches=("${delete_branches[@]}") | ||||||
|  |         _script_push_branches | ||||||
|  |         _scripta <<<fi | ||||||
|  |     fi | ||||||
|  |     hook="AFTER_PUSH_$MERGE_DEST"; [ -n "${!hook}" ] && _scripta <<EOF | ||||||
|  | ( | ||||||
|  | ${!hook} | ||||||
|  | )$or_die | ||||||
|  | EOF | ||||||
|  |     _scripta <<EOF | ||||||
|  | fi | ||||||
|  | EOF | ||||||
|  | 
 | ||||||
|  |     [ -n "$Delete" -o -z "$ShouldDelete" ] && Deleted=1 || Deleted= | ||||||
|  |     [ -n "$ShouldDelete" -a -n "$Delete" ] && ShouldDelete= | ||||||
|  |     [ -n "$ShouldPush" -a -n "$Push" ] && ShouldPush= | ||||||
|  |     if [ -n "$_Fake" ]; then | ||||||
|  |         cat "$script" | ||||||
|  |     elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then | ||||||
|  |         eimportant "\ | ||||||
|  | Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}' | ||||||
|  | En cas d'erreur de merge, veuillez corriger les erreurs puis continuer avec | ||||||
|  |     git merge --continue | ||||||
|  | Sinon, veuillez consulter le script et/ou le relancer | ||||||
|  |     ./$script${Delete:+ delete}${Push:+ push}" | ||||||
|  |         die | ||||||
|  |     elif [ -n "$Deleted" -a -n "$Push" ]; then | ||||||
|  |         [ -n "$_KeepScript" ] || rm "$script" | ||||||
|  |         [ -n "$AfterMerge" ] && eval "$AfterMerge" | ||||||
|  |     else | ||||||
|  |         local msg="\ | ||||||
|  | Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}' | ||||||
|  | Vous pouvez consulter le script et/ou le relancer | ||||||
|  |     ./$script${ShouldDelete:+ delete}${ShouldPush:+ push}" | ||||||
|  |         [ -n "$AfterMerge" ] && msg="$msg | ||||||
|  | Il y a aussi les commandes supplémentaires suivantes: | ||||||
|  |     ${AfterMerge// | ||||||
|  | / | ||||||
|  |     }" | ||||||
|  |         einfo "$msg" | ||||||
|  |     fi | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function merge_action() { | ||||||
|  |     [ -n "$REF_UNIQUE" ] || resolve_unique_branch "$@" | ||||||
|  |     ensure_merge_branches -a | ||||||
|  | 
 | ||||||
|  |     if [ -n "$PREL_MERGE" ]; then | ||||||
|  |         [ -n "$ForceMerge" ] || die "$MergeSrc: cette branche doit être fusionnée dans $MergeDest avec prel" | ||||||
|  |     fi | ||||||
|  |     if [ -n "$DELETE_MERGED" ]; then | ||||||
|  |         ShouldDelete=1 | ||||||
|  |         [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$MergeDest" | ||||||
|  |     else | ||||||
|  |         ShouldDelete= | ||||||
|  |         Delete= | ||||||
|  |         [ -n "$AfterMerge" ] || setx AfterMerge=qvals git checkout -q "$MergeSrc" | ||||||
|  |     fi | ||||||
|  |     [ -z "$_Fake" ] && git_ensure_cleancheckout | ||||||
|  | 
 | ||||||
|  |     if ! array_contains LocalBranches "$MergeSrc" && array_contains AllBranches "$MergeSrc"; then | ||||||
|  |         enote "$MergeSrc: une branche du même nom existe dans l'origine" | ||||||
|  |     fi | ||||||
|  |     if ! array_contains LocalBranches "$MergeDest" && array_contains AllBranches "$MergeDest"; then | ||||||
|  |         enote "$MergeDest: une branche du même nom existe dans l'origine" | ||||||
|  |     fi | ||||||
|  |     array_contains LocalBranches "$MergeSrc" || die "$MergeSrc: branche locale introuvable" | ||||||
|  |     array_contains LocalBranches "$MergeDest" || die "$MergeDest: branche locale introuvable" | ||||||
|  | 
 | ||||||
|  |     resolve_should_push | ||||||
|  |     _merge_action "$@" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function rebase_action() { | ||||||
|  |     die "non implémenté" | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | ################################################################################ | ||||||
|  | 
 | ||||||
|  | chdir= | ||||||
|  | Origin= | ||||||
|  | ConfigBranch= | ||||||
|  | ConfigFile= | ||||||
|  | _Fake= | ||||||
|  | _KeepScript= | ||||||
|  | action=checkout | ||||||
|  | ShowLevel=0 | ||||||
|  | TechMerge= | ||||||
|  | SquashMsg= | ||||||
|  | Push=1 | ||||||
|  | Delete=1 | ||||||
|  | AfterMerge= | ||||||
|  | 
 | ||||||
|  | loaded_config= | ||||||
|  | merge_dir= | ||||||
|  | if [ "$MYNAME" == ptool ]; then | ||||||
|  |     if [ "$1" == --help ]; then | ||||||
|  |         exit_with eecho "$MYNAME: gérer les branches d'un projet | ||||||
|  | 
 | ||||||
|  | USAGE | ||||||
|  |     $MYNAME [-t|-f] REF args... | ||||||
|  | 
 | ||||||
|  | OPTIONS | ||||||
|  |     REF | ||||||
|  |     -f, --merge-from REF | ||||||
|  |         spécifier la branche de référence et indiquer que la fusion se fait dans | ||||||
|  |         le sens REF --> DEST. DEST est calculé en fonction de REF | ||||||
|  |     -t, --merge-to REF | ||||||
|  |         spécifier la branche de référence et indiquer que la fusion se fait dans | ||||||
|  |         le sens SRC --> REF. SRC est calculé en fonction de REF" | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     ref="$1"; shift | ||||||
|  |     merge_dir=to | ||||||
|  |     [ -n "$ref" ] || die "vous spécifier la branche de référence" | ||||||
|  | 
 | ||||||
|  |     case "$ref" in | ||||||
|  |     -f|--merge-from) | ||||||
|  |         ref="$1"; shift | ||||||
|  |         merge_dir=from | ||||||
|  |         ;; | ||||||
|  |     -f*) | ||||||
|  |         ref="${ref#-f}" | ||||||
|  |         merge_dir=from | ||||||
|  |         ;; | ||||||
|  |     -t|--merge-to) | ||||||
|  |         ref="$1"; shift | ||||||
|  |         merge_dir=to | ||||||
|  |         ;; | ||||||
|  |     -t*) | ||||||
|  |         ref="${ref#-t}" | ||||||
|  |         merge_dir=to | ||||||
|  |         ;; | ||||||
|  |     esac | ||||||
|  |     REF_BRANCH="${ref^^}" | ||||||
|  |     array_contains PMAN_BRANCHES "$REF_BRANCH" || die "$ref: invalid branch" | ||||||
|  | 
 | ||||||
|  | else | ||||||
|  |     REF_BRANCH="PMAN_TOOL_${MYNAME^^}"; REF_BRANCH="${!REF_BRANCH}" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if check_gitdir; then | ||||||
|  |     load_branches all | ||||||
|  |     load_config | ||||||
|  |     set_pman_vars "$merge_dir" | ||||||
|  |     load_branches current | ||||||
|  |     loaded_config=1 | ||||||
|  | else | ||||||
|  |     set_pman_vars "$merge_dir" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | RefDesc= | ||||||
|  | MergeSrcDesc= | ||||||
|  | MergeDestDesc= | ||||||
|  | if [ -n "$REF_BRANCH" ]; then | ||||||
|  |     RefDesc="${COULEUR_BLANCHE}<$REF_BRANCH>" | ||||||
|  |     [ -n "$RefBranch" -a -n "$REF_UNIQUE" ] && RefDesc="$RefDesc ($RefBranch)" | ||||||
|  |     RefDesc="$RefDesc${COULEUR_NORMALE}" | ||||||
|  | fi | ||||||
|  | if [ -n "$MERGE_SRC" ]; then | ||||||
|  |     MergeSrcDesc="${COULEUR_BLEUE}<$MERGE_SRC>" | ||||||
|  |     [ -n "$MergeSrc" -a -n "$REF_UNIQUE" ] && MergeSrcDesc="$MergeSrcDesc ($MergeSrc)" | ||||||
|  |     MergeSrcDesc="$MergeSrcDesc${COULEUR_NORMALE}" | ||||||
|  | fi | ||||||
|  | if [ -n "$MERGE_DEST" ]; then | ||||||
|  |     MergeDestDesc="${COULEUR_ROUGE}<$MERGE_DEST>" | ||||||
|  |     [ -n "$MergeDest" -a -n "$REF_UNIQUE" ] && MergeDestDesc="$MergeDestDesc ($MergeDest)" | ||||||
|  |     MergeDestDesc="$MergeDestDesc${COULEUR_NORMALE}" | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -n "$REF_UNIQUE" ] | ||||||
|  | then purpose="gérer la branche $RefDesc" | ||||||
|  | else purpose="gérer les branches $RefDesc" | ||||||
|  | fi | ||||||
|  | usage="--checkout" | ||||||
|  | variables= | ||||||
|  | 
 | ||||||
|  | chdir_def=(chdir= "répertoire dans lequel se placer avant de lancer les opérations") | ||||||
|  | origin_def=(Origin= "++origine à partir de laquelle les branches distantes sont considérées") | ||||||
|  | config_branch_def=(ConfigBranch= "++branche à partir de laquelle charger la configuration") | ||||||
|  | config_file_def=(ConfigFile= "++\ | ||||||
|  | fichier de configuration des branches. le fichier .pman.conf dans le répertoire | ||||||
|  | du dépôt est utilisé par défaut s'il existe. cette option est prioritaire sur | ||||||
|  | --config-branch") | ||||||
|  | fake_def=(_Fake=1 "++option non documentée") | ||||||
|  | keep_script_def=(_KeepScript=1 "++option non documentée") | ||||||
|  | dump_action_def=(action=dump "++afficher les noms des branches") | ||||||
|  | checkout_action_def=('$:' "++non applicable") | ||||||
|  | show_action_def=('$:' "++non applicable") | ||||||
|  | rebase_action_def=('$:' "++non applicable") | ||||||
|  | merge_action_def=('$:' "++non applicable") | ||||||
|  | tech_merge_def=('$:' "++non applicable") | ||||||
|  | squash_def=('$:' "++non applicable") | ||||||
|  | force_merge_def=('$:' "++non applicable") | ||||||
|  | no_push_def=('$:' "++non applicable") | ||||||
|  | push_def=('$:' "++non applicable") | ||||||
|  | no_delete_def=('$:' "++non applicable") | ||||||
|  | delete_def=('$:' "++non applicable") | ||||||
|  | after_merge_def=('$:' "++non applicable") | ||||||
|  | 
 | ||||||
|  | if [ -n "$RefBranch" -a -n "$REF_UNIQUE" ]; then | ||||||
|  |     checkout_action_def=(action=checkout "++\ | ||||||
|  | créer le cas échéant la branche $RefDesc et basculer vers elle. | ||||||
|  | c'est l'option par défaut") | ||||||
|  | elif [ -z "$REF_UNIQUE" ]; then | ||||||
|  |     checkout_action_def=(action=checkout "\ | ||||||
|  | créer le cas échéant la branche $RefDesc et basculer vers elle. | ||||||
|  | c'est l'option par défaut") | ||||||
|  | else | ||||||
|  |     checkout_action_def=(action=checkout "\ | ||||||
|  | créer la branche $MergeDestDesc et basculer vers elle. | ||||||
|  | c'est l'option par défaut") | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | if [ -n "$MERGE_SRC" -a -n "$MERGE_DEST" ]; then | ||||||
|  |     if [ -n "$REF_UNIQUE" ] | ||||||
|  |     then usage="${usage}|--show|--merge" | ||||||
|  |     else usage="${usage} $REF_BRANCH | ||||||
|  | --show|--merge" | ||||||
|  |     fi | ||||||
|  |     if [ "$REF_BRANCH" != "$MERGE_SRC" ] | ||||||
|  |     then bewareDir=" | ||||||
|  | NB: la fusion se fait dans le sens inverse" | ||||||
|  |     else bewareDir= | ||||||
|  |     fi | ||||||
|  |     variables="Les variables supplémentaires suivantes peuvent être définies: | ||||||
|  |     BEFORE_MERGE_${MERGE_SRC} | ||||||
|  |     AFTER_MERGE_${MERGE_SRC}" | ||||||
|  | 
 | ||||||
|  |     show_action_def=('$action=show; inc@ ShowLevel' "\ | ||||||
|  | lister ce qui serait fusionné dans la branche $MergeDestDesc") | ||||||
|  |     rebase_action_def=('$:' "++non implémenté") | ||||||
|  | #    rebase_action_def=(action=rebase "\ | ||||||
|  | #lancer git rebase -i sur la branche $MergeSrcDesc. cela permet de réordonner | ||||||
|  | #les commits pour nettoyer l'historique avant la fusion") | ||||||
|  |     merge_action_def=(action=merge "\ | ||||||
|  | fusionner la branche $MergeSrcDesc dans la branche $MergeDestDesc$bewareDir") | ||||||
|  |     tech_merge_def=('$action=merge; TechMerge=1' "++option non documentée") | ||||||
|  |     squash_def=('$action=merge; res@ SquashMsg' "fusionner les modifications de la branche comme un seul commit") | ||||||
|  |     [ -n "$PREL_MERGE" ] && force_merge_def=(ForceMerge=1 "++\ | ||||||
|  | forcer la fusion pour une branche qui devrait être traitée par prel") | ||||||
|  |     no_push_def=(Push= "ne pas pousser les branches vers leur origine après la fusion") | ||||||
|  |     push_def=(Push=1 "++\ | ||||||
|  | pousser les branches vers leur origine après la fusion. | ||||||
|  | c'est l'option par défaut") | ||||||
|  | 
 | ||||||
|  |     if [ -n "$DELETE_MERGED" ]; then | ||||||
|  |         variables="${variables} | ||||||
|  |     AFTER_DELETE_${MERGE_SRC}" | ||||||
|  |         no_delete_def=(Delete= "\ | ||||||
|  | ne pas supprimer la branche $MergeSrcDesc après la fusion dans la branche | ||||||
|  | $MergeDestDesc. cette option ne devrait pas être utilisée avec --squash") | ||||||
|  |         delete_def=(Delete=1 "++\ | ||||||
|  | supprimer la branche $MergeSrcDesc après la fusion dans la branche | ||||||
|  | $MergeDestDesc. | ||||||
|  | c'est l'option par défaut") | ||||||
|  |     fi | ||||||
|  | 
 | ||||||
|  |     [ -n "$MERGE_DEST" ] && variables="${variables} | ||||||
|  |     BEFORE_PUSH_${MERGE_DEST} | ||||||
|  |     AFTER_PUSH_${MERGE_DEST}" | ||||||
|  | 
 | ||||||
|  |     after_merge_def=(AfterMerge= "évaluer le script spécifié après une fusion *réussie*") | ||||||
|  | fi | ||||||
|  | 
 | ||||||
|  | args=( | ||||||
|  |     "$purpose" | ||||||
|  |     "\ | ||||||
|  |  $usage | ||||||
|  | 
 | ||||||
|  | CONFIGURATION | ||||||
|  | 
 | ||||||
|  | Le fichier .pman.conf contient la configuration des branches. | ||||||
|  | $variables" | ||||||
|  |     -d:,--chdir:BASEDIR "${chdir_def[@]}" | ||||||
|  |     -O:,--origin "${origin_def[@]}" | ||||||
|  |     -B:,--config-branch "${config_branch_def[@]}" | ||||||
|  |     -c:,--config-file:CONFIG  "${config_file_def[@]}" | ||||||
|  |     --fake "${fake_def[@]}" | ||||||
|  |     --keep-script "${keep_script_def[@]}" | ||||||
|  |     --dump "${dump_action_def[@]}" | ||||||
|  |     --checkout "${checkout_action_def[@]}" | ||||||
|  |     -w,--show "${show_action_def[@]}" | ||||||
|  |     -b,--rebase "${rebase_action_def[@]}" | ||||||
|  |     -m,--merge "${merge_action_def[@]}" | ||||||
|  |     --tech-merge "${tech_merge_def[@]}" | ||||||
|  |     -s:,--squash:COMMIT_MSG "${squash_def[@]}" | ||||||
|  |     -f,--force-merge "${force_merge_def[@]}" | ||||||
|  |     -n,--no-push "${no_push_def[@]}" | ||||||
|  |     --push "${push_def[@]}" | ||||||
|  |     -k,--no-delete "${no_delete_def[@]}" | ||||||
|  |     --delete "${delete_def[@]}" | ||||||
|  |     -a:,--after-merge "${after_merge_def[@]}" | ||||||
|  | ) | ||||||
|  | parse_args "$@"; set -- "${args[@]}" | ||||||
|  | 
 | ||||||
|  | if [ -z "$loaded_config" -o -n "$chdir" -o -n "$ConfigFile" -o -n "$ConfigBranch" ]; then | ||||||
|  |     # charger la configuration | ||||||
|  |     ensure_gitdir "$chdir" | ||||||
|  |     load_branches all | ||||||
|  |     load_config | ||||||
|  |     set_pman_vars "$merge_dir" | ||||||
|  |     load_branches current | ||||||
|  | fi | ||||||
|  | resolve_should_push quiet | ||||||
|  | 
 | ||||||
|  | "${action}_action" "$@" | ||||||
							
								
								
									
										60
									
								
								bin/pwip
									
									
									
									
									
								
							
							
						
						
									
										60
									
								
								bin/pwip
									
									
									
									
									
								
							| @ -1,60 +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 |  | ||||||
| require: git pman pman.conf |  | ||||||
| 
 |  | ||||||
| git_cleancheckout_DIRTY="\ |  | ||||||
| Vous avez des modifications locales. |  | ||||||
| Enregistrez ces modifications avant de créer une nouvelle branche" |  | ||||||
| 
 |  | ||||||
| chdir= |  | ||||||
| Origin= |  | ||||||
| ConfigBranch= |  | ||||||
| ConfigFile= |  | ||||||
| [ -z "$PMAN_NO_PUSH" ] && Push=1 || Push= |  | ||||||
| args=( |  | ||||||
|     "créer une branche de feature" |  | ||||||
|     "<feature>" |  | ||||||
|     -d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations" |  | ||||||
|     -O:,--origin Origin= "++\ |  | ||||||
| origine à partir de laquelle les branches distantes sont considérées" |  | ||||||
|     -B:,--config-branch ConfigBranch= "++\ |  | ||||||
| branche à partir de laquelle charger la configuration" |  | ||||||
|     -c:,--config-file:CONFIG ConfigFile= "++\ |  | ||||||
| fichier de configuration des branches. cette option est prioritaire sur --config-branch |  | ||||||
| par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe" |  | ||||||
|     -n,--no-push Push= "\ |  | ||||||
| ne pas pousser les branches vers leur origine après la fusion" |  | ||||||
|     --push Push=1 "++\ |  | ||||||
| pousser les branches vers leur origine après la fusion. |  | ||||||
| c'est l'option par défaut" |  | ||||||
| ) |  | ||||||
| parse_args "$@"; set -- "${args[@]}" |  | ||||||
| 
 |  | ||||||
| # charger la configuration |  | ||||||
| ensure_gitdir "$chdir" |  | ||||||
| load_branches all |  | ||||||
| load_config "$MYNAME" |  | ||||||
| load_branches current |  | ||||||
| 
 |  | ||||||
| branch="$1" |  | ||||||
| if [ -z "$branch" -a ${#FeatureBranches[*]} -eq 1 ]; then |  | ||||||
|     branch="${FeatureBranches[0]}" |  | ||||||
| fi |  | ||||||
| [ -n "$branch" ] || die "Vous devez spécifier la branche à créer" |  | ||||||
| branch="$FEATURE${branch#$FEATURE}" |  | ||||||
| 
 |  | ||||||
| resolve_should_push |  | ||||||
| git_ensure_cleancheckout |  | ||||||
| 
 |  | ||||||
| if array_contains AllBranches "$branch"; then |  | ||||||
|     git checkout -q "$branch" |  | ||||||
| else |  | ||||||
|     # si la branche source n'existe pas, la créer |  | ||||||
|     args=(--origin "$Origin") |  | ||||||
|     if [ -n "$ConfigFile" ]; then args+=(--config-file "$ConfigFile") |  | ||||||
|     elif [ -n "$ConfigBranch" ]; then args+=(--config-branch "$ConfigBranch") |  | ||||||
|     fi |  | ||||||
|     [ -z "$Push" ] && args+=(--no-push) |  | ||||||
|     exec "$MYDIR/pman" "${args[@]}" "$branch" |  | ||||||
| fi |  | ||||||
| @ -19,6 +19,7 @@ while true; do | |||||||
|     fi |     fi | ||||||
|     cd .. |     cd .. | ||||||
| done | done | ||||||
|  | cd "$owd" | ||||||
| 
 | 
 | ||||||
| export RUNPHP_MOUNT= | export RUNPHP_MOUNT= | ||||||
| if [ "$MYNAME" == composer ]; then | if [ "$MYNAME" == composer ]; then | ||||||
|  | |||||||
							
								
								
									
										1
									
								
								bin/sqlite.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/sqlite.capacitor.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
							
								
								
									
										1
									
								
								bin/yml2json.php
									
									
									
									
									
										Symbolic link
									
								
							
							
						
						
									
										1
									
								
								bin/yml2json.php
									
									
									
									
									
										Symbolic link
									
								
							| @ -0,0 +1 @@ | |||||||
|  | runphp | ||||||
| @ -18,7 +18,10 @@ | |||||||
| 		"nulib/php": "*" | 		"nulib/php": "*" | ||||||
| 	}, | 	}, | ||||||
| 	"require": { | 	"require": { | ||||||
| 		"symfony/yaml": "^7.1", | 		"symfony/yaml": "^7.3", | ||||||
|  | 		"symfony/expression-language": "^7.3", | ||||||
|  | 		"phpmailer/phpmailer": "^6.8", | ||||||
|  | 		"league/commonmark": "^2.7", | ||||||
| 		"ext-json": "*", | 		"ext-json": "*", | ||||||
| 		"php": "^8.2" | 		"php": "^8.2" | ||||||
| 	}, | 	}, | ||||||
| @ -35,7 +38,8 @@ | |||||||
| 	}, | 	}, | ||||||
| 	"autoload": { | 	"autoload": { | ||||||
| 		"psr-4": { | 		"psr-4": { | ||||||
| 			"nulib\\": "php/src" | 			"nulib\\": "php/src", | ||||||
|  |       "cli\\": "php/cli" | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
| 	"autoload-dev": { | 	"autoload-dev": { | ||||||
| @ -43,6 +47,15 @@ | |||||||
| 			"nulib\\": "php/tests" | 			"nulib\\": "php/tests" | ||||||
| 		} | 		} | ||||||
| 	}, | 	}, | ||||||
|  | 	"bin": [ | ||||||
|  | 		"php/bin/cachectl.php", | ||||||
|  | 		"php/bin/dumpser.php", | ||||||
|  | 		"php/bin/json2yml.php", | ||||||
|  | 		"php/bin/yml2json.php", | ||||||
|  | 		"php/bin/sqlite.capacitor.php", | ||||||
|  | 		"php/bin/mysql.capacitor.php", | ||||||
|  | 		"php/bin/pgsql.capacitor.php" | ||||||
|  | 	], | ||||||
| 	"config": { | 	"config": { | ||||||
| 		"vendor-dir": "php/vendor" | 		"vendor-dir": "php/vendor" | ||||||
| 	}, | 	}, | ||||||
|  | |||||||
							
								
								
									
										1404
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1404
									
								
								composer.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										7
									
								
								php/bin/cachectl.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/cachectl.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\CachectlApp; | ||||||
|  | 
 | ||||||
|  | CachectlApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/dumpser.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/dumpser.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\DumpserApp; | ||||||
|  | 
 | ||||||
|  | DumpserApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/json2yml.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/json2yml.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\Json2yamlApp; | ||||||
|  | 
 | ||||||
|  | Json2yamlApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/mysql.capacitor.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/mysql.capacitor.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\MysqlCapacitorApp; | ||||||
|  | 
 | ||||||
|  | MysqlCapacitorApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/pgsql.capacitor.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/pgsql.capacitor.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\PgsqlCapacitorApp; | ||||||
|  | 
 | ||||||
|  | PgsqlCapacitorApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/sqlite.capacitor.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/sqlite.capacitor.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\SqliteCapacitorApp; | ||||||
|  | 
 | ||||||
|  | SqliteCapacitorApp::run(); | ||||||
							
								
								
									
										7
									
								
								php/bin/yml2json.php
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										7
									
								
								php/bin/yml2json.php
									
									
									
									
									
										Executable file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | #!/usr/bin/php
 | ||||||
|  | <?php | ||||||
|  | require $_composer_autoload_path?? __DIR__.'/../vendor/autoload.php'; | ||||||
|  | 
 | ||||||
|  | use cli\Yaml2jsonApp; | ||||||
|  | 
 | ||||||
|  | Yaml2jsonApp::run(); | ||||||
							
								
								
									
										111
									
								
								php/cli/AbstractCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								php/cli/AbstractCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\db\Capacitor; | ||||||
|  | use nulib\db\CapacitorChannel; | ||||||
|  | use nulib\db\CapacitorStorage; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\file\Stream; | ||||||
|  | use nulib\output\msg; | ||||||
|  | 
 | ||||||
|  | abstract class AbstractCapacitorApp extends Application { | ||||||
|  |   const ACTION_RESET = 0, ACTION_QUERY = 1, ACTION_SQL = 2; | ||||||
|  | 
 | ||||||
|  |   protected ?string $tableName = null; | ||||||
|  | 
 | ||||||
|  |   protected ?string $channelClass = null; | ||||||
|  | 
 | ||||||
|  |   protected int $action = self::ACTION_QUERY; | ||||||
|  | 
 | ||||||
|  |   protected bool $recreate = true; | ||||||
|  | 
 | ||||||
|  |   protected static function isa_cond(string $arg, ?array &$ms=null): bool { | ||||||
|  |     return preg_match('/^(.+?)\s*(=|<>|<|>|<=|>=|(?:is\s+)?null|(?:is\s+)?not\s+null)\s*(.*)$/', $arg, $ms); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function storageCtl(CapacitorStorage $storage): void { | ||||||
|  |     $args = $this->args; | ||||||
|  | 
 | ||||||
|  |     $channelClass = $this->channelClass; | ||||||
|  |     $tableName = $this->tableName; | ||||||
|  |     if ($channelClass === null && $tableName === null) { | ||||||
|  |       $name = A::shift($args); | ||||||
|  |       if ($name !== null) { | ||||||
|  |         if (!$storage->channelExists($name, $row)) { | ||||||
|  |           self::die("$name: nom de canal de données introuvable"); | ||||||
|  |         } | ||||||
|  |         if ($row["class_name"] !== "class@anonymous") $channelClass = $row["class_name"]; | ||||||
|  |         else $tableName = $row["table_name"]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if ($channelClass !== null) { | ||||||
|  |       $channelClass = str_replace("/", "\\", $channelClass); | ||||||
|  |       $channel = new $channelClass; | ||||||
|  |     } elseif ($tableName !== null) { | ||||||
|  |       $channel = new class($tableName) extends CapacitorChannel { | ||||||
|  |         function __construct(?string $name=null) { | ||||||
|  |           parent::__construct($name); | ||||||
|  |           $this->tableName = $name; | ||||||
|  |         } | ||||||
|  |       }; | ||||||
|  |     } else { | ||||||
|  |       $found = false; | ||||||
|  |       foreach ($storage->getChannels() as $row) { | ||||||
|  |         msg::print($row["name"]); | ||||||
|  |         $found = true; | ||||||
|  |       } | ||||||
|  |       if ($found) self::exit(); | ||||||
|  |       self::die("Vous devez spécifier le canal de données"); | ||||||
|  |     } | ||||||
|  |     $capacitor = new Capacitor($storage, $channel); | ||||||
|  | 
 | ||||||
|  |     switch ($this->action) { | ||||||
|  |     case self::ACTION_RESET: | ||||||
|  |       $capacitor->reset($this->recreate); | ||||||
|  |       break; | ||||||
|  |     case self::ACTION_QUERY: | ||||||
|  |       if (!$args) { | ||||||
|  |         # lister les id
 | ||||||
|  |         $out = new Stream(STDOUT); | ||||||
|  |         $primaryKeys = $storage->getPrimaryKeys($channel); | ||||||
|  |         $rows = $storage->db()->all([ | ||||||
|  |           "select", | ||||||
|  |           "cols" => $primaryKeys, | ||||||
|  |           "from" => $channel->getTableName(), | ||||||
|  |         ]); | ||||||
|  |         $out->fputcsv($primaryKeys); | ||||||
|  |         foreach ($rows as $row) { | ||||||
|  |           $rowIds = $storage->getRowIds($channel, $row); | ||||||
|  |           $out->fputcsv($rowIds); | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         # afficher les lignes correspondantes
 | ||||||
|  |         if (count($args) == 1 && !self::isa_cond($args[0])) { | ||||||
|  |           $filter = $args[0]; | ||||||
|  |         } else { | ||||||
|  |           $filter = []; | ||||||
|  |           $ms = null; | ||||||
|  |           foreach ($args as $arg) { | ||||||
|  |             if (self::isa_cond($arg, $ms)) { | ||||||
|  |               $filter[$ms[1]] = [$ms[2], $ms[3]]; | ||||||
|  |             } else { | ||||||
|  |               $filter[$arg] = ["not null"]; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         $first = true; | ||||||
|  |         $capacitor->each($filter, function ($row) use (&$first) { | ||||||
|  |           if ($first) $first = false; | ||||||
|  |           else echo "---\n"; | ||||||
|  |           yaml::dump($row); | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |       break; | ||||||
|  |     case self::ACTION_SQL: | ||||||
|  |       echo $capacitor->getCreateSql()."\n"; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										122
									
								
								php/cli/BgLauncherApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										122
									
								
								php/cli/BgLauncherApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,122 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\app\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; | ||||||
|  | 
 | ||||||
|  |   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; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										132
									
								
								php/cli/CachectlApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										132
									
								
								php/cli/CachectlApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,132 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use Exception; | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\cache\CacheFile; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\os\path; | ||||||
|  | use nulib\output\msg; | ||||||
|  | 
 | ||||||
|  | class CachectlApp extends Application { | ||||||
|  |   const ACTION_READ = 10, ACTION_INFOS = 20, ACTION_CLEAN = 30; | ||||||
|  |   const ACTION_UPDATE = 40, ACTION_UPDATE_ADD = 41, ACTION_UPDATE_SUB = 42, ACTION_UPDATE_SET = 43; | ||||||
|  | 
 | ||||||
|  |   const ARGS = [ | ||||||
|  |     "merge" => parent::ARGS, | ||||||
|  |     "purpose" => "gestion de fichiers cache", | ||||||
|  |     ["-r", "--read", "name" => "action", "value" => self::ACTION_READ, | ||||||
|  |       "help" => "Afficher le contenu d'un fichier cache", | ||||||
|  |     ], | ||||||
|  |     ["-d::", "--data", | ||||||
|  |       "help" => "Identifiant de la donnée à afficher", | ||||||
|  |     ], | ||||||
|  |     ["-i", "--infos", "name" => "action", "value" => self::ACTION_INFOS, | ||||||
|  |       "help" => "Afficher des informations sur le fichier cache", | ||||||
|  |     ], | ||||||
|  |     ["-k", "--clean", "name" => "action", "value" => self::ACTION_CLEAN, | ||||||
|  |       "help" => "Supprimer le fichier cache s'il a expiré", | ||||||
|  |     ], | ||||||
|  |     ["-a", "--add-duration", "args" => 1, | ||||||
|  |       "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_ADD], | ||||||
|  |       "help" => "Ajouter le nombre de secondes spécifié à la durée du cache", | ||||||
|  |     ], | ||||||
|  |     ["-b", "--sub-duration", "args" => 1, | ||||||
|  |       "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SUB], | ||||||
|  |       "help" => "Enlever le nombre de secondes spécifié à la durée du cache", | ||||||
|  |     ], | ||||||
|  |     #XXX pas encore implémenté
 | ||||||
|  |     //["-s", "--set-duration", "args" => 1,
 | ||||||
|  |     //  "action" => [null, "->setActionUpdate", self::ACTION_UPDATE_SET],
 | ||||||
|  |     //  "help" => "Mettre à jour la durée du cache à la valeur spécifiée",
 | ||||||
|  |     //],
 | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   protected $action = self::ACTION_READ; | ||||||
|  | 
 | ||||||
|  |   protected $updateAction, $updateDuration; | ||||||
|  | 
 | ||||||
|  |   protected $data = null; | ||||||
|  | 
 | ||||||
|  |   function setActionUpdate(int $action, $updateDuration): void { | ||||||
|  |     $this->action = self::ACTION_UPDATE; | ||||||
|  |     switch ($action) { | ||||||
|  |     case self::ACTION_UPDATE_SUB: | ||||||
|  |       $this->updateAction = CacheFile::UPDATE_SUB; | ||||||
|  |       break; | ||||||
|  |     case self::ACTION_UPDATE_SET: | ||||||
|  |       $this->updateAction = CacheFile::UPDATE_SET; | ||||||
|  |       break; | ||||||
|  |     case self::ACTION_UPDATE_ADD: | ||||||
|  |       $this->updateAction = CacheFile::UPDATE_ADD; | ||||||
|  |       break; | ||||||
|  |     } | ||||||
|  |     $this->updateDuration = $updateDuration; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function findCaches(string $dir, ?array &$files): void { | ||||||
|  |     foreach (glob("$dir/*") as $file) { | ||||||
|  |       if (is_dir($file)) { | ||||||
|  |         $this->findCaches($file, $files); | ||||||
|  |       } elseif (is_file($file) && fnmatch("*.cache", $file)) { | ||||||
|  |         $files[] = $file; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function main() { | ||||||
|  |     $files = []; | ||||||
|  |     foreach ($this->args as $arg) { | ||||||
|  |       if (is_dir($arg)) { | ||||||
|  |         $this->findCaches($arg, $files); | ||||||
|  |       } elseif (is_file($arg)) { | ||||||
|  |         $files[] = $arg; | ||||||
|  |       } else { | ||||||
|  |         msg::warning("$arg: fichier introuvable"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $showSection = count($files) > 1; | ||||||
|  |     foreach ($files as $file) { | ||||||
|  |       switch ($this->action) { | ||||||
|  |       case self::ACTION_READ: | ||||||
|  |         if ($showSection) msg::section($file); | ||||||
|  |         $cache = new CacheFile($file, null, [ | ||||||
|  |           "readonly" => true, | ||||||
|  |           "duration" => "INF", | ||||||
|  |           "override_duration" => true, | ||||||
|  |         ]); | ||||||
|  |         yaml::dump($cache->get($this->data)); | ||||||
|  |         break; | ||||||
|  |       case self::ACTION_INFOS: | ||||||
|  |         if ($showSection) msg::section($file); | ||||||
|  |         $cache = new CacheFile($file, null, [ | ||||||
|  |           "readonly" => true, | ||||||
|  |         ]); | ||||||
|  |         yaml::dump($cache->getInfos()); | ||||||
|  |         break; | ||||||
|  |       case self::ACTION_CLEAN: | ||||||
|  |         msg::action(path::ppath($file)); | ||||||
|  |         $cache = new CacheFile($file); | ||||||
|  |         try { | ||||||
|  |           if ($cache->deleteExpired()) msg::asuccess("fichier supprimé"); | ||||||
|  |           else msg::adone("fichier non expiré"); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |           msg::afailure($e); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       case self::ACTION_UPDATE: | ||||||
|  |         msg::action(path::ppath($file)); | ||||||
|  |         $cache = new CacheFile($file); | ||||||
|  |         try { | ||||||
|  |           $cache->updateDuration($this->updateDuration, $this->updateAction); | ||||||
|  |           msg::asuccess("fichier mis à jour"); | ||||||
|  |         } catch (Exception $e) { | ||||||
|  |           msg::afailure($e); | ||||||
|  |         } | ||||||
|  |         break; | ||||||
|  |       default: | ||||||
|  |         self::die("$this->action: action non implémentée"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								php/cli/DumpserApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								php/cli/DumpserApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\file\SharedFile; | ||||||
|  | use nulib\output\msg; | ||||||
|  | 
 | ||||||
|  | class DumpserApp extends Application { | ||||||
|  |   const ARGS = [ | ||||||
|  |     "merge" => parent::ARGS, | ||||||
|  |     "purpose" => "afficher des données sérialisées", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function main() { | ||||||
|  |     $files = []; | ||||||
|  |     foreach ($this->args as $arg) { | ||||||
|  |       if (is_file($arg)) { | ||||||
|  |         $files[] = $arg; | ||||||
|  |       } else { | ||||||
|  |         msg::warning("$arg: fichier invalide ou introuvable"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $showSection = count($files) > 1; | ||||||
|  |     foreach ($files as $file) { | ||||||
|  |       if ($showSection) msg::section($file); | ||||||
|  |       $sfile = new SharedFile($file); | ||||||
|  |       yaml::dump($sfile->unserialize()); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								php/cli/Json2yamlApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								php/cli/Json2yamlApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\ext\json; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\os\path; | ||||||
|  | 
 | ||||||
|  | class Json2yamlApp extends Application { | ||||||
|  |   function main() { | ||||||
|  |     $input = $this->args[0] ?? null; | ||||||
|  |     if ($input === null || $input === "-") { | ||||||
|  |       $output = null; | ||||||
|  |     } else { | ||||||
|  |       $output = path::ensure_ext($input, ".yml", ".json"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $data = json::load($input); | ||||||
|  |     yaml::dump($data, $output); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								php/cli/MysqlCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								php/cli/MysqlCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\app\config; | ||||||
|  | use nulib\db\mysql\MysqlStorage; | ||||||
|  | 
 | ||||||
|  | class MysqlCapacitorApp extends AbstractCapacitorApp { | ||||||
|  |   const ARGS = [ | ||||||
|  |     "merge" => parent::ARGS, | ||||||
|  |     "purpose" => "gestion d'un capacitor mysql", | ||||||
|  |     "usage" => [ | ||||||
|  |       "DBCONN [channelName | -t table | -c ChannelClass] [--query] key=value...", | ||||||
|  |       "DBCONN [channelName | -t table | -c ChannelClass] --sql-create", | ||||||
|  |     ], | ||||||
|  |     ["-t:table", "--table-name", | ||||||
|  |       "help" => "nom de la table porteuse du canal de données", | ||||||
|  |     ], | ||||||
|  |     ["-c:class", "--channel-class", | ||||||
|  |       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||||
|  |     ], | ||||||
|  |     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||||
|  |       "help" => "réinitialiser le canal", | ||||||
|  |     ], | ||||||
|  |     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||||
|  |       "help" => "ne pas recréer la table correspondant au canal" | ||||||
|  |     ], | ||||||
|  |     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||||
|  |       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||||
|  |     ], | ||||||
|  |     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||||
|  |       "help" => "afficher la requête pour créer la table", | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function main() { | ||||||
|  |     $dbconn = A::shift($this->args); | ||||||
|  |     if ($dbconn === null) self::die("Vous devez spécifier la base de données"); | ||||||
|  |     $tmp = config::db($dbconn); | ||||||
|  |     if ($tmp === null) self::die("$dbconn: base de données invalide"); | ||||||
|  |     $storage = new MysqlStorage($tmp); | ||||||
|  | 
 | ||||||
|  |     $this->storageCtl($storage); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								php/cli/PgsqlCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								php/cli/PgsqlCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\app\config; | ||||||
|  | use nulib\db\pgsql\PgsqlStorage; | ||||||
|  | 
 | ||||||
|  | class PgsqlCapacitorApp extends AbstractCapacitorApp { | ||||||
|  |   const ARGS = [ | ||||||
|  |     "merge" => parent::ARGS, | ||||||
|  |     "purpose" => "gestion d'un capacitor pgsql", | ||||||
|  |     "usage" => [ | ||||||
|  |       "DBCONN [channelName | -t table | -c ChannelClass] [--query] key=value...", | ||||||
|  |       "DBCONN [channelName | -t table | -c ChannelClass] --sql-create", | ||||||
|  |     ], | ||||||
|  |     ["-t:table", "--table-name", | ||||||
|  |       "help" => "nom de la table porteuse du canal de données", | ||||||
|  |     ], | ||||||
|  |     ["-c:class", "--channel-class", | ||||||
|  |       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||||
|  |     ], | ||||||
|  |     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||||
|  |       "help" => "réinitialiser le canal", | ||||||
|  |     ], | ||||||
|  |     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||||
|  |       "help" => "ne pas recréer la table correspondant au canal" | ||||||
|  |     ], | ||||||
|  |     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||||
|  |       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||||
|  |     ], | ||||||
|  |     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||||
|  |       "help" => "afficher la requête pour créer la table", | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function main() { | ||||||
|  |     $dbconn = A::shift($this->args); | ||||||
|  |     if ($dbconn === null) self::die("Vous devez spécifier la base de données"); | ||||||
|  |     $tmp = config::db($dbconn); | ||||||
|  |     if ($tmp === null) self::die("$dbconn: base de données invalide"); | ||||||
|  |     $storage = new PgsqlStorage($tmp); | ||||||
|  | 
 | ||||||
|  |     $this->storageCtl($storage); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										43
									
								
								php/cli/SqliteCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								php/cli/SqliteCapacitorApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\db\sqlite\SqliteStorage; | ||||||
|  | 
 | ||||||
|  | class SqliteCapacitorApp extends AbstractCapacitorApp { | ||||||
|  |   const ARGS = [ | ||||||
|  |     "merge" => parent::ARGS, | ||||||
|  |     "purpose" => "gestion d'un capacitor sqlite", | ||||||
|  |     "usage" => [ | ||||||
|  |       "DBFILE [channelName | -t table | -c ChannelClass] [--query] key=value...", | ||||||
|  |       "DBFILE [channelName | -t table | -c ChannelClass] --sql-create", | ||||||
|  |     ], | ||||||
|  |     ["-t:table", "--table-name", | ||||||
|  |       "help" => "nom de la table porteuse du canal de données", | ||||||
|  |     ], | ||||||
|  |     ["-c:class", "--channel-class", | ||||||
|  |       "help" => "nom de la classe dérivée de CapacitorChannel", | ||||||
|  |     ], | ||||||
|  |     ["-z", "--reset", "name" => "action", "value" => self::ACTION_RESET, | ||||||
|  |       "help" => "réinitialiser le canal", | ||||||
|  |     ], | ||||||
|  |     ["-n", "--no-recreate", "name" => "recreate", "value" => false, | ||||||
|  |       "help" => "ne pas recréer la table correspondant au canal" | ||||||
|  |     ], | ||||||
|  |     ["--query", "name" => "action", "value" => self::ACTION_QUERY, | ||||||
|  |       "help" => "lister les lignes correspondant aux valeurs spécifiées. c'est l'action par défaut", | ||||||
|  |     ], | ||||||
|  |     ["-s", "--sql-create", "name" => "action", "value" => self::ACTION_SQL, | ||||||
|  |       "help" => "afficher la requête pour créer la table", | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function main() { | ||||||
|  |     $dbfile = A::shift($this->args); | ||||||
|  |     if ($dbfile === null) self::die("Vous devez spécifier la base de données"); | ||||||
|  |     if (!file_exists($dbfile)) self::die("$dbfile: fichier introuvable"); | ||||||
|  |     $storage = new SqliteStorage($dbfile); | ||||||
|  | 
 | ||||||
|  |     $this->storageCtl($storage); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										21
									
								
								php/cli/Yaml2jsonApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								php/cli/Yaml2jsonApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\ext\json; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\os\path; | ||||||
|  | 
 | ||||||
|  | class Yaml2jsonApp extends Application { | ||||||
|  |   function main() { | ||||||
|  |     $input = $this->args[0] ?? null; | ||||||
|  |     if ($input === null || $input === "-") { | ||||||
|  |       $output = null; | ||||||
|  |     } else { | ||||||
|  |       $output = path::ensure_ext($input, ".json", [".yml", ".yaml"]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $data = yaml::load($input); | ||||||
|  |     json::dump($data, $output); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										53
									
								
								php/cli/_SteamTrainApp.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								php/cli/_SteamTrainApp.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,53 @@ | |||||||
|  | <?php | ||||||
|  | namespace cli; | ||||||
|  | 
 | ||||||
|  | use nulib\app\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", "--count", | ||||||
|  |       "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,18 +1,17 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib\tools\pman; | namespace cli\pman; | ||||||
| 
 | 
 | ||||||
| use nulib\cl; | use nulib\cl; | ||||||
|  | use nulib\exceptions; | ||||||
| use nulib\ext\json; | use nulib\ext\json; | ||||||
| use nulib\file; | use nulib\file; | ||||||
| use nulib\os\path; | use nulib\os\path; | ||||||
| use nulib\ValueException; |  | ||||||
| 
 | 
 | ||||||
| class ComposerFile { | class ComposerFile { | ||||||
|   function __construct(string $composerFile=".", bool $ensureExists=true) { |   function __construct(string $composerFile=".", bool $ensureExists=true) { | ||||||
|     if (is_dir($composerFile)) $composerFile = path::join($composerFile, 'composer.json'); |     if (is_dir($composerFile)) $composerFile = path::join($composerFile, 'composer.json'); | ||||||
|     if ($ensureExists && !file_exists($composerFile)) { |     if ($ensureExists && !file_exists($composerFile)) { | ||||||
|       $message = path::ppath($composerFile).": fichier introuvable"; |       throw exceptions::invalid_value(path::ppath($composerFile), "ce fichier", "il est introuvable"); | ||||||
|       throw new ValueException($message); |  | ||||||
|     } |     } | ||||||
|     $this->composerFile = $composerFile; |     $this->composerFile = $composerFile; | ||||||
|     $this->load(); |     $this->load(); | ||||||
| @ -1,11 +1,11 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib\tools\pman; | namespace cli\pman; | ||||||
| 
 | 
 | ||||||
| use nulib\A; | use nulib\A; | ||||||
|  | use nulib\exceptions; | ||||||
| use nulib\ext\yaml; | use nulib\ext\yaml; | ||||||
| use nulib\os\path; | use nulib\os\path; | ||||||
| use nulib\str; | use nulib\str; | ||||||
| use nulib\ValueException; |  | ||||||
| 
 | 
 | ||||||
| class ComposerPmanFile { | class ComposerPmanFile { | ||||||
|   const NAMES = [".composer.pman", ".pman"]; |   const NAMES = [".composer.pman", ".pman"]; | ||||||
| @ -29,8 +29,7 @@ class ComposerPmanFile { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if ($ensureExists && !file_exists($configFile)) { |     if ($ensureExists && !file_exists($configFile)) { | ||||||
|       $message = path::ppath($configFile).": fichier introuvable"; |       throw exceptions::invalid_value(path::ppath($configFile), "ce fichier", "il est introuvable"); | ||||||
|       throw new ValueException($message); |  | ||||||
|     } |     } | ||||||
|     $this->configFile = $configFile; |     $this->configFile = $configFile; | ||||||
|     $this->load(); |     $this->load(); | ||||||
| @ -66,9 +65,7 @@ class ComposerPmanFile { | |||||||
| 
 | 
 | ||||||
|   function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array { |   function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array { | ||||||
|     $config = $this->data["composer"][$profile] ?? null; |     $config = $this->data["composer"][$profile] ?? null; | ||||||
|     if ($config === null) { |     if ($config === null) throw exceptions::invalid_value($profile, "ce profil"); | ||||||
|       throw new ValueException("$profile: profil invalide"); |  | ||||||
|     } |  | ||||||
|     if ($composerRequires !== null) { |     if ($composerRequires !== null) { | ||||||
|       $matchRequires = $this->data["composer"]["match_require"]; |       $matchRequires = $this->data["composer"]["match_require"]; | ||||||
|       foreach ($composerRequires as $dep => $version) { |       foreach ($composerRequires as $dep => $version) { | ||||||
| @ -1,5 +1,5 @@ | |||||||
| #!/bin/bash | #!/bin/bash | ||||||
| # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 | ||||||
| MYDIR="$(dirname -- "$0")" | MYDIR="$(dirname -- "$0")" | ||||||
| VENDOR="$MYDIR/../vendor" | VENDOR="$MYDIR/vendor" | ||||||
| "$VENDOR/bin/phpunit" --bootstrap "$VENDOR/autoload.php" "$@" "$MYDIR/tests" | "$VENDOR/bin/phpunit" --bootstrap "$VENDOR/autoload.php" "$@" "$MYDIR/tests" | ||||||
|  | |||||||
| @ -1,7 +1,6 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib; | namespace nulib; | ||||||
| 
 | 
 | ||||||
| use nulib\php\func; |  | ||||||
| use Traversable; | use Traversable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  | |||||||
| @ -1,36 +1,38 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib; | namespace nulib; | ||||||
| 
 | 
 | ||||||
|  | use RuntimeException; | ||||||
|  | 
 | ||||||
| /** | /** | ||||||
|  * Class AccessException: indiquer que la resource ou l'objet auquel on veut |  * Class AccessException: indiquer que la resource ou l'objet auquel on veut | ||||||
|  * accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur |  * accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur | ||||||
|  */ |  */ | ||||||
| class AccessException extends UserException { | class AccessException extends RuntimeException { | ||||||
|   static final function read_only(?string $dest=null, ?string $prefix=null): self { |   static final function read_only(?string $dest=null, ?string $prefix=null): self { | ||||||
|     if ($prefix) $prefix = "$prefix: "; |     if ($prefix) $prefix = "$prefix: "; | ||||||
|     if ($dest === null) $dest = "this property"; |     if ($dest === null) $dest = "this property"; | ||||||
|     $message = "$dest is read-only"; |     $message = "$dest is read-only"; | ||||||
|     return new static($prefix.$message); |     return new static("$prefix$message"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static final function immutable_object(?string $dest=null, ?string $prefix=null): self { |   static final function immutable_object(?string $dest=null, ?string $prefix=null): self { | ||||||
|     if ($prefix) $prefix = "$prefix: "; |     if ($prefix) $prefix = "$prefix: "; | ||||||
|     if ($dest === null) $dest = "this object"; |     if ($dest === null) $dest = "this object"; | ||||||
|     $message = "$dest is immutable"; |     $message = "$dest is immutable"; | ||||||
|     return new static($prefix.$message); |     return new static("$prefix$message"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static final function not_allowed(?string $action=null, ?string $prefix=null): self { |   static final function not_allowed(?string $action=null, ?string $prefix=null): self { | ||||||
|     if ($prefix) $prefix = "$prefix: "; |     if ($prefix) $prefix = "$prefix: "; | ||||||
|     if ($action === null) $action = "this operation"; |     if ($action === null) $action = "this operation"; | ||||||
|     $message = "$action is not allowed"; |     $message = "$action is not allowed"; | ||||||
|     return new static($prefix.$message); |     return new static("$prefix$message"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static final function not_accessible(?string $dest=null, ?string $prefix=null): self { |   static final function not_accessible(?string $dest=null, ?string $prefix=null): self { | ||||||
|     if ($prefix) $prefix = "$prefix: "; |     if ($prefix) $prefix = "$prefix: "; | ||||||
|     if ($dest === null) $dest = "this resource"; |     if ($dest === null) $dest = "this resource"; | ||||||
|     $message = "$dest is not accessible"; |     $message = "$dest is not accessible"; | ||||||
|     return new static($prefix.$message); |     return new static("$prefix$message"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -38,17 +38,22 @@ class ExceptionShadow { | |||||||
|     $this->trace = self::extract_trace($exception->getTrace()); |     $this->trace = self::extract_trace($exception->getTrace()); | ||||||
|     $previous = $exception->getPrevious(); |     $previous = $exception->getPrevious(); | ||||||
|     if ($previous !== null) $this->previous = new static($previous); |     if ($previous !== null) $this->previous = new static($previous); | ||||||
|  |     if ($exception instanceof UserException) { | ||||||
|  |       $this->userMessage = $exception->getUserMessage(); | ||||||
|  |       $this->techMessage = $exception->getTechMessage(); | ||||||
|  |     } else { | ||||||
|  |       $this->userMessage = null; | ||||||
|  |       $this->techMessage = null; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var string */ |   protected string $class; | ||||||
|   protected $class; |  | ||||||
| 
 | 
 | ||||||
|   function getClass(): string { |   function getClass(): string { | ||||||
|     return $this->class; |     return $this->class; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var string */ |   protected string $message; | ||||||
|   protected $message; |  | ||||||
| 
 | 
 | ||||||
|   function getMessage(): string { |   function getMessage(): string { | ||||||
|     return $this->message; |     return $this->message; | ||||||
| @ -61,22 +66,19 @@ class ExceptionShadow { | |||||||
|     return $this->code; |     return $this->code; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var string */ |   protected string $file; | ||||||
|   protected $file; |  | ||||||
| 
 | 
 | ||||||
|   function getFile(): string { |   function getFile(): string { | ||||||
|     return $this->file; |     return $this->file; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var int */ |   protected int $line; | ||||||
|   protected $line; |  | ||||||
| 
 | 
 | ||||||
|   function getLine(): int { |   function getLine(): int { | ||||||
|     return $this->line; |     return $this->line; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var array */ |   protected array $trace; | ||||||
|   protected $trace; |  | ||||||
| 
 | 
 | ||||||
|   function getTrace(): array { |   function getTrace(): array { | ||||||
|     return $this->trace; |     return $this->trace; | ||||||
| @ -92,10 +94,21 @@ class ExceptionShadow { | |||||||
|     return implode("\n", $lines); |     return implode("\n", $lines); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var ExceptionShadow */ |   protected ?ExceptionShadow $previous; | ||||||
|   protected $previous; |  | ||||||
| 
 | 
 | ||||||
|   function getPrevious(): ?ExceptionShadow { |   function getPrevious(): ?ExceptionShadow { | ||||||
|     return $this->previous; |     return $this->previous; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $userMessage; | ||||||
|  | 
 | ||||||
|  |   function getUserMessage(): ?array { | ||||||
|  |     return $this->userMessage; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $techMessage; | ||||||
|  | 
 | ||||||
|  |   function getTechMessage(): ?array { | ||||||
|  |     return $this->techMessage; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -18,8 +18,7 @@ class ExitError extends Error { | |||||||
|     return $this->getCode() !== 0; |     return $this->getCode() !== 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @var ?string */ |   protected ?string $userMessage; | ||||||
|   protected $userMessage; |  | ||||||
| 
 | 
 | ||||||
|   function haveUserMessage(): bool { |   function haveUserMessage(): bool { | ||||||
|     return $this->userMessage !== null; |     return $this->userMessage !== null; | ||||||
|  | |||||||
| @ -12,12 +12,12 @@ class StateException extends LogicException { | |||||||
|     if ($method === null) $method = "this method"; |     if ($method === null) $method = "this method"; | ||||||
|     $message = "$method is not implemented"; |     $message = "$method is not implemented"; | ||||||
|     if ($prefix) $prefix = "$prefix: "; |     if ($prefix) $prefix = "$prefix: "; | ||||||
|     return new static($prefix.$message); |     return new static("$prefix$message"); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   static final function unexpected_state(?string $suffix=null): self { |   static final function unexpected_state(?string $suffix=null): self { | ||||||
|     $message = "unexpected state"; |     $message = "unexpected state"; | ||||||
|     if ($suffix) $suffix = ": $suffix"; |     if ($suffix) $suffix = ": $suffix"; | ||||||
|     return new static($message.$suffix); |     return new static("$message$suffix"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -1,90 +1,35 @@ | |||||||
| <?php | <?php | ||||||
| namespace nulib; | namespace nulib; | ||||||
| 
 | 
 | ||||||
|  | use nulib\php\content\c; | ||||||
| use RuntimeException; | use RuntimeException; | ||||||
| use Throwable; | use Throwable; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Class UserException: une exception qui peut en plus contenir un message |  * Class UserException: une exception qui peut contenir un message utilisateur | ||||||
|  * utilisateur |  * et un message technique | ||||||
|  */ |  */ | ||||||
| class UserException extends RuntimeException { | class UserException extends RuntimeException { | ||||||
|   /** @param Throwable|ExceptionShadow $e */ |   function __construct($userMessage, $code=0, ?Throwable $previous=null) { | ||||||
|   static function get_user_message($e): ?string { |     $this->userMessage = $userMessage = c::resolve($userMessage); | ||||||
|     if ($e instanceof self) return $e->getUserMessage(); |     parent::__construct(c::to_string($userMessage), $code, $previous); | ||||||
|     else return null; |  | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /** @param Throwable|ExceptionShadow $e */ |   protected ?array $userMessage; | ||||||
|   static final function get_user_summary($e): string { |  | ||||||
|     $parts = []; |  | ||||||
|     $first = true; |  | ||||||
|     while ($e !== null) { |  | ||||||
|       $message = self::get_user_message($e); |  | ||||||
|       if (!$message) $message = "(no message)"; |  | ||||||
|       if ($first) $first = false; |  | ||||||
|       else $parts[] = "caused by "; |  | ||||||
|       $parts[] = get_class($e) . ": " . $message; |  | ||||||
|       $e = $e->getPrevious(); |  | ||||||
|     } |  | ||||||
|     return implode(", ", $parts); |  | ||||||
|   } |  | ||||||
| 
 | 
 | ||||||
|   /** @param Throwable|ExceptionShadow $e */ |   function getUserMessage(): ?array { | ||||||
|   static function get_message($e): ?string { |  | ||||||
|     $message = $e->getMessage(); |  | ||||||
|     if (!$message && $e instanceof self) $message = $e->getUserMessage(); |  | ||||||
|     return $message; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** @param Throwable|ExceptionShadow $e */ |  | ||||||
|   static final function get_summary($e): string { |  | ||||||
|     $parts = []; |  | ||||||
|     $first = true; |  | ||||||
|     while ($e !== null) { |  | ||||||
|       $message = self::get_message($e); |  | ||||||
|       if (!$message) $message = "(no message)"; |  | ||||||
|       if ($first) $first = false; |  | ||||||
|       else $parts[] = "caused by "; |  | ||||||
|       if ($e instanceof ExceptionShadow) $class = $e->getClass(); |  | ||||||
|       else $class = get_class($e); |  | ||||||
|       $parts[] = "$class: $message"; |  | ||||||
|       $e = $e->getPrevious(); |  | ||||||
|     } |  | ||||||
|     return implode(", ", $parts); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** @param Throwable|ExceptionShadow $e */ |  | ||||||
|   static final function get_traceback($e): string { |  | ||||||
|     $tbs = []; |  | ||||||
|     $previous = false; |  | ||||||
|     while ($e !== null) { |  | ||||||
|       if (!$previous) { |  | ||||||
|         $efile = $e->getFile(); |  | ||||||
|         $eline = $e->getLine(); |  | ||||||
|         $tbs[] = "at $efile($eline)"; |  | ||||||
|       } else { |  | ||||||
|         $tbs[] = "~~ caused by: " . self::get_summary($e); |  | ||||||
|       } |  | ||||||
|       $tbs[] = $e->getTraceAsString(); |  | ||||||
|       $e = $e->getPrevious(); |  | ||||||
|       $previous = true; |  | ||||||
|       #XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui
 |  | ||||||
|       # ont déjà été affichées
 |  | ||||||
|     } |  | ||||||
|     return implode("\n", $tbs); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   function __construct($userMessage, $techMessage=null, $code=0, ?Throwable $previous=null) { |  | ||||||
|     $this->userMessage = $userMessage; |  | ||||||
|     if ($techMessage === null) $techMessage = $userMessage; |  | ||||||
|     parent::__construct($techMessage, $code, $previous); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   /** @var ?string */ |  | ||||||
|   protected $userMessage; |  | ||||||
| 
 |  | ||||||
|   function getUserMessage(): ?string { |  | ||||||
|     return $this->userMessage; |     return $this->userMessage; | ||||||
|   } |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $techMessage = null; | ||||||
|  | 
 | ||||||
|  |   function getTechMessage(): ?array { | ||||||
|  |     return $this->techMessage; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setTechMessage($techMessage): self { | ||||||
|  |     if ($techMessage !== null) $techMessage = c::resolve($techMessage); | ||||||
|  |     $this->techMessage = $techMessage; | ||||||
|  |     return $this; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -5,72 +5,4 @@ namespace nulib; | |||||||
|  * Class ValueException: indiquer qu'une valeur est invalide |  * Class ValueException: indiquer qu'une valeur est invalide | ||||||
|  */ |  */ | ||||||
| class ValueException extends UserException { | class ValueException extends UserException { | ||||||
|   private static function value($value): string { |  | ||||||
|     if (is_object($value)) { |  | ||||||
|       return "<".get_class($value).">"; |  | ||||||
|     } elseif (is_array($value)) { |  | ||||||
|       $values = $value; |  | ||||||
|       $parts = []; |  | ||||||
|       $index = 0; |  | ||||||
|       foreach ($values as $key => $value) { |  | ||||||
|         if ($key === $index) { |  | ||||||
|           $index++; |  | ||||||
|           $parts[] = self::value($value); |  | ||||||
|         } else { |  | ||||||
|           $parts[] = "$key=>".self::value($value); |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|       return "[" . implode(", ", $parts) . "]"; |  | ||||||
|     } elseif (is_string($value)) { |  | ||||||
|       return $value; |  | ||||||
|     } else { |  | ||||||
|       return var_export($value, true); |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   private static function message($value, ?string $message, ?string $kind, ?string $prefix, ?string $suffix): string { |  | ||||||
|     if ($kind === null) $kind = "value"; |  | ||||||
|     if ($message === null) $message = "$kind$suffix"; |  | ||||||
|     if ($value !== null) { |  | ||||||
|       $value = self::value($value); |  | ||||||
|       if ($prefix) $prefix = "$prefix: $value"; |  | ||||||
|       else $prefix = $value; |  | ||||||
|     } |  | ||||||
|     if ($prefix) $prefix = "$prefix: "; |  | ||||||
|     return $prefix.$message; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function null(?string $kind=null, ?string $prefix=null, ?string $message=null): self { |  | ||||||
|     return new static(self::message(null, $message, $kind, $prefix, " should not be null")); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function check_null($value, ?string $kind=null, ?string $prefix=null, ?string $message=null) { |  | ||||||
|     if ($value === null) throw static::null($kind, $prefix, $message); |  | ||||||
|     return $value; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function invalid_kind($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { |  | ||||||
|     return new static(self::message($value, $message, $kind, $prefix, " is invalid")); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function invalid_key($value, ?string $prefix=null, ?string $message=null): self { |  | ||||||
|     return self::invalid_kind($value, "key", $prefix, $message); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function invalid_value($value, ?string $prefix=null, ?string $message=null): self { |  | ||||||
|     return self::invalid_kind($value, "value", $prefix, $message); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function invalid_type($value, string $expected_type): self { |  | ||||||
|     return new static(self::message($value, null, "type", null, " is invalid, expected $expected_type")); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function invalid_class($class, string $expected_class): self { |  | ||||||
|     if (is_object($class)) $class = get_class($class); |  | ||||||
|     return new static(self::message($class, null, "class", null, " is invalid, expected $expected_class")); |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   static final function forbidden($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self { |  | ||||||
|     return new static(self::message($value, $message, $kind, $prefix, " is forbidden")); |  | ||||||
|   } |  | ||||||
| } | } | ||||||
|  | |||||||
| @ -2,7 +2,6 @@ | |||||||
| namespace nulib\app; | namespace nulib\app; | ||||||
| 
 | 
 | ||||||
| use nulib\A; | use nulib\A; | ||||||
| use nulib\app; |  | ||||||
| use nulib\cl; | use nulib\cl; | ||||||
| use nulib\file\SharedFile; | use nulib\file\SharedFile; | ||||||
| use nulib\os\path; | use nulib\os\path; | ||||||
|  | |||||||
| @ -1,8 +1,5 @@ | |||||||
| # nulib\app | # nulib\app | ||||||
| 
 | 
 | ||||||
| * [ ] ajouter des méthodes normalisées `app::get_cachedir()` et |  | ||||||
|   `app::get_cachefile($name)` avec la valeur par défaut |  | ||||||
|   `cachedir = $vardir/cache` |  | ||||||
| * [ ] `app::action()` et `app::step()` appellent automatiquement | * [ ] `app::action()` et `app::step()` appellent automatiquement | ||||||
|   `app::_dispatch_signals()` |   `app::_dispatch_signals()` | ||||||
| 
 | 
 | ||||||
|  | |||||||
							
								
								
									
										655
									
								
								php/src/app/app.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										655
									
								
								php/src/app/app.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,655 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\app\cli\Application; | ||||||
|  | use nulib\app\config\ProfileManager; | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\exceptions; | ||||||
|  | use nulib\ExitError; | ||||||
|  | use nulib\os\path; | ||||||
|  | use nulib\os\sh; | ||||||
|  | use nulib\php\func; | ||||||
|  | use nulib\ref\ref_profiles; | ||||||
|  | use nulib\str; | ||||||
|  | 
 | ||||||
|  | class app { | ||||||
|  |   private static function isa_Application($app): bool { | ||||||
|  |     if (!is_string($app)) return false; | ||||||
|  |     #XXX support legacy
 | ||||||
|  |     $legacyApplication = 'nur\cli\Application'; | ||||||
|  |     if ($app === $legacyApplication || is_subclass_of($app, $legacyApplication)) return true; | ||||||
|  |     return $app === Application::class | ||||||
|  |       || is_subclass_of($app, Application::class); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static function get_params($app): array { | ||||||
|  |     if ($app instanceof self) { | ||||||
|  |       $params = $app->getParams(); | ||||||
|  |     } elseif ($app instanceof Application) { | ||||||
|  |       $class = get_class($app); | ||||||
|  |       $params = [ | ||||||
|  |         "class" => $class, | ||||||
|  |         "projdir" => $app::PROJDIR, | ||||||
|  |         "vendor" => $app::VENDOR, | ||||||
|  |         "projcode" => $app::PROJCODE, | ||||||
|  |         "datadir" => $app::DATADIR, | ||||||
|  |         "etcdir" => $app::ETCDIR, | ||||||
|  |         "vardir" => $app::VARDIR, | ||||||
|  |         "cachedir" => $app::CACHEDIR, | ||||||
|  |         "logdir" => $app::LOGDIR, | ||||||
|  |         "appgroup" => $app::APPGROUP, | ||||||
|  |         "name" => $app::NAME, | ||||||
|  |         "title" => $app::TITLE, | ||||||
|  |       ]; | ||||||
|  |     } elseif (self::isa_Application($app)) { | ||||||
|  |       $class = $app; | ||||||
|  |       $params = [ | ||||||
|  |         "class" => $class, | ||||||
|  |         "projdir" => constant("$app::PROJDIR"), | ||||||
|  |         "vendor" => constant("$app::VENDOR"), | ||||||
|  |         "projcode" => constant("$app::PROJCODE"), | ||||||
|  |         "datadir" => constant("$app::DATADIR"), | ||||||
|  |         "etcdir" => constant("$app::ETCDIR"), | ||||||
|  |         "vardir" => constant("$app::VARDIR"), | ||||||
|  |         "cachedir" => constant("$app::CACHEDIR"), | ||||||
|  |         "logdir" => constant("$app::LOGDIR"), | ||||||
|  |         "appgroup" => constant("$app::APPGROUP"), | ||||||
|  |         "name" => constant("$app::NAME"), | ||||||
|  |         "title" => constant("$app::TITLE"), | ||||||
|  |       ]; | ||||||
|  |     } elseif (is_array($app)) { | ||||||
|  |       $params = $app; | ||||||
|  |     } else { | ||||||
|  |       throw exceptions::invalid_type($app, "app", Application::class); | ||||||
|  |     } | ||||||
|  |     return $params; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected static ?self $app = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @param Application|string|array $app | ||||||
|  |    * @param Application|string|array|null $proj | ||||||
|  |    */ | ||||||
|  |   static function with($app, $proj=null): self { | ||||||
|  |     $params = self::get_params($app); | ||||||
|  |     $proj ??= self::params_getenv(); | ||||||
|  |     $proj ??= self::$app; | ||||||
|  |     $proj_params = $proj !== null? self::get_params($proj): null; | ||||||
|  |     if ($proj_params !== null) { | ||||||
|  |       A::merge($params, cl::select($proj_params, [ | ||||||
|  |         "projdir", | ||||||
|  |         "vendor", | ||||||
|  |         "projcode", | ||||||
|  |         "cwd", | ||||||
|  |         "datadir", | ||||||
|  |         "etcdir", | ||||||
|  |         "vardir", | ||||||
|  |         "cachedir", | ||||||
|  |         "logdir", | ||||||
|  |         "profile", | ||||||
|  |         "facts", | ||||||
|  |         "debug", | ||||||
|  |       ])); | ||||||
|  |     } | ||||||
|  |     return new static($params, $proj_params !== null); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function init($app, $proj=null): void { | ||||||
|  |     self::$app = static::with($app, $proj); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function get(): self { | ||||||
|  |     return self::$app ??= new static(null); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function params_putenv(): void { | ||||||
|  |     $params = serialize(self::get()->getParams()); | ||||||
|  |     putenv("NULIB_APP_app_params=$params"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function params_getenv(): ?array { | ||||||
|  |     $params = getenv("NULIB_APP_app_params"); | ||||||
|  |     if ($params === false) return null; | ||||||
|  |     return unserialize($params); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function get_profile(?bool &$productionMode=null): string { | ||||||
|  |     return self::get()->getProfile($productionMode); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function is_production_mode(): bool { | ||||||
|  |     return self::get()->isProductionMode(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function is_prod(): bool { | ||||||
|  |     return self::get_profile() === ref_profiles::PROD; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function is_test(): bool { | ||||||
|  |     return self::get_profile() === ref_profiles::TEST; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function is_devel(): bool { | ||||||
|  |     return self::get_profile() === ref_profiles::DEVEL; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function set_profile(?string $profile=null, ?bool $productionMode=null): void { | ||||||
|  |     self::get()->setProfile($profile, $productionMode); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const FACT_WEB_APP = "web-app"; | ||||||
|  |   const FACT_CLI_APP = "cli-app"; | ||||||
|  | 
 | ||||||
|  |   static final function is_fact(string $fact, $value=true): bool { | ||||||
|  |     return self::get()->isFact($fact, $value); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static final function set_fact(string $fact, $value=true): void { | ||||||
|  |     self::get()->setFact($fact, $value); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function is_debug(): bool { | ||||||
|  |     return self::get()->isDebug(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function set_debug(?bool $debug=true): void { | ||||||
|  |     self::get()->setDebug($debug); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var array répertoires vendor exprimés relativement à PROJDIR | ||||||
|  |    */ | ||||||
|  |   const DEFAULT_VENDOR = [ | ||||||
|  |     "bindir" => "vendor/bin", | ||||||
|  |     "autoload" => "vendor/autoload.php", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function __construct(?array $params, bool $useProjParams=false) { | ||||||
|  |     if ($useProjParams) { | ||||||
|  |       [ | ||||||
|  |         "projdir" => $projdir, | ||||||
|  |         "vendor" => $vendor, | ||||||
|  |         "projcode" => $projcode, | ||||||
|  |         "datadir" => $datadir, | ||||||
|  |         "etcdir" => $etcdir, | ||||||
|  |         "vardir" => $vardir, | ||||||
|  |         "cachedir" => $cachedir, | ||||||
|  |         "logdir" => $logdir, | ||||||
|  |       ] = $params; | ||||||
|  |       $cwd = $params["cwd"] ?? null; | ||||||
|  |       $datadirIsDefined = true; | ||||||
|  |     } else { | ||||||
|  |       # projdir
 | ||||||
|  |       $projdir = $params["projdir"] ?? null; | ||||||
|  |       if ($projdir === null) { | ||||||
|  |         global $_composer_autoload_path, $_composer_bin_dir; | ||||||
|  |         $autoload = $_composer_autoload_path ?? null; | ||||||
|  |         $bindir = $_composer_bin_dir ?? null; | ||||||
|  |         if ($autoload !== null) { | ||||||
|  |           $vendor = preg_replace('/\/[^\/]+\.php$/', "", $autoload); | ||||||
|  |           $bindir ??= "$vendor/bin"; | ||||||
|  |           $projdir = preg_replace('/\/[^\/]+$/', "", $vendor); | ||||||
|  |           $params["vendor"] = [ | ||||||
|  |             "autoload" => $autoload, | ||||||
|  |             "bindir" => $bindir, | ||||||
|  |           ]; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if ($projdir === null) $projdir = "."; | ||||||
|  |       $projdir = path::abspath($projdir); | ||||||
|  |       # vendor
 | ||||||
|  |       $vendor = $params["vendor"] ?? self::DEFAULT_VENDOR; | ||||||
|  |       $vendor["bindir"] = path::reljoin($projdir, $vendor["bindir"]); | ||||||
|  |       $vendor["autoload"] = path::reljoin($projdir, $vendor["autoload"]); | ||||||
|  |       # projcode
 | ||||||
|  |       $projcode = $params["projcode"] ?? null; | ||||||
|  |       if ($projcode === null) { | ||||||
|  |         $projcode = str::without_suffix("-app", path::basename($projdir)); | ||||||
|  |       } | ||||||
|  |       $PROJCODE = str_replace("-", "_", strtoupper($projcode)); | ||||||
|  |       # cwd
 | ||||||
|  |       $cwd = $params["cwd"] ?? null; | ||||||
|  |       # datadir
 | ||||||
|  |       $datadir = getenv("${PROJCODE}_DATADIR"); | ||||||
|  |       $datadirIsDefined = $datadir !== false; | ||||||
|  |       if ($datadir === false) $datadir = $params["datadir"] ?? null; | ||||||
|  |       if ($datadir === null) $datadir = "devel"; | ||||||
|  |       $datadir = path::reljoin($projdir, $datadir); | ||||||
|  |       # etcdir
 | ||||||
|  |       $etcdir = getenv("${PROJCODE}_ETCDIR"); | ||||||
|  |       if ($etcdir === false) $etcdir = $params["etcdir"] ?? null; | ||||||
|  |       if ($etcdir === null) $etcdir = "etc"; | ||||||
|  |       $etcdir = path::reljoin($datadir, $etcdir); | ||||||
|  |       # vardir
 | ||||||
|  |       $vardir = getenv("${PROJCODE}_VARDIR"); | ||||||
|  |       if ($vardir === false) $vardir = $params["vardir"] ?? null; | ||||||
|  |       if ($vardir === null) $vardir = "var"; | ||||||
|  |       $vardir = path::reljoin($datadir, $vardir); | ||||||
|  |       # cachedir
 | ||||||
|  |       $cachedir = getenv("${PROJCODE}_CACHEDIR"); | ||||||
|  |       if ($cachedir === false) $cachedir = $params["cachedir"] ?? null; | ||||||
|  |       if ($cachedir === null) $cachedir = "cache"; | ||||||
|  |       $cachedir = path::reljoin($vardir, $cachedir); | ||||||
|  |       # logdir
 | ||||||
|  |       $logdir = getenv("${PROJCODE}_LOGDIR"); | ||||||
|  |       if ($logdir === false) $logdir = $params["logdir"] ?? null; | ||||||
|  |       if ($logdir === null) $logdir = "log"; | ||||||
|  |       $logdir = path::reljoin($datadir, $logdir); | ||||||
|  |     } | ||||||
|  |     # cwd
 | ||||||
|  |     $cwd ??= getcwd(); | ||||||
|  |     # profile
 | ||||||
|  |     $this->profileManager = new ProfileManager([ | ||||||
|  |       "app" => true, | ||||||
|  |       "name" => $projcode, | ||||||
|  |       "default_profile" => $datadirIsDefined? "prod": "devel", | ||||||
|  |       "profile" => $params["profile"] ?? null, | ||||||
|  |     ]); | ||||||
|  |     # $facts
 | ||||||
|  |     $this->facts = $params["facts"] ?? null; | ||||||
|  |     # debug
 | ||||||
|  |     $this->debug = $params["debug"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->projdir = $projdir; | ||||||
|  |     $this->vendor = $vendor; | ||||||
|  |     $this->projcode = $projcode; | ||||||
|  |     $this->cwd = $cwd; | ||||||
|  |     $this->datadir = $datadir; | ||||||
|  |     $this->etcdir = $etcdir; | ||||||
|  |     $this->vardir = $vardir; | ||||||
|  |     $this->cachedir = $cachedir; | ||||||
|  |     $this->logdir = $logdir; | ||||||
|  | 
 | ||||||
|  |     # name, title
 | ||||||
|  |     $appgroup = $params["appgroup"] ?? null; | ||||||
|  |     $name = $params["name"] ?? $params["class"] ?? null; | ||||||
|  |     if ($name === null) { | ||||||
|  |       $name = $projcode; | ||||||
|  |     } else { | ||||||
|  |       # si $name est une classe, enlever le package et normaliser i.e
 | ||||||
|  |       # my\package\MyApplication --> my-application.php
 | ||||||
|  |       $name = preg_replace('/.*\\\\/', "", $name); | ||||||
|  |       $name = str::camel2us($name, false, "-"); | ||||||
|  |       $name = str::without_suffix("-app", $name); | ||||||
|  |     } | ||||||
|  |     $this->appgroup = $appgroup; | ||||||
|  |     $this->name = $name; | ||||||
|  |     $this->title = $params["title"] ?? null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  |   # Paramètres partagés par tous les scripts d'un projet (et les scripts lancés
 | ||||||
|  |   # à partir d'une application de ce projet)
 | ||||||
|  | 
 | ||||||
|  |   protected string $projdir; | ||||||
|  | 
 | ||||||
|  |   function getProjdir(): string { | ||||||
|  |     return $this->projdir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $vendor; | ||||||
|  | 
 | ||||||
|  |   function getVendorBindir(): string { | ||||||
|  |     return $this->vendor["bindir"]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getVendorAutoload(): string { | ||||||
|  |     return $this->vendor["autoload"]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $projcode; | ||||||
|  | 
 | ||||||
|  |   function getProjcode(): string { | ||||||
|  |     return $this->projcode; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $cwd; | ||||||
|  | 
 | ||||||
|  |   function getCwd(): string { | ||||||
|  |     return $this->cwd; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $datadir; | ||||||
|  | 
 | ||||||
|  |   function getDatadir(): string { | ||||||
|  |     return $this->datadir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $etcdir; | ||||||
|  | 
 | ||||||
|  |   function getEtcdir(): string { | ||||||
|  |     return $this->etcdir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $vardir; | ||||||
|  | 
 | ||||||
|  |   function getVardir(): string { | ||||||
|  |     return $this->vardir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $cachedir; | ||||||
|  | 
 | ||||||
|  |   function getCachedir(): string { | ||||||
|  |     return $this->cachedir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $logdir; | ||||||
|  | 
 | ||||||
|  |   function getLogdir(): string { | ||||||
|  |     return $this->logdir; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ProfileManager $profileManager; | ||||||
|  | 
 | ||||||
|  |   function getProfile(?bool &$productionMode=null): string { | ||||||
|  |     return $this->profileManager->getProfile($productionMode); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function isProductionMode(): bool { | ||||||
|  |     return $this->profileManager->isProductionMode(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setProfile(?string $profile, ?bool $productionMode=null): void { | ||||||
|  |     $this->profileManager->setProfile($profile, $productionMode); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $facts; | ||||||
|  | 
 | ||||||
|  |   function isFact(string $fact, $value=true): bool { | ||||||
|  |     return ($this->facts[$fact] ?? false) === $value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setFact(string $fact, $value=true): void { | ||||||
|  |     $this->facts[$fact] = $value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?bool $debug; | ||||||
|  | 
 | ||||||
|  |   function isDebug(): bool { | ||||||
|  |     $debug = $this->debug; | ||||||
|  |     if ($debug === null) { | ||||||
|  |       $debug = defined("DEBUG")? DEBUG: null; | ||||||
|  |       $DEBUG = getenv("DEBUG"); | ||||||
|  |       $debug ??= $DEBUG !== false? $DEBUG: null; | ||||||
|  |       $debug ??= config::k("debug"); | ||||||
|  |       $debug ??= false; | ||||||
|  |       $this->debug = $debug; | ||||||
|  |     } | ||||||
|  |     return $debug; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setDebug(bool $debug=true): void { | ||||||
|  |     $this->debug = $debug; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @param ?string|false $profile | ||||||
|  |    * | ||||||
|  |    * false === pas de profil | ||||||
|  |    * null === profil par défaut | ||||||
|  |    */ | ||||||
|  |   function withProfile(string $file, $profile): string { | ||||||
|  |     if ($profile !== false) { | ||||||
|  |       $profile ??= $this->getProfile(); | ||||||
|  |       [$dir, $filename] = path::split($file); | ||||||
|  |       $basename = path::basename($filename); | ||||||
|  |       $ext = path::ext($file); | ||||||
|  |       $file = path::join($dir, "$basename.$profile$ext"); | ||||||
|  |     } | ||||||
|  |     return $file; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function findFile(array $dirs, array $names, $profile=null): string { | ||||||
|  |     # d'abord chercher avec le profil
 | ||||||
|  |     if ($profile !== false) { | ||||||
|  |       foreach ($dirs as $dir) { | ||||||
|  |         foreach ($names as $name) { | ||||||
|  |           $file = path::join($dir, $name); | ||||||
|  |           $file = $this->withProfile($file, $profile); | ||||||
|  |           if (file_exists($file)) return $file; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     # puis sans profil
 | ||||||
|  |     foreach ($dirs as $dir) { | ||||||
|  |       foreach ($names as $name) { | ||||||
|  |         $file = path::join($dir, $name); | ||||||
|  |         if (file_exists($file)) return $file; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     # la valeur par défaut est avec profil
 | ||||||
|  |     return $this->withProfile(path::join($dirs[0], $names[0]), $profile); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function fencedJoin(string $basedir, ?string ...$paths): string { | ||||||
|  |     $path = path::reljoin($basedir, ...$paths); | ||||||
|  |     if (!path::is_within($path, $basedir)) { | ||||||
|  |       throw exceptions::invalid_value($path, "path"); | ||||||
|  |     } | ||||||
|  |     return $path; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  |   # Paramètres spécifiques à cette application
 | ||||||
|  | 
 | ||||||
|  |   protected ?string $appgroup; | ||||||
|  | 
 | ||||||
|  |   function getAppgroup(): ?string { | ||||||
|  |     return $this->appgroup; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $name; | ||||||
|  | 
 | ||||||
|  |   function getName(): ?string { | ||||||
|  |     return $this->name; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?string $title; | ||||||
|  | 
 | ||||||
|  |   function getTitle(): ?string { | ||||||
|  |     return $this->title; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  |   # Méthodes outils
 | ||||||
|  | 
 | ||||||
|  |   /** recréer le tableau des paramètres */ | ||||||
|  |   function getParams(): array { | ||||||
|  |     return [ | ||||||
|  |       "projdir" => $this->projdir, | ||||||
|  |       "vendor" => $this->vendor, | ||||||
|  |       "projcode" => $this->projcode, | ||||||
|  |       "cwd" => $this->cwd, | ||||||
|  |       "datadir" => $this->datadir, | ||||||
|  |       "etcdir" => $this->etcdir, | ||||||
|  |       "vardir" => $this->vardir, | ||||||
|  |       "cachedir" => $this->cachedir, | ||||||
|  |       "logdir" => $this->logdir, | ||||||
|  |       "profile" => $this->getProfile(), | ||||||
|  |       "facts" => $this->facts, | ||||||
|  |       "debug" => $this->debug, | ||||||
|  |       "appgroup" => $this->appgroup, | ||||||
|  |       "name" => $this->name, | ||||||
|  |       "title" => $this->title, | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin vers le fichier de configuration. par défaut, retourner | ||||||
|  |    * une valeur de la forme "$ETCDIR/$name[.$profile].conf" | ||||||
|  |    */ | ||||||
|  |   function getEtcfile(?string $name=null, $profile=null): string { | ||||||
|  |     $name ??= "{$this->name}.conf"; | ||||||
|  |     return $this->findFile([$this->etcdir], [$name], $profile); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin vers le fichier de travail. par défaut, retourner une | ||||||
|  |    * valeur de la forme "$VARDIR/$appgroup/$name[.$profile].tmp" | ||||||
|  |    */ | ||||||
|  |   function getVarfile(?string $name=null, $profile=null): string { | ||||||
|  |     $name ??= "{$this->name}.tmp"; | ||||||
|  |     $file = $this->fencedJoin($this->vardir, $this->appgroup, $name); | ||||||
|  |     $file = $this->withProfile($file, $profile); | ||||||
|  |     sh::mkdirof($file); | ||||||
|  |     return $file; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin vers le fichier de cache. par défaut, retourner une | ||||||
|  |    * valeur de la forme "$CACHEDIR/$appgroup/$name[.$profile].cache" | ||||||
|  |    */ | ||||||
|  |   function getCachefile(?string $name=null, $profile=null): string { | ||||||
|  |     $name ??= "{$this->name}.cache"; | ||||||
|  |     $file = $this->fencedJoin($this->cachedir, $this->appgroup, $name); | ||||||
|  |     $file = $this->withProfile($file, $profile); | ||||||
|  |     sh::mkdirof($file); | ||||||
|  |     return $file; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin vers le fichier de log. par défaut, retourner une | ||||||
|  |    * valeur de la forme "$LOGDIR/$appgroup/$name.log" (sans le profil, parce | ||||||
|  |    * qu'il s'agit du fichier de log par défaut) | ||||||
|  |    * | ||||||
|  |    * Si $name est spécifié, la valeur retournée sera de la forme | ||||||
|  |    * "$LOGDIR/$appgroup/$basename[.$profile].$ext" | ||||||
|  |    */ | ||||||
|  |   function getLogfile(?string $name=null, $profile=null): string { | ||||||
|  |     if ($name === null) { | ||||||
|  |       $name = "{$this->name}.log"; | ||||||
|  |       $profile ??= false; | ||||||
|  |     } | ||||||
|  |     $logfile = $this->fencedJoin($this->logdir, $this->appgroup, $name); | ||||||
|  |     $logfile = $this->withProfile($logfile, $profile); | ||||||
|  |     sh::mkdirof($logfile); | ||||||
|  |     return $logfile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin absolu vers un fichier de travail | ||||||
|  |    * - si le chemin est absolu, il est inchangé | ||||||
|  |    * - sinon le chemin est exprimé par rapport à $vardir/$appgroup | ||||||
|  |    * | ||||||
|  |    * is $ensureDir, créer le répertoire du fichier s'il n'existe pas déjà | ||||||
|  |    * | ||||||
|  |    * la différence avec {@link self::getVarfile()} est que le fichier peut | ||||||
|  |    * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de | ||||||
|  |    * valeur par défaut pour $file | ||||||
|  |    */ | ||||||
|  |   function getWorkfile(string $file, $profile=null, bool $ensureDir=true): string { | ||||||
|  |     $file = path::reljoin($this->vardir, $this->appgroup, $file); | ||||||
|  |     $file = $this->withProfile($file, $profile); | ||||||
|  |     if ($ensureDir) sh::mkdirof($file); | ||||||
|  |     return $file; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir le chemin absolu vers un fichier spécifié par l'utilisateur. | ||||||
|  |    * - si le chemin commence par /, il est laissé en l'état | ||||||
|  |    * - si le chemin commence par ./ ou ../, il est exprimé par rapport à $cwd | ||||||
|  |    * - sinon le chemin est exprimé par rapport à $vardir/$appgroup | ||||||
|  |    * | ||||||
|  |    * la différence est avec {@link self::getVarfile()} est que le fichier peut | ||||||
|  |    * au final être situé ailleurs que dans $vardir. de plus, il n'y a pas de | ||||||
|  |    * valeur par défaut pour $file | ||||||
|  |    */ | ||||||
|  |   function getUserfile(string $file): string { | ||||||
|  |     if (path::is_qualified($file)) { | ||||||
|  |       return path::reljoin($this->cwd, $file); | ||||||
|  |     } else { | ||||||
|  |       return path::reljoin($this->vardir, $this->appgroup, $file); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?RunFile $runfile = null; | ||||||
|  | 
 | ||||||
|  |   function getRunfile(): RunFile { | ||||||
|  |     $name = $this->name; | ||||||
|  |     $runfile = $this->getWorkfile($name); | ||||||
|  |     $logfile = $this->getLogfile("$name.out", false); | ||||||
|  |     return $this->runfile ??= new RunFile($name, $runfile, $logfile); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $lockFiles = null; | ||||||
|  | 
 | ||||||
|  |   function getLockfile(?string $name=null): LockFile { | ||||||
|  |     $this->lockFiles[$name] ??= $this->getRunfile()->getLockFile($name, $this->title); | ||||||
|  |     return $this->lockFiles[$name]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  | 
 | ||||||
|  |   const EC_FORK_CHILD = 250; | ||||||
|  |   const EC_FORK_PARENT = 251; | ||||||
|  |   const EC_DISABLED = 252; | ||||||
|  |   const EC_LOCKED = 253; | ||||||
|  |   const EC_BAD_COMMAND = 254; | ||||||
|  |   const EC_UNEXPECTED = 255; | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  | 
 | ||||||
|  |   static bool $dispach_signals = false; | ||||||
|  | 
 | ||||||
|  |   static function install_signal_handler(bool $allow=true): void { | ||||||
|  |     if (!$allow) return; | ||||||
|  |     $signalHandler = function(int $signo, $siginfo) { | ||||||
|  |       throw new ExitError(128 + $signo); | ||||||
|  |     }; | ||||||
|  |     pcntl_signal(SIGHUP, $signalHandler); | ||||||
|  |     pcntl_signal(SIGINT, $signalHandler); | ||||||
|  |     pcntl_signal(SIGQUIT, $signalHandler); | ||||||
|  |     pcntl_signal(SIGTERM, $signalHandler); | ||||||
|  |     self::$dispach_signals = true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function _dispatch_signals() { | ||||||
|  |     if (self::$dispach_signals) pcntl_signal_dispatch(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  | 
 | ||||||
|  |   static ?func $bgapplication_enabled = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * spécifier la fonction permettant de vérifier si l'exécution de tâches | ||||||
|  |    * de fond est autorisée. Si cette méthode n'est pas utilisée, par défaut, | ||||||
|  |    * les tâches planifiées sont autorisées | ||||||
|  |    * | ||||||
|  |    * si $func===true, spécifier une fonction qui retourne toujours vrai | ||||||
|  |    * si $func===false, spécifiée une fonction qui retourne toujours faux | ||||||
|  |    * sinon, $func doit être une fonction valide | ||||||
|  |    */ | ||||||
|  |   static function set_bgapplication_enabled($func): void { | ||||||
|  |     if (is_bool($func)) { | ||||||
|  |       $enabled = $func; | ||||||
|  |       $func = function () use ($enabled) { | ||||||
|  |         return $enabled; | ||||||
|  |       }; | ||||||
|  |     } | ||||||
|  |     self::$bgapplication_enabled = func::with($func); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Si les exécutions en tâche de fond sont autorisée, retourner. Sinon | ||||||
|  |    * afficher une erreur et quitter l'application | ||||||
|  |    */ | ||||||
|  |   static function check_bgapplication_enabled(bool $forceEnabled=false): void { | ||||||
|  |     if (self::$bgapplication_enabled === null || $forceEnabled) return; | ||||||
|  |     if (!self::$bgapplication_enabled->invoke()) { | ||||||
|  |       throw new ExitError(self::EC_DISABLED, "Planifications désactivées. La tâche n'a pas été lancée"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  | 
 | ||||||
|  |   static function action(?string $title, ?int $maxSteps=null): void { | ||||||
|  |     self::get()->getRunfile()->action($title, $maxSteps); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function step(int $nbSteps=1): void { | ||||||
|  |     self::get()->getRunfile()->step($nbSteps); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										110
									
								
								php/src/app/args/AbstractArgsParser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								php/src/app/args/AbstractArgsParser.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,110 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use stdClass; | ||||||
|  | 
 | ||||||
|  | abstract class AbstractArgsParser { | ||||||
|  |   protected function notEnoughArgs(int $needed, ?string $arg=null): ArgsException { | ||||||
|  |     if ($arg !== null) $arg .= ": "; | ||||||
|  |     $reason = $arg._exceptions::missing_value_message($needed); | ||||||
|  |     return _exceptions::missing_value(null, null, $reason); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function checkEnoughArgs(?string $option, int $count): void { | ||||||
|  |     if ($count > 0) throw $this->notEnoughArgs($count, $option); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function tooManyArgs(int $count, int $expected, ?string $arg=null): ArgsException { | ||||||
|  |     if ($arg !== null) $arg .= ": "; | ||||||
|  |     $reason = $arg._exceptions::unexpected_value_message($count - $expected); | ||||||
|  |     return _exceptions::unexpected_value(null, null, $reason); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function invalidArg(string $arg): ArgsException { | ||||||
|  |     return _exceptions::invalid_value($arg); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function ambiguousArg(string $arg, array $candidates): ArgsException { | ||||||
|  |     $candidates = implode(", ", $candidates); | ||||||
|  |     return new ArgsException("$arg: cet argument est ambigû (les valeurs possibles sont $candidates)"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * consommer les arguments de $src en avançant l'index $srci et provisionner | ||||||
|  |    * $dest à partir de $desti. si $desti est plus grand que 0, celà veut dire | ||||||
|  |    * que $dest a déjà commencé à être provisionné, et qu'il faut continuer. | ||||||
|  |    * | ||||||
|  |    * $destmin est le nombre minimum d'arguments à consommer. $destmax est le | ||||||
|  |    * nombre maximum d'arguments à consommer. | ||||||
|  |    * | ||||||
|  |    * $srci est la position de l'élément courant à consommer le cas échéant | ||||||
|  |    * retourner le nombre d'arguments qui manquent (ou 0 si tous les arguments | ||||||
|  |    * ont été consommés) | ||||||
|  |    * | ||||||
|  |    * pour les arguments optionnels, ils sont consommés tant qu'il y en a de | ||||||
|  |    * disponible, ou jusqu'à la présence de '--'. Si $keepsep, l'argument '--' | ||||||
|  |    * est gardé dans la liste des arguments optionnels. | ||||||
|  |    */ | ||||||
|  |   protected static function consume_args($src, &$srci, &$dest, $desti, $destmin, $destmax, bool $keepsep): int { | ||||||
|  |     $srcmax = count($src); | ||||||
|  |     # arguments obligatoires
 | ||||||
|  |     while ($desti < $destmin) { | ||||||
|  |       if ($srci < $srcmax) { | ||||||
|  |         $dest[] = $src[$srci]; | ||||||
|  |       } else { | ||||||
|  |         # pas assez d'arguments
 | ||||||
|  |         return $destmin - $desti; | ||||||
|  |       } | ||||||
|  |       $srci++; | ||||||
|  |       $desti++; | ||||||
|  |     } | ||||||
|  |     # arguments facultatifs
 | ||||||
|  |     $eoo = false; // l'option a-t-elle été terminée?
 | ||||||
|  |     while ($desti < $destmax && $srci < $srcmax) { | ||||||
|  |       $opt = $src[$srci]; | ||||||
|  |       $srci++; | ||||||
|  |       $desti++; | ||||||
|  |       if ($opt === "--") { | ||||||
|  |         # fin des arguments facultatifs en entrée
 | ||||||
|  |         $eoo = true; | ||||||
|  |         if ($keepsep) $dest[] = "--"; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       $dest[] = $opt; | ||||||
|  |     } | ||||||
|  |     if (!$eoo && $desti < $destmax) { | ||||||
|  |       # pas assez d'arguments en entrée, terminer avec "--"
 | ||||||
|  |       if ($keepsep) $dest[] = "--"; | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   abstract function normalize(array $args): array; | ||||||
|  | 
 | ||||||
|  |   /** @var object|array objet destination */ | ||||||
|  |   protected $dest; | ||||||
|  | 
 | ||||||
|  |   protected function setDest(&$dest): void { | ||||||
|  |     $this->dest =& $dest; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function unsetDest(): void { | ||||||
|  |     unset($this->dest); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   abstract function process(array $args); | ||||||
|  | 
 | ||||||
|  |   function parse(&$dest, array $args=null): void { | ||||||
|  |     if ($args === null) { | ||||||
|  |       global $argv; | ||||||
|  |       $args = array_slice($argv, 1); | ||||||
|  |     } | ||||||
|  |     $args = $this->normalize($args); | ||||||
|  |     $dest ??= new stdClass(); | ||||||
|  |     $this->setDest($dest); | ||||||
|  |     $this->process($args); | ||||||
|  |     $this->unsetDest(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   abstract function actionPrintHelp(string $arg): void; | ||||||
|  | } | ||||||
							
								
								
									
										643
									
								
								php/src/app/args/Aodef.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										643
									
								
								php/src/app/args/Aodef.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,643 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\php\akey; | ||||||
|  | use nulib\php\func; | ||||||
|  | use nulib\php\oprop; | ||||||
|  | use nulib\php\types\varray; | ||||||
|  | use nulib\php\types\vbool; | ||||||
|  | use nulib\php\valx; | ||||||
|  | use nulib\str; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Aodef: une définition d'un argument | ||||||
|  |  * | ||||||
|  |  * il y a 3 temps dans l'initialisation de l'objet: | ||||||
|  |  * - constructeur: accumuler les informations | ||||||
|  |  * - setup1($extends): calculer les options effectives. $extends permet de | ||||||
|  |  * cibler les définitions qui étendent une définition existante | ||||||
|  |  * - setup2(): calculer les arguments et les actions | ||||||
|  |  */ | ||||||
|  | class Aodef { | ||||||
|  |   const TYPE_SHORT = 0, TYPE_LONG = 1, TYPE_COMMAND = 2; | ||||||
|  |   const ARGS_NONE = 0, ARGS_MANDATORY = 1, ARGS_OPTIONAL = 2; | ||||||
|  | 
 | ||||||
|  |   function __construct(array $def) { | ||||||
|  |     $this->origDef = $def; | ||||||
|  |     $this->mergeParse($def); | ||||||
|  |     //$this->debugTrace("construct");
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $origDef; | ||||||
|  | 
 | ||||||
|  |   public bool $show = true; | ||||||
|  |   public ?bool $disabled = null; | ||||||
|  |   public ?bool $isRemains = null; | ||||||
|  |   public ?string $extends = null; | ||||||
|  | 
 | ||||||
|  |   protected ?array $_removes = null; | ||||||
|  |   protected ?array $_adds = null; | ||||||
|  | 
 | ||||||
|  |   protected ?array $_args = null; | ||||||
|  |   public ?string $argsdesc = null; | ||||||
|  | 
 | ||||||
|  |   public ?bool $ensureArray = null; | ||||||
|  |   public $action = null; | ||||||
|  |   public ?func $func = null; | ||||||
|  |   public ?bool $inverse = null; | ||||||
|  |   public $value = null; | ||||||
|  |   public ?string $name = null; | ||||||
|  |   public ?string $property = null; | ||||||
|  |   public ?string $key = null; | ||||||
|  | 
 | ||||||
|  |   public ?string $help = null; | ||||||
|  | 
 | ||||||
|  |   protected ?array $_options = []; | ||||||
|  | 
 | ||||||
|  |   public bool $haveShortOptions = false; | ||||||
|  |   public bool $haveLongOptions = false; | ||||||
|  |   public bool $isCommand = false; | ||||||
|  |   public bool $isHelp = false; | ||||||
|  | 
 | ||||||
|  |   public bool $haveArgs = false; | ||||||
|  |   public ?int $minArgs = null; | ||||||
|  |   public ?int $maxArgs = null; | ||||||
|  | 
 | ||||||
|  |   protected function mergeParse(array $def): void { | ||||||
|  |     $merges = $defs["merges"] ?? null; | ||||||
|  |     $merge = $defs["merge"] ?? null; | ||||||
|  |     if ($merge !== null) $merges[] = $merge; | ||||||
|  |     if ($merges !== null) { | ||||||
|  |       foreach ($merges as $merge) { | ||||||
|  |         if ($merge !== null) $this->mergeParse($merge); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $this->parse($def); | ||||||
|  | 
 | ||||||
|  |     $merge = $defs["merge_after"] ?? null; | ||||||
|  |     if ($merge !== null) $this->mergeParse($merge); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static function verifix_args(?array &$options): ?array { | ||||||
|  |     $args = null; | ||||||
|  |     if ($options !== null) { | ||||||
|  |       foreach ($options as &$option) { | ||||||
|  |         if (preg_match('/^(.*:)([^:].*)$/', $option, $ms)) { | ||||||
|  |           $option = $ms[1]; | ||||||
|  |           $args ??= explode(",", $ms[2]); | ||||||
|  |         } | ||||||
|  |       }; unset($option); | ||||||
|  |     } | ||||||
|  |     return $args; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function parse(array $def): void { | ||||||
|  |     [$options, $params] = cl::split_assoc($def); | ||||||
|  | 
 | ||||||
|  |     $this->show ??= $params["show"] ?? true; | ||||||
|  |     $this->extends ??= $params["extends"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $args ??= $params["args"] ?? null; | ||||||
|  |     $args ??= $params["arg"] ?? null; | ||||||
|  |     if ($args === true) $args = 1; | ||||||
|  |     elseif ($args === "*") $args = [null]; | ||||||
|  |     elseif ($args === "+") $args = ["value", null]; | ||||||
|  |     if (is_int($args)) $args = array_fill(0, $args, "value"); | ||||||
|  | 
 | ||||||
|  |     $this->disabled = vbool::withn($params["disabled"] ?? null); | ||||||
|  |     $adds = varray::withn($params["add"] ?? null); | ||||||
|  |     A::merge($this->_adds, $adds); | ||||||
|  |     A::merge($this->_adds, $options); | ||||||
|  |     $args ??= self::verifix_args($this->_adds); | ||||||
|  |     $removes = varray::withn($params["remove"] ?? null); | ||||||
|  |     A::merge($this->_removes, $removes); | ||||||
|  |     self::verifix_args($this->_adds); | ||||||
|  | 
 | ||||||
|  |     $this->_args ??= cl::withn($args); | ||||||
|  |     $this->argsdesc ??= $params["argsdesc"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->ensureArray ??= $params["ensure_array"] ?? null; | ||||||
|  |     $this->action = $params["action"] ?? null; | ||||||
|  |     $this->inverse ??= $params["inverse"] ?? null; | ||||||
|  |     $this->value ??= $params["value"] ?? null; | ||||||
|  |     $this->name ??= $params["name"] ?? null; | ||||||
|  |     $this->property ??= $params["property"] ?? null; | ||||||
|  |     $this->key ??= $params["key"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->help ??= $params["help"] ?? null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function isExtends(): bool { | ||||||
|  |     return $this->extends !== null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setup1(bool $extends=false, ?Aolist $aolist=null): void { | ||||||
|  |     if (!$extends && !$this->isExtends()) { | ||||||
|  |       $this->processOptions(); | ||||||
|  |     } elseif ($extends && $this->isExtends()) { | ||||||
|  |       $this->processExtends($aolist); | ||||||
|  |     } | ||||||
|  |     $this->initRemains(); | ||||||
|  |     //$this->debugTrace("setup1");
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function processExtends(Aolist $argdefs): void { | ||||||
|  |     $option = $this->extends; | ||||||
|  |     if ($option === null) { | ||||||
|  |       throw _exceptions::null_value("extends", "il doit spécifier l'argument destination"); | ||||||
|  |     } | ||||||
|  |     $dest = $argdefs->get($option); | ||||||
|  |     if ($dest === null) { | ||||||
|  |       throw _exceptions::invalid_value($option, "extends", "il doit spécifier un argument valide"); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ($this->ensureArray !== null) $dest->ensureArray = $this->ensureArray; | ||||||
|  |     if ($this->action !== null) $dest->action = $this->action; | ||||||
|  |     if ($this->inverse !== null) $dest->inverse = $this->inverse; | ||||||
|  |     if ($this->value !== null) $dest->value = $this->value; | ||||||
|  |     if ($this->name !== null) $dest->name = $this->name; | ||||||
|  |     if ($this->property !== null) $dest->property = $this->property; | ||||||
|  |     if ($this->key !== null) $dest->key = $this->key; | ||||||
|  | 
 | ||||||
|  |     A::merge($dest->_removes, $this->_removes); | ||||||
|  |     A::merge($dest->_adds, $this->_adds); | ||||||
|  |     $dest->processOptions(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function buildOptions(?array $options): array { | ||||||
|  |     $result = []; | ||||||
|  |     if ($options !== null) { | ||||||
|  |       foreach ($options as $option) { | ||||||
|  |         if (substr($option, 0, 2) === "--") { | ||||||
|  |           $type = self::TYPE_LONG; | ||||||
|  |           if (preg_match('/^--([^:-][^:]*)(::?)?$/', $option, $ms)) { | ||||||
|  |             $name = $ms[1]; | ||||||
|  |             $args = $ms[2] ?? null; | ||||||
|  |             $option = "--$name"; | ||||||
|  |           } else { | ||||||
|  |             throw _exceptions::invalid_value($option, "cette option longue"); | ||||||
|  |           } | ||||||
|  |         } elseif (substr($option, 0, 1) === "-") { | ||||||
|  |           $type = self::TYPE_SHORT; | ||||||
|  |           if (preg_match('/^-([^:-])(::?)?$/', $option, $ms)) { | ||||||
|  |             $name = $ms[1]; | ||||||
|  |             $args = $ms[2] ?? null; | ||||||
|  |             $option = "-$name"; | ||||||
|  |           } else { | ||||||
|  |             throw _exceptions::invalid_value($option, " cette option courte"); | ||||||
|  |           } | ||||||
|  |         } else { | ||||||
|  |           $type = self::TYPE_COMMAND; | ||||||
|  |           if (preg_match('/^([^:-][^:]*)$/', $option, $ms)) { | ||||||
|  |             $name = $ms[1]; | ||||||
|  |             $args = null; | ||||||
|  |             $option = "$name"; | ||||||
|  |           } else { | ||||||
|  |             throw _exceptions::invalid_value($option, "cette commande"); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |         if ($args === ":") { | ||||||
|  |           $argsType = self::ARGS_MANDATORY; | ||||||
|  |         } elseif ($args === "::") { | ||||||
|  |           $argsType = self::ARGS_OPTIONAL; | ||||||
|  |         } else { | ||||||
|  |           $argsType = self::ARGS_NONE; | ||||||
|  |         } | ||||||
|  |         $result[$option] = [ | ||||||
|  |           "name" => $name, | ||||||
|  |           "option" => $option, | ||||||
|  |           "type" => $type, | ||||||
|  |           "args_type" => $argsType, | ||||||
|  |         ]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return $result; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function initRemains(): void { | ||||||
|  |     if ($this->isRemains === null) { | ||||||
|  |       $options = array_fill_keys(array_keys($this->_options), true); | ||||||
|  |       foreach (array_keys($this->buildOptions($this->_removes)) as $option) { | ||||||
|  |         unset($options[$option]); | ||||||
|  |       } | ||||||
|  |       foreach (array_keys($this->buildOptions($this->_adds)) as $option) { | ||||||
|  |         unset($options[$option]); | ||||||
|  |       } | ||||||
|  |       if (!$options) $this->isRemains = true; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** traiter le paramètre parent */ | ||||||
|  |   protected function processOptions(): void { | ||||||
|  |     $this->removeOptions($this->_removes); | ||||||
|  |     $this->_removes = null; | ||||||
|  |     $this->addOptions($this->_adds); | ||||||
|  |     $this->_adds = null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function addOptions(?array $options): void { | ||||||
|  |     // les options pouvant être numériques (e.g "-1"), utiliser A::merge2
 | ||||||
|  |     A::merge2($this->_options, $this->buildOptions($options)); | ||||||
|  |     $this->updateType(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function removeOptions(?array $options): void { | ||||||
|  |     foreach ($this->buildOptions($options) as $option) { | ||||||
|  |       unset($this->_options[$option["option"]]); | ||||||
|  |     } | ||||||
|  |     $this->updateType(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function removeOption(string $option): void { | ||||||
|  |     unset($this->_options[$option]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** mettre à jour le type d'option */ | ||||||
|  |   protected function updateType(): void { | ||||||
|  |     $haveShortOptions = false; | ||||||
|  |     $haveLongOptions = false; | ||||||
|  |     $isCommand = false; | ||||||
|  |     $isHelp = false; | ||||||
|  |     foreach ($this->_options as $option) { | ||||||
|  |       switch ($option["type"]) { | ||||||
|  |       case self::TYPE_SHORT: | ||||||
|  |         $haveShortOptions = true; | ||||||
|  |         break; | ||||||
|  |       case self::TYPE_LONG: | ||||||
|  |         $haveLongOptions = true; | ||||||
|  |         break; | ||||||
|  |       case self::TYPE_COMMAND: | ||||||
|  |         $isCommand = true; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |       switch ($option["option"]) { | ||||||
|  |       case "--help": | ||||||
|  |       case "--help++": | ||||||
|  |         $isHelp = true; | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $this->haveShortOptions = $haveShortOptions; | ||||||
|  |     $this->haveLongOptions = $haveLongOptions; | ||||||
|  |     $this->isCommand = $isCommand; | ||||||
|  |     $this->isHelp = $isHelp; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setup2(): void { | ||||||
|  |     $this->processArgs(); | ||||||
|  |     $this->processAction(); | ||||||
|  |     $this->afterSetup(); | ||||||
|  |     //$this->debugTrace("setup2");
 | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * traiter les informations concernant les arguments puis calculer les nombres | ||||||
|  |    * minimum et maximum d'arguments que prend l'option | ||||||
|  |    */ | ||||||
|  |   protected function processArgs(): void { | ||||||
|  |     $args = $this->_args; | ||||||
|  |     if ($this->isRemains) { | ||||||
|  |       $args ??= [null]; | ||||||
|  |       $haveArgs = boolval($args); | ||||||
|  |     } elseif ($args === null) { | ||||||
|  |       $haveArgs = false; | ||||||
|  |       $optionalArgs = null; | ||||||
|  |       foreach ($this->_options as $option) { | ||||||
|  |         switch ($option["args_type"]) { | ||||||
|  |         case self::ARGS_NONE: | ||||||
|  |           break; | ||||||
|  |         case self::ARGS_MANDATORY: | ||||||
|  |           $haveArgs = true; | ||||||
|  |           $optionalArgs = false; | ||||||
|  |           break; | ||||||
|  |         case self::ARGS_OPTIONAL: | ||||||
|  |           $haveArgs = true; | ||||||
|  |           $optionalArgs ??= true; | ||||||
|  |           break; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       $optionalArgs ??= false; | ||||||
|  |       if ($haveArgs) { | ||||||
|  |         $args = ["value"]; | ||||||
|  |         if ($optionalArgs) $args = [$args]; | ||||||
|  |       } | ||||||
|  |     } else { | ||||||
|  |       $haveArgs = boolval($args); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ($this->isRemains) $desc = "remaining args"; | ||||||
|  |     else $desc = cl::first($this->_options)["option"]; | ||||||
|  | 
 | ||||||
|  |     $args ??= []; | ||||||
|  |     $argsdesc = []; | ||||||
|  |     $reqs = []; | ||||||
|  |     $haveNull = false; | ||||||
|  |     $optArgs = null; | ||||||
|  |     foreach ($args as $arg) { | ||||||
|  |       if (is_string($arg)) { | ||||||
|  |         $reqs[] = $arg; | ||||||
|  |         $argsdesc[] = strtoupper($arg); | ||||||
|  |       } elseif (is_array($arg)) { | ||||||
|  |         $optArgs = $arg; | ||||||
|  |         break; | ||||||
|  |       } elseif ($arg === null) { | ||||||
|  |         $haveNull = true; | ||||||
|  |         break; | ||||||
|  |       } else { | ||||||
|  |         throw _exceptions::invalid_value("$desc: $arg"); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $opts = []; | ||||||
|  |     $optArgsdesc = null; | ||||||
|  |     $lastarg = "VALUE"; | ||||||
|  |     if ($optArgs !== null) { | ||||||
|  |       $haveOpt = false; | ||||||
|  |       foreach ($optArgs as $arg) { | ||||||
|  |         if (is_string($arg)) { | ||||||
|  |           $haveOpt = true; | ||||||
|  |           $opts[] = $arg; | ||||||
|  |           $lastarg = strtoupper($arg); | ||||||
|  |           $optArgsdesc[] = $lastarg; | ||||||
|  |         } elseif ($arg === null) { | ||||||
|  |           $haveNull = true; | ||||||
|  |           break; | ||||||
|  |         } else { | ||||||
|  |           throw _exceptions::invalid_value("$desc: $arg"); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if (!$haveOpt) $haveNull = true; | ||||||
|  |     } | ||||||
|  |     if ($haveNull) $optArgsdesc[] = "${lastarg}s..."; | ||||||
|  |     if ($optArgsdesc !== null) { | ||||||
|  |       $argsdesc[] = "[".implode(" ", $optArgsdesc)."]"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $minArgs = count($reqs); | ||||||
|  |     if ($haveNull) $maxArgs = PHP_INT_MAX; | ||||||
|  |     else $maxArgs = $minArgs + count($opts); | ||||||
|  | 
 | ||||||
|  |     $this->haveArgs = $haveArgs; | ||||||
|  |     $this->minArgs = $minArgs; | ||||||
|  |     $this->maxArgs = $maxArgs; | ||||||
|  |     $this->argsdesc ??= implode(" ", $argsdesc); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static function get_longest(array $options, int $type): ?string { | ||||||
|  |     $longest = null; | ||||||
|  |     $maxlen = 0; | ||||||
|  |     foreach ($options as $option) { | ||||||
|  |       if ($option["type"] !== $type) continue; | ||||||
|  |       $name = $option["name"]; | ||||||
|  |       $len = strlen($name); | ||||||
|  |       if ($len > $maxlen) { | ||||||
|  |         $longest = $name; | ||||||
|  |         $maxlen = $len; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return $longest; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function processAction(): void { | ||||||
|  |     $this->ensureArray ??= $this->isRemains || $this->maxArgs > 1; | ||||||
|  | 
 | ||||||
|  |     $action = $this->action; | ||||||
|  |     $func = $this->func; | ||||||
|  |     if ($action === null) { | ||||||
|  |       if ($this->isCommand) $action = "--set-command"; | ||||||
|  |       elseif ($this->isRemains) $action = "--set-args"; | ||||||
|  |       elseif ($this->isHelp) $action = "--show-help"; | ||||||
|  |       elseif ($this->haveArgs) $action = "--set"; | ||||||
|  |       elseif ($this->value !== null) $action = "--set"; | ||||||
|  |       else $action = "--inc"; | ||||||
|  |     } | ||||||
|  |     if (is_string($action) && substr($action, 0, 2) === "--") { | ||||||
|  |       # fonction interne
 | ||||||
|  |     } else { | ||||||
|  |       $func = func::with($action); | ||||||
|  |       $action = "--func"; | ||||||
|  |     } | ||||||
|  |     $this->action = $action; | ||||||
|  |     $this->func = $func; | ||||||
|  | 
 | ||||||
|  |     $name = $this->name; | ||||||
|  |     $property = $this->property; | ||||||
|  |     $key = $this->key; | ||||||
|  |     if ($action !== "--func" && !$this->isRemains && | ||||||
|  |       $name === null && $property === null && $key === null | ||||||
|  |     ) { | ||||||
|  |       # si on ne précise pas le nom de la propriété, la dériver à partir du
 | ||||||
|  |       # nom de l'option la plus longue
 | ||||||
|  |       $longest = self::get_longest($this->_options, self::TYPE_LONG); | ||||||
|  |       $longest ??= self::get_longest($this->_options, self::TYPE_COMMAND); | ||||||
|  |       $longest ??= self::get_longest($this->_options, self::TYPE_SHORT); | ||||||
|  |       if ($longest !== null) { | ||||||
|  |         $longest = preg_replace('/[^A-Za-z0-9]+/', "_", $longest); | ||||||
|  |         # les options --no-name mettent à jour la valeur $name et inversent
 | ||||||
|  |         # le traitement
 | ||||||
|  |         if ($longest !== "no_" && str::del_prefix($longest, "no_")) { | ||||||
|  |           $this->inverse ??= true; | ||||||
|  |         } | ||||||
|  |         if (preg_match('/^[0-9]/', $longest)) { | ||||||
|  |           # le nom de la propriété ne doit pas commencer par un chiffre
 | ||||||
|  |           $longest = "p$longest"; | ||||||
|  |         } | ||||||
|  |         $name = $longest; | ||||||
|  |       } | ||||||
|  |     } elseif ($name === null && $property !== null) { | ||||||
|  |       $name = $property; | ||||||
|  |     } elseif ($name === null && $key !== null) { | ||||||
|  |       $name = $key; | ||||||
|  |     } | ||||||
|  |     $this->name = $name; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function afterSetup(): void { | ||||||
|  |     $this->disabled ??= false; | ||||||
|  |     $this->ensureArray ??= false; | ||||||
|  |     $this->inverse ??= false; | ||||||
|  |     if (str::del_prefix($this->help, "++")) { | ||||||
|  |       $this->show = false; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getOptions(): array { | ||||||
|  |     if ($this->disabled) return []; | ||||||
|  |     else return array_keys($this->_options); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function isEmpty(): bool { | ||||||
|  |     return $this->disabled || (!$this->_options && !$this->isRemains); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function printHelp(?array $what=null): void { | ||||||
|  |     $showDef = $what["show"] ?? $this->show; | ||||||
|  |     if (!$showDef || $this->isRemains) return; | ||||||
|  | 
 | ||||||
|  |     $prefix = $what["prefix"] ?? null; | ||||||
|  |     if ($prefix !== null) echo $prefix; | ||||||
|  | 
 | ||||||
|  |     $showOptions = $what["options"] ?? true; | ||||||
|  |     if ($showOptions) { | ||||||
|  |       echo "    "; | ||||||
|  |       echo implode(", ", array_keys($this->_options)); | ||||||
|  |       if ($this->haveArgs) { | ||||||
|  |         echo " "; | ||||||
|  |         echo $this->argsdesc; | ||||||
|  |       } | ||||||
|  |       echo "\n"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $showHelp = $what["help"] ?? true; | ||||||
|  |     if ($this->help && $showHelp) { | ||||||
|  |       echo str::indent($this->help, "        "); | ||||||
|  |       echo "\n"; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function action(&$dest, $value, ?string $arg, AbstractArgsParser $parser): void { | ||||||
|  |     if ($this->ensureArray) { | ||||||
|  |       varray::ensure($value); | ||||||
|  |     } elseif (is_array($value)) { | ||||||
|  |       $count = count($value); | ||||||
|  |       if ($count == 0) $value = null; | ||||||
|  |       elseif ($count == 1) $value = $value[0]; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     switch ($this->action) { | ||||||
|  |     case "--set": $this->actionSet($dest, $value); break; | ||||||
|  |     case "--inc": $this->actionInc($dest); break; | ||||||
|  |     case "--dec": $this->actionDec($dest); break; | ||||||
|  |     case "--add": $this->actionAdd($dest, $value); break; | ||||||
|  |     case "--adds": $this->actionAdds($dest, $value); break; | ||||||
|  |     case "--merge": $this->actionMerge($dest, $value); break; | ||||||
|  |     case "--merges": $this->actionMerges($dest, $value); break; | ||||||
|  |     case "--func": $this->func->bind($dest)->invoke([$value, $arg, $this]); break; | ||||||
|  |     case "--set-args": $this->actionSetArgs($dest, $value); break; | ||||||
|  |     case "--set-command": $this->actionSetCommand($dest, $value); break; | ||||||
|  |     case "--show-help": $parser->actionPrintHelp($arg); break; | ||||||
|  |     default: throw _exceptions::invalid_value($this->action, null, "action non supportée"); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionSet(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       oprop::set($dest, $this->property, $value); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       akey::set($dest, $this->key, $value); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       valx::set($dest, $this->name, $value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionInc(&$dest): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       if ($this->inverse) oprop::dec($dest, $this->property); | ||||||
|  |       else oprop::inc($dest, $this->property); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       if ($this->inverse) akey::dec($dest, $this->key); | ||||||
|  |       else akey::inc($dest, $this->key); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       if ($this->inverse) valx::dec($dest, $this->name); | ||||||
|  |       else valx::inc($dest, $this->name); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionDec(&$dest): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       if ($this->inverse) oprop::inc($dest, $this->property); | ||||||
|  |       else oprop::dec($dest, $this->property); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       if ($this->inverse) akey::inc($dest, $this->key); | ||||||
|  |       else akey::dec($dest, $this->key); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       if ($this->inverse) valx::inc($dest, $this->name); | ||||||
|  |       else valx::dec($dest, $this->name); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionAdd(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       oprop::append($dest, $this->property, $value); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       akey::append($dest, $this->key, $value); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       valx::append($dest, $this->name, $value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionAdds(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         oprop::append($dest, $this->property, $value); | ||||||
|  |       } | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         akey::append($dest, $this->key, $value); | ||||||
|  |       } | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         valx::append($dest, $this->name, $value); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionMerge(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       oprop::merge($dest, $this->property, $value); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       akey::merge($dest, $this->key, $value); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       valx::merge($dest, $this->name, $value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionMerges(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         oprop::merge($dest, $this->property, $value); | ||||||
|  |       } | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         akey::merge($dest, $this->key, $value); | ||||||
|  |       } | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       foreach (cl::with($value) as $value) { | ||||||
|  |         valx::merge($dest, $this->name, $value); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionSetArgs(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       oprop::set($dest, $this->property, $value); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       akey::set($dest, $this->key, $value); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       valx::set($dest, $this->name, $value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionSetCommand(&$dest, $value): void { | ||||||
|  |     if ($this->property !== null) { | ||||||
|  |       oprop::set($dest, $this->property, $value); | ||||||
|  |     } elseif ($this->key !== null) { | ||||||
|  |       akey::set($dest, $this->key, $value); | ||||||
|  |     } elseif ($this->name !== null) { | ||||||
|  |       valx::set($dest, $this->name, $value); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function __toString(): string { | ||||||
|  |     $options = implode(",", $this->getOptions()); | ||||||
|  |     $args = $this->haveArgs? " ({$this->minArgs}-{$this->maxArgs})": false; | ||||||
|  |     return "$options$args"; | ||||||
|  |   } | ||||||
|  |   private function debugTrace(string $message): void { | ||||||
|  |     $options = implode(",", cl::split_assoc($this->origDef)[0] ?? []); | ||||||
|  |     echo "$options $message\n"; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										36
									
								
								php/src/app/args/Aogroup.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								php/src/app/args/Aogroup.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Aogroup: groupe d'arguments fonctionnant ensemble | ||||||
|  |  */ | ||||||
|  | class Aogroup extends Aolist { | ||||||
|  |   function __construct(array $defs, bool $setup=false) { | ||||||
|  |     $marker = A::pop($defs, 0); | ||||||
|  |     if ($marker !== "group") { | ||||||
|  |       throw _exceptions::missing_value(null, null, "ce n'est pas un groupe valide"); | ||||||
|  |     } | ||||||
|  |     # réordonner les clés numériques
 | ||||||
|  |     $defs = array_merge($defs); | ||||||
|  |     parent::__construct($defs, $setup); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function printHelp(?array $what=null): void { | ||||||
|  |     $showGroup = $what["show"] ?? true; | ||||||
|  |     if (!$showGroup) return; | ||||||
|  | 
 | ||||||
|  |     $prefix = $what["prefix"] ?? null; | ||||||
|  |     if ($prefix !== null) echo $prefix; | ||||||
|  | 
 | ||||||
|  |     $firstAodef = null; | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $firstAodef ??= $aodef; | ||||||
|  |       $aodef->printHelp(["help" => false]); | ||||||
|  |     } | ||||||
|  |     if ($firstAodef !== null) { | ||||||
|  |       $firstAodef->printHelp(["options" => false]); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										268
									
								
								php/src/app/args/Aolist.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										268
									
								
								php/src/app/args/Aolist.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,268 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\str; | ||||||
|  | use const true; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Aodefs: une liste d'objets Aodef | ||||||
|  |  */ | ||||||
|  | abstract class Aolist { | ||||||
|  |   function __construct(array $defs, bool $setup=true) { | ||||||
|  |     $this->origDefs = $defs; | ||||||
|  |     $this->initDefs($defs, $setup); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $origDefs; | ||||||
|  | 
 | ||||||
|  |   protected ?array $aomain; | ||||||
|  |   protected ?array $aosections; | ||||||
|  |   protected ?array $aospecials; | ||||||
|  | 
 | ||||||
|  |   public ?Aodef $remainsArgdef = null; | ||||||
|  | 
 | ||||||
|  |   function initDefs(array $defs, bool $setup=true): void { | ||||||
|  |     $this->mergeParse($defs, $aobjects); | ||||||
|  |     $this->aomain = $aobjects["main"] ?? null; | ||||||
|  |     $this->aosections = $aobjects["sections"] ?? null; | ||||||
|  |     $this->aospecials = $aobjects["specials"] ?? null; | ||||||
|  |     if ($setup) $this->setup(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function mergeParse(array $defs, ?array &$aobjects, bool $parse=true): void { | ||||||
|  |     $aobjects ??= []; | ||||||
|  | 
 | ||||||
|  |     $merges = $defs["merges"] ?? null; | ||||||
|  |     $merge = $defs["merge"] ?? null; | ||||||
|  |     if ($merge !== null) $merges[] = $merge; | ||||||
|  |     if ($merges !== null) { | ||||||
|  |       foreach ($merges as $merge) { | ||||||
|  |         $this->mergeParse($merge, $aobjects, false); | ||||||
|  |         $this->parse($merge, $aobjects); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if ($parse) $this->parse($defs, $aobjects); | ||||||
|  | 
 | ||||||
|  |     $merge = $defs["merge_after"] ?? null; | ||||||
|  |     if ($merge !== null) { | ||||||
|  |       $this->mergeParse($merge, $aobjects, false); | ||||||
|  |       $this->parse($merge, $aobjects); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function parse(array $defs, array &$aobjects): void { | ||||||
|  |     [$defs, $params] = cl::split_assoc($defs); | ||||||
|  |     if ($defs !== null) { | ||||||
|  |       $aomain =& $aobjects["main"]; | ||||||
|  |       foreach ($defs as $def) { | ||||||
|  |         $first = $def[0] ?? null; | ||||||
|  |         if ($first === "group") { | ||||||
|  |           $aobject = new Aogroup($def); | ||||||
|  |         } else { | ||||||
|  |           $aobject = new Aodef($def); | ||||||
|  |         } | ||||||
|  |         $aomain[] = $aobject; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $sections = $params["sections"] ?? null; | ||||||
|  |     if ($sections !== null) { | ||||||
|  |       $aosections =& $aobjects["sections"]; | ||||||
|  |       $index = 0; | ||||||
|  |       foreach ($sections as $key => $section) { | ||||||
|  |         if ($key === $index) { | ||||||
|  |           $index++; | ||||||
|  |           $aosections[] = new Aosection($section); | ||||||
|  |         } else { | ||||||
|  |           /** @var Aosection $aosection */ | ||||||
|  |           $aosection = $aosections[$key] ?? null; | ||||||
|  |           if ($aosection === null) { | ||||||
|  |             $aosections[$key] = new Aosection($section); | ||||||
|  |           } else { | ||||||
|  |             #XXX il faut implémenter la fusion en cas de section existante
 | ||||||
|  |             # pour le moment, la liste existante est écrasée
 | ||||||
|  |             $aosection->initDefs($section); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $this->parseParams($params); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function parseParams(?array $params): void { | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function all(?array $what=null): iterable { | ||||||
|  |     $returnsAodef = $what["aodef"] ?? true; | ||||||
|  |     $returnsAolist = $what["aolist"] ?? false; | ||||||
|  |     $returnExtends = $what["extends"] ?? false; | ||||||
|  |     $withSpecials = $what["aospecials"] ?? true; | ||||||
|  |     # lister les sections avant, pour que les options de la section principale
 | ||||||
|  |     # soient prioritaires
 | ||||||
|  |     $aosections = $this->aosections; | ||||||
|  |     if ($aosections !== null) { | ||||||
|  |       /** @var Aosection $aobject */ | ||||||
|  |       foreach ($aosections as $aosection) { | ||||||
|  |         if ($returnsAolist) { | ||||||
|  |           yield $aosection; | ||||||
|  |         } elseif ($returnsAodef) { | ||||||
|  |           yield from $aosection->all($what); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $aomain = $this->aomain; | ||||||
|  |     if ($aomain !== null) { | ||||||
|  |       /** @var Aodef $aobject */ | ||||||
|  |       foreach ($aomain as $aobject) { | ||||||
|  |         if ($aobject instanceof Aodef) { | ||||||
|  |           if ($returnsAodef) { | ||||||
|  |             if ($returnExtends) { | ||||||
|  |               if ($aobject->isExtends()) yield $aobject; | ||||||
|  |             } else { | ||||||
|  |               if (!$aobject->isExtends()) yield $aobject; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } elseif ($aobject instanceof Aolist) { | ||||||
|  |           if ($returnsAolist) { | ||||||
|  |             yield $aobject; | ||||||
|  |           } elseif ($returnsAodef) { | ||||||
|  |             yield from $aobject->all($what); | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $aospecials = $this->aospecials; | ||||||
|  |     if ($withSpecials && $aospecials !== null) { | ||||||
|  |       /** @var Aodef $aobject */ | ||||||
|  |       foreach ($aospecials as $aobject) { | ||||||
|  |         yield $aobject; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function filter(callable $callback): void { | ||||||
|  |     $aomain = $this->aomain; | ||||||
|  |     if ($aomain !== null) { | ||||||
|  |       $filtered = []; | ||||||
|  |       /** @var Aodef $aobject */ | ||||||
|  |       foreach ($aomain as $aobject) { | ||||||
|  |         if ($aobject instanceof Aolist) { | ||||||
|  |           $aobject->filter($callback); | ||||||
|  |         } | ||||||
|  |         if (call_user_func($callback, $aobject)) { | ||||||
|  |           $filtered[] = $aobject; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       $this->aomain = $filtered; | ||||||
|  |     } | ||||||
|  |     $aosections = $this->aosections; | ||||||
|  |     if ($aosections !== null) { | ||||||
|  |       $filtered = []; | ||||||
|  |       /** @var Aosection $aosection */ | ||||||
|  |       foreach ($aosections as $aosection) { | ||||||
|  |         $aosection->filter($callback); | ||||||
|  |         if (call_user_func($callback, $aosection)) { | ||||||
|  |           $filtered[] = $aosection; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       $this->aosections = $filtered; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function setup(): void { | ||||||
|  |     # calculer les options
 | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $aodef->setup1(); | ||||||
|  |     } | ||||||
|  |     /** @var Aodef $aodef */ | ||||||
|  |     foreach ($this->all(["extends" => true]) as $aodef) { | ||||||
|  |       $aodef->setup1(true, $this); | ||||||
|  |     } | ||||||
|  |     # ne garder que les objets non vides
 | ||||||
|  |     $this->filter(function($aobject): bool { | ||||||
|  |       if ($aobject instanceof Aodef) { | ||||||
|  |         return !$aobject->isEmpty(); | ||||||
|  |       } elseif ($aobject instanceof Aolist) { | ||||||
|  |         return !$aobject->isEmpty(); | ||||||
|  |       } else { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     # puis calculer nombre d'arguments et actions
 | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $aodef->setup2(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function isEmpty(): bool { | ||||||
|  |     foreach ($this->all() as $aobject) { | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function get(string $option): ?Aodef { | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function actionPrintHelp(string $arg): void { | ||||||
|  |     $this->printHelp([ | ||||||
|  |       "show_all" => $arg === "--help++", | ||||||
|  |     ]); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function printHelp(?array $what=null): void { | ||||||
|  |     $show = $what["show_all"] ?? false; | ||||||
|  |     if (!$show) $show = null; | ||||||
|  | 
 | ||||||
|  |     $aosections = $this->aosections; | ||||||
|  |     if ($aosections !== null) { | ||||||
|  |       /** @var Aosection $aosection */ | ||||||
|  |       foreach ($aosections as $aosection) { | ||||||
|  |         $aosection->printHelp(cl::merge($what, [ | ||||||
|  |           "show" => $show, | ||||||
|  |           "prefix" => "\n", | ||||||
|  |         ])); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $aomain = $this->aomain; | ||||||
|  |     if ($aomain !== null) { | ||||||
|  |       echo "\nOPTIONS\n"; | ||||||
|  |       foreach ($aomain as $aobject) { | ||||||
|  |         $aobject->printHelp(cl::merge($what, [ | ||||||
|  |           "show" => $show, | ||||||
|  |         ])); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function __toString(): string { | ||||||
|  |     $items = []; | ||||||
|  |     $what = [ | ||||||
|  |       "aodef" => true, | ||||||
|  |       "aolist" => true, | ||||||
|  |     ]; | ||||||
|  |     foreach ($this->all($what) as $aobject) { | ||||||
|  |       if ($aobject instanceof Aodef) { | ||||||
|  |         $items[] = strval($aobject); | ||||||
|  |       } elseif ($aobject instanceof Aogroup) { | ||||||
|  |         $items[] = implode("\n", [ | ||||||
|  |           "group", | ||||||
|  |           str::indent(strval($aobject)), | ||||||
|  |         ]); | ||||||
|  |       } elseif ($aobject instanceof Aosection) { | ||||||
|  |         $items[] = implode("\n", [ | ||||||
|  |           "section", | ||||||
|  |           str::indent(strval($aobject)), | ||||||
|  |         ]); | ||||||
|  |       } else { | ||||||
|  |         $items[] = false; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return implode("\n", $items); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										45
									
								
								php/src/app/args/Aosection.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								php/src/app/args/Aosection.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,45 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\php\types\vbool; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Aosection: un regroupement d'arguments pour améliorer la mise en forme | ||||||
|  |  * de l'affichage de l'aide | ||||||
|  |  */ | ||||||
|  | class Aosection extends Aolist { | ||||||
|  |   function __construct(array $defs, bool $setup=false) { | ||||||
|  |     parent::__construct($defs, $setup); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public bool $show = true; | ||||||
|  |   public ?string $prefix = null; | ||||||
|  |   public ?string $title = null; | ||||||
|  |   public ?string $description = null; | ||||||
|  |   public ?string $suffix = null; | ||||||
|  | 
 | ||||||
|  |   protected function parseParams(?array $params): void { | ||||||
|  |     $this->show = vbool::with($params["show"] ?? true); | ||||||
|  |     $this->prefix ??= $params["prefix"] ?? null; | ||||||
|  |     $this->title ??= $params["title"] ?? null; | ||||||
|  |     $this->description ??= $params["description"] ?? null; | ||||||
|  |     $this->suffix ??= $params["suffix"] ?? null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function printHelp(?array $what=null): void { | ||||||
|  |     $showSection = $what["show"] ?? $this->show; | ||||||
|  |     if (!$showSection) return; | ||||||
|  | 
 | ||||||
|  |     $prefix = $what["prefix"] ?? null; | ||||||
|  |     if ($prefix !== null) echo $prefix; | ||||||
|  | 
 | ||||||
|  |     if ($this->prefix) echo "{$this->prefix}\n"; | ||||||
|  |     if ($this->title) echo "{$this->title}\n"; | ||||||
|  |     if ($this->description) echo "\n{$this->description}\n"; | ||||||
|  |     /** @var Aodef|Aolist $aobject */ | ||||||
|  |     foreach ($this->all(["aolist" => true]) as $aobject) { | ||||||
|  |       $aobject->printHelp(); | ||||||
|  |     } | ||||||
|  |     if ($this->suffix) echo "{$this->suffix}\n"; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										7
									
								
								php/src/app/args/ArgsException.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								php/src/app/args/ArgsException.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\UserException; | ||||||
|  | 
 | ||||||
|  | class ArgsException extends UserException { | ||||||
|  | } | ||||||
							
								
								
									
										185
									
								
								php/src/app/args/SimpleAolist.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										185
									
								
								php/src/app/args/SimpleAolist.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,185 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\php\types\vbool; | ||||||
|  | use nulib\str; | ||||||
|  | use const true; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class SimpleArgdefs: une définition simple des arguments et des options | ||||||
|  |  * valides d'un programme: les commandes ne sont pas supportées, ni les suites | ||||||
|  |  * de commandes | ||||||
|  |  * | ||||||
|  |  * i.e | ||||||
|  |  *   -x --long            est supporté | ||||||
|  |  *   cmd -a -b            n'est PAS supporté | ||||||
|  |  *   cmd1 -x // cmd2 -y   n'est PAS supporté
 | ||||||
|  |  */ | ||||||
|  | class SimpleAolist extends Aolist { | ||||||
|  |   public ?string $prefix = null; | ||||||
|  |   public ?string $name = null; | ||||||
|  |   public ?string $purpose = null; | ||||||
|  |   public $usage = null; | ||||||
|  |   public ?string $description = null; | ||||||
|  |   public ?string $suffix = null; | ||||||
|  | 
 | ||||||
|  |   public ?string $commandname = null; | ||||||
|  |   public ?string $commandproperty = null; | ||||||
|  |   public ?string $commandkey = null; | ||||||
|  | 
 | ||||||
|  |   public ?string $argsname = null; | ||||||
|  |   public ?string $argsproperty = null; | ||||||
|  |   public ?string $argskey = null; | ||||||
|  | 
 | ||||||
|  |   public ?bool $autohelp = null; | ||||||
|  |   public ?bool $autoremains = null; | ||||||
|  | 
 | ||||||
|  |   protected array $index; | ||||||
|  | 
 | ||||||
|  |   protected function parseParams(?array $params): void { | ||||||
|  |     # méta-informations
 | ||||||
|  |     $this->prefix ??= $params["prefix"] ?? null; | ||||||
|  |     $this->name ??= $params["name"] ?? null; | ||||||
|  |     $this->purpose ??= $params["purpose"] ?? null; | ||||||
|  |     $this->usage ??= $params["usage"] ?? null; | ||||||
|  |     $this->description ??= $params["description"] ?? null; | ||||||
|  |     $this->suffix ??= $params["suffix"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->commandname ??= $params["commandname"] ?? null; | ||||||
|  |     $this->commandproperty ??= $params["commandproperty"] ?? null; | ||||||
|  |     $this->commandkey ??= $params["commandkey"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->argsname ??= $params["argsname"] ?? null; | ||||||
|  |     $this->argsproperty ??= $params["argsproperty"] ?? null; | ||||||
|  |     $this->argskey ??= $params["argskey"] ?? null; | ||||||
|  | 
 | ||||||
|  |     $this->autohelp ??= vbool::withn($params["autohelp"] ?? null); | ||||||
|  |     $this->autoremains ??= vbool::withn($params["autoremains"] ?? null); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** @return string[] */ | ||||||
|  |   function getOptions(): array { | ||||||
|  |     return array_keys($this->index); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function indexAodefs(): void { | ||||||
|  |     $this->index = []; | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $options = $aodef->getOptions(); | ||||||
|  |       foreach ($options as $option) { | ||||||
|  |         /** @var Aodef $prevAodef */ | ||||||
|  |         $prevAodef = $this->index[$option] ?? null; | ||||||
|  |         if ($prevAodef !== null) $prevAodef->removeOption($option); | ||||||
|  |         $this->index[$option] = $aodef; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function setup(): void { | ||||||
|  |     # calculer les options pour les objets déjà fusionnés
 | ||||||
|  |     /** @var Aodef $aodef */ | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $aodef->setup1(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     # puis traiter les extensions d'objets et calculer les options pour ces
 | ||||||
|  |     # objets sur la base de l'index que l'on crée une première fois
 | ||||||
|  |     $this->indexAodefs(); | ||||||
|  |     /** @var Aodef $aodef */ | ||||||
|  |     foreach ($this->all(["extends" => true]) as $aodef) { | ||||||
|  |       $aodef->setup1(true, $this); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     # ne garder que les objets non vides
 | ||||||
|  |     $this->filter(function($aobject) { | ||||||
|  |       if ($aobject instanceof Aodef) { | ||||||
|  |         return !$aobject->isEmpty(); | ||||||
|  |       } elseif ($aobject instanceof Aolist) { | ||||||
|  |         return !$aobject->isEmpty(); | ||||||
|  |       } else { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  | 
 | ||||||
|  |     # rajouter remains et help si nécessaire
 | ||||||
|  |     $this->aospecials = []; | ||||||
|  |     $helpArgdef = null; | ||||||
|  |     $remainsArgdef = null; | ||||||
|  |     /** @var Aodef $aodef */ | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       if ($aodef->isHelp) $helpArgdef = $aodef; | ||||||
|  |       if ($aodef->isRemains) $remainsArgdef = $aodef; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $this->autohelp ??= true; | ||||||
|  |     if ($helpArgdef === null && $this->autohelp) { | ||||||
|  |       $helpArgdef = new Aodef([ | ||||||
|  |         "--help", "--help++", | ||||||
|  |         "action" => "--show-help", | ||||||
|  |         "help" => "Afficher l'aide", | ||||||
|  |       ]); | ||||||
|  |       $helpArgdef->setup1(); | ||||||
|  |       $this->aospecials[] = $helpArgdef; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $this->autoremains ??= true; | ||||||
|  |     if ($remainsArgdef === null && $this->autoremains) { | ||||||
|  |       $remainsArgdef = new Aodef([ | ||||||
|  |         "args" => [null], | ||||||
|  |         "action" => "--set-args", | ||||||
|  |         "name" => $this->argsname ?? "args", | ||||||
|  |         "property" => $this->argsproperty, | ||||||
|  |         "key" => $this->argskey, | ||||||
|  |       ]); | ||||||
|  |       $remainsArgdef->setup1(); | ||||||
|  |       $this->aospecials[] = $remainsArgdef; | ||||||
|  |     } | ||||||
|  |     $this->remainsArgdef = $remainsArgdef; | ||||||
|  | 
 | ||||||
|  |     # puis calculer nombre d'arguments et actions
 | ||||||
|  |     $this->indexAodefs(); | ||||||
|  |     /** @var Aodef $aodef */ | ||||||
|  |     foreach ($this->all() as $aodef) { | ||||||
|  |       $aodef->setup2(); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function get(string $option): ?Aodef { | ||||||
|  |     return $this->index[$option] ?? null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function printHelp(?array $what = null): void { | ||||||
|  |     $showList = $what["show"] ?? true; | ||||||
|  |     if (!$showList) return; | ||||||
|  | 
 | ||||||
|  |     $prefix = $what["prefix"] ?? null; | ||||||
|  |     if ($prefix !== null) echo $prefix; | ||||||
|  | 
 | ||||||
|  |     if ($this->prefix) echo "{$this->prefix}\n"; | ||||||
|  |     if ($this->purpose) { | ||||||
|  |       echo "{$this->name}: {$this->purpose}\n"; | ||||||
|  |     } elseif (!$this->prefix) { | ||||||
|  |       # s'il y a un préfixe sans purpose, il remplace purpose
 | ||||||
|  |       echo "{$this->name}\n"; | ||||||
|  |     } | ||||||
|  |     if ($this->usage) { | ||||||
|  |       echo "\nUSAGE\n"; | ||||||
|  |       foreach (cl::with($this->usage) as $usage) { | ||||||
|  |         echo "    {$this->name} $usage\n"; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     if ($this->description) echo "\n{$this->description}\n"; | ||||||
|  |     parent::printHelp($what); | ||||||
|  |     if ($this->suffix) echo "{$this->suffix}\n"; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function __toString(): string { | ||||||
|  |     return implode("\n", [ | ||||||
|  |       "objects:", | ||||||
|  |       str::indent(parent::__toString()), | ||||||
|  |       "index:", | ||||||
|  |       str::indent(implode("\n", array_keys($this->index))), | ||||||
|  |     ]); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										247
									
								
								php/src/app/args/SimpleArgsParser.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										247
									
								
								php/src/app/args/SimpleArgsParser.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,247 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\ExitError; | ||||||
|  | use nulib\StateException; | ||||||
|  | 
 | ||||||
|  | class SimpleArgsParser extends AbstractArgsParser { | ||||||
|  |   function __construct(array $defs) { | ||||||
|  |     global $argv; | ||||||
|  |     $defs["name"] ??= basename($argv[0]); | ||||||
|  |     $this->aolist = new SimpleAolist($defs); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected SimpleAolist $aolist; | ||||||
|  | 
 | ||||||
|  |   protected function getArgdef(string $option): ?Aodef { | ||||||
|  |     return $this->aolist->get($option); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function getOptions(): array { | ||||||
|  |     return $this->aolist->getOptions(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function normalize(array $args): array { | ||||||
|  |     $i = 0; | ||||||
|  |     $max = count($args); | ||||||
|  |     $options = []; | ||||||
|  |     $remains = []; | ||||||
|  |     $parseOpts = true; | ||||||
|  |     while ($i < $max) { | ||||||
|  |       $arg = $args[$i++]; | ||||||
|  |       if (!$parseOpts) { | ||||||
|  |         # le reste n'est que des arguments
 | ||||||
|  |         $remains[] = $arg; | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  |       if ($arg === "--") { | ||||||
|  |         # fin des options
 | ||||||
|  |         $parseOpts = false; | ||||||
|  |         continue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (substr($arg, 0, 2) === "--") { | ||||||
|  |         #######################################################################
 | ||||||
|  |         # option longue
 | ||||||
|  |         $pos = strpos($arg, "="); | ||||||
|  |         if ($pos !== false) { | ||||||
|  |           # option avec valeur
 | ||||||
|  |           $option = substr($arg, 0, $pos); | ||||||
|  |           $value = substr($arg, $pos + 1); | ||||||
|  |         } else { | ||||||
|  |           # option sans valeur
 | ||||||
|  |           $option = $arg; | ||||||
|  |           $value = null; | ||||||
|  |         } | ||||||
|  |         $argdef = $this->getArgdef($option); | ||||||
|  |         if ($argdef === null) { | ||||||
|  |           # chercher une correspondance
 | ||||||
|  |           $len = strlen($option); | ||||||
|  |           $candidates = []; | ||||||
|  |           foreach ($this->getOptions() as $candidate) { | ||||||
|  |             if (substr($candidate, 0, $len) === $option) { | ||||||
|  |               $candidates[] = $candidate; | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |           switch (count($candidates)) { | ||||||
|  |           case 0: throw $this->invalidArg($option); | ||||||
|  |           case 1: $option = $candidates[0]; break; | ||||||
|  |           default: throw $this->ambiguousArg($option, $candidates); | ||||||
|  |           } | ||||||
|  |           $argdef = $this->getArgdef($option); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         if ($argdef->haveArgs) { | ||||||
|  |           $minArgs = $argdef->minArgs; | ||||||
|  |           $maxArgs = $argdef->maxArgs; | ||||||
|  |           $values = []; | ||||||
|  |           if ($value !== null) { | ||||||
|  |             $values[] = $value; | ||||||
|  |             $offset = 1; | ||||||
|  |           } elseif ($minArgs == 0) { | ||||||
|  |             # cas particulier: la première valeur doit être collée à l'option
 | ||||||
|  |             # si $maxArgs == 1
 | ||||||
|  |             $offset = $maxArgs == 1 ? 1 : 0; | ||||||
|  |           } else { | ||||||
|  |             $offset = 0; | ||||||
|  |           } | ||||||
|  |           $this->checkEnoughArgs($option, | ||||||
|  |             self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); | ||||||
|  | 
 | ||||||
|  |           if ($minArgs == 0 && $maxArgs == 1) { | ||||||
|  |             # cas particulier: la première valeur doit être collée à l'option
 | ||||||
|  |             if (count($values) > 0) { | ||||||
|  |               $options[] = "$option=$values[0]"; | ||||||
|  |               $values = array_slice($values, 1); | ||||||
|  |             } else { | ||||||
|  |               $options[] = $option; | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             $options[] = $option; | ||||||
|  |           } | ||||||
|  |           $options = array_merge($options, $values); | ||||||
|  |         } elseif ($value !== null) { | ||||||
|  |           throw $this->tooManyArgs(1, 0, $option); | ||||||
|  |         } else { | ||||||
|  |           $options[] = $option; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |       } elseif (substr($arg, 0, 1) === "-") { | ||||||
|  |         #######################################################################
 | ||||||
|  |         # option courte
 | ||||||
|  |         $pos = 1; | ||||||
|  |         $len = strlen($arg); | ||||||
|  |         while ($pos < $len) { | ||||||
|  |           $option = "-".substr($arg, $pos, 1); | ||||||
|  |           $argdef = $this->getArgdef($option); | ||||||
|  |           if ($argdef === null) throw $this->invalidArg($option); | ||||||
|  |           if ($argdef->haveArgs) { | ||||||
|  |             $minArgs = $argdef->minArgs; | ||||||
|  |             $maxArgs = $argdef->maxArgs; | ||||||
|  |             $values = []; | ||||||
|  |             if ($len > $pos + 1) { | ||||||
|  |               $values[] = substr($arg, $pos + 1); | ||||||
|  |               $offset = 1; | ||||||
|  |               $pos = $len; | ||||||
|  |             } elseif ($minArgs == 0) { | ||||||
|  |               # cas particulier: la première valeur doit être collée à l'option
 | ||||||
|  |               # si $maxArgs == 1
 | ||||||
|  |               $offset = $maxArgs == 1 ? 1 : 0; | ||||||
|  |             } else { | ||||||
|  |               $offset = 0; | ||||||
|  |             } | ||||||
|  |             $this->checkEnoughArgs($option, | ||||||
|  |               self::consume_args($args, $i, $values, $offset, $minArgs, $maxArgs, true)); | ||||||
|  | 
 | ||||||
|  |             if ($minArgs == 0 && $maxArgs == 1) { | ||||||
|  |               # cas particulier: la première valeur doit être collée à l'option
 | ||||||
|  |               if (count($values) > 0) { | ||||||
|  |                 $options[] = "$option$values[0]"; | ||||||
|  |                 $values = array_slice($values, 1); | ||||||
|  |               } else { | ||||||
|  |                 $options[] = $option; | ||||||
|  |               } | ||||||
|  |             } else { | ||||||
|  |               $options[] = $option; | ||||||
|  |             } | ||||||
|  |             $options = array_merge($options, $values); | ||||||
|  |           } else { | ||||||
|  |             $options[] = $option; | ||||||
|  |           } | ||||||
|  |           $pos++; | ||||||
|  |         } | ||||||
|  |       } else { | ||||||
|  |         #XXX implémenter les commandes
 | ||||||
|  | 
 | ||||||
|  |         #######################################################################
 | ||||||
|  |         # argument
 | ||||||
|  |         $remains[] = $arg; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return array_merge($options, ["--"], $remains); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function process(array $args) { | ||||||
|  |     $i = 0; | ||||||
|  |     $max = count($args); | ||||||
|  |     # d'abord traiter les options
 | ||||||
|  |     while ($i < $max) { | ||||||
|  |       $arg = $args[$i++]; | ||||||
|  |       if ($arg === "--") { | ||||||
|  |         # fin des options
 | ||||||
|  |         break; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       if (preg_match('/^(--[^=]+)(?:=(.*))?/', $arg, $ms)) { | ||||||
|  |         # option longue
 | ||||||
|  |       } elseif (preg_match('/^(-.)(.+)?/', $arg, $ms)) { | ||||||
|  |         # option courte
 | ||||||
|  |       } else { | ||||||
|  |         # commande
 | ||||||
|  |         throw StateException::unexpected_state("commands are not supported"); | ||||||
|  |       } | ||||||
|  |       $option = $ms[1]; | ||||||
|  |       $ovalue = $ms[2] ?? null; | ||||||
|  |       $argdef = $this->getArgdef($option); | ||||||
|  |       if ($argdef === null) throw StateException::unexpected_state(); | ||||||
|  |       $defvalue = $argdef->value; | ||||||
|  |       if ($argdef->haveArgs) { | ||||||
|  |         $minArgs = $argdef->minArgs; | ||||||
|  |         $maxArgs = $argdef->maxArgs; | ||||||
|  |         if ($minArgs == 0 && $maxArgs == 1) { | ||||||
|  |           # argument facultatif
 | ||||||
|  |           if ($ovalue !== null) $value = [$ovalue]; | ||||||
|  |           else $value = cl::with($defvalue); | ||||||
|  |           $offset = 1; | ||||||
|  |         } else { | ||||||
|  |           $value = []; | ||||||
|  |           $offset = 0; | ||||||
|  |         } | ||||||
|  |         self::consume_args($args, $i, $value, $offset, $minArgs, $maxArgs, false); | ||||||
|  |       } else { | ||||||
|  |         $value = $defvalue; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|  |       $this->action($value, $arg, $argdef); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     # construire la liste des arguments qui restent
 | ||||||
|  |     $args = array_slice($args, $i); | ||||||
|  |     $i = 0; | ||||||
|  |     $max = count($args); | ||||||
|  |     $argdef = $this->aolist->remainsArgdef; | ||||||
|  |     if ($argdef !== null && $argdef->haveArgs) { | ||||||
|  |       $minArgs = $argdef->minArgs; | ||||||
|  |       $maxArgs = $argdef->maxArgs; | ||||||
|  |       if ($maxArgs == PHP_INT_MAX) { | ||||||
|  |         # cas particulier: si le nombre d'arguments restants est non borné,
 | ||||||
|  |         # les prendre tous sans distinction ni traitement de '--'
 | ||||||
|  |         $value = $args; | ||||||
|  |         # mais tester tout de même s'il y a le minimum requis d'arguments
 | ||||||
|  |         $this->checkEnoughArgs(null,  $minArgs - $max); | ||||||
|  |       } else { | ||||||
|  |         $value = []; | ||||||
|  |         $this->checkEnoughArgs(null, | ||||||
|  |           self::consume_args($args, $i, $value, 0, $minArgs, $maxArgs, false)); | ||||||
|  |         if ($i <= $max - 1) throw $this->tooManyArgs($max, $i); | ||||||
|  |       } | ||||||
|  |       $this->action($value, null, $argdef); | ||||||
|  |     } elseif ($i <= $max - 1) { | ||||||
|  |       throw $this->tooManyArgs($max, $i); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function action($value, ?string $arg, Aodef $argdef) { | ||||||
|  |     $argdef->action($this->dest, $value, $arg, $this); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   public function actionPrintHelp(string $arg): void { | ||||||
|  |     $this->aolist->actionPrintHelp($arg); | ||||||
|  |     throw new ExitError(0); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function showDebugInfos() { | ||||||
|  |     echo $this->aolist."\n"; #XXX
 | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										20
									
								
								php/src/app/args/TODO.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								php/src/app/args/TODO.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | |||||||
|  | # nulib\app\args | ||||||
|  | 
 | ||||||
|  | * [ ] transformer un schéma en définition d'arguments, un tableau en liste d'arguments, et vice-versa | ||||||
|  | * [ ] faire une implémentation ArgsParser qui supporte les commandes, et les options dynamiques | ||||||
|  |   * commandes: | ||||||
|  |     `program [options] command [options]` | ||||||
|  |   * multi-commandes: | ||||||
|  |     `program [options] command [options] // command [options] // ...` | ||||||
|  |   * dynamique: la liste des options et des commandes supportées est calculée dynamiquement | ||||||
|  | 
 | ||||||
|  | ## support des commandes | ||||||
|  | 
 | ||||||
|  | faire une interface Runnable qui représente un composant pouvant être exécuté. | ||||||
|  | Application implémente Runnable, mais l'analyse des arguments peut retourner une | ||||||
|  | autre instance de runnable pour faciliter l'implémentation de différents | ||||||
|  | sous-outils | ||||||
|  | 
 | ||||||
|  | ## BUGS | ||||||
|  | 
 | ||||||
|  | -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary | ||||||
							
								
								
									
										10
									
								
								php/src/app/args/_exceptions.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								php/src/app/args/_exceptions.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,10 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\args; | ||||||
|  | 
 | ||||||
|  | use nulib\exceptions; | ||||||
|  | 
 | ||||||
|  | class _exceptions extends exceptions { | ||||||
|  |   const EXCEPTION = ArgsException::class; | ||||||
|  | 
 | ||||||
|  |   const WORD = "masc:l'argument#s"; | ||||||
|  | } | ||||||
							
								
								
									
										354
									
								
								php/src/app/cli/Application.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										354
									
								
								php/src/app/cli/Application.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,354 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\cli; | ||||||
|  | 
 | ||||||
|  | use Exception; | ||||||
|  | use nulib\app\app; | ||||||
|  | use nulib\app\args\AbstractArgsParser; | ||||||
|  | use nulib\app\args\ArgsException; | ||||||
|  | use nulib\app\args\SimpleArgsParser; | ||||||
|  | use nulib\app\config; | ||||||
|  | use nulib\app\RunFile; | ||||||
|  | use nulib\ExitError; | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | use nulib\output\con; | ||||||
|  | use nulib\output\log; | ||||||
|  | use nulib\output\msg; | ||||||
|  | use nulib\output\say; | ||||||
|  | use nulib\output\std\ConsoleMessenger; | ||||||
|  | use nulib\output\std\LogMessenger; | ||||||
|  | use nulib\output\std\ProxyMessenger; | ||||||
|  | use nulib\ref\ref_profiles; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class Application: application de base | ||||||
|  |  */ | ||||||
|  | abstract class Application { | ||||||
|  |   /** @var string répertoire du projet (celui qui contient composer.json */ | ||||||
|  |   const PROJDIR = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var array répertoires vendor exprimés relativement à PROJDIR | ||||||
|  |    * | ||||||
|  |    * les clés suivantes doivent être présentes dans le tableau: | ||||||
|  |    * - autoload (chemin vers vendor/autoload.php) | ||||||
|  |    * - bindir (chemin vers vendor/bin) | ||||||
|  |    */ | ||||||
|  |   const VENDOR = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var string code du projet, utilisé pour dériver le noms de certains des | ||||||
|  |    * paramètres extraits de l'environnement, e.g MY_APP_DATADIR si le projet a | ||||||
|  |    * pour code my-app | ||||||
|  |    * | ||||||
|  |    * si non définie, cette valeur est calculée automatiquement à partir de | ||||||
|  |    * self::PROJDIR sans le suffixe "-app" | ||||||
|  |    */ | ||||||
|  |   const PROJCODE = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var string|null identifiant d'un groupe auquel l'application appartient. | ||||||
|  |    * les applications du même groupe enregistrent leur fichiers de controle au | ||||||
|  |    * même endroit $VARDIR/$APPGROUP | ||||||
|  |    */ | ||||||
|  |   const APPGROUP = null; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var string code de l'application, utilisé pour inférer le nom de certains | ||||||
|  |    * fichiers spécifiques à l'application. | ||||||
|  |    * | ||||||
|  |    * si non définie, cette valeur est calculée automatiquement à partir de | ||||||
|  |    * static::class | ||||||
|  |    */ | ||||||
|  |   const NAME = null; | ||||||
|  | 
 | ||||||
|  |   /** @var string description courte de l'application */ | ||||||
|  |   const TITLE = null; | ||||||
|  | 
 | ||||||
|  |   const DATADIR = null; | ||||||
|  |   const ETCDIR = null; | ||||||
|  |   const VARDIR = null; | ||||||
|  |   const CACHEDIR = null; | ||||||
|  |   const LOGDIR = null; | ||||||
|  | 
 | ||||||
|  |   /** @var bool faut-il activer automatiquement l'écriture dans les logs */ | ||||||
|  |   const USE_LOGFILE = null; | ||||||
|  | 
 | ||||||
|  |   /** @var bool faut-il maintenir un fichier de suivi du process? */ | ||||||
|  |   const USE_RUNFILE = false; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var bool faut-il empêcher deux instances de cette application de se lancer | ||||||
|  |    * en même temps? | ||||||
|  |    * | ||||||
|  |    * nécessite USE_RUNFILE==true | ||||||
|  |    */ | ||||||
|  |   const USE_RUNLOCK = false; | ||||||
|  | 
 | ||||||
|  |   /** @var bool faut-il installer le gestionnaire de signaux? */ | ||||||
|  |   const INSTALL_SIGNAL_HANDLER = false; | ||||||
|  | 
 | ||||||
|  |   private static function _info(string $message, int $ec=0): int { | ||||||
|  |     fwrite(STDERR, "INFO: $message\n"); | ||||||
|  |     return $ec; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   private static function _error(string $message, int $ec=1): int { | ||||||
|  |     fwrite(STDERR, "ERROR: $message\n"); | ||||||
|  |     return $ec; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function _manage_runfile(int &$argc, array &$argv, RunFile $runfile): void { | ||||||
|  |     if ($argc <= 1 || $argv[1] !== "//") return; | ||||||
|  |     array_splice($argv, 1, 1); $argc--; | ||||||
|  |     $ec = 0; | ||||||
|  |     switch ($argv[1] ?? "infos") { | ||||||
|  |     case "help": | ||||||
|  |       self::_info(<<<EOT | ||||||
|  | Valid commands: | ||||||
|  |   infos | ||||||
|  |   dump | ||||||
|  |   reset | ||||||
|  |   release | ||||||
|  |   start | ||||||
|  |   kill | ||||||
|  | 
 | ||||||
|  | EOT); | ||||||
|  |       break; | ||||||
|  |     case "infos": | ||||||
|  |     case "i": | ||||||
|  |       $desc = $runfile->getDesc(); | ||||||
|  |       echo implode("\n", $desc["message"])."\n"; | ||||||
|  |       $ec = $desc["exitcode"] ?? 0; | ||||||
|  |       break; | ||||||
|  |     case "dump": | ||||||
|  |     case "d": | ||||||
|  |       yaml::dump($runfile->read()); | ||||||
|  |       break; | ||||||
|  |     case "reset": | ||||||
|  |     case "z": | ||||||
|  |       if (!$runfile->isRunning()) $runfile->reset(); | ||||||
|  |       else $ec = self::_error("cannot reset while running"); | ||||||
|  |       break; | ||||||
|  |     case "release": | ||||||
|  |     case "rl": | ||||||
|  |       $runfile->release(); | ||||||
|  |       break; | ||||||
|  |     case "start": | ||||||
|  |     case "s": | ||||||
|  |       array_splice($argv, 1, 1); $argc--; | ||||||
|  |       return; | ||||||
|  |     case "kill": | ||||||
|  |     case "k": | ||||||
|  |       if ($runfile->isRunning()) $runfile->wfKill(); | ||||||
|  |       else $ec = self::_error("not running"); | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       $ec = self::_error("$argv[1]: unexpected command", app::EC_BAD_COMMAND); | ||||||
|  |     } | ||||||
|  |     exit($ec); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static function run(?Application $app=null): void { | ||||||
|  |     $unlock = false; | ||||||
|  |     $stop = false; | ||||||
|  |     $shutdown = function () use (&$unlock, &$stop) { | ||||||
|  |       if ($unlock) { | ||||||
|  |         app::get()->getRunfile()->release(); | ||||||
|  |         $unlock = false; | ||||||
|  |       } | ||||||
|  |       if ($stop) { | ||||||
|  |         app::get()->getRunfile()->wfStop(); | ||||||
|  |         $stop = false; | ||||||
|  |       } | ||||||
|  |     }; | ||||||
|  |     register_shutdown_function($shutdown); | ||||||
|  |     app::install_signal_handler(static::INSTALL_SIGNAL_HANDLER); | ||||||
|  |     try { | ||||||
|  |       static::_initialize_app(); | ||||||
|  |       $useRunfile = static::USE_RUNFILE; | ||||||
|  |       $useRunlock = static::USE_RUNLOCK; | ||||||
|  |       if ($useRunfile) { | ||||||
|  |         $runfile = app::get()->getRunfile(); | ||||||
|  | 
 | ||||||
|  |         global $argc, $argv; | ||||||
|  |         self::_manage_runfile($argc, $argv, $runfile); | ||||||
|  |         if ($useRunlock && $runfile->warnIfLocked()) exit(app::EC_LOCKED); | ||||||
|  | 
 | ||||||
|  |         $runfile->wfStart(); | ||||||
|  |         $stop = true; | ||||||
|  |         if ($useRunlock) { | ||||||
|  |           $runfile->lock(); | ||||||
|  |           $unlock = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |       if ($app === null) $app = new static(); | ||||||
|  |       static::_configure_app($app); | ||||||
|  |       static::_start_app($app); | ||||||
|  |     } catch (ExitError $e) { | ||||||
|  |       if ($e->haveUserMessage()) msg::error($e->getUserMessage()); | ||||||
|  |       exit($e->getCode()); | ||||||
|  |     } catch (Exception $e) { | ||||||
|  |       msg::error($e); | ||||||
|  |       exit(app::EC_UNEXPECTED); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected static function _initialize_app(): void { | ||||||
|  |     app::init(static::class); | ||||||
|  |     app::set_fact(app::FACT_CLI_APP); | ||||||
|  |     $con = new ConsoleMessenger([ | ||||||
|  |       "min_level" => msg::DEBUG, | ||||||
|  |     ]); | ||||||
|  |     say::set_messenger($con); | ||||||
|  |     msg::set_messenger($con); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected static function _configure_app(Application $app): void { | ||||||
|  |     config::configure(config::CONFIGURE_INITIAL_ONLY); | ||||||
|  | 
 | ||||||
|  |     $con = con::set_messenger(new ConsoleMessenger([ | ||||||
|  |       "min_level" => con::NORMAL, | ||||||
|  |     ])); | ||||||
|  |     say::set_messenger($con, true); | ||||||
|  |     msg::set_messenger($con, true); | ||||||
|  |     if (static::USE_LOGFILE) { | ||||||
|  |       $log = log::set_messenger(new LogMessenger([ | ||||||
|  |         "output" => app::get()->getLogfile(), | ||||||
|  |         "min_level" => msg::MINOR, | ||||||
|  |       ])); | ||||||
|  |     } else { | ||||||
|  |       $log = log::set_messenger(new ProxyMessenger()); | ||||||
|  |     } | ||||||
|  |     msg::set_messenger($log); | ||||||
|  | 
 | ||||||
|  |     $app->parseArgs(); | ||||||
|  |     config::configure(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected static function _start_app(Application $app): void { | ||||||
|  |     $retcode = $app->main(); | ||||||
|  |     if (is_int($retcode)) exit($retcode); | ||||||
|  |     elseif (is_bool($retcode)) exit($retcode? 0: 1); | ||||||
|  |     elseif ($retcode !== null) exit(strval($retcode)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * sortir de l'application avec un code d'erreur, qui est 0 par défaut (i.e | ||||||
|  |    * pas d'erreur) | ||||||
|  |    * | ||||||
|  |    * équivalent à lancer l'exception {@link ExitError} | ||||||
|  |    */ | ||||||
|  |   protected static final function exit(int $exitcode=0, $message=null) { | ||||||
|  |     throw new ExitError($exitcode, $message); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * sortir de l'application avec un code d'erreur, qui vaut 1 par défaut (i.e | ||||||
|  |    * une erreur s'est produite) | ||||||
|  |    * | ||||||
|  |    * équivalent à lancer l'exception {@link ExitError} | ||||||
|  |    */ | ||||||
|  |   protected static final function die($message=null, int $exitcode=1) { | ||||||
|  |     throw new ExitError($exitcode, $message); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const PROFILE_SECTION = [ | ||||||
|  |     "title" => "PROFIL D'EXECUTION", | ||||||
|  |     "show" => false, | ||||||
|  |     ["-c", "--config", "--app-config", | ||||||
|  |       "args" => "file", "argsdesc" => "CONFIG.yml", | ||||||
|  |       "action" => [config::class, "load_config"], | ||||||
|  |       "help" => "spécifier un fichier de configuration", | ||||||
|  |     ], | ||||||
|  |     ["group", | ||||||
|  |       ["-g", "--profile", "--app-profile", | ||||||
|  |         "args" => 1, "argsdesc" => "PROFILE", | ||||||
|  |         "action" => [app::class, "set_profile"], | ||||||
|  |         "help" => "spécifier le profil d'exécution", | ||||||
|  |       ], | ||||||
|  |       ["-P", "--prod", "action" => [app::class, "set_profile", ref_profiles::PROD]], | ||||||
|  |       ["-T", "--test", "action" => [app::class, "set_profile", ref_profiles::TEST]], | ||||||
|  |       ["--devel", "action" => [app::class, "set_profile", ref_profiles::DEVEL]], | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   const VERBOSITY_SECTION = [ | ||||||
|  |     "title" => "NIVEAU D'INFORMATION", | ||||||
|  |     "show" => false, | ||||||
|  |     ["group", | ||||||
|  |       ["-V", "--verbosity", | ||||||
|  |         "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", | ||||||
|  |         "action" => [con::class, "set_verbosity"], | ||||||
|  |         "help" => "Spécifier le niveau d'informations affiché sur la console", | ||||||
|  |       ], | ||||||
|  |       ["-q", "--quiet", "action" => [con::class, "set_verbosity", "quiet"]], | ||||||
|  |       ["-v", "--verbose", "action" => [con::class, "set_verbosity", "verbose"]], | ||||||
|  |       ["-D", "--debug", "action" => [con::class, "set_verbosity", "debug"]], | ||||||
|  |     ], | ||||||
|  |     ["group", | ||||||
|  |       ["--color", | ||||||
|  |         "action" => [con::class, "set_color", true], | ||||||
|  |         "help" => "Afficher (resp. ne pas afficher) la sortie en couleur par défaut", | ||||||
|  |       ], | ||||||
|  |       ["--no-color", "action" => [con::class, "set_color", false]], | ||||||
|  |     ], | ||||||
|  |     ["group", | ||||||
|  |       ["-L", "--logfile", | ||||||
|  |         "args" => "output", | ||||||
|  |         "action" => [log::class, "set_output"], | ||||||
|  |         "help" => "Logger les messages de l'application dans le fichier spécifié", | ||||||
|  |       ], | ||||||
|  |       ["--lV", "--lverbosity", | ||||||
|  |         "args" => "verbosity", "argsdesc" => "silent|quiet|verbose|debug", | ||||||
|  |         "action" => [log::class, "set_verbosity"], | ||||||
|  |         "help" => "Spécifier le niveau des informations ajoutées dans les logs", | ||||||
|  |       ], | ||||||
|  |       ["--lq", "--lquiet", "action" => [log::class, "set_verbosity", "quiet"]], | ||||||
|  |       ["--lv", "--lverbose", "action" => [log::class, "set_verbosity", "verbose"]], | ||||||
|  |       ["--lD", "--ldebug", "action" => [log::class, "set_verbosity", "debug"]], | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   const ARGS = [ | ||||||
|  |     "sections" => [ | ||||||
|  |       self::PROFILE_SECTION, | ||||||
|  |       self::VERBOSITY_SECTION, | ||||||
|  |     ], | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   protected function getArgsParser(): AbstractArgsParser { | ||||||
|  |     return new SimpleArgsParser(static::ARGS); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** @throws ArgsException */ | ||||||
|  |   function parseArgs(array $args=null): void { | ||||||
|  |     $this->getArgsParser()->parse($this, $args); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   const PROFILE_COLORS = [ | ||||||
|  |     ref_profiles::PROD => "@r", | ||||||
|  |     ref_profiles::TEST => "@g", | ||||||
|  |     ref_profiles::DEVEL => "@w", | ||||||
|  |   ]; | ||||||
|  |   const DEFAULT_PROFILE_COLOR = "y"; | ||||||
|  | 
 | ||||||
|  |   /** retourner le profil courant en couleur */ | ||||||
|  |   static function get_profile(?string $profile=null): string { | ||||||
|  |     if ($profile === null) $profile = app::get_profile(); | ||||||
|  |     foreach (static::PROFILE_COLORS as $text => $color) { | ||||||
|  |       if (strpos($profile, $text) !== false) { | ||||||
|  |         return $color? "<color $color>$profile</color>": $profile; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $color = static::DEFAULT_PROFILE_COLOR; | ||||||
|  |     return $color? "<color $color>$profile</color>": $profile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?array $args = null; | ||||||
|  | 
 | ||||||
|  |   abstract function main(); | ||||||
|  | 
 | ||||||
|  |   static function runfile(): RunFile { | ||||||
|  |     return app::with(static::class)->getRunfile(); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @ -3,7 +3,7 @@ | |||||||
| # les constantes suivantes doivent être définies AVANT de chager ce script:
 | # les constantes suivantes doivent être définies AVANT de chager ce script:
 | ||||||
| # - NULIB_APP_app_params : paramètres du projet
 | # - NULIB_APP_app_params : paramètres du projet
 | ||||||
| 
 | 
 | ||||||
| use nulib\app; | use nulib\app\app; | ||||||
| use nulib\os\path; | use nulib\os\path; | ||||||
| 
 | 
 | ||||||
| if ($argc <= 1) die("invalid arguments"); | if ($argc <= 1) die("invalid arguments"); | ||||||
|  | |||||||
							
								
								
									
										56
									
								
								php/src/app/config.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								php/src/app/config.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app; | ||||||
|  | 
 | ||||||
|  | use nulib\app\config\ConfigManager; | ||||||
|  | use nulib\app\config\JsonConfig; | ||||||
|  | use nulib\app\config\YamlConfig; | ||||||
|  | use nulib\exceptions; | ||||||
|  | use nulib\os\path; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class config: gestion de la configuration de l'application | ||||||
|  |  */ | ||||||
|  | class config { | ||||||
|  |   protected static ConfigManager $config; | ||||||
|  | 
 | ||||||
|  |   static function init_configurator($configurators): void { | ||||||
|  |     self::$config->addConfigurator($configurators); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   # certains types de configurations sont normalisés
 | ||||||
|  |   /** ne configurer que le minimum pour que l'application puisse s'initialiser */ | ||||||
|  |   const CONFIGURE_INITIAL_ONLY = ["include" => "initial"]; | ||||||
|  |   /** ne configurer que les routes */ | ||||||
|  |   const CONFIGURE_ROUTES_ONLY = ["include" => "routes"]; | ||||||
|  |   /** configurer uniquement ce qui ne nécessite pas d'avoir une session */ | ||||||
|  |   const CONFIGURE_NO_SESSION = ["exclude" => "session"]; | ||||||
|  | 
 | ||||||
|  |   static function configure(?array $params=null): void { | ||||||
|  |     self::$config->configure($params); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static final function add($config, string ...$profiles): void { self::$config->addConfig($config, $profiles); } | ||||||
|  |   static final function load_config($file): void { | ||||||
|  |     $ext = path::ext($file); | ||||||
|  |     if ($ext === ".yml" || $ext === ".yaml") { | ||||||
|  |       $config = new YamlConfig($file); | ||||||
|  |     } elseif ($ext === ".json") { | ||||||
|  |       $config = new JsonConfig($file); | ||||||
|  |     } else { | ||||||
|  |       throw exceptions::invalid_value($file, "config file"); | ||||||
|  |     } | ||||||
|  |     self::add($config); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   static final function get(string $pkey, $default=null, ?string $profile=null) { return self::$config->getValue($pkey, $default, $profile); } | ||||||
|  |   static final function k(string $pkey, $default=null) { return self::$config->getValue("app.$pkey", $default); } | ||||||
|  |   static final function db(string $pkey, $default=null) { return self::$config->getValue("dbs.$pkey", $default); } | ||||||
|  |   static final function m(string $pkey, $default=null) { return self::$config->getValue("msgs.$pkey", $default); } | ||||||
|  |   static final function l(string $pkey, $default=null) { return self::$config->getValue("mails.$pkey", $default); } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | new class extends config { | ||||||
|  |   function __construct() { | ||||||
|  |     self::$config = new ConfigManager(); | ||||||
|  |   } | ||||||
|  | }; | ||||||
							
								
								
									
										50
									
								
								php/src/app/config/ArrayConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								php/src/app/config/ArrayConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | 
 | ||||||
|  | class ArrayConfig implements IConfig { | ||||||
|  |   protected function APP(): array { | ||||||
|  |     return static::APP; | ||||||
|  |   } const APP = []; | ||||||
|  | 
 | ||||||
|  |   protected function DBS(): array { | ||||||
|  |     return static::DBS; | ||||||
|  |   } const DBS = []; | ||||||
|  | 
 | ||||||
|  |   protected function MSGS(): array { | ||||||
|  |     return static::MSGS; | ||||||
|  |   } const MSGS = []; | ||||||
|  | 
 | ||||||
|  |   protected function MAILS(): array { | ||||||
|  |     return static::MAILS; | ||||||
|  |   } const MAILS = []; | ||||||
|  | 
 | ||||||
|  |   function __construct(?array $config) { | ||||||
|  |     foreach (self::CONFIG_KEYS as $key) { | ||||||
|  |       switch ($key) { | ||||||
|  |       case "app": $default = $this->APP(); break; | ||||||
|  |       case "dbs": $default = $this->DBS(); break; | ||||||
|  |       case "msgs": $default = $this->MSGS(); break; | ||||||
|  |       case "mails": $default = $this->MAILS(); break; | ||||||
|  |       default: $default = []; | ||||||
|  |       } | ||||||
|  |       $config[$key] ??= $default; | ||||||
|  |     } | ||||||
|  |     $this->config = $config; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $config; | ||||||
|  | 
 | ||||||
|  |   function has(string $pkey, string $profile): bool { | ||||||
|  |     return cl::phas($this->config, $pkey); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function get(string $pkey, string $profile) { | ||||||
|  |     return cl::pget($this->config, $pkey); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function set(string $pkey, $value, string $profile): void { | ||||||
|  |     cl::pset($this->config, $pkey, $value); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										148
									
								
								php/src/app/config/ConfigManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										148
									
								
								php/src/app/config/ConfigManager.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,148 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\A; | ||||||
|  | use nulib\app\app; | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\exceptions; | ||||||
|  | use nulib\php\func; | ||||||
|  | use ReflectionClass; | ||||||
|  | 
 | ||||||
|  | class ConfigManager { | ||||||
|  |   protected array $configurators = []; | ||||||
|  | 
 | ||||||
|  |   /** ajouter une classe ou un objet à la liste des configurateurs */ | ||||||
|  |   function addConfigurator($configurators): void { | ||||||
|  |     A::merge($this->configurators, cl::with($configurators)); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $configured = []; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * configurer les objets et les classes qui ne l'ont pas encore été. la liste | ||||||
|  |    * des objets et des classes à configurer est fournie en appelant la méthode | ||||||
|  |    * {@link addConfigurator()} | ||||||
|  |    * | ||||||
|  |    * par défaut, la configuration se fait en appelant toutes les méthodes | ||||||
|  |    * publiques des objets et toutes les méthodes statiques des classes qui | ||||||
|  |    * commencent par 'configure', e.g 'configureThis()' ou 'configure_db()', | ||||||
|  |    * si elles n'ont pas déjà été appelées | ||||||
|  |    * | ||||||
|  |    * Il est possible de modifier la liste des méthodes appelées avec le tableau | ||||||
|  |    * $params, qui doit être conforme au schema de {@link func::CALL_ALL_SCHEMA} | ||||||
|  |    */ | ||||||
|  |   function configure(?array $params=null): void { | ||||||
|  |     $params["prefix"] ??= "configure"; | ||||||
|  |     foreach ($this->configurators as $key => $configurator) { | ||||||
|  |       $configured =& $this->configured[$key]; | ||||||
|  |       /** @var func[] $methods */ | ||||||
|  |       $methods = func::get_all($configurator, $params); | ||||||
|  |       foreach ($methods as $method) { | ||||||
|  |         $name = $method->getName() ?? "(no name)"; | ||||||
|  |         $done = $configured[$name] ?? false; | ||||||
|  |         if (!$done) { | ||||||
|  |           $method->invoke(); | ||||||
|  |           $configured[$name] = true; | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   #############################################################################
 | ||||||
|  | 
 | ||||||
|  |   protected $cache = []; | ||||||
|  | 
 | ||||||
|  |   protected function resetCache(): void { | ||||||
|  |     $this->cache = []; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function cacheHas(string $pkey, string $profile) { | ||||||
|  |     return array_key_exists("$profile.$pkey", $this->cache); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function cacheGet(string $pkey, string $profile) { | ||||||
|  |     return cl::get($this->cache, "$profile.$pkey"); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected function cacheSet(string $pkey, $value, string $profile): void { | ||||||
|  |     $this->cache["$profile.$pkey"] = $value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $profileConfigs = []; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * Ajouter une configuration valide pour le(s) profil(s) spécifié(s) | ||||||
|  |    * | ||||||
|  |    * $config est un objet ou une classe qui définit une ou plusieurs des | ||||||
|  |    * constantes APP, DBS, MSGS, MAILS | ||||||
|  |    * | ||||||
|  |    * si !$inProfiles, la configuration est valide dans tous les profils | ||||||
|  |    */ | ||||||
|  |   function addConfig($config, ?array $inProfiles=null): void { | ||||||
|  |     if (is_string($config)) { | ||||||
|  |       $c = new ReflectionClass($config); | ||||||
|  |       if ($c->implementsInterface(IConfig::class)) { | ||||||
|  |         $config = $c->newInstance(); | ||||||
|  |       } else { | ||||||
|  |         $config = []; | ||||||
|  |         foreach (IConfig::CONFIG_KEYS as $key) { | ||||||
|  |           $config[$key] = cl::with($c->getConstant(strtoupper($key))); | ||||||
|  |         } | ||||||
|  |         $config = new ArrayConfig($config); | ||||||
|  |       } | ||||||
|  |     } elseif (is_array($config)) { | ||||||
|  |       $config = new ArrayConfig($config); | ||||||
|  |     } elseif (!($config instanceof IConfig)) { | ||||||
|  |       throw exceptions::invalid_type($config, "config", ["array", IConfig::class]); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     if (!$inProfiles) $inProfiles = [IConfig::PROFILE_ALL]; | ||||||
|  |     foreach ($inProfiles as $profile) { | ||||||
|  |       $this->profileConfigs[$profile][] = $config; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $this->resetCache(); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function _getValue(string $pkey, $default, string $inProfile) { | ||||||
|  |     $profiles = [$inProfile]; | ||||||
|  |     if ($inProfile !== IConfig::PROFILE_ALL) $profiles[] = IConfig::PROFILE_ALL; | ||||||
|  |     foreach ($profiles as $profile) { | ||||||
|  |       /** @var IConfig[] $configs */ | ||||||
|  |       $configs = $this->profileConfigs[$profile] ?? []; | ||||||
|  |       foreach (array_reverse($configs) as $config) { | ||||||
|  |         if ($config->has($pkey, $profile)) { | ||||||
|  |           return $config->get($pkey, $profile); | ||||||
|  |         } | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     return $default; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * obtenir la valeur au chemin de clé $pkey dans le profil spécifié | ||||||
|  |    * | ||||||
|  |    * le $inProfile===null, prendre le profil par défaut. | ||||||
|  |    */ | ||||||
|  |   function getValue(string $pkey, $default=null, ?string $inProfile=null) { | ||||||
|  |     $inProfile ??= app::get_profile(); | ||||||
|  | 
 | ||||||
|  |     if ($this->cacheHas($pkey, $inProfile)) { | ||||||
|  |       return $this->cacheGet($pkey, $inProfile); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     $value = $this->_getValue($pkey, $default, $inProfile); | ||||||
|  |     $this->cacheSet($pkey, $value, $inProfile); | ||||||
|  |     return $value; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setValue(string $pkey, $value, ?string $inProfile=null): void { | ||||||
|  |     $inProfile ??= app::get_profile(); | ||||||
|  |     /** @var IConfig[] $configs */ | ||||||
|  |     $configs =& $this->profileConfigs[$inProfile]; | ||||||
|  |     if ($configs === null) $key = 0; | ||||||
|  |     else $key = array_key_last($configs); | ||||||
|  |     $configs[$key] ??= new ArrayConfig([]); | ||||||
|  |     $configs[$key]->set($pkey, $value, $inProfile); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										112
									
								
								php/src/app/config/EnvConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								php/src/app/config/EnvConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,112 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\cl; | ||||||
|  | use nulib\ext\json; | ||||||
|  | use nulib\file; | ||||||
|  | use nulib\str; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class EnvConfig: configuration extraite depuis les variables d'environnement | ||||||
|  |  * | ||||||
|  |  * les variables doivent être de la forme {PREFIX}_{PROFILE}_{PKEY} où: | ||||||
|  |  * - PREFIX vaut CONFIG, FILE_CONFIG, JSON_CONFIG ou JSON_FILE_CONFIG | ||||||
|  |  * - PROFILE est le profil dans lequel la configuration est valide, ou ALL si | ||||||
|  |  *   la valeur doit être valide dans tous les profils | ||||||
|  |  * - PKEY est le chemin de clé dans lequel les caractères '.' sont remplacés | ||||||
|  |  *   par '__' | ||||||
|  |  * | ||||||
|  |  * par exemple, la valeur dbs.my_auth.type du profil par défaut est pris dans | ||||||
|  |  * la variable 'CONFIG_ALL_dbs__my_auth__type'. pour le profil prod c'est la | ||||||
|  |  * variable 'CONFIG_prod_dbs__my_auth__type' | ||||||
|  |  * | ||||||
|  |  * pour représenter le tableau suivant: | ||||||
|  |  *   [ "type" => "mysql", "name" => "mysql:host=authdb;dbname=auth;charset=utf8", | ||||||
|  |  *     "user" => "auth_int", "pass" => "auth" ] | ||||||
|  |  * situé au chemin de clé dbs.auth dans le profil prod, on peut par exemple | ||||||
|  |  * définir les variables suivantes: | ||||||
|  |  *   CONFIG_prod_dbs__auth__type="mysql" | ||||||
|  |  *   CONFIG_prod_dbs__auth__name="mysql:host=authdb;dbname=auth;charset=utf8" | ||||||
|  |  *   CONFIG_prod_dbs__auth__user="auth_int" | ||||||
|  |  *   CONFIG_prod_dbs__auth__pass="auth" | ||||||
|  |  * ou alternativement: | ||||||
|  |  *   JSON_CONFIG_prod_dbs__auth='{"type":"mysql","name":"mysql:host=authdb;dbname=auth;charset=utf8","user":"auth_int","pass":"auth"}' | ||||||
|  |  * | ||||||
|  |  * Les préfixes supportés sont, dans l'ordre de précédence: | ||||||
|  |  * - JSON_FILE_CONFIG -- une valeur au format JSON inscrite dans un fichier | ||||||
|  |  * - JSON_CONFIG -- une valeur au format JSON | ||||||
|  |  * - FILE_CONFIG -- une valeur inscrite dans un fichier | ||||||
|  |  * - CONFIG -- une valeur scalaire | ||||||
|  |  */ | ||||||
|  | class EnvConfig implements IConfig{ | ||||||
|  |   protected ?array $profileConfigs = null; | ||||||
|  | 
 | ||||||
|  |   /** analyser $name et retourner [$pkey, $profile] */ | ||||||
|  |   private static function parse_pkey_profile($name): array { | ||||||
|  |     $i = strpos($name, "_"); | ||||||
|  |     if ($i === false) return [false, false]; | ||||||
|  |     $profile = substr($name, 0, $i); | ||||||
|  |     if ($profile === "ALL") $profile = IConfig::PROFILE_ALL; | ||||||
|  |     $name = substr($name, $i + 1); | ||||||
|  |     $pkey = str_replace("__", ".", $name); | ||||||
|  |     return [$pkey, $profile]; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function loadEnvConfig(): void { | ||||||
|  |     if ($this->profileConfigs !== null) return; | ||||||
|  |     $json_files = []; | ||||||
|  |     $jsons = []; | ||||||
|  |     $files = []; | ||||||
|  |     $vars = []; | ||||||
|  |     foreach (getenv() as $name => $value) { | ||||||
|  |       if (str::starts_with("JSON_FILE_CONFIG_", $name)) { | ||||||
|  |         $json_files[str::without_prefix("JSON_FILE_CONFIG_", $name)] = $value; | ||||||
|  |       } elseif (str::starts_with("JSON_CONFIG_", $name)) { | ||||||
|  |         $jsons[str::without_prefix("JSON_CONFIG_", $name)] = $value; | ||||||
|  |       } elseif (str::starts_with("FILE_CONFIG_", $name)) { | ||||||
|  |         $files[str::without_prefix("FILE_CONFIG_", $name)] = $value; | ||||||
|  |       } elseif (str::starts_with("CONFIG_", $name)) { | ||||||
|  |         $vars[str::without_prefix("CONFIG_", $name)] = $value; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $profileConfigs = []; | ||||||
|  |     foreach ($json_files as $name => $file) { | ||||||
|  |       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||||
|  |       $value = json::load($file); | ||||||
|  |       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||||
|  |     } | ||||||
|  |     foreach ($jsons as $name => $json) { | ||||||
|  |       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||||
|  |       $value = json::decode($json); | ||||||
|  |       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||||
|  |     } | ||||||
|  |     foreach ($files as $name => $file) { | ||||||
|  |       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||||
|  |       $value = file::reader($file)->getContents(); | ||||||
|  |       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||||
|  |     } | ||||||
|  |     foreach ($vars as $name => $value) { | ||||||
|  |       [$pkey, $profile] = self::parse_pkey_profile($name); | ||||||
|  |       cl::pset($profileConfigs, "$profile.$pkey", $value); | ||||||
|  |     } | ||||||
|  |     $this->profileConfigs = $profileConfigs; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function has(string $pkey, string $profile): bool { | ||||||
|  |     $this->loadEnvConfig(); | ||||||
|  |     $config = $this->profileConfigs[$profile] ?? null; | ||||||
|  |     return cl::phas($config, $pkey); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function get(string $pkey, string $profile) { | ||||||
|  |     $this->loadEnvConfig(); | ||||||
|  |     $config = $this->profileConfigs[$profile] ?? null; | ||||||
|  |     return cl::pget($config, $pkey); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function set(string $pkey, $value, string $profile): void { | ||||||
|  |     $this->loadEnvConfig(); | ||||||
|  |     $config =& $this->profileConfigs[$profile]; | ||||||
|  |     cl::pset($config, $pkey, $value); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										24
									
								
								php/src/app/config/IConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								php/src/app/config/IConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Interface IConfig: un objet fournissant une configuration | ||||||
|  |  */ | ||||||
|  | interface IConfig { | ||||||
|  |   /** | ||||||
|  |    * @var string profil indiquant qu'une configuration est valide dans tous les | ||||||
|  |    * profils | ||||||
|  |    */ | ||||||
|  |   const PROFILE_ALL = "-ALL-"; | ||||||
|  | 
 | ||||||
|  |   const CONFIG_KEYS = [ | ||||||
|  |     "app", | ||||||
|  |     "dbs", "msgs", "mails", | ||||||
|  |   ]; | ||||||
|  | 
 | ||||||
|  |   function has(string $pkey, string $profile): bool; | ||||||
|  | 
 | ||||||
|  |   function get(string $pkey, string $profile); | ||||||
|  | 
 | ||||||
|  |   function set(string $pkey, $value, string $profile): void; | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								php/src/app/config/JsonConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								php/src/app/config/JsonConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\ext\json; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class JsonConfig: une configuration chargée depuis un fichier JSON | ||||||
|  |  */ | ||||||
|  | class JsonConfig extends ArrayConfig { | ||||||
|  |   function __construct(string $input) { | ||||||
|  |     parent::__construct(json::load($input)); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										140
									
								
								php/src/app/config/ProfileManager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								php/src/app/config/ProfileManager.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\app\app; | ||||||
|  | use nulib\app\config; | ||||||
|  | use nulib\ref\ref_profiles; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class ProfileManager: gestionnaire de profils | ||||||
|  |  */ | ||||||
|  | class ProfileManager { | ||||||
|  |   /** | ||||||
|  |    * @var string code du système dont on gère le profil | ||||||
|  |    * | ||||||
|  |    * ce code est utilisé pour dériver le nom du paramètre dans la configuration | ||||||
|  |    * ainsi que la variable d'environnement depuis laquelle est chargée la valeur | ||||||
|  |    * du profil | ||||||
|  |    */ | ||||||
|  |   const NAME = null; | ||||||
|  | 
 | ||||||
|  |   /** @var array|null liste des profils valides */ | ||||||
|  |   const PROFILES = null; | ||||||
|  | 
 | ||||||
|  |   /** @var array profils dont le mode production doit être actif */ | ||||||
|  |   const PRODUCTION_MODES = ref_profiles::PRODUCTION_MODES; | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var array mapping profil d'application --> profil effectif | ||||||
|  |    * | ||||||
|  |    * ce mapping est utilisé quand il faut calculer le profil courant s'il n'a | ||||||
|  |    * pas été spécifié par l'utilisateur. il permet de faire correspondre le | ||||||
|  |    * profil courant de l'application avec le profil effectif à sélectionner | ||||||
|  |    */ | ||||||
|  |   const PROFILE_MAP = null; | ||||||
|  | 
 | ||||||
|  |   function __construct(?array $params=null) { | ||||||
|  |     $this->isAppProfile = $params["app"] ?? false; | ||||||
|  |     $this->profiles = static::PROFILES; | ||||||
|  |     $this->productionModes = static::PRODUCTION_MODES; | ||||||
|  |     $this->profileMap = static::PROFILE_MAP; | ||||||
|  |     $name = $params["name"] ?? static::NAME; | ||||||
|  |     if ($name === null) { | ||||||
|  |       $this->configKey = null; | ||||||
|  |       $this->envKeys = ["APP_PROFILE"]; | ||||||
|  |     } else { | ||||||
|  |       $configKey = "${name}_profile"; | ||||||
|  |       $envKey = strtoupper($configKey); | ||||||
|  |       if ($this->isAppProfile) { | ||||||
|  |         $this->configKey = null; | ||||||
|  |         $this->envKeys = [$envKey, "APP_PROFILE"]; | ||||||
|  |       } else { | ||||||
|  |         $this->configKey = $configKey; | ||||||
|  |         $this->envKeys = [$envKey]; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |     $this->defaultProfile = $params["default_profile"] ?? null; | ||||||
|  |     $profile = $params["profile"] ?? null; | ||||||
|  |     $productionMode = $params["production_mode"] ?? null; | ||||||
|  |     $productionMode ??= $this->productionModes[$profile] ?? false; | ||||||
|  |     $this->profile = $profile; | ||||||
|  |     $this->productionMode = $productionMode; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * @var bool cet objet est-il utilisé pour gérer le profil de l'application? | ||||||
|  |    */ | ||||||
|  |   protected bool $isAppProfile; | ||||||
|  | 
 | ||||||
|  |   protected ?array $profiles; | ||||||
|  | 
 | ||||||
|  |   protected ?array $productionModes; | ||||||
|  | 
 | ||||||
|  |   protected ?array $profileMap; | ||||||
|  | 
 | ||||||
|  |   protected function mapProfile(?string $profile): ?string { | ||||||
|  |     return $this->profileMap[$profile] ?? $profile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?string $configKey; | ||||||
|  | 
 | ||||||
|  |   function getConfigProfile(): ?string { | ||||||
|  |     if ($this->configKey === null) return null; | ||||||
|  |     return config::k($this->configKey); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected array $envKeys; | ||||||
|  | 
 | ||||||
|  |   function getEnvProfile(): ?string { | ||||||
|  |     foreach ($this->envKeys as $envKey) { | ||||||
|  |       $profile = getenv($envKey); | ||||||
|  |       if ($profile !== false) return $profile; | ||||||
|  |     } | ||||||
|  |     return null; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?string $defaultProfile; | ||||||
|  | 
 | ||||||
|  |   function getDefaultProfile(): ?string { | ||||||
|  |     return $this->defaultProfile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setDefaultProfile(?string $profile): void { | ||||||
|  |     $this->defaultProfile = $profile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?string $profile; | ||||||
|  | 
 | ||||||
|  |   protected bool $productionMode; | ||||||
|  | 
 | ||||||
|  |   protected function resolveProfile(): void { | ||||||
|  |     $profile ??= $this->getenvProfile(); | ||||||
|  |     $profile ??= $this->getConfigProfile(); | ||||||
|  |     $profile ??= $this->getDefaultProfile(); | ||||||
|  |     if ($this->isAppProfile) { | ||||||
|  |       $profile ??= $this->profiles[0] ?? ref_profiles::PROD; | ||||||
|  |     } else { | ||||||
|  |       $profile ??= $this->mapProfile(app::get_profile()); | ||||||
|  |     } | ||||||
|  |     $this->profile = $profile; | ||||||
|  |     $this->productionMode = $this->productionModes[$profile] ?? false; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function getProfile(?bool &$productionMode=null): string { | ||||||
|  |     if ($this->profile === null) $this->resolveProfile(); | ||||||
|  |     $productionMode = $this->productionMode; | ||||||
|  |     return $this->profile; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function isProductionMode(): bool { | ||||||
|  |     return $this->productionMode; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   function setProfile(?string $profile=null, ?bool $productionMode=null): void { | ||||||
|  |     if ($profile === null) $this->profile = null; | ||||||
|  |     $profile ??= $this->getProfile($productionMode); | ||||||
|  |     $productionMode ??= $this->productionModes[$profile] ?? false; | ||||||
|  |     $this->profile = $profile; | ||||||
|  |     $this->productionMode = $productionMode; | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										13
									
								
								php/src/app/config/YamlConfig.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								php/src/app/config/YamlConfig.php
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,13 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\app\config; | ||||||
|  | 
 | ||||||
|  | use nulib\ext\yaml; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class YamlConfig: une configuration chargée depuis un fichier yaml | ||||||
|  |  */ | ||||||
|  | class YamlConfig extends ArrayConfig { | ||||||
|  |   function __construct(string $input) { | ||||||
|  |     parent::__construct(yaml::load($input)); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										50
									
								
								php/src/cache/CacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								php/src/cache/CacheData.php
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | |||||||
|  | <?php | ||||||
|  | namespace nulib\cache; | ||||||
|  | 
 | ||||||
|  | use nulib\php\func; | ||||||
|  | 
 | ||||||
|  | /** | ||||||
|  |  * Class CacheData: gestion d'une donnée mise en cache | ||||||
|  |  */ | ||||||
|  | abstract class CacheData { | ||||||
|  |   function __construct(?string $name, $compute) { | ||||||
|  |     $this->name = $name ?? ""; | ||||||
|  |     $this->compute = func::withn($compute ?? static::COMPUTE); | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected string $name; | ||||||
|  | 
 | ||||||
|  |   function getName() : string { | ||||||
|  |     return $this->name; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   protected ?func $compute; | ||||||
|  | 
 | ||||||
|  |   /** calculer la donnée */ | ||||||
|  |   function compute() { | ||||||
|  |     $compute = $this->compute; | ||||||
|  |     $data = $compute !== null? $compute->invoke(): null; | ||||||
|  |     return $data; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  |   /** | ||||||
|  |    * le cache est-il externe? si non, utiliser {@link setDatafile()} pour | ||||||
|  |    * spécifier le fichier destination de la valeur | ||||||
|  |    */ | ||||||
|  |   abstract function isExternal(): bool; | ||||||
|  | 
 | ||||||
|  |   /** spécifier le chemin du cache à partir du fichier de base */ | ||||||
|  |   abstract function setDatafile(?string $basefile): void; | ||||||
|  | 
 | ||||||
|  |   /** indiquer si le cache existe */ | ||||||
|  |   abstract function exists(): bool; | ||||||
|  | 
 | ||||||
|  |   /** charger la donnée depuis le cache */ | ||||||
|  |   abstract function load(); | ||||||
|  | 
 | ||||||
|  |   /** sauvegarder la donnée dans le cache et la retourner */ | ||||||
|  |   abstract function save($data); | ||||||
|  | 
 | ||||||
|  |   /** supprimer le cache */ | ||||||
|  |   abstract function delete(); | ||||||
|  | } | ||||||
Some files were not shown because too many files have changed in this diff Show More
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user