<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$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/php/cli" isTestSource="false" packagePrefix="cli\" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/php/vendor" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
|
||||
30
.idea/php-docker-settings.xml
generated
30
.idea/php-docker-settings.xml
generated
@ -17,6 +17,36 @@
|
||||
</DockerContainerSettings>
|
||||
</value>
|
||||
</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>
|
||||
</list>
|
||||
</component>
|
||||
|
||||
77
.idea/php.xml
generated
77
.idea/php.xml
generated
@ -2,7 +2,7 @@
|
||||
<project version="4">
|
||||
<component name="MessDetector">
|
||||
<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>
|
||||
</component>
|
||||
<component name="MessDetectorOptionsConfiguration">
|
||||
@ -17,44 +17,61 @@
|
||||
</component>
|
||||
<component name="PhpCodeSniffer">
|
||||
<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>
|
||||
</component>
|
||||
<component name="PhpIncludePathManager">
|
||||
<include_path>
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-timer" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/composer" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/dflydev/dot-access-data" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/league/commonmark" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/league/config" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/myclabs/deep-copy" />
|
||||
<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/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-enumerator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/version" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/global-state" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-file-iterator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-text-template" />
|
||||
<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/diff" />
|
||||
<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/resource-operations" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/diff" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/cli-parser" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/doctrine/instantiator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/comparator" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/lines-of-code" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit-reverse-lookup" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/code-unit" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/object-reflector" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/exporter" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/phpunit" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phpunit/php-invoker" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phar-io/version" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/phar-io/manifest" />
|
||||
<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" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/type" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/sebastian/version" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/cache" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/cache-contracts" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/deprecation-contracts" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/expression-language" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-ctype" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php73" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/polyfill-php80" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/service-contracts" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/var-exporter" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/symfony/yaml" />
|
||||
<path value="$PROJECT_DIR$/php/vendor/theseer/tokenizer" />
|
||||
</include_path>
|
||||
</component>
|
||||
<component name="PhpProjectSharedConfiguration" php_language_level="7.4">
|
||||
|
||||
@ -4,7 +4,7 @@ UPSTREAM=dev74
|
||||
DEVELOP=dev82
|
||||
FEATURE=wip82/
|
||||
RELEASE=rel82-
|
||||
MAIN=dist82
|
||||
MAIN=main82
|
||||
TAG_SUFFIX=p82
|
||||
HOTFIX=hotf82-
|
||||
DIST=
|
||||
|
||||
4
TODO.md
4
TODO.md
@ -1,3 +1,7 @@
|
||||
# nulib
|
||||
|
||||
* [wip](wip/TODO.md)
|
||||
|
||||
# nulib/bash
|
||||
|
||||
* [nulib/bash](bash/TODO.md)
|
||||
|
||||
@ -6,7 +6,8 @@ function __esection() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" -
|
||||
|
||||
recho "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
recho "
|
||||
$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
|
||||
[ -n "$*" ] || return 0
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
|
||||
@ -6,7 +6,8 @@ function __esection() {
|
||||
local length="${COLUMNS:-80}"
|
||||
setx lsep=__complete "$prefix" "$length" -
|
||||
|
||||
recho "$lsep"
|
||||
recho "
|
||||
$lsep"
|
||||
[ -n "$*" ] || return 0
|
||||
length=$((length - 1))
|
||||
setx -a lines=echo "$1"
|
||||
|
||||
@ -184,7 +184,7 @@ function __nulib_args_parse_args() {
|
||||
*) die "Invalid arg definition: expected option, got '$1'" || return;;
|
||||
esac
|
||||
# est-ce que l'option prend un argument?
|
||||
local __def __longdef __witharg __valdesc
|
||||
local __def __longdef __witharg __valdesc __defvaldesc
|
||||
__witharg=
|
||||
for __def in "${__defs[@]}"; do
|
||||
if [ "${__def%::*}" != "$__def" ]; then
|
||||
@ -346,16 +346,19 @@ $prefix$usage"
|
||||
fi
|
||||
# est-ce que l'option prend un argument?
|
||||
__witharg=
|
||||
__valdesc=value
|
||||
__valdesc=
|
||||
__defvaldesc=value
|
||||
for __def in "${__defs[@]}"; do
|
||||
if [ "${__def%::*}" != "$__def" ]; then
|
||||
[ "$__witharg" != : ] && __witharg=::
|
||||
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
|
||||
__defvaldesc="[value]"
|
||||
elif [ "${__def%:*}" != "$__def" ]; then
|
||||
__witharg=:
|
||||
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
|
||||
fi
|
||||
done
|
||||
[ -n "$__valdesc" ] || __valdesc="$__defvaldesc"
|
||||
# description de l'option
|
||||
local first=1 thelp tdesc
|
||||
for __def in "${__defs[@]}"; do
|
||||
|
||||
@ -21,6 +21,13 @@ if [ -z "$NULIB_NO_INIT_ENV" ]; then
|
||||
fi
|
||||
[ -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
|
||||
[ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/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
|
||||
|
||||
## configuration par défaut
|
||||
|
||||
UPSTREAM=
|
||||
DEVELOP=develop
|
||||
FEATURE=wip/
|
||||
|
||||
428
bash/src/pman.sh
428
bash/src/pman.sh
@ -25,9 +25,146 @@ DIST=
|
||||
NOAUTO=
|
||||
|
||||
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() {
|
||||
setx date=date +%d/%m/%Y-%H:%M
|
||||
ac_set_tmpfile changelog
|
||||
@ -77,7 +214,7 @@ $1 == "|" {
|
||||
}
|
||||
|
||||
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"
|
||||
git log --oneline --graph --no-decorate "$mergebase..$source" |
|
||||
grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' |
|
||||
@ -85,7 +222,7 @@ function _list_commits() {
|
||||
}
|
||||
|
||||
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"
|
||||
git diff ${_sd_COLOR:+--color=$_sd_COLOR} "$mergebase..$source"
|
||||
}
|
||||
@ -147,22 +284,27 @@ EOF
|
||||
################################################################################
|
||||
# Config
|
||||
|
||||
function ensure_gitdir() {
|
||||
function check_gitdir() {
|
||||
# commencer dans le répertoire indiqué
|
||||
local chdir="$1"
|
||||
if [ -n "$chdir" ]; then
|
||||
cd "$chdir" || die || return
|
||||
cd "$chdir" || return 1
|
||||
fi
|
||||
|
||||
# se mettre à la racine du dépôt git
|
||||
local gitdir
|
||||
git_ensure_gitvcs
|
||||
git_check_gitvcs || return 1
|
||||
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() {
|
||||
local what="${1:-all}"; shift
|
||||
local branch what="${1:-all}"; shift
|
||||
case "$what" in
|
||||
all)
|
||||
[ -n "$Origin" ] || Origin=origin
|
||||
@ -172,30 +314,6 @@ function load_branches() {
|
||||
setx -a AllBranches=git_list_pbranches "$Origin"
|
||||
;;
|
||||
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=
|
||||
FeatureBranches=()
|
||||
DevelopBranch=
|
||||
@ -203,23 +321,32 @@ function load_branches() {
|
||||
HotfixBranch=
|
||||
MainBranch=
|
||||
DistBranch=
|
||||
IfRefBranch=
|
||||
IfBaseBranch=
|
||||
IfMergeSrc=
|
||||
IfMergeDest=
|
||||
for branch in "${LocalBranches[@]}"; do
|
||||
if [ "$branch" == "$UPSTREAM" ]; then
|
||||
UpstreamBranch="$branch"
|
||||
elif [[ "$branch" == "$FEATURE"* ]]; then
|
||||
elif [ -n "$FEATURE" ] && [[ "$branch" == "$FEATURE"* ]]; then
|
||||
FeatureBranches+=("$branch")
|
||||
elif [ "$branch" == "$DEVELOP" ]; then
|
||||
elif [ -n "$DEVELOP" -a "$branch" == "$DEVELOP" ]; then
|
||||
DevelopBranch="$branch"
|
||||
elif [[ "$branch" == "$RELEASE"* ]]; then
|
||||
elif [ -n "$RELEASE" ] && [[ "$branch" == "$RELEASE"* ]]; then
|
||||
ReleaseBranch="$branch"
|
||||
elif [[ "$branch" == "$HOTFIX"* ]]; then
|
||||
elif [ -n "$HOTFIX" ] && [[ "$branch" == "$HOTFIX"* ]]; then
|
||||
HotfixBranch="$branch"
|
||||
elif [ "$branch" == "$MAIN" ]; then
|
||||
elif [ -n "$MAIN" -a "$branch" == "$MAIN" ]; then
|
||||
MainBranch="$branch"
|
||||
elif [ "$branch" == "$DIST" ]; then
|
||||
elif [ -n "$DIST" -a "$branch" == "$DIST" ]; then
|
||||
DistBranch="$branch"
|
||||
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
|
||||
[ -n "$IfMergeSrc" -a "$IfMergeDest" ] && IfCanMerge=1 || IfCanMerge=
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -244,9 +371,6 @@ function load_config() {
|
||||
elif [ -f .pman.conf ]; then
|
||||
ConfigFile="$(pwd)/.pman.conf"
|
||||
source "$ConfigFile"
|
||||
elif [ -n "$1" -a -n "${MYNAME#$1}" ]; then
|
||||
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
|
||||
source "$ConfigFile"
|
||||
else
|
||||
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
|
||||
fi
|
||||
@ -319,10 +443,8 @@ function _mscript_start() {
|
||||
#!/bin/bash
|
||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
||||
|
||||
$(echo_setv SrcBranch="$SrcBranch")
|
||||
$(echo_setv SrcType="$SrcType")
|
||||
$(echo_setv DestBranch="$DestBranch")
|
||||
$(echo_setv DestType="$DestType")
|
||||
$(echo_setv MergeSrc="$MergeSrc")
|
||||
$(echo_setv MergeDest="$MergeDest")
|
||||
|
||||
merge=
|
||||
delete=
|
||||
@ -342,32 +464,32 @@ function _mscript_merge_branch() {
|
||||
local msg
|
||||
|
||||
# basculer sur la branche
|
||||
_scripta "switch to branch $DestBranch" <<EOF
|
||||
$comment$(qvals git checkout "$DestBranch")$or_die
|
||||
_scripta "switch to branch $MergeDest" <<EOF
|
||||
$comment$(qvals git checkout "$MergeDest")$or_die
|
||||
EOF
|
||||
|
||||
if [ -n "$SquashMsg" ]; then
|
||||
msg="$SquashMsg"
|
||||
[ -n "$TechMerge" ] && msg="<pman>$msg"
|
||||
_scripta "squash merge $SrcBranch" <<EOF
|
||||
$comment$(qvals git merge "$SrcBranch" --squash)$or_die
|
||||
_scripta "squash merge $MergeSrc" <<EOF
|
||||
$comment$(qvals git merge "$MergeSrc" --squash)$or_die
|
||||
$comment$(qvals git commit -m "$msg")$or_die
|
||||
EOF
|
||||
else
|
||||
msg="Intégration de la branche $SrcBranch"
|
||||
msg="Intégration de la branche $MergeSrc"
|
||||
[ -n "$TechMerge" ] && msg="<pman>$msg"
|
||||
_scripta "merge $SrcBranch" <<EOF
|
||||
$comment$(qvals git merge "$SrcBranch" --no-ff -m "$msg")$or_die
|
||||
_scripta "merge $MergeSrc" <<EOF
|
||||
$comment$(qvals git merge "$MergeSrc" --no-ff -m "$msg")$or_die
|
||||
EOF
|
||||
fi
|
||||
array_addu push_branches "$DestBranch"
|
||||
array_addu push_branches "$MergeDest"
|
||||
}
|
||||
|
||||
function _mscript_delete_branch() {
|
||||
_scripta "delete branch $SrcBranch" <<EOF
|
||||
$comment$(qvals git branch -D "$SrcBranch")$or_die
|
||||
_scripta "delete branch $MergeSrc" <<EOF
|
||||
$comment$(qvals git branch -D "$MergeSrc")$or_die
|
||||
EOF
|
||||
array_addu delete_branches ":$SrcBranch"
|
||||
array_addu delete_branches ":$MergeSrc"
|
||||
}
|
||||
|
||||
################################################################################
|
||||
@ -379,13 +501,11 @@ function _rscript_start() {
|
||||
#!/bin/bash
|
||||
$(qvals source "$NULIBDIR/load.sh") || exit 1
|
||||
|
||||
$(echo_setv SrcBranch="$SrcBranch")
|
||||
$(echo_setv SrcType="$SrcType")
|
||||
$(echo_setv MergeSrc="$MergeSrc")
|
||||
$(echo_setv Version="$Version")
|
||||
$(echo_setv Tag="$Tag")
|
||||
$(echo_setv ReleaseBranch="$ReleaseBranch")
|
||||
$(echo_setv DestBranch="$DestBranch")
|
||||
$(echo_setv DestType="$DestType")
|
||||
$(echo_setv MergeDest="$MergeDest")
|
||||
|
||||
create=
|
||||
merge=
|
||||
@ -409,25 +529,28 @@ function _rscript_create_release_branch() {
|
||||
## Release $Tag du $date
|
||||
"
|
||||
_list_commits | _filter_changes | _format_md >>"$changelog"
|
||||
if [ -s CHANGES.md ]; then
|
||||
echo >>"$changelog"
|
||||
cat CHANGES.md >>"$changelog"
|
||||
fi
|
||||
"${EDITOR:-nano}" +7 "$changelog"
|
||||
[ -s "$changelog" ] || exit_with ewarn "Création de la release annulée"
|
||||
|
||||
# créer la branche de release et basculer dessus
|
||||
_scripta "create branch $ReleaseBranch" <<EOF
|
||||
$(qvals git checkout -b "$ReleaseBranch" "$SrcBranch")$or_die
|
||||
$(qvals git checkout -b "$ReleaseBranch" "$MergeSrc")$or_die
|
||||
EOF
|
||||
|
||||
# créer le changelog
|
||||
_scripta "update CHANGES.md" <<EOF
|
||||
tmpchanges=/tmp/pman_CHANGES.$$.md
|
||||
$(qvals echo "$(awk <"$changelog" '
|
||||
BEGIN { p = 0 }
|
||||
p == 0 && $0 == "" { p = 1; next }
|
||||
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
|
||||
EOF
|
||||
|
||||
@ -471,3 +594,176 @@ function _rscript_delete_release_branch() {
|
||||
$comment$(qvals git branch -D "$ReleaseBranch")$or_die
|
||||
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
|
||||
|
||||
## 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=
|
||||
DEVELOP=dev74
|
||||
FEATURE=wip74/
|
||||
RELEASE=rel74-
|
||||
MAIN=dist74
|
||||
MAIN=main74
|
||||
TAG_PREFIX=
|
||||
TAG_SUFFIX=p74
|
||||
HOTFIX=hotf74-
|
||||
|
||||
@ -1,14 +1,10 @@
|
||||
# -*- 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
|
||||
DEVELOP=dev82
|
||||
FEATURE=wip82/
|
||||
RELEASE=rel82-
|
||||
MAIN=dist82
|
||||
MAIN=main82
|
||||
TAG_PREFIX=
|
||||
TAG_SUFFIX=p82
|
||||
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 à
|
||||
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() {
|
||||
local src="$1" dest="$2"
|
||||
local srcdir srcname lsrcname
|
||||
@ -37,8 +42,28 @@ function template_copy_replace() {
|
||||
lsrcname="${srcname#.}.local"
|
||||
[ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname"
|
||||
|
||||
[ -e "$src" ] || return 2
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -51,7 +76,13 @@ et \$2 vaudra alors 'file'
|
||||
si un fichier \${1#.}.local existe (e.g 'file.ext.local'), prendre ce fichier à
|
||||
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() {
|
||||
local src="$1" dest="$2"
|
||||
local srcdir srcname lsrcname
|
||||
@ -63,15 +94,33 @@ function template_copy_missing() {
|
||||
dest="$srcdir/$dest"
|
||||
fi
|
||||
|
||||
userfiles+=("$dest")
|
||||
if [ ! -e "$dest" ]; then
|
||||
lsrcname="${srcname#.}.local"
|
||||
[ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname"
|
||||
lsrcname="${srcname#.}.local"
|
||||
[ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname"
|
||||
|
||||
cp -P "$src" "$dest"
|
||||
return 0
|
||||
[ -e "$src" ] || return 2
|
||||
|
||||
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
|
||||
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 "\
|
||||
@ -219,8 +268,13 @@ function _template_can_process() {
|
||||
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() {
|
||||
local awkscript sedscript workfile userfile
|
||||
local have_backup
|
||||
ac_set_tmpfile awkscript
|
||||
ac_set_tmpfile 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 testdiff "$workfile" "$userfile"; then
|
||||
# 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
|
||||
done
|
||||
|
||||
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
|
||||
require __DIR__ . "/../php/vendor/autoload.php";
|
||||
|
||||
use nulib\tools\pman\ComposerFile;
|
||||
use nulib\tools\pman\ComposerPmanFile;
|
||||
use nulib\ValueException;
|
||||
use cli\pman\ComposerFile;
|
||||
|
||||
$composer = new ComposerFile();
|
||||
|
||||
@ -2,8 +2,8 @@
|
||||
<?php
|
||||
require __DIR__ . "/../php/vendor/autoload.php";
|
||||
|
||||
use nulib\tools\pman\ComposerFile;
|
||||
use nulib\tools\pman\ComposerPmanFile;
|
||||
use cli\pman\ComposerFile;
|
||||
use cli\pman\ComposerPmanFile;
|
||||
use nulib\ValueException;
|
||||
|
||||
$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[@]}"
|
||||
|
||||
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)
|
||||
[ -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
|
||||
flavour="$RUNPHP_BUILD_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 ~/.bash_profile ] && source ~/.bash_profile
|
||||
|
||||
# Modifier le PATH. Ajouter aussi le chemin vers les uapps python
|
||||
PATH=$(qval "$NULIBDIR/bin:$PATH")
|
||||
# Modifier le PATH
|
||||
PATH=$(qval "$NULIBDIR/wip:$NULIBDIR/bin:$PATH")
|
||||
|
||||
if [ -n '$DEFAULT_PS1' ]; then
|
||||
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.
|
||||
Enregistrez ces modifications avant de créer une release"
|
||||
|
||||
function show_action() {
|
||||
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"
|
||||
|
||||
function ensure_rel_infos() {
|
||||
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
|
||||
local -a tags
|
||||
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
|
||||
enote "\
|
||||
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 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}"
|
||||
else
|
||||
enote "\
|
||||
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
|
||||
Vous devrez:
|
||||
- mettre à jour les informations de release puis relancer ce script"
|
||||
@ -123,8 +97,8 @@ EOF
|
||||
$BEFORE_MERGE_RELEASE
|
||||
)$or_die
|
||||
EOF
|
||||
_rscript_merge_release_branch "$DestBranch" "$Tag"
|
||||
_rscript_merge_release_branch "$SrcBranch"
|
||||
_rscript_merge_release_branch "$MergeDest" "$Tag"
|
||||
_rscript_merge_release_branch "$MergeSrc"
|
||||
_rscript_delete_release_branch
|
||||
[ -n "$AFTER_MERGE_RELEASE" ] && _scripta <<EOF
|
||||
(
|
||||
@ -183,14 +157,14 @@ function merge_release_action() {
|
||||
enote "\
|
||||
Vous allez:
|
||||
- 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
|
||||
}
|
||||
|
||||
function merge_hotfix_action() {
|
||||
enote "\
|
||||
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
|
||||
}
|
||||
|
||||
@ -206,14 +180,14 @@ _Fake=
|
||||
_KeepScript=
|
||||
action=release
|
||||
ShowLevel=0
|
||||
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
|
||||
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
|
||||
Merge=1
|
||||
Push=1
|
||||
Version=
|
||||
CurrentVersion=
|
||||
ForceCreate=
|
||||
args=(
|
||||
"faire une nouvelle release à partir de la branche source"
|
||||
" -v VERSION [source]
|
||||
"faire une nouvelle release"
|
||||
" -v VERSION
|
||||
|
||||
CONFIGURATION
|
||||
Le fichier .pman.conf contient la configuration des branches. Les variables
|
||||
@ -261,8 +235,10 @@ parse_args "$@"; set -- "${args[@]}"
|
||||
# charger la configuration
|
||||
ensure_gitdir "$chdir"
|
||||
load_branches all
|
||||
load_config "$MYNAME"
|
||||
load_branches current "$1"; shift
|
||||
load_config
|
||||
REF_BRANCH=DEVELOP
|
||||
set_pman_vars
|
||||
load_branches current
|
||||
|
||||
[ -n "$Merge" -a -n "$NOAUTO" ] && ManualRelease=1 || ManualRelease=
|
||||
[ -n "$ManualRelease" ] && Merge=
|
||||
@ -272,19 +248,12 @@ resolve_should_push quiet
|
||||
|
||||
# puis faire l'action que l'on nous demande
|
||||
case "$action" in
|
||||
show)
|
||||
git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY"
|
||||
ensure_branches
|
||||
show_action "$@"
|
||||
;;
|
||||
show) show_action "$@";;
|
||||
release)
|
||||
[ -z "$_Fake" ] && git_ensure_cleancheckout
|
||||
ensure_branches
|
||||
case "$SrcType" in
|
||||
release) merge_release_action "$@";;
|
||||
hotfix) merge_hotfix_action "$@";;
|
||||
*) create_release_action "$@";;
|
||||
esac
|
||||
ensure_merge_branches
|
||||
ensure_rel_infos
|
||||
create_release_action "$@"
|
||||
;;
|
||||
*)
|
||||
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
|
||||
cd ..
|
||||
done
|
||||
cd "$owd"
|
||||
|
||||
export RUNPHP_MOUNT=
|
||||
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": "*"
|
||||
},
|
||||
"require": {
|
||||
"symfony/yaml": "^7.1",
|
||||
"symfony/yaml": "^7.3",
|
||||
"symfony/expression-language": "^7.3",
|
||||
"phpmailer/phpmailer": "^6.8",
|
||||
"league/commonmark": "^2.7",
|
||||
"ext-json": "*",
|
||||
"php": "^8.2"
|
||||
},
|
||||
@ -35,7 +38,8 @@
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"nulib\\": "php/src"
|
||||
"nulib\\": "php/src",
|
||||
"cli\\": "php/cli"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
@ -43,6 +47,15 @@
|
||||
"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": {
|
||||
"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
|
||||
namespace nulib\tools\pman;
|
||||
namespace cli\pman;
|
||||
|
||||
use nulib\cl;
|
||||
use nulib\exceptions;
|
||||
use nulib\ext\json;
|
||||
use nulib\file;
|
||||
use nulib\os\path;
|
||||
use nulib\ValueException;
|
||||
|
||||
class ComposerFile {
|
||||
function __construct(string $composerFile=".", bool $ensureExists=true) {
|
||||
if (is_dir($composerFile)) $composerFile = path::join($composerFile, 'composer.json');
|
||||
if ($ensureExists && !file_exists($composerFile)) {
|
||||
$message = path::ppath($composerFile).": fichier introuvable";
|
||||
throw new ValueException($message);
|
||||
throw exceptions::invalid_value(path::ppath($composerFile), "ce fichier", "il est introuvable");
|
||||
}
|
||||
$this->composerFile = $composerFile;
|
||||
$this->load();
|
||||
@ -1,11 +1,11 @@
|
||||
<?php
|
||||
namespace nulib\tools\pman;
|
||||
namespace cli\pman;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\exceptions;
|
||||
use nulib\ext\yaml;
|
||||
use nulib\os\path;
|
||||
use nulib\str;
|
||||
use nulib\ValueException;
|
||||
|
||||
class ComposerPmanFile {
|
||||
const NAMES = [".composer.pman", ".pman"];
|
||||
@ -29,8 +29,7 @@ class ComposerPmanFile {
|
||||
}
|
||||
}
|
||||
if ($ensureExists && !file_exists($configFile)) {
|
||||
$message = path::ppath($configFile).": fichier introuvable";
|
||||
throw new ValueException($message);
|
||||
throw exceptions::invalid_value(path::ppath($configFile), "ce fichier", "il est introuvable");
|
||||
}
|
||||
$this->configFile = $configFile;
|
||||
$this->load();
|
||||
@ -66,9 +65,7 @@ class ComposerPmanFile {
|
||||
|
||||
function getProfileConfig(string $profile, ?array $composerRequires=null, ?array $composerRequireDevs=null): array {
|
||||
$config = $this->data["composer"][$profile] ?? null;
|
||||
if ($config === null) {
|
||||
throw new ValueException("$profile: profil invalide");
|
||||
}
|
||||
if ($config === null) throw exceptions::invalid_value($profile, "ce profil");
|
||||
if ($composerRequires !== null) {
|
||||
$matchRequires = $this->data["composer"]["match_require"];
|
||||
foreach ($composerRequires as $dep => $version) {
|
||||
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
|
||||
MYDIR="$(dirname -- "$0")"
|
||||
VENDOR="$MYDIR/../vendor"
|
||||
VENDOR="$MYDIR/vendor"
|
||||
"$VENDOR/bin/phpunit" --bootstrap "$VENDOR/autoload.php" "$@" "$MYDIR/tests"
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\php\func;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
|
||||
@ -1,36 +1,38 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
class AccessException extends UserException {
|
||||
class AccessException extends RuntimeException {
|
||||
static final function read_only(?string $dest=null, ?string $prefix=null): self {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this property";
|
||||
$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 {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this object";
|
||||
$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 {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($action === null) $action = "this operation";
|
||||
$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 {
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
if ($dest === null) $dest = "this resource";
|
||||
$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());
|
||||
$previous = $exception->getPrevious();
|
||||
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 $class;
|
||||
protected string $class;
|
||||
|
||||
function getClass(): string {
|
||||
return $this->class;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $message;
|
||||
protected string $message;
|
||||
|
||||
function getMessage(): string {
|
||||
return $this->message;
|
||||
@ -61,22 +66,19 @@ class ExceptionShadow {
|
||||
return $this->code;
|
||||
}
|
||||
|
||||
/** @var string */
|
||||
protected $file;
|
||||
protected string $file;
|
||||
|
||||
function getFile(): string {
|
||||
return $this->file;
|
||||
}
|
||||
|
||||
/** @var int */
|
||||
protected $line;
|
||||
protected int $line;
|
||||
|
||||
function getLine(): int {
|
||||
return $this->line;
|
||||
}
|
||||
|
||||
/** @var array */
|
||||
protected $trace;
|
||||
protected array $trace;
|
||||
|
||||
function getTrace(): array {
|
||||
return $this->trace;
|
||||
@ -92,10 +94,21 @@ class ExceptionShadow {
|
||||
return implode("\n", $lines);
|
||||
}
|
||||
|
||||
/** @var ExceptionShadow */
|
||||
protected $previous;
|
||||
protected ?ExceptionShadow $previous;
|
||||
|
||||
function getPrevious(): ?ExceptionShadow {
|
||||
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;
|
||||
}
|
||||
|
||||
/** @var ?string */
|
||||
protected $userMessage;
|
||||
protected ?string $userMessage;
|
||||
|
||||
function haveUserMessage(): bool {
|
||||
return $this->userMessage !== null;
|
||||
|
||||
@ -12,12 +12,12 @@ class StateException extends LogicException {
|
||||
if ($method === null) $method = "this method";
|
||||
$message = "$method is not implemented";
|
||||
if ($prefix) $prefix = "$prefix: ";
|
||||
return new static($prefix.$message);
|
||||
return new static("$prefix$message");
|
||||
}
|
||||
|
||||
static final function unexpected_state(?string $suffix=null): self {
|
||||
$message = "unexpected state";
|
||||
if ($suffix) $suffix = ": $suffix";
|
||||
return new static($message.$suffix);
|
||||
return new static("$message$suffix");
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,90 +1,35 @@
|
||||
<?php
|
||||
namespace nulib;
|
||||
|
||||
use nulib\php\content\c;
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Class UserException: une exception qui peut en plus contenir un message
|
||||
* utilisateur
|
||||
* Class UserException: une exception qui peut contenir un message utilisateur
|
||||
* et un message technique
|
||||
*/
|
||||
class UserException extends RuntimeException {
|
||||
/** @param Throwable|ExceptionShadow $e */
|
||||
static function get_user_message($e): ?string {
|
||||
if ($e instanceof self) return $e->getUserMessage();
|
||||
else return null;
|
||||
function __construct($userMessage, $code=0, ?Throwable $previous=null) {
|
||||
$this->userMessage = $userMessage = c::resolve($userMessage);
|
||||
parent::__construct(c::to_string($userMessage), $code, $previous);
|
||||
}
|
||||
|
||||
/** @param Throwable|ExceptionShadow $e */
|
||||
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);
|
||||
}
|
||||
protected ?array $userMessage;
|
||||
|
||||
/** @param Throwable|ExceptionShadow $e */
|
||||
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 {
|
||||
function getUserMessage(): ?array {
|
||||
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 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;
|
||||
|
||||
use nulib\A;
|
||||
use nulib\app;
|
||||
use nulib\cl;
|
||||
use nulib\file\SharedFile;
|
||||
use nulib\os\path;
|
||||
|
||||
@ -1,8 +1,5 @@
|
||||
# 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::_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:
|
||||
# - NULIB_APP_app_params : paramètres du projet
|
||||
|
||||
use nulib\app;
|
||||
use nulib\app\app;
|
||||
use nulib\os\path;
|
||||
|
||||
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