From 40babfe24acc4bc51ecc1c5d18ad60d38aa230ba Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 3 Oct 2016 11:00:41 +0400 Subject: [PATCH 01/39] =?UTF-8?q?sqlcsv:=20l'option=20-l=20supporte=20une?= =?UTF-8?q?=20url=20jdbc,=20et=20la=20recherche=20de=20sqlcsv.properties?= =?UTF-8?q?=20se=20fait=20dans=20tous=20les=20r=C3=A9pertoires=20parent=20?= =?UTF-8?q?jusqu'=C3=A0=20$HOME?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- sqlcsv | 64 ++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 49 insertions(+), 15 deletions(-) diff --git a/sqlcsv b/sqlcsv index 7744200..a1e832c 100755 --- a/sqlcsv +++ b/sqlcsv @@ -77,6 +77,25 @@ public class sqlcsv { static final String USER_CONFIG = USER_CONFDIR + "/" + DEFAULT_CONFIG, SYSTEM_CONFIG = SYSTEM_CONFDIR + "/" + DEFAULT_CONFIG; + // ------------------------------------------------------------------------ + public static final File getCanonical(File file) { + try { + return file.getCanonicalFile(); + } catch (IOException e) { + return file.getAbsoluteFile(); + } + } + + private static final File[] ROOTS = File.listRoots(); + + public static final boolean isRoot(File file) { + if (file == null) return false; + for (File root : ROOTS) { + if (root.equals(file)) return true; + } + return false; + } + // ------------------------------------------------------------------------ public static class ResultSetHelper { public static final int CLOBBUFFERSIZE = 2048; @@ -1151,20 +1170,31 @@ public class sqlcsv { log.setLevel(Level.parse(loglevel.toUpperCase())); } - // Charger les propriétés + // Charger les propriétés... + // essayer depuis le répertoire courant et les répertoires parents jusqu'à $HOME + if (config == null) { + String userHome = System.getProperty("user.home"); + if (userHome != null && userHome.length() == 0) userHome = null; + File dir = getCanonical(new File(".")); + File homedir = null; + if (userHome != null) homedir = getCanonical(new File(userHome)); + while (true) { + File file = new File(dir, DEFAULT_CONFIG); + if (file.exists()) { + config = file.getPath(); + break; + } + if ((homedir != null && dir.equals(homedir)) || isRoot(dir)) break; + dir = dir.getParentFile(); + if (dir == null) break; + } + } + // puis dans le répertoire de configuration utilisateur + if (config == null && new File(USER_CONFIG).exists()) config = USER_CONFIG; + // puis dans le répertoire de configuration système + if (config == null && new File(SYSTEM_CONFIG).exists()) config = SYSTEM_CONFIG; + Properties props = null; - if (config == null && new File(DEFAULT_CONFIG).exists()) { - // essayer depuis le répertoire courant - config = DEFAULT_CONFIG; - } - if (config == null && new File(USER_CONFIG).exists()) { - // puis dans le répertoire de configuration utilisateur - config = USER_CONFIG; - } - if (config == null && new File(SYSTEM_CONFIG).exists()) { - // puis dans le répertoire de configuration système - config = SYSTEM_CONFIG; - } if (config != null) { log.config("Chargement des propriétés de " + config); props = new Properties(); @@ -1187,7 +1217,11 @@ public class sqlcsv { } String jdbcUrl = null; - if (connid == null && props != null) { + if (connid != null && connid.startsWith("jdbc:")) { + jdbcUrl = connid; + connid = null; + } + if (jdbcUrl == null && connid == null && props != null) { // Essayer de deviner connid en parcourant les propriétés de props @SuppressWarnings("unchecked") Enumeration en = (Enumeration)props.propertyNames(); @@ -1206,7 +1240,7 @@ public class sqlcsv { connid = null; } } - if (connid != null) { + if (jdbcUrl == null && connid != null) { if (props != null) { String jdbcUrlKey = connid + ".url"; if (props.containsKey(jdbcUrlKey)) jdbcUrl = props.getProperty(jdbcUrlKey); From f3ba5e3b7c322b29793ad18efa7410de467a3339 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 6 Oct 2016 00:18:54 +0400 Subject: [PATCH 02/39] =?UTF-8?q?prel:=20g=C3=A9n=C3=A9rer=20un=20fichier?= =?UTF-8?q?=20CHANGES.md=20par=20d=C3=A9faut=20avec=20une=20syntaxe=20un?= =?UTF-8?q?=20peu=20diff=C3=A9rente?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- prel | 44 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/prel b/prel index 0d32b09..69f4bd5 100755 --- a/prel +++ b/prel @@ -86,10 +86,10 @@ OPTIONS fichier VERSION.txt avec pver. Utiliser cette option si la mise à jour du numéro de version doit être faite d'une manière particulière. -e, --edit - Editer le fichier CHANGES.txt autogénéré par -u -w - Cette option est surtout utile si -m est utilisé avec -u, pour donner la - possibilité de corriger la liste des modifications avant leur - enregistrement définitif. + Editer le fichier CHANGES.md autogénéré par les options -u -w ou un + fichier CHANGES.txt existant. Cette option est surtout utile si -m est + utilisé avec -u, pour donner la possibilité de corriger la liste des + modifications avant leur enregistrement définitif. -m, --merge Si la branche actuelle est une branche de release, ou s'il existe une @@ -108,7 +108,7 @@ OPTIONS -s, --summary Afficher la liste des différences entre la branche develop et la branche master, comme elle serait générée par les options -u -w pour le fichier - CHANGES.txt + CHANGES.txt (la syntaxe pour CHANGES.md est légèrement différente) -l, --log Afficher les modifications actuellement effectuée dans la branche de release par rapport à develop. @@ -123,6 +123,25 @@ function show_summary() { grep -v "Intégration de la branche release-" } +function format_md() { + awk ' +$1 == "+" { + $1 = "*" + $2 = "`" $2 "`" + print; next +} +$1 == "|" { + $1 = " *" + $2 = "`" $2 "`" + print; next +} +{ + $1 = "* `" $1 "`" + print; next +} +' +} + projdir= origin=origin action=auto @@ -320,7 +339,14 @@ Vous allez créer la nouvelle branche de release ${COULEUR_VERTE}$release${COULE changelog="## Version $version du $(date +%d/%m/%Y-%H:%M)" setx mergebase=git merge-base master "$release" - setxp modifs=show_summary "$mergebase..$release" + tmpcmd=(show_summary "$mergebase..$release") + if [ -f CHANGES.txt ]; then + changes=CHANGES.txt + else + changes=CHANGES.md + array_add tmpcmd // format_md + fi + setxp modifs "${tmpcmd[@]}" [ -n "$modifs" ] && changelog="$changelog $modifs" @@ -333,11 +359,11 @@ $modifs" "${EDITOR:-vi}" "$tmpchanges" fi - if [ -f CHANGES.txt ]; then + if [ -f "$changes" ]; then echo >>"$tmpchanges" - cat CHANGES.txt >>"$tmpchanges" + cat "$changes" >>"$tmpchanges" fi - cat "$tmpchanges" >CHANGES.txt + cat "$tmpchanges" >"$changes" ac_clean "$tmpchanges" git add -A From d2a56d6f1f9b4ec9b809923276dc948964028b07 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 6 Oct 2016 00:26:56 +0400 Subject: [PATCH 03/39] prel: ajout de l'option --uc pour convertir CHANGES.txt en CHANGES.md --- prel | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/prel b/prel index 69f4bd5..ac9a7ff 100755 --- a/prel +++ b/prel @@ -125,6 +125,7 @@ function show_summary() { function format_md() { awk ' +$0 == "" || $0 ~ /^#/ { print; next } $1 == "+" { $1 = "*" $2 = "`" $2 "`" @@ -177,6 +178,7 @@ parse_opts "${PRETTYOPTS[@]}" \ -s,--summary action=summary \ -l,--log '$action=diff; log=1' \ -d,--diff '$action=diff; diff=1' \ + --upgrade-changes,--uc action=upgrade-changes \ @ args -- "$@" && set -- "${args[@]}" || die "$args" if [ "$action" == auto ]; then @@ -196,6 +198,14 @@ if [ -n "$projdir" ]; then cd "$projdir" || die fi +if [ "$action" == upgrade-changes ]; then + [ -f CHANGES.txt ] || die "CHANGES.txt: fichier introuvable" + format_md CHANGES.md || die + rm CHANGES.txt + enote "Conversion CHANGES.txt --> CHANGES.md effectuée" + exit 0 +fi + git_ensure_gitvcs push_branches=() From 94615de67a791251f418ff06fc63918cd2d989e4 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 6 Oct 2016 00:34:02 +0400 Subject: [PATCH 04/39] conversion de CHANGES.txt en CHANGES.md --- CHANGES.md | 619 ++++++++++++++++++++++++++++++++++++++++++++++++++++ CHANGES.txt | 619 ---------------------------------------------------- 2 files changed, 619 insertions(+), 619 deletions(-) create mode 100644 CHANGES.md delete mode 100644 CHANGES.txt diff --git a/CHANGES.md b/CHANGES.md new file mode 100644 index 0000000..925395c --- /dev/null +++ b/CHANGES.md @@ -0,0 +1,619 @@ +## Version 5.3.0 du 28/09/2016-23:31 + +* `5c466d8` ldif et ldap: match des suffixes sans tenir compte de la casse. modrdn prend un nouvel argument SUFFIX +* `9eb566a` runsmod: modifier chemins par défaut +* `a082788` bash_completion: bug sur certains serveurs + +## Version 5.2.1 du 01/09/2016-15:44 + +* `9995f95` runsmod: bug + +## Version 5.2.0 du 31/08/2016-22:30 + +* `361f1b2` Intégration de la branche runsmod-goodies + * `8854469` maj de la config par défaut + * `f0a71e4` bug avec la génération de sysinfos.conf + * `830a33b` rruns calcule aussi les chemins d'hôtes automatiquement + * `40f5347` préparer le calcul automatique des chemins + * `2fbc599` bug avec git clone lors du basculement sur la branche develop + * `5bef9b1` utiliser la même logique que uproject pour cloner en mode devel + * `96afb2a` mapping des répertoires de destination + * `4b212d8` ne pas utiliser %n tout de suite + * `30e5dda` runs et rruns: améliorer le calcul des chemins + * `55c5755` support de //...%h... + * `409e406` préparer implémentation de %n +* `208f30d` Intégration de la branche better-fndate + * `f388f27` ajouter des liens pour les fonctions courantes de fndate. En mode automatique, si le fichier contient déjà une mention de date, ne pas la modifier. rajouter l'option -@ pour forcer la modification de la date + +## Version 5.1.0 du 25/08/2016-15:36 + +* `8a33418` ulib/ldif: support de l'opération modrdn +* `b710461` Intégration de la branche apacheconfig + * `e52d6c6` déplacer la logique de apacheconfig dans le module apache.tools + +## Version 5.0.0 du 09/08/2016-09:42 + +* `c00d9e4` cgilsxml.py: prévoir le cas où le fichier disparait pendant que la liste est construite +* `2fc7392` uxpath.py: bug +* `f85cd8a` foreach: implémenter la syntaxe %var par défaut +* `de383be` foreach: définir des variables supplémentaires pour chaque fichier trouvé +* `a7fd880` foreach: maj doc +* `c01eb20` ulib/awk: maj doc +* `04b723f` qsql: compatibilité avec anciennes versions de bash +* `dcf0e61` module apache: ajout de HTDOCSBASE pour le déploiement multi-homé +* `7565461` woinst: support du déploiement d'applications construites avec Maven +* `eb0a3be` woctl _create: enlever le suffixe .woa par défaut +* `d251a4e` woinst: tenir compte des variables utilisateurs +* `2fd51b6` pclone bascule automatiquement sur la branche develop si elle existe +* `d056be4` foreach: ajouter l'option --parent +* `63e78f4` ulib: compatibilité avec versions précédentes de bash +* `64699c0` ensure_hostname(): clarification du message affiché +* `22acf0a` ulib: ajouter _qsql() et qsql() pour quoter des chaines sql +* `18b2501` prel: améliorer l'affichage du résumé +* `b8bf04b` uawk dump: ajouter le format -v qui affiches des variables individuelles +* `d57839d` Intégration de la branche cgilsxml + * `6e67302` la disposition, le type et l'encoding peuvent être spécifique à un expression --spec ou --glob + * `b7600aa` possibilité de matcher des sous-répertoires dans l'expression --spec. Support du match de type --glob + * `a49d6e2` les valeurs None ne sont pas affichées. n'a pas de valeur +* `16c1936` uawk dump: avec la méthode -a, afficher par défaut un tableau vide avant +* `c6b6dd4` uawk: ajout de l'alias dump qui avait été oublié +* `d8ab7f7` cgilsxml.py: cosmetic +* `0204a21` cgilsxml.py: possibilité de classifier des fichiers au sein d'un groupe avec --break-on +* `3a9bf04` cgilsxml.py: possibilité de spécifier la disposition du contenu, le type de contenu, ainsi que l'encoding +* `a730609` cgilsxml.py: permettre de télécharger le fichier avec la disposition inline +* `ce0193f` cgilsxml.py: corriger la doc +* `1eba9a7` foreach: améliorer l'affichage +* `c669e8f` utempl -t wosrc: si on crée un wosrc dans le répertoire java, enlever automatiquement le package pour les fichiers resources pour une configuration maven +* `f0f7a6f` utempl -t wosrc: génère des pages bootstrap par défaut +* `8fced87` woctl run: permettre de choisir la version de java utilisée pour le lancement +* `9fe17b3` Ajouter le script foreach +* `f5db579` uxpath: support de la modification de la valeur d'un noeud +* `3653f7d` Ajout des fonctions {build,eval}cmd et upvar +* `7091ecf` support de la création d'un template wosrc pour un projet maven +* `34dbba9` support de l'utilisation des valeurs spécifiques de GIT_ANNEX pour rsync avec git-annex +* `470d7cf` pcrone peut utiliser un répertoire existant +* `e4b7729` corriger le template shell +* `28bb531` activer l'auto-complétion sur debian jessie +* `317dbca` ajout de compileAndGo dans les templates +* `01b9a68` ulib/vcs: corriger le code de git_track_branch. Utiliser ce code dans pdev --sync +* `fc75fcd` runsmod: quelques corrections mineures +* `5058085` runsmod: Implémenter les fonctionnalités documentées +* `12c521c` indenter l'affichage des fonctions qui demandent une saisie de l'utilisateur +* `86a5787` em: forcer -nw en mode non display +* `e74d9e4` bug +* `9218941` ajouter l'option -g à caturl +* `fcacf64` maj doc +* `01ea57e` changer l'affichage de udir, et mettre à jour la doc +* `9fe71b3` ajouter fndate pour dater les fichiers +* `8143452` rendre ppath() plus résistant +* `c9ce115` Intégration de la branche improve-tls + * `d9153af` support de la configuration tls selon https://wiki.mozilla.org/Security/Server_Side_TLS +* `135dbf6` diverses corrections sur runs +* `3d5aeb2` Ajouter l'option -c à umountr +* `278bf5a` renommer rumount un umountr +* `db76c12` Intégration de la branche runsmod-paths + * `a31f8c2` changer la convention de nommage pour les répertoires ~/runs +* `5a31e7e` ulib/java: ajout de la fonction get_java_version() +* `f74b5e3` Générer la doc au format markdown pour tagadoc +* `ded89be` pdev: corrections pour supporter nouvelle version de git +* `5d3e9fe` runsmod: corriger les valeurs par défaut pour les hôtes dans la bulle serveur +* `ada3686` uenv: bug dans le calcul des répertoires destination +* `deaa416` Intégration de la branche runsmod-only + * `f96c482` proposer de créer la configuration par défaut + * `f04e862` implémentation initiale de runsmod +* `42b9590` cosmetic +* `9bc373f` cgiparams.py: implémenter le code de retour +* `1834d29` intégrer les fonctionnalités de cgiupload.py dans cgiparams.py + +## Version 4.4.1 du 15/04/2016-12:24 + +* `a45fd99` awkrun: ajouter l'argument suffix à qsql(), cqsql(), cval() + +## Version 4.4.0 du 15/04/2016-12:04 + +* `73391a9` awkrun: ajout d'un champ field à cqsql +* `2f60fec` git_track_branch(): support de git >= 1.8.x +* `130b4d1` tenir compte du fichier /etc/debian_chroot pour initialiser UTOOLS_CHROOT +* `48c0420` ne pas afficher d'erreur si apache n'est pas installé +* `5e1a6cf` Intégration de la branche rumount +* `1ad804b` implémentation initiale de rumount +* `f460399` améliorer le calcul du répertoire destination avec pclone/pcrone +* `6411e63` configurer la valeur de l'umask +* `eb4362d` apacheconfig: déploiement pour plusieurs systèmes à partir des mêmes sources + +## Version 4.3.0 du 07/04/2016-14:57 + +* `d021c97` support de on_debian() avec des arguments + +## Version 4.2.0 du 06/04/2016-14:25 + +* `a710c5a` Intégration de la branche stdout +* `ba969e3` ajout de stdredir pour pallier l'absence éventuelle de /dev/std* +* `472f2c7` Intégration de la branche sysinfos +* `1864cdf` fonctions pour faciliter la gestion des dépendances sous debian +* `2c27f03` uproject: ajouter la commande xconfig-export +* `ed3de6d` umail: ajout de l'option --gencmd + +## Version 4.1.1 du 08/03/2016-12:04 + +* `0bd2b1f` correction de bugs avec apacheconfig + +## Version 4.1.0 du 03/03/2016-11:02 + +* `4024b2a` pyulib/umail: possibilité de spécifier le type de contenu + +## Version 4.0.0 du 01/03/2016-19:08 + +* `c46626c` ldif: support de dumpcsv et printcsv dans get_transform_cmd() +* `d661e43` doinplace fonctionne comme la fonction. la version étendue est un lien nommé doinplacex +* `d6540ac` dumpcsv: ajouter l'option --hname +* `903d500` dumpcsv: ajouter les options --keep-fields, --skip-fields, --dump-headers +* `af18332` implémenter printcsv +* `383410a` *csv: améliorer cohérences des arguments +* `4808fa0` dumpcsv: ajout de l'option -b +* `939b5ad` implémenter dumpcsv et nettoyer un peu le code +* `cb0e553` script doinplace qui mélange les fonctionnalités des fonctions doinplace et evalp +* `0cf8e04` scripts noerror, noout, noerr qui fonctionnent comme les fonctions du même nom +* `66249c9` nutools base: ajout de noerror(), noout(), noerr() +* `db76488` maj doc +* `40f1bf6` prel: après avoir fusionné une branche, revenir sur develop + +## Version 3.5.0 du 26/02/2016-11:35 + +* `4648b3f` awkrun: ajout de la fonction qarr() +* `81e7f9b` correction de certains messages affichés +* `0ea24ec` ajout de sqlcsv +* `ad06535` pu: autoriser fast-forward même en cas de modifications locales +* `1c397e8` ajout des fonctions awk cqsql(), sval(), cval(). réorganiser et clarifier la doc + +## Version 3.4.2 du 10/02/2016-11:45 + +## Version 3.4.1 du 10/02/2016-11:45 + +* `a702b89` mergecsv: bug quand left ne contient qu'un seul champ vide + +## Version 3.4.0 du 09/02/2016-11:25 + +* `788e1ff` cgilsxml.py: ajout de l'option -E. bug avec le tri sur des valeurs inexistantes + +## Version 3.3.0 du 08/02/2016-09:41 + +* `da3ef60` cgilsxml.py: ajouter de squery_string permettant d'ajouter un préfixe +* `bb25711` cgi: cgierror et cgiredirect arrêtent le script + +## Version 3.2.0 du 07/02/2016-23:05 + +* `24f3211` cgiparams.py et cgilsxml.py: modifications pour gérer la construction de QUERY_STRING + +## Version 3.1.0 du 04/02/2016-13:52 + +* `3190a32` cgilsxml.py: ajout de la variable script_base, qui permet de faire des urls relatives +* `f4537af` ussh: bug dans l'analyse des arguments +* `9992505` ussh: pas de valeur par défaut pour DEFAULT_USER; --parse affiche toutes les options +* `b19059e` Intégration de la branche ussh-auto-persist +* `5c75737` ussh: configuration du multiplexage pour certains hôtes + +## Version 3.0.1 du 31/12/2015-12:36 + +* `56c305a` awk ne supporte pas \s + +## Version 3.0.0 du 24/12/2015-15:39 + +* `4b1c30b` nouvelles fonctions pour le module cgi. cgilsxml, cgiupload et cgiparams sont rassemblés dans le module cgisupport + +## Version 2.26.0 du 24/12/2015-12:17 + +* `d5a2cce` cgilsxml.py: rajouter la variable env/query_vars + +## Version 2.25.0 du 24/12/2015-11:15 + +* `6c73940` cgilsxml.py: désactiver le cache par défaut + +## Version 2.24.1 du 24/12/2015-10:12 + +* `3aba3e3` bug + +## Version 2.24.0 du 24/12/2015-09:58 + +* `b288cf2` apacheconfig: support des fichiers de confs --> /etc/apache2/cond.d sur debian wheezy- +* `2e3fe66` ussh: forcer l'allocation d'un tty avec --cc +* `58fa40c` ussh: ajouter le support de uwatch +* `7bc63c5` maj doc uwatch +* `2d2754b` ajout de uwatch pour afficher un compte +* `4376fff` maj doc +* `bb176d7` maj du template pour runsconfig + +## Version 2.23.1 du 15/12/2015-11:01 + +* `d8d0cc8` cgilsxml: sélectionner automatiquement un groupe seul + +## Version 2.23.0 du 15/12/2015-10:06 + +* `501a7d2` cgilsxml: script_name et xslt peuvent être spécifié dans les paramètres QUERY_STRING +* `ebe4599` Intégration de la branche runs +* `35eadc1` correction d'un bug avec la recherche d'un fichier de recette +* `98ba626` les recettes ont maintenant l'extension .rr par défaut +* `a2e863e` chercher aussi les scripts dans le sous-répertoire runs dans les répertoires d'hôte + +## Version 2.22.0 du 07/12/2015-19:05 + +* `dbc4d9a` uscrontab: ne pas afficher plusieurs fois un même message quand le verrou est posé. n'afficher le message sur la pause qu'avec un tty +* `9821d20` ajouter le lien vers cgilsxml.py + +## Version 2.21.0 du 07/12/2015-15:12 + +* `d7b468d` cgilsxml: ajouter les variables dlname, size, Y, m, d, H, M, S. maj doc +* `eac94a7` cgiupload: possibilité de filtrer sur le nom du fichier ou son type de contenu +* `fae2111` ajout de cgilsxml +* `26fe709` apacheconfig: -S peut aussi être utilisé avec --localhosts +* `7c6ad6a` apacheconfig: nouvelles options pour le développement +* `c2d5eab` pxs fait aussi git pull avec un dépôt sans annexe +* `dca7bee` pxs fonctionne pour les dépôt normaux comme pci -A + +## Version 2.20.0 du 12/11/2015-15:41 + +* `16d95d2` apache_autoconf(): déploiement des certificats autorité +* `775a021` nutools: ajout de cpvcs() pour copier avec la sémantique de cpnovcs() les fichiers de VCS aussi +* `1ca0ee5` runs: ajout des options --force, --copy-links et --with-vcs à ref() +* `91686fb` templatectl: il n'est plus nécessaire de spécifier explicitement fichier.template + +## Version 2.19.0 du 02/11/2015-11:03 + +* `37f3bff` apacheconfig: diverses corrections de bugs +* `5d3861b` apacheconfig: si host==@@dhost@@, alors hostname=@@dhostname@@ et aliases=@@daliases@@ +* `27592d4` apacheconfig: support des hôtes dynamiques ou statiques +* `93ac7ea` re-écrire la gestion des templates pour apacheconfig et runsconfig + +## Version 2.18.0 du 27/10/2015-12:05 + +* `434403a` Intégration de la branche uscrontab +* `2118885` uscrontab: possibilité de mettre en pause sans modifier les planifications en cours +* `0eeee20` awkrun: renommer les fonctions quote_* +* `18691c8` upassword: maj doc et support des commentaires pour le mode batch + +## Version 2.17.2 du 26/09/2015-12:25 + +* `1837a0c` pxs: corriger xget, xcopy, xmove pour utiliser directement git annex sync + +## Version 2.17.1 du 26/09/2015-12:21 + +* `c8c6744` pxs: support des versions <= 5.20150825 + +## Version 2.17.0 du 26/09/2015-11:51 + +* `552e4ba` améliorer pxs pour un dépôt en mode direct +* `2ce66e6` maj doc + +## Version 2.16.0 du 10/09/2015-11:16 + +* `42aa7ea` fonctions e*: changer l'affichage en mode NO_COLOR pour afficher les préfixes complets + +## Version 2.15.0 du 08/09/2015-10:14 + +* `69ee45a` Intégration de la branche prel-fixes +* `47b6e5b` créer VERSION.txt et CHANGES.txt à la racine du projet +* `9d8a747` faire le push à la fin des opérations d'update et/ou de merge + +## Version 2.14.0 du 03/09/2015-09:04 + +* `9dcd60b` pxs transfert les données aussi en mode direct + +## Version 2.13.0 du 01/09/2015-15:44 + +* `8d2e386` uscrontab: permettre d'ignorer le code d'erreur pour une commande planifiée +* `aa989ef` Intégration de la branche uinst +* `58d9f16` uinst -y pose toujours la question si le répertoire est calculé automatiquement +* `594fd4e` activer l'autocomplétion pour uinst -h + +## Version 2.12.1 du 24/08/2015-16:12 + +* `e356c2b` bug avec le calcul du système +* `e040cac` Avec les fonctions e*, afficher le statut de façon explicite en mode non coloré + +## Version 2.12.0 du 23/08/2015-22:57 + +* `dcb85ac` Améliorer le déploiement uinst:rsync avec préfixes pour les hôtes distants + +## Version 2.11.0 du 21/08/2015-10:31 + +* `d9282f7` uinst: ajouter l'option -h pour la méthode uinst:rsync +* `7643573` nettoyer la branche + +## Version 2.10.0 du 19/08/2015-10:23 + +* `6ba41b9` support de --uses-su pour ruinst -C + +## Version 2.9.0 du 10/08/2015-14:04 + +* `447d13f` support de nouvelles versions d'OS pour sysinfos +* `48e111a` fconv et fnconv: le premier argument est le fichier s'il n'est pas spécifié avec -f +* `c29f4b9` mkiso: nouvelles options -A et -V +* `662b82f` caturl --check ne doit pas afficher l'url + +## Version 2.8.0 du 22/07/2015-15:09 + +* `0f5b572` uinst installe maintenant les profils en mode partagé par défaut +* `fb6b492` améliorer l'affichage des scripts geturl et openurl + +## Version 2.7.0 du 17/07/2015-22:03 + +* `7e2a6c3` update-nutools ignore par défaut l'eventuel proxy défini par http_proxy +* `13215c3` apacheconfig: améliorer l'affichage de l'aide et support du répertoire .apacheconfig +* `27f5f7a` pyulib honore maintenant UTOOLS_NO_COLORS +* `3ee6cb4` diminuer l'indentation pour les fonctions e* +* `d4ac5f7` modifier l'affichage des fonction e* + +## Version 2.6.0 du 10/07/2015-17:32 + +* `efa6b3a` Intégration de la branche ftps +* `3288999` authftp: support tls en forçant l'utilisation de lftp +* `58e7f6f` pconf reconnait maintenant les arguments offline et online + +## Version 2.5.1 du 03/07/2015-08:29 + +* `abc4cb9` dumpclients: possibilité de choisir les champs supplémentaires à afficher + +## Version 2.5.0 du 03/07/2015-07:39 + +* `2e15cbe` Ajouter l'option -z à dumpclients + +## Version 2.4.0 du 01/07/2015-04:04 + +* `fda97a8` mise à jour de upassword + +## Version 2.3.0 du 01/07/2015-03:09 + +* `f581aa9` Intégration de la branche pdev-merges +* `80d7a73` ajouter les options -b et -s à pdev + +## Version 2.2.0 du 24/06/2015-16:19 + +* `98cfdfc` Intégration de la branche update-upassword +* `34f027b` bug avec la présence du scheme dans sha +* `b5e6c13` Intégration de la branche update-upassword +* `e4e2fa7` mise à jour upassword pour afficher sha en hexadécimal + +## Version 2.1.1 du 22/06/2015-11:37 + +* `40225b0` Intégration de la branche proxy-default +* `2fa0791` profile.d/proxy lit aussi les valeurs par défaut pour l'hôte + +## Version 2.1.0 du 22/06/2015-10:25 + +* `2a3eb76` Intégration de la branche uenv-modifs +* `bd8c7f8` correction d'un bug avec le nom final +* `d73c929` correction d'un bug avec le choix du répertoire de destination +* `e9089d8` les répertoires de profil partagé et de machine sont traités comme un seul répertoire +* `e3f76cd` possibilité de spécifier si un profil doit être installé dans le répertoire de profil partagé + +## Version 2.0.0 du 22/06/2015-00:48 + +* `8286b29` Intégration de la branche uenv-modifs +* `c43cda0` les répertoires de profil sont spécifiques au nom de la machine + +## Version 1.19.0 du 21/06/2015-10:27 + +* `387dec2` Intégration de la branche add-pxwa +* `4d0b734` ajout de la commande xwebapp + +## Version 1.18.0 du 09/06/2015-10:40 + +* `01e2929` Intégration de la branche fix-javaproperties +* `b24e9ff` norm_properties() affiche les caractères en majuscule, comme JavaProperties#store() + +## Version 1.17.0 du 07/06/2015-14:00 + +* `e2d0c0c` Intégration de la branche pdev-fix +* `1cc3bd4` ajouter l'option --sync à pdev + +## Version 1.16.0 du 04/06/2015-22:14 + +* `4a15085` Intégration de la branche verbose-update +* `8a27561` pu: afficher un message s'il y a des modifications locales + +## Version 1.15.0 du 22/05/2015-17:03 + +* `e715087` Intégration de la branche update-upassword +* `a74a373` upassword: possibilité de spécifier de façon dynamique la ligne à afficher après + +## Version 1.14.0 du 22/05/2015-16:41 + +* `d8f20c3` Intégration de la branche update-upassword +* `dea524a` ajout du mode batch + +## Version 1.13.0 du 22/05/2015-16:04 + +* `275bff8` Intégration de la branche update-upassword +* `1ce1d98` mise à jour de upassword + +## Version 1.12.0 du 22/05/2015-13:37 + +* `57fc84d` correction d'un bug avec prel -s +* `cf108d7` Intégration de la branche prel-show-modifs +* `a744f48` prel -s afficher la liste des modifications qui seraient enregistrées + +## Version 1.11.0 du 22/05/2015-11:41 + +* `2984052` Intégration de la branche quiet-completion +* `755cfa7` ne pas afficher les erreurs pour les fonctions de complétion de git +* `2b5eb2a` Intégration de la branche pu +* `b004730` uproject update fait fetch puis merge, manuellement + +## Version 1.10.0 du 20/05/2015-12:08 + +* `bf1d86a` Intégration de la branche pu-auto-forward +* `8bf8164` fast-forwarder automatiquement les branches locales par rapport aux branches distantes +* `8baabea` Intégration de la branche ptools-maj-topic +* `5ff5b7d` déterminer les branches de topic: ignorer les branches avec un slash dans le nom + +## Version 1.9.0 du 19/05/2015-18:25 + +* `5933089` Intégration de la branche awkfsv +* `caeb06e` possibilité de spécifier les types des champs pour mysqlloadcsv +* `b2c9f81` uawk requière maintenant de spécifier le nom de l'outil + +## Version 1.8.1 du 19/05/2015-10:45 + +* `de8d690` Intégration de la branche annex-support +* `2044181` ne pas synchroniser l'annexe en mode automatique + +## Version 1.8.0 du 19/05/2015-10:27 + +* `4779695` Intégration de la branche annex-support +* `89ad130` support de git-annex pour pp et pdev + +## Version 1.7.0 du 15/05/2015-16:11 + +* `16ad285` Intégration de la branche nutools +* `93d8262` rajouter le script nutools pour vérifier la version installée de nutools. renommer la fonction nutools() en pconf() + +## Version 1.6.0 du 04/05/2015-12:40 + +* `0463b2c` Intégration de la branche norm_props +* `48ca720` ajout de l'en-tête + +## Version 1.5.1 du 29/04/2015-10:14 + +* `4c92f54` Intégration de la branche push-all +* `02c15f9` pp -a pousse toutes les branches et tous les tags. ajouter les options -b et -t pour pousser respectivement toutes les branches et tous les tags + +## Version 1.4.1 du 29/04/2015-10:12 + +Release erronée, à ignorer + +## Version 1.4.0 du 27/04/2015-18:12 + +* `7122fc4` Intégration de la branche norm-properties +* `845c54e` ajout de la fonction norm_properties + +## Version 1.3.0 du 24/04/2015-12:32 + +* `bdc6e9b` Intégration de la branche localhost +* `94b2b48` reconnaitre la valeur spéciale localhost pour check_hostname() + +## Version 1.2.4 du 20/04/2015-17:55 + +* `988e045` bug + +## Version 1.2.3 du 20/04/2015-17:43 + +Correction d'un bug avec la copie des fichiers + +* `d3ab6b8` bug +* `7e0d44b` cosmetic + +## Version 1.2.2 du 20/04/2015-01:16 + +## Version 1.2.1 du 20/04/2015-01:15 + +* `145acfa` bug + +## Version 1.2.0 du 16/04/2015-13:59 + +* `d756a6c` Intégration de la branche ldapt +* `57dac04` support de xempty explicite, et de la possibilité de désactiver ce traitement +* `75f21b5` support des attributs ordonnés +* `00088b3` cosmetic + +## Version 1.1.1 du 16/04/2015-12:10 + +Mise à jour de .ulibver + +## Version 1.1.0 du 16/04/2015-12:09 + +* `4b445a2` Intégration de la branche ldapt-ev +* `f8bd000` support de la nouvelle fonction ensureval +* `dbd9605` Intégration de la branche ptools +* `1fb27d6` pousser le tag quand il a été créé + +## Version 1.0.0 du 14/04/2015-10:05 + +commencer à suivre les versions de nutools: cette release est la première de la +série + +* `66bbf23` Intégration de la branche update-ulibver +* `f5aeff7` maj de ulib +* `073e7b0` Intégration de la branche ptools +* `8f6a32b` forcer la création de la destination. afficher un message informatif +* `48d9104` Intégration de la branche ptools +* `6890d30` workaround pour le bug sous debian squeeze +* `91de409` Intégration de la branche ptools +* `cd50646` ne pas mettre à jour le fichier local +* `f321f1c` Intégration de la branche ptools +* `6258a6b` utiliser la version au lieu de la release +* `5589314` bug +* `10a7d4e` Intégration de la branche ptools +* `cd92832` ne pas supprimer la branche develop +* `3f1dc3e` Intégration de la branche ptools +* `ce050db` ajout d'un changelog possibilité d'éditer le changelog avec -e supprimer les options courtes -z et -Z +* `2bc314c` ajout de --merge-log supprimer les options courtes -z et -Z +* `fc35a05` inclure la liste des modifications dans le log +* `4578219` Intégration de la branche ptools +* `5b0715e` vérifier la présence dans le dépôt distant +* `0e6e94c` Intégration de la feature branch ptools +* `10152b4` calcul de newver action delete +* `6c8e750` implémener -z et -Z +* `2f41de3` cosmetic +* `5f6d5bb` ajout des options courtes -z et -Z +* `18bb510` supprimer l'option courte -d qui est déjà utilisée pour diff +* `dd2a712` ajout de -d et --force-delete +* `38f04ff` maj doc +* `523d3f1` doc pour nouvelle fonctionnalité à implémenter +* `13dfb9a` Intégration de la feature branch prel-tag +* `da96f72` ajout d'un tag après fusion de la branche dans master +* `2a71a51` Intégration de la feature branch prel-tag +* `09c59d8` ne pas afficher les erreurs +* `3e514d5` maj doc +* `f9d4556` Intégration de la feature branch linedots +* `7ae9b48` ajout de elinedots +* `ffa9195` fast-forwarder si possible +* `f64117c` cosmetic +* `d8a3ed4` checkout par défaut +* `89be42e` ne pas utiliser csort +* `881bcde` tenir compte de la valeur de origin +* `7c67a7f` tenir compte des branches potentielles +* `b757ca1` Intégration de la feature branch git-opts +* `c3683a5` bug +* `743e17b` fast-forwarder automatiquement la branche vers laquelle on bascule +* `e3e3bbb` ajout de git_fast_forward +* `1661ee5` ajout des fonctions git_is_ancestor(), git_should_ff(), git_should_push(), git_is_merged() +* `2e215cc` utiliser l'api +* `b975724` maj doc +* `262d12e` toujours charger le module +* `8ae0a8d` utiliser nouvelle API --add-metadata +* `259e1be` ajout de --add-metadata support de --allow-empty pour --update +* `8d3ab79` ajout de semver_addmetadata utiliser API moderne pour les perfs +* `ac940b8` ne garder que les infos sur la branche courante dans les metadata +* `1e11a57` ajouter pz pour faire une archive d'un projet +* `9389fb0` autoriser --allow-emptyp pour l'action --show +* `f90ad4d` bug +* `26aa98f` cosmetic +* `8deda74` cosmetic +* `f8c4a54` calcul de la version avec --prel +* `22a26d9` support des numéros style maven +* `4f0a746` support des fichiers pom.xml +* `6d85330` Intégration de la feature branch ptools +* `f60787a` prel est maintenant en topevel +* `7647d2d` prel est prêt à passer en toplevel +* `7c36761` possibilité de faire -u et -m dans la même commande +* `de618ae` possibilité de ne pas supprimer une feature branch après son intégration +* `57bf621` cosmetic +* `8d4d35d` mettre à jour la version avec pver +* `f98c977` support de -v en mode auto +* `4b64305` finaliser l'option -u +* `5862e75` ne pas charger le fichier s'il n'existe pas +* `e5b8b81` début d'implémentation de update +* `9235ce1` nettoyage +* `ffaed8a` quelques corrections +* `9182dcf` maj api chargement depuis ~/etc/completion.d +* `bbbc35a` maj api chargement depuis ~/etc/completion.d +* `d2133c7` intégrer les modifications faites à l'ur +* `90772f0` Intégration de la feature branch completion +* `163df88` bug +* `e8d683b` bug +* `041a830` suite du support de bash_completion +* `9c17a60` framework pour la complétion automatique +* `bbbe6c4` l'option -c ne fait que le basculement ou la création. Le choix de la version se fera avec -u +* `c5ba03b` ajouter les completions pour prel +* `2b2b7f5` début d'implémentation de prel -c +* `3f925ca` déplacer le code de pver dans ptools diff --git a/CHANGES.txt b/CHANGES.txt deleted file mode 100644 index 4113551..0000000 --- a/CHANGES.txt +++ /dev/null @@ -1,619 +0,0 @@ -## Version 5.3.0 du 28/09/2016-23:31 - -5c466d8 ldif et ldap: match des suffixes sans tenir compte de la casse. modrdn prend un nouvel argument SUFFIX -9eb566a runsmod: modifier chemins par défaut -a082788 bash_completion: bug sur certains serveurs - -## Version 5.2.1 du 01/09/2016-15:44 - -9995f95 runsmod: bug - -## Version 5.2.0 du 31/08/2016-22:30 - -+ 361f1b2 Intégration de la branche runsmod-goodies -| 8854469 maj de la config par défaut -| f0a71e4 bug avec la génération de sysinfos.conf -| 830a33b rruns calcule aussi les chemins d'hôtes automatiquement -| 40f5347 préparer le calcul automatique des chemins -| 2fbc599 bug avec git clone lors du basculement sur la branche develop -| 5bef9b1 utiliser la même logique que uproject pour cloner en mode devel -| 96afb2a mapping des répertoires de destination -| 4b212d8 ne pas utiliser %n tout de suite -| 30e5dda runs et rruns: améliorer le calcul des chemins -| 55c5755 support de //...%h... -| 409e406 préparer implémentation de %n -+ 208f30d Intégration de la branche better-fndate -| f388f27 ajouter des liens pour les fonctions courantes de fndate. En mode automatique, si le fichier contient déjà une mention de date, ne pas la modifier. rajouter l'option -@ pour forcer la modification de la date - -## Version 5.1.0 du 25/08/2016-15:36 - -8a33418 ulib/ldif: support de l'opération modrdn -+ b710461 Intégration de la branche apacheconfig -| e52d6c6 déplacer la logique de apacheconfig dans le module apache.tools - -## Version 5.0.0 du 09/08/2016-09:42 - -c00d9e4 cgilsxml.py: prévoir le cas où le fichier disparait pendant que la liste est construite -2fc7392 uxpath.py: bug -f85cd8a foreach: implémenter la syntaxe %var par défaut -de383be foreach: définir des variables supplémentaires pour chaque fichier trouvé -a7fd880 foreach: maj doc -c01eb20 ulib/awk: maj doc -04b723f qsql: compatibilité avec anciennes versions de bash -dcf0e61 module apache: ajout de HTDOCSBASE pour le déploiement multi-homé -7565461 woinst: support du déploiement d'applications construites avec Maven -eb0a3be woctl _create: enlever le suffixe .woa par défaut -d251a4e woinst: tenir compte des variables utilisateurs -2fd51b6 pclone bascule automatiquement sur la branche develop si elle existe -d056be4 foreach: ajouter l'option --parent -63e78f4 ulib: compatibilité avec versions précédentes de bash -64699c0 ensure_hostname(): clarification du message affiché -22acf0a ulib: ajouter _qsql() et qsql() pour quoter des chaines sql -18b2501 prel: améliorer l'affichage du résumé -b8bf04b uawk dump: ajouter le format -v qui affiches des variables individuelles -+ d57839d Intégration de la branche cgilsxml -| 6e67302 la disposition, le type et l'encoding peuvent être spécifique à un expression --spec ou --glob -| b7600aa possibilité de matcher des sous-répertoires dans l'expression --spec. Support du match de type --glob -| a49d6e2 les valeurs None ne sont pas affichées. n'a pas de valeur -16c1936 uawk dump: avec la méthode -a, afficher par défaut un tableau vide avant -c6b6dd4 uawk: ajout de l'alias dump qui avait été oublié -d8ab7f7 cgilsxml.py: cosmetic -0204a21 cgilsxml.py: possibilité de classifier des fichiers au sein d'un groupe avec --break-on -3a9bf04 cgilsxml.py: possibilité de spécifier la disposition du contenu, le type de contenu, ainsi que l'encoding -a730609 cgilsxml.py: permettre de télécharger le fichier avec la disposition inline -ce0193f cgilsxml.py: corriger la doc -1eba9a7 foreach: améliorer l'affichage -c669e8f utempl -t wosrc: si on crée un wosrc dans le répertoire java, enlever automatiquement le package pour les fichiers resources pour une configuration maven -f0f7a6f utempl -t wosrc: génère des pages bootstrap par défaut -8fced87 woctl run: permettre de choisir la version de java utilisée pour le lancement -9fe17b3 Ajouter le script foreach -f5db579 uxpath: support de la modification de la valeur d'un noeud -3653f7d Ajout des fonctions {build,eval}cmd et upvar -7091ecf support de la création d'un template wosrc pour un projet maven -34dbba9 support de l'utilisation des valeurs spécifiques de GIT_ANNEX pour rsync avec git-annex -470d7cf pcrone peut utiliser un répertoire existant -e4b7729 corriger le template shell -28bb531 activer l'auto-complétion sur debian jessie -317dbca ajout de compileAndGo dans les templates -01b9a68 ulib/vcs: corriger le code de git_track_branch. Utiliser ce code dans pdev --sync -fc75fcd runsmod: quelques corrections mineures -5058085 runsmod: Implémenter les fonctionnalités documentées -12c521c indenter l'affichage des fonctions qui demandent une saisie de l'utilisateur -86a5787 em: forcer -nw en mode non display -e74d9e4 bug -9218941 ajouter l'option -g à caturl -fcacf64 maj doc -01ea57e changer l'affichage de udir, et mettre à jour la doc -9fe71b3 ajouter fndate pour dater les fichiers -8143452 rendre ppath() plus résistant -+ c9ce115 Intégration de la branche improve-tls -| d9153af support de la configuration tls selon https://wiki.mozilla.org/Security/Server_Side_TLS -135dbf6 diverses corrections sur runs -3d5aeb2 Ajouter l'option -c à umountr -278bf5a renommer rumount un umountr -+ db76c12 Intégration de la branche runsmod-paths -| a31f8c2 changer la convention de nommage pour les répertoires ~/runs -5a31e7e ulib/java: ajout de la fonction get_java_version() -f74b5e3 Générer la doc au format markdown pour tagadoc -ded89be pdev: corrections pour supporter nouvelle version de git -5d3e9fe runsmod: corriger les valeurs par défaut pour les hôtes dans la bulle serveur -ada3686 uenv: bug dans le calcul des répertoires destination -+ deaa416 Intégration de la branche runsmod-only -| f96c482 proposer de créer la configuration par défaut -| f04e862 implémentation initiale de runsmod -42b9590 cosmetic -9bc373f cgiparams.py: implémenter le code de retour -1834d29 intégrer les fonctionnalités de cgiupload.py dans cgiparams.py - -## Version 4.4.1 du 15/04/2016-12:24 - -a45fd99 awkrun: ajouter l'argument suffix à qsql(), cqsql(), cval() - -## Version 4.4.0 du 15/04/2016-12:04 - -73391a9 awkrun: ajout d'un champ field à cqsql -2f60fec git_track_branch(): support de git >= 1.8.x -130b4d1 tenir compte du fichier /etc/debian_chroot pour initialiser UTOOLS_CHROOT -48c0420 ne pas afficher d'erreur si apache n'est pas installé -5e1a6cf Intégration de la branche rumount -1ad804b implémentation initiale de rumount -f460399 améliorer le calcul du répertoire destination avec pclone/pcrone -6411e63 configurer la valeur de l'umask -eb4362d apacheconfig: déploiement pour plusieurs systèmes à partir des mêmes sources - -## Version 4.3.0 du 07/04/2016-14:57 - -d021c97 support de on_debian() avec des arguments - -## Version 4.2.0 du 06/04/2016-14:25 - -a710c5a Intégration de la branche stdout -ba969e3 ajout de stdredir pour pallier l'absence éventuelle de /dev/std* -472f2c7 Intégration de la branche sysinfos -1864cdf fonctions pour faciliter la gestion des dépendances sous debian -2c27f03 uproject: ajouter la commande xconfig-export -ed3de6d umail: ajout de l'option --gencmd - -## Version 4.1.1 du 08/03/2016-12:04 - -0bd2b1f correction de bugs avec apacheconfig - -## Version 4.1.0 du 03/03/2016-11:02 - -4024b2a pyulib/umail: possibilité de spécifier le type de contenu - -## Version 4.0.0 du 01/03/2016-19:08 - -c46626c ldif: support de dumpcsv et printcsv dans get_transform_cmd() -d661e43 doinplace fonctionne comme la fonction. la version étendue est un lien nommé doinplacex -d6540ac dumpcsv: ajouter l'option --hname -903d500 dumpcsv: ajouter les options --keep-fields, --skip-fields, --dump-headers -af18332 implémenter printcsv -383410a *csv: améliorer cohérences des arguments -4808fa0 dumpcsv: ajout de l'option -b -939b5ad implémenter dumpcsv et nettoyer un peu le code -cb0e553 script doinplace qui mélange les fonctionnalités des fonctions doinplace et evalp -0cf8e04 scripts noerror, noout, noerr qui fonctionnent comme les fonctions du même nom -66249c9 nutools base: ajout de noerror(), noout(), noerr() -db76488 maj doc -40f1bf6 prel: après avoir fusionné une branche, revenir sur develop - -## Version 3.5.0 du 26/02/2016-11:35 - -4648b3f awkrun: ajout de la fonction qarr() -81e7f9b correction de certains messages affichés -0ea24ec ajout de sqlcsv -ad06535 pu: autoriser fast-forward même en cas de modifications locales -1c397e8 ajout des fonctions awk cqsql(), sval(), cval(). réorganiser et clarifier la doc - -## Version 3.4.2 du 10/02/2016-11:45 - -## Version 3.4.1 du 10/02/2016-11:45 - -a702b89 mergecsv: bug quand left ne contient qu'un seul champ vide - -## Version 3.4.0 du 09/02/2016-11:25 - -788e1ff cgilsxml.py: ajout de l'option -E. bug avec le tri sur des valeurs inexistantes - -## Version 3.3.0 du 08/02/2016-09:41 - -da3ef60 cgilsxml.py: ajouter de squery_string permettant d'ajouter un préfixe -bb25711 cgi: cgierror et cgiredirect arrêtent le script - -## Version 3.2.0 du 07/02/2016-23:05 - -24f3211 cgiparams.py et cgilsxml.py: modifications pour gérer la construction de QUERY_STRING - -## Version 3.1.0 du 04/02/2016-13:52 - -3190a32 cgilsxml.py: ajout de la variable script_base, qui permet de faire des urls relatives -f4537af ussh: bug dans l'analyse des arguments -9992505 ussh: pas de valeur par défaut pour DEFAULT_USER; --parse affiche toutes les options -b19059e Intégration de la branche ussh-auto-persist -5c75737 ussh: configuration du multiplexage pour certains hôtes - -## Version 3.0.1 du 31/12/2015-12:36 - -56c305a awk ne supporte pas \s - -## Version 3.0.0 du 24/12/2015-15:39 - -4b1c30b nouvelles fonctions pour le module cgi. cgilsxml, cgiupload et cgiparams sont rassemblés dans le module cgisupport - -## Version 2.26.0 du 24/12/2015-12:17 - -d5a2cce cgilsxml.py: rajouter la variable env/query_vars - -## Version 2.25.0 du 24/12/2015-11:15 - -6c73940 cgilsxml.py: désactiver le cache par défaut - -## Version 2.24.1 du 24/12/2015-10:12 - -3aba3e3 bug - -## Version 2.24.0 du 24/12/2015-09:58 - -b288cf2 apacheconfig: support des fichiers de confs --> /etc/apache2/cond.d sur debian wheezy- -2e3fe66 ussh: forcer l'allocation d'un tty avec --cc -58fa40c ussh: ajouter le support de uwatch -7bc63c5 maj doc uwatch -2d2754b ajout de uwatch pour afficher un compte -4376fff maj doc -bb176d7 maj du template pour runsconfig - -## Version 2.23.1 du 15/12/2015-11:01 - -d8d0cc8 cgilsxml: sélectionner automatiquement un groupe seul - -## Version 2.23.0 du 15/12/2015-10:06 - -501a7d2 cgilsxml: script_name et xslt peuvent être spécifié dans les paramètres QUERY_STRING -ebe4599 Intégration de la branche runs -35eadc1 correction d'un bug avec la recherche d'un fichier de recette -98ba626 les recettes ont maintenant l'extension .rr par défaut -a2e863e chercher aussi les scripts dans le sous-répertoire runs dans les répertoires d'hôte - -## Version 2.22.0 du 07/12/2015-19:05 - -dbc4d9a uscrontab: ne pas afficher plusieurs fois un même message quand le verrou est posé. n'afficher le message sur la pause qu'avec un tty -9821d20 ajouter le lien vers cgilsxml.py - -## Version 2.21.0 du 07/12/2015-15:12 - -d7b468d cgilsxml: ajouter les variables dlname, size, Y, m, d, H, M, S. maj doc -eac94a7 cgiupload: possibilité de filtrer sur le nom du fichier ou son type de contenu -fae2111 ajout de cgilsxml -26fe709 apacheconfig: -S peut aussi être utilisé avec --localhosts -7c6ad6a apacheconfig: nouvelles options pour le développement -c2d5eab pxs fait aussi git pull avec un dépôt sans annexe -dca7bee pxs fonctionne pour les dépôt normaux comme pci -A - -## Version 2.20.0 du 12/11/2015-15:41 - -16d95d2 apache_autoconf(): déploiement des certificats autorité -775a021 nutools: ajout de cpvcs() pour copier avec la sémantique de cpnovcs() les fichiers de VCS aussi -1ca0ee5 runs: ajout des options --force, --copy-links et --with-vcs à ref() -91686fb templatectl: il n'est plus nécessaire de spécifier explicitement fichier.template - -## Version 2.19.0 du 02/11/2015-11:03 - -37f3bff apacheconfig: diverses corrections de bugs -5d3861b apacheconfig: si host==@@dhost@@, alors hostname=@@dhostname@@ et aliases=@@daliases@@ -27592d4 apacheconfig: support des hôtes dynamiques ou statiques -93ac7ea re-écrire la gestion des templates pour apacheconfig et runsconfig - -## Version 2.18.0 du 27/10/2015-12:05 - -434403a Intégration de la branche uscrontab -2118885 uscrontab: possibilité de mettre en pause sans modifier les planifications en cours -0eeee20 awkrun: renommer les fonctions quote_* -18691c8 upassword: maj doc et support des commentaires pour le mode batch - -## Version 2.17.2 du 26/09/2015-12:25 - -1837a0c pxs: corriger xget, xcopy, xmove pour utiliser directement git annex sync - -## Version 2.17.1 du 26/09/2015-12:21 - -c8c6744 pxs: support des versions <= 5.20150825 - -## Version 2.17.0 du 26/09/2015-11:51 - -552e4ba améliorer pxs pour un dépôt en mode direct -2ce66e6 maj doc - -## Version 2.16.0 du 10/09/2015-11:16 - -42aa7ea fonctions e*: changer l'affichage en mode NO_COLOR pour afficher les préfixes complets - -## Version 2.15.0 du 08/09/2015-10:14 - -69ee45a Intégration de la branche prel-fixes -47b6e5b créer VERSION.txt et CHANGES.txt à la racine du projet -9d8a747 faire le push à la fin des opérations d'update et/ou de merge - -## Version 2.14.0 du 03/09/2015-09:04 - -9dcd60b pxs transfert les données aussi en mode direct - -## Version 2.13.0 du 01/09/2015-15:44 - -8d2e386 uscrontab: permettre d'ignorer le code d'erreur pour une commande planifiée -aa989ef Intégration de la branche uinst -58d9f16 uinst -y pose toujours la question si le répertoire est calculé automatiquement -594fd4e activer l'autocomplétion pour uinst -h - -## Version 2.12.1 du 24/08/2015-16:12 - -e356c2b bug avec le calcul du système -e040cac Avec les fonctions e*, afficher le statut de façon explicite en mode non coloré - -## Version 2.12.0 du 23/08/2015-22:57 - -dcb85ac Améliorer le déploiement uinst:rsync avec préfixes pour les hôtes distants - -## Version 2.11.0 du 21/08/2015-10:31 - -d9282f7 uinst: ajouter l'option -h pour la méthode uinst:rsync -7643573 nettoyer la branche - -## Version 2.10.0 du 19/08/2015-10:23 - -6ba41b9 support de --uses-su pour ruinst -C - -## Version 2.9.0 du 10/08/2015-14:04 - -447d13f support de nouvelles versions d'OS pour sysinfos -48e111a fconv et fnconv: le premier argument est le fichier s'il n'est pas spécifié avec -f -c29f4b9 mkiso: nouvelles options -A et -V -662b82f caturl --check ne doit pas afficher l'url - -## Version 2.8.0 du 22/07/2015-15:09 - -0f5b572 uinst installe maintenant les profils en mode partagé par défaut -fb6b492 améliorer l'affichage des scripts geturl et openurl - -## Version 2.7.0 du 17/07/2015-22:03 - -7e2a6c3 update-nutools ignore par défaut l'eventuel proxy défini par http_proxy -13215c3 apacheconfig: améliorer l'affichage de l'aide et support du répertoire .apacheconfig -27f5f7a pyulib honore maintenant UTOOLS_NO_COLORS -3ee6cb4 diminuer l'indentation pour les fonctions e* -d4ac5f7 modifier l'affichage des fonction e* - -## Version 2.6.0 du 10/07/2015-17:32 - -efa6b3a Intégration de la branche ftps -3288999 authftp: support tls en forçant l'utilisation de lftp -58e7f6f pconf reconnait maintenant les arguments offline et online - -## Version 2.5.1 du 03/07/2015-08:29 - -abc4cb9 dumpclients: possibilité de choisir les champs supplémentaires à afficher - -## Version 2.5.0 du 03/07/2015-07:39 - -2e15cbe Ajouter l'option -z à dumpclients - -## Version 2.4.0 du 01/07/2015-04:04 - -fda97a8 mise à jour de upassword - -## Version 2.3.0 du 01/07/2015-03:09 - -f581aa9 Intégration de la branche pdev-merges -80d7a73 ajouter les options -b et -s à pdev - -## Version 2.2.0 du 24/06/2015-16:19 - -98cfdfc Intégration de la branche update-upassword -34f027b bug avec la présence du scheme dans sha -b5e6c13 Intégration de la branche update-upassword -e4e2fa7 mise à jour upassword pour afficher sha en hexadécimal - -## Version 2.1.1 du 22/06/2015-11:37 - -40225b0 Intégration de la branche proxy-default -2fa0791 profile.d/proxy lit aussi les valeurs par défaut pour l'hôte - -## Version 2.1.0 du 22/06/2015-10:25 - -2a3eb76 Intégration de la branche uenv-modifs -bd8c7f8 correction d'un bug avec le nom final -d73c929 correction d'un bug avec le choix du répertoire de destination -e9089d8 les répertoires de profil partagé et de machine sont traités comme un seul répertoire -e3f76cd possibilité de spécifier si un profil doit être installé dans le répertoire de profil partagé - -## Version 2.0.0 du 22/06/2015-00:48 - -8286b29 Intégration de la branche uenv-modifs -c43cda0 les répertoires de profil sont spécifiques au nom de la machine - -## Version 1.19.0 du 21/06/2015-10:27 - -387dec2 Intégration de la branche add-pxwa -4d0b734 ajout de la commande xwebapp - -## Version 1.18.0 du 09/06/2015-10:40 - -01e2929 Intégration de la branche fix-javaproperties -b24e9ff norm_properties() affiche les caractères en majuscule, comme JavaProperties#store() - -## Version 1.17.0 du 07/06/2015-14:00 - -e2d0c0c Intégration de la branche pdev-fix -1cc3bd4 ajouter l'option --sync à pdev - -## Version 1.16.0 du 04/06/2015-22:14 - -4a15085 Intégration de la branche verbose-update -8a27561 pu: afficher un message s'il y a des modifications locales - -## Version 1.15.0 du 22/05/2015-17:03 - -e715087 Intégration de la branche update-upassword -a74a373 upassword: possibilité de spécifier de façon dynamique la ligne à afficher après - -## Version 1.14.0 du 22/05/2015-16:41 - -d8f20c3 Intégration de la branche update-upassword -dea524a ajout du mode batch - -## Version 1.13.0 du 22/05/2015-16:04 - -275bff8 Intégration de la branche update-upassword -1ce1d98 mise à jour de upassword - -## Version 1.12.0 du 22/05/2015-13:37 - -57fc84d correction d'un bug avec prel -s -cf108d7 Intégration de la branche prel-show-modifs -a744f48 prel -s afficher la liste des modifications qui seraient enregistrées - -## Version 1.11.0 du 22/05/2015-11:41 - -2984052 Intégration de la branche quiet-completion -755cfa7 ne pas afficher les erreurs pour les fonctions de complétion de git -2b5eb2a Intégration de la branche pu -b004730 uproject update fait fetch puis merge, manuellement - -## Version 1.10.0 du 20/05/2015-12:08 - -bf1d86a Intégration de la branche pu-auto-forward -8bf8164 fast-forwarder automatiquement les branches locales par rapport aux branches distantes -8baabea Intégration de la branche ptools-maj-topic -5ff5b7d déterminer les branches de topic: ignorer les branches avec un slash dans le nom - -## Version 1.9.0 du 19/05/2015-18:25 - -5933089 Intégration de la branche awkfsv -caeb06e possibilité de spécifier les types des champs pour mysqlloadcsv -b2c9f81 uawk requière maintenant de spécifier le nom de l'outil - -## Version 1.8.1 du 19/05/2015-10:45 - -de8d690 Intégration de la branche annex-support -2044181 ne pas synchroniser l'annexe en mode automatique - -## Version 1.8.0 du 19/05/2015-10:27 - -4779695 Intégration de la branche annex-support -89ad130 support de git-annex pour pp et pdev - -## Version 1.7.0 du 15/05/2015-16:11 - -16ad285 Intégration de la branche nutools -93d8262 rajouter le script nutools pour vérifier la version installée de nutools. renommer la fonction nutools() en pconf() - -## Version 1.6.0 du 04/05/2015-12:40 - -0463b2c Intégration de la branche norm_props -48ca720 ajout de l'en-tête - -## Version 1.5.1 du 29/04/2015-10:14 - -4c92f54 Intégration de la branche push-all -02c15f9 pp -a pousse toutes les branches et tous les tags. ajouter les options -b et -t pour pousser respectivement toutes les branches et tous les tags - -## Version 1.4.1 du 29/04/2015-10:12 - -Release erronée, à ignorer - -## Version 1.4.0 du 27/04/2015-18:12 - -7122fc4 Intégration de la branche norm-properties -845c54e ajout de la fonction norm_properties - -## Version 1.3.0 du 24/04/2015-12:32 - -bdc6e9b Intégration de la branche localhost -94b2b48 reconnaitre la valeur spéciale localhost pour check_hostname() - -## Version 1.2.4 du 20/04/2015-17:55 - -988e045 bug - -## Version 1.2.3 du 20/04/2015-17:43 - -Correction d'un bug avec la copie des fichiers - -d3ab6b8 bug -7e0d44b cosmetic - -## Version 1.2.2 du 20/04/2015-01:16 - -## Version 1.2.1 du 20/04/2015-01:15 - -145acfa bug - -## Version 1.2.0 du 16/04/2015-13:59 - -d756a6c Intégration de la branche ldapt -57dac04 support de xempty explicite, et de la possibilité de désactiver ce traitement -75f21b5 support des attributs ordonnés -00088b3 cosmetic - -## Version 1.1.1 du 16/04/2015-12:10 - -Mise à jour de .ulibver - -## Version 1.1.0 du 16/04/2015-12:09 - -4b445a2 Intégration de la branche ldapt-ev -f8bd000 support de la nouvelle fonction ensureval -dbd9605 Intégration de la branche ptools -1fb27d6 pousser le tag quand il a été créé - -## Version 1.0.0 du 14/04/2015-10:05 - -commencer à suivre les versions de nutools: cette release est la première de -la série - -66bbf23 Intégration de la branche update-ulibver -f5aeff7 maj de ulib -073e7b0 Intégration de la branche ptools -8f6a32b forcer la création de la destination. afficher un message informatif -48d9104 Intégration de la branche ptools -6890d30 workaround pour le bug sous debian squeeze -91de409 Intégration de la branche ptools -cd50646 ne pas mettre à jour le fichier local -f321f1c Intégration de la branche ptools -6258a6b utiliser la version au lieu de la release -5589314 bug -10a7d4e Intégration de la branche ptools -cd92832 ne pas supprimer la branche develop -3f1dc3e Intégration de la branche ptools -ce050db ajout d'un changelog possibilité d'éditer le changelog avec -e supprimer les options courtes -z et -Z -2bc314c ajout de --merge-log supprimer les options courtes -z et -Z -fc35a05 inclure la liste des modifications dans le log -4578219 Intégration de la branche ptools -5b0715e vérifier la présence dans le dépôt distant -0e6e94c Intégration de la feature branch ptools -10152b4 calcul de newver action delete -6c8e750 implémener -z et -Z -2f41de3 cosmetic -5f6d5bb ajout des options courtes -z et -Z -18bb510 supprimer l'option courte -d qui est déjà utilisée pour diff -dd2a712 ajout de -d et --force-delete -38f04ff maj doc -523d3f1 doc pour nouvelle fonctionnalité à implémenter -13dfb9a Intégration de la feature branch prel-tag -da96f72 ajout d'un tag après fusion de la branche dans master -2a71a51 Intégration de la feature branch prel-tag -09c59d8 ne pas afficher les erreurs -3e514d5 maj doc -f9d4556 Intégration de la feature branch linedots -7ae9b48 ajout de elinedots -ffa9195 fast-forwarder si possible -f64117c cosmetic -d8a3ed4 checkout par défaut -89be42e ne pas utiliser csort -881bcde tenir compte de la valeur de origin -7c67a7f tenir compte des branches potentielles -b757ca1 Intégration de la feature branch git-opts -c3683a5 bug -743e17b fast-forwarder automatiquement la branche vers laquelle on bascule -e3e3bbb ajout de git_fast_forward -1661ee5 ajout des fonctions git_is_ancestor(), git_should_ff(), git_should_push(), git_is_merged() -2e215cc utiliser l'api -b975724 maj doc -262d12e toujours charger le module -8ae0a8d utiliser nouvelle API --add-metadata -259e1be ajout de --add-metadata support de --allow-empty pour --update -8d3ab79 ajout de semver_addmetadata utiliser API moderne pour les perfs -ac940b8 ne garder que les infos sur la branche courante dans les metadata -1e11a57 ajouter pz pour faire une archive d'un projet -9389fb0 autoriser --allow-emptyp pour l'action --show -f90ad4d bug -26aa98f cosmetic -8deda74 cosmetic -f8c4a54 calcul de la version avec --prel -22a26d9 support des numéros style maven -4f0a746 support des fichiers pom.xml -6d85330 Intégration de la feature branch ptools -f60787a prel est maintenant en topevel -7647d2d prel est prêt à passer en toplevel -7c36761 possibilité de faire -u et -m dans la même commande -de618ae possibilité de ne pas supprimer une feature branch après son intégration -57bf621 cosmetic -8d4d35d mettre à jour la version avec pver -f98c977 support de -v en mode auto -4b64305 finaliser l'option -u -5862e75 ne pas charger le fichier s'il n'existe pas -e5b8b81 début d'implémentation de update -9235ce1 nettoyage -ffaed8a quelques corrections -9182dcf maj api chargement depuis ~/etc/completion.d -bbbc35a maj api chargement depuis ~/etc/completion.d -d2133c7 intégrer les modifications faites à l'ur -90772f0 Intégration de la feature branch completion -163df88 bug -e8d683b bug -041a830 suite du support de bash_completion -9c17a60 framework pour la complétion automatique -bbbe6c4 l'option -c ne fait que le basculement ou la création. Le choix de la version se fera avec -u -c5ba03b ajouter les completions pour prel -2b2b7f5 début d'implémentation de prel -c -3f925ca déplacer le code de pver dans ptools From 2a6d0899cacb8997d51c89a21ada1de6fb01c4bd Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 7 Oct 2016 17:20:06 +0400 Subject: [PATCH 05/39] =?UTF-8?q?ulib/base:=20impl=C3=A9menter=20ask=5Fany?= =?UTF-8?q?()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/base | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/lib/ulib/base b/lib/ulib/base index ee0fccd..c71fc4d 100644 --- a/lib/ulib/base +++ b/lib/ulib/base @@ -3533,6 +3533,79 @@ function ask_yesno() { is_yes "$default" fi } +function ask_any() { +# Afficher le message $1 suivi du texte "[$2]" (qui vaut par défaut +Oq), puis +# lire la réponse. Les lettres de la chaine de format $2 sont numérotées de 0 à +# $((${#2} - 1)). Le code de retour est le numéro de la lettre qui a été +# sélectionnée. Cette fonction est une généralisation de ask_yesno() pour +# n'importe quel ensemble de lettres. +# La première lettre en majuscule est la lettre sélectionnée par défaut. +# La lettre O matche toutes les lettres qui signifient oui: o, y, 1, v, t +# La lettre N matche toutes les lettres qui signifient non: n, f, 0 +# Il y a des raccourcis: +# +O --> On +# +N --> oN +# +C --> oN si on est en mode interactif, On sinon +# +X --> On si on est en mode interactifn oN sinon +# Si $1 est une option, elle est utilisée avec check_interaction pour savoir si +# on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées +# ($2=message, $3=format) + local interactive=1 + if [[ "$1" == -* ]]; then + if [ "$1" != -- ]; then + check_interaction "$1" || interactive= + fi + shift + else + check_interaction -c || interactive= + fi + local format="${2:-+Oq}" + format="${format/+O/On}" + format="${format/+N/oN}" + if [ -n "$interactive" ]; then + format="${format/+C/oN}" + format="${format/+X/On}" + else + format="${format/+C/On}" + format="${format/+X/oN}" + fi + local i count="${#format}" + + if [ -n "$interactive" ]; then + eflush + local message="${1:-Voulez-vous continuer?}" + local prompt="[$format]" + local r f lf defi + while true; do + __eecho_ "$message $prompt " 1>&2 + uread r + r="$(strlower "${r:0:1}")" + i=0; defi= + while [ $i -lt $count ]; do + f="${format:$i:1}" + lf="$(strlower "$f")" + [ "$r" == "$lf" ] && return $i + if [ -z "$defi" ]; then + [[ "$f" =~ [A-Z] ]] && defi="$i" + fi + if [ "$lf" == o ]; then + case "$r" in o|y|1|v|t) return $i;; esac + elif [ "$lf" == n ]; then + case "$r" in n|f|0) return $i;; esac + fi + i=$(($i + 1)) + done + [ -z "$r" ] && return ${defi:-0} + done + else + i=0 + while [ $i -lt $count ]; do + [[ "${format:$i:1}" =~ [A-Z] ]] && return $i + i=$(($i + 1)) + done + return 0 + fi +} function read_value() { # Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non # vide, puis lire la valeur donnée par l'utilisateur. Cette valeur doit être non From 44fe622ac825dc3139c81c256a56e045aa937192 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 14 Oct 2016 13:52:51 +0400 Subject: [PATCH 06/39] =?UTF-8?q?chrono.py:=20chronom=C3=A8tre=20graphique?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chrono.py | 311 +++++++++++++++++++++++++++++++++++++++++++++++++ lib/chrono.wav | Bin 0 -> 1114236 bytes 2 files changed, 311 insertions(+) create mode 100755 chrono.py create mode 100644 lib/chrono.wav diff --git a/chrono.py b/chrono.py new file mode 100755 index 0000000..e82f521 --- /dev/null +++ b/chrono.py @@ -0,0 +1,311 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""Afficher un chronomètre""" + +import os, sys, re, subprocess, traceback +from os import path +from datetime import date as Date, time as Time +from datetime import datetime as Datetime, timedelta as Timedelta +from types import IntType, LongType + +DEFAULT_SOUND = path.join(path.dirname(__file__), 'lib', 'chrono.wav') + +def win_playSound(name): + try: import winsound as s + except: return + if name is None: + s.PlaySound(None, s.SND_ASYNC) + else: + scriptdir = path.split(path.abspath(sys.argv[0]))[0] + soundfile = path.join(scriptdir, name) + s.PlaySound(soundfile, s.SND_FILENAME + s.SND_ASYNC) + +def linux_playSound(name): + subprocess.call(['/usr/bin/aplay', '-Nq', name]) + +def playSound(name=None): + if os.name == 'nt': + return win_playSound(name) + elif sys.platform.startswith('linux'): + return linux_playSound(name) + +def isnum(i): + return type(i) in (IntType, LongType) + +DEFAULT_TIMEOUT = '5' + +RE_DESTHOUR = re.compile(r'@(\d+)(?:[:.](\d+)(?:[:.](\d+))?)?$') +def parse_desthour(s): + mo = RE_DESTHOUR.match(s) + if mo is None: return None + h, m, s = mo.groups() + if h is None: h = 0 + if m is None: m = 0 + if s is None: s = 0 + h, m, s = int(h), int(m), int(s) + src = Datetime.today() + srcdate = src.date(); srctime = src.time() + destdate = srcdate; desttime = Time(h, m, s) + if desttime <= srctime: destdate = destdate + Timedelta(1) + src = Datetime.combine(srcdate, srctime) + dest = Datetime.combine(destdate, desttime) + delta = dest - src + return delta.total_seconds() + +RE_TIMEOUT = re.compile(r'(\d+)(?:[:.](\d+)(?:[:.](\d+))?)?$') +def parse_timeout(s): + mo = RE_TIMEOUT.match(s) + if mo is None: return None + h, m, s = mo.groups() + if m is None and s is None: + # M + m = h + h = None + elif s is None: + # M:S + s = m + m = h + h = None + else: + # H:M:S + pass + if h is None: h = 0 + if m is None: m = 0 + if s is None: s = 0 + h, m, s = int(h), int(m), int(s) + return h * 3600 + m * 60 + s + +class Chrono: + timeout = None + date_start = None + date_end = None + initial = None + started = None + + def __init__(self, timeout=None, start=False): + self.set_timeout(timeout) + if start: self.start() + + def __format(self, delta): + h = delta.seconds // 3600 + seconds = delta.seconds % 3600 + m = seconds // 60 + s = seconds % 60 + if h > 0: return '%02i:%02i:%02i' % (h, m, s) + else: return '%02i:%02i' % (m, s) + + def __delta(self, timeout): + return Timedelta(seconds=timeout) + + def set_timeout(self, timeout=None): + if timeout is not None and not isnum(timeout): + tmp = parse_desthour(str(timeout)) + if tmp is None: tmp = parse_timeout(timeout) + if tmp is None: tmp = int(timeout) * 60 + timeout = tmp + self.timeout = timeout + if timeout is None: self.initial = '00:00' + else: self.initial = self.__format(self.__delta(timeout)) + + def start(self, timeout=None): + if timeout is None: timeout = self.timeout + self.date_start = Datetime.today() + if timeout is None: self.date_end = None + else: self.date_end = self.date_start + self.__delta(timeout) + self.started = True + + def stop(self): + self.started = False + + def is_started(self): + return self.started + + def is_end(self): + return self.started and self.date_end is not None and Datetime.today() >= self.date_end + + def __repr__(self): + now = Datetime.today() + if self.date_end is None: delta = now - self.date_start + elif now > self.date_end: delta = Timedelta() + else: delta = self.date_end - now + return self.__format(delta) + +def run_chronometre(timeout=None, autostart=False): + from Tkinter import Tk, Toplevel, Frame, Label, Entry, Button + import tkMessageBox + + class Dialog(Toplevel): + def __init__(self, parent, title=None): + self.result = None + self.have_result = False + + Toplevel.__init__(self, parent) + self.transient(parent) + if title: self.title(title) + self.parent = parent + + body = Frame(self) + self.initial_focus = self.body(body) + body.pack(padx=5, pady=5) + self.buttonbox() + self.grab_set() + if not self.initial_focus: self.initial_focus = self + self.protocol("WM_DELETE_WINDOW", self.cancel) + self.geometry("+%d+%d" % (parent.winfo_rootx()+50, + parent.winfo_rooty()+50)) + self.initial_focus.focus_set() + self.wait_window(self) + + def set_result(self, result): + self.result = result + self.have_result = True + + def body(self, master): + pass + + def buttonbox(self): + box = Frame(self) + w = Button(box, text="OK", width=10, command=self.ok, default='active') + w.pack(side='left', padx=5, pady=5) + w = Button(box, text="Annuler", width=10, command=self.cancel) + w.pack(side='left', padx=5, pady=5) + self.bind("", self.ok) + self.bind("", self.cancel) + box.pack() + + def ok(self, event=None): + if not self.validate(): + self.initial_focus.focus_set() + return + self.withdraw() + self.update_idletasks() + self.apply() + self.cancel() + + def cancel(self, event=None): + self.parent.focus_set() + self.destroy() + + def validate(self): + return True + + def apply(self): + pass + + class Config(Dialog): + def body(self, master): + Label(master, text="Nb minutes", padx=20).grid(row=0) + self.entry = Entry(master) + self.entry.grid(row=0, column=1) + return self.entry + + def apply(self): + value = self.entry.get() + if value == "": result = None + else: result = value + self.set_result(result) + + class Application(Frame): + root = None + chrono = None + stop = None + + def __init__(self, timeout=None, autostart=False, **kw): + self.chrono = Chrono(timeout) + self.stop = False + + root = Tk() + root.title("Chronomètre") + root.columnconfigure(0, weight=1) + root.rowconfigure(0, weight=1) + root.bind("c", lambda event: self.do_config()) + root.bind("s", lambda event: self.do_start()) + root.bind("q", lambda event: self.quit()) + self.root = root + + kw.update(master=root) + Frame.__init__(self, **kw) + + self.TIME = Label(self, width=10, height=2, text=self.chrono.initial, padx=30, pady=10, font=('Helvetica', 18, "normal")) + self.START = Button(self, text="Démarrer", command=self.do_start) + self.CONFIG = Button(self, text="Config", command=self.do_config) + self.QUIT = Button(self, text="Quitter", command=self.quit) + + self.grid(column=0, row=0, sticky='nsew') + self.TIME.grid(column=0, row=0, columnspan=3, sticky='nsew') + self.START.grid(column=0, row=1, sticky='ew') + self.CONFIG.grid(column=1, row=1, sticky='ew') + self.QUIT.grid(column=2, row=1, sticky='ew') + + self.columnconfigure(0, weight=2) + self.columnconfigure(1, weight=1) + self.columnconfigure(2, weight=2) + self.rowconfigure(0, weight=1) + + if autostart: self.do_start() + + def update_time(self): + chrono = self.chrono + self.TIME.configure(text=chrono) + if chrono.is_started(): + if chrono.is_end(): + playSound(DEFAULT_SOUND) + else: + self.root.after(300, self.update_time) + + def do_start(self): + self.chrono.start() + self.update_time() + + def do_config(self): + chrono = self.chrono + chrono.stop() + config = Config(self.root) + if config.have_result: + try: + chrono.set_timeout(config.result) + self.TIME.configure(text=chrono.initial) + except: + traceback.print_exc() + tkMessageBox.showerror("Valeur invalide", sys.exc_info()[1]) + + Application(timeout, autostart).mainloop() + +if __name__ == '__main__': + from argparse import ArgumentParser, RawTextHelpFormatter + AP = ArgumentParser( + formatter_class=RawTextHelpFormatter, + usage=u"%(prog)s [options] [TIMEOUT]", + description=u"Afficher un chronomètre", + epilog=u"Si TIMEOUT est spécifié, par défaut le décompte démarre automatiquement." + ) + AP.set_defaults(autostart=None, timeout=None) + AP.add_argument('timeout', metavar='TIMEOUT', nargs='?', + help=u"""\ +(valeur vide) + chronomètre qui démarre à 0:00 et ne s'arrête pas +H:M:S (heures:minutes:secondes) +ou M:S (minutes:secondes) +ou M (minutes) + minuteur qui démarre à H:M:S et fait un décompte jusqu'à 0:00. A la fin + du décompte, une sonnerie retentit. +@H[:M[:S]] + minuteur qui fonctionne comme précédemment, sauf qu'on spécifie l'heure + d'arrivée, et que la durée est calculée automatiquement""") + AP.add_argument('-n', '--no-autostart', dest='autostart', action='store_false', + help=u"Ne pas démarrer automatiquement le décompte même si TIMEOUT est spécifié.") + AP.add_argument('-s', '--autostart', dest='autostart', action='store_true', + help=u"Forcer le démarrage automatique du décompte, même si TIMEOUT n'est pas spécifié.") + o = AP.parse_args() + + autostart = o.autostart + if autostart is None: autostart = o.timeout is not None + o.autostart = autostart + + timeout = o.timeout + if timeout is None: timeout = DEFAULT_TIMEOUT + elif timeout == '': timeout = None + o.timeout = timeout + + run_chronometre(o.timeout, o.autostart) diff --git a/lib/chrono.wav b/lib/chrono.wav new file mode 100644 index 0000000000000000000000000000000000000000..077ae850f2051aef425449628f457f52241f6f49 GIT binary patch literal 1114236 zcmeEP^>>t4w;p$&$;3kvf;+|Ci@QT{C=M+Yx8lX!-L({VEk%nLNdh6BjJw<2&#Zg@ zgZpE?wX*0}nq=m@=j`+BNA~pY)S<&nhLSO$b^rF`rp!!LFc=IbgT-ifj=@k|WHC4l z1!K&}nInS{{7u>a%2uFk1SYz4|zplk)oR-kMJ%2uFk1SYz4|zplk)oR-kMJ z%2uFk1SYz4|zplk)oR-kMJ%2uFk1SYz4|zplk)oR-kMJ%2uFk1SYz4|z zplk)oR-kMJ%2uFk1SYz4|zplk)oR-kMJ%2uFk1^&NQV2C@-zsP?)c!Kkh#bz~T zTJ+T%uXPLTs~r=(xp^l{uixbqlz!e(vdy~BoyDuciBIXHn%rbt`B2*lRc6)SlA4}W zLHSYgjz5E0z%X0tyR*J-*ULXEO4@vv7;6^%Vy>6h(j?7YXFOfJ$=coVqnF7%z*r$U z%uh@xlGi9dDMnbXM0Gy#tDf9vvd(iCmGrV*Ecn~Z z%fDx8oHNNV=v$F~WZq!Yrov8^;Uz5F6l09@7uRN=ld+ZYLHq+>t7#%Ro2*iOuXrw2 zU9EY-)*6ozo77mF__X4|*tb z744fm@9iE>f+NFQ-xhQ)wr+5DwV(1VbcVcRJP-WZ05eoCTpS)0j)V^f?V){vzM<2; z^k9ZN;+t!K;HgXBGuD>qX=*><`R1PPUmoZX+8$aNj$yTB_T(I4EoYh-ZzDyKj8NN+>wx2yy?RPzIy$1tBBKsrL1r@mO)C$SCWSug(()-wYwH_w5s1r&~sj)Cok>QP* z8CNU|N|y`PvoTMtim#287D zjNtzjJ5|;>y-+ixN}c%J>U|UUSKE{jn-Pe4oX|sYPc=z=L2{GN5YFeOb5q#%(8KTt zXB}UE!vsf{{1Vgj%;lvmKin;R{O)tXst@|YqR(4P#^tOutSI_sIiwHR@0oI)Pi*}? zsm@2<#jahxiT<6zjgdQ%k(_g^>*93&zp7uw%=l(Mt@+pH=cN01(#>8-? zKI!j*FWimn+;Fc*nty6=h<8AsmVZugduUDg22;YEEJ)|BlzkTbs%k1;t-T>#7w42E z#cE_#)aS)}#Y+AYrkc6KyD;$1{D-S^(QV80ob`s;nX5{F`*gXu;inD7gTG{#q<*cW z=j5fDDwKS+{%CICs_z#16CyJr?S;pVs-uLSmZU*9-hPK$mlNWz_TiiqGNF^d1lHp)uyx=nvLnAm_O6Y#Y|3ppq3b3u1i74djKz$r-VK3)HjQVn>4fty!y$*mINrI)R>jlJ9qY%wtIxd4 z$Y2|oNvzJyb&(s9Km2Ed9h}+TDdsM&GsJXT4M@K7f$nOje;2dZDEqTm;8q-#~BH6E8nKoH-t^C87hvlEfJWutg>m}7v zW@`=7TFTj?hvF)H5x)~h&w9c%g$G7n2W8=`z`RhB?@a*vS?9|16U2IDy|o3&M|Re@NI_%*M1qaezmHfo-5#yI^P3EYMrYFV>�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 z
4$3eY8gTb$cJ5isT&EQUvk3lO&m-DOW8`RHqLo^=lMt7%Yucx8~(PO}Y=|=dr zopJV-xA4?An4sMRF`9SwYUjSn=e@O=O zbLkD#|IPsVINTI(w!7n%_@HU!Ww_9TPhYUGeOhvC>FV*waK;}~htNTs8hvAtYadKf z51`NUv&i?eUgUS+-sMs+j0Qs{D-O>&AR9x5C|ubyRZHQqXvEwPJbbhvdL6x>j1@jR z{|wLT&(TN2NpN|vby`}r%YbNMX=Qkga8i88uy*Sb7hRkC3tk8xka;IB¨m6fX#$ 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�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 FcospVvO& z#N5M-`G+6hd(tDcPHIcnwJrRH=kA&Ti-bv7@%|r+=AT)uZO6Xx$*XVitG#Y~%>~%G z?OxY})4|y2edjlquFJRBb9Bjb?uainz5!S8Rt))WMG4LL4Rd3{1^&_~zqm4!XpSCwha->(5?z9=rG?$rXn!WLF_ODX=cxD=CI~wad z?e9E~KqASizE@R-28&E)A|gCI97vE2S8#27e_KmM7pOnPNNPyF!-cid*}#VQ;5g^E zyq7lh$J8Lqou4(k>+{vOpYL5Q(5|Q92`~XSVryHTn|wKI1n1#=@}+p@nq(b# zWKIPZszbto@?z#^!L|6#{J(pP_zp%mAoh1n8Z9w(nrmvVDY4^K>p4X|?7 zT*kn7tfS?dz0dW#et+tAa6+zuiQu8UiM@>L4F}}D^A_+xT+g*r{?7ARKTU0U?gc(M z13m(G(I(!n*fm){=^yn5xlO*{Ct_P2|NQ&}c|e`Tet4p}G}mT>IW@4@181`ZbN#{=? z2uyIE$q{NQIf8maeaZN9KNGG%Cy?XTimZ*W-rhBL^@g~}{qh{c@iwq#tvTxq{=sKr zhOsav*7NW$IR%!Z-FjaJ{vm>GWU^) zk!q2N576<%2fiS7V8q%32N2)6Mo-+?g9mPC`P=Us?3z88AkVBt^1`e`#45AE)F4Ji zEb$F~11tE2rasZ{_-VGxm7Oosr_Ez{eD2FKR$MQ!V%MRoHTwGfAxpH=$az7q7~4syAL6*IaX-Jq1Ab>L#40+Z|7RUF z{PZQh(Vw#(!>{n~=GI+%!40^RUxSZ%A30bC*W&!vPwb0(e9QU39zR?Ko&+DWo>rR_ zC($R>C~At?e_H1N7x-%GQ@OPDF8qo8<_aTa#FIPaP4a(!mk->LT9dUAVmfunzR#6= zUEcpZ^#~k94Fh9b2Oq;7_>UjPIP(gh!Li&=u-4);{pMq0Z~W0C&6l~apx^ftKXlz5 zUoeKV$zh3XbO-NcoV2MG?m7rv|}~HvD;OzMeeGwYRUL;lL8y08h-^qRxVU zd`83O*?10(!ZD{d<9v8x=Sue1$eDca1pJYCaca)n8m#NDuDw!=cpvX4pTG!s!e%f5 zrr}V=z*tTkFmADc##EcYA>iWPvgarorE8PXDf`~l)F^5bI0XMji&T#=J{s(zCc%4* zJ!mqkcV5q&Ca&!92V2D3`##K*lWWa4{GVL9f0w^GzOEmuGwR}&<{>Yi8mo_chDTK3ztIJg>}YusBnTdaUt@Di+~zRX-ei-`*{0Uo9v zp@xV7V?Xh&N_FWfJtGi~XUI!af z-?|>VakjtXBzr6}-wED&Uy$>oeZUL&fY%3UY8^n%|{=TGvw>`+1~4^&%idesYSA`wC6|oM*pbYsKxl_yuW3Q zO{T;K@D1L<3AxPJ7%Ta`=QDfn0NijkKW9;{nBVF8TpQR}**c6EU_Y3t?r^ z4ZG8x>qqL6+0Qj4F{L)i95QP#j6>fM8*m*AM1O{xn``({Z6YrdcOED9U8~3M_+Nd- zdAR}@<(gcZIXqY=ri}9~Uy6mPMKd45kNA}HGJZ02YEkfbo6A}=$p5sgk?=e%nuGNq z*FCxx#2m;b{?C5;#k`mrME~GpeRS4qY?5Ql8S2mGnapkQ5xhbzVI5vg7hRY8>wc~+ z_tdNHICkFafV04mT*NiSN^mlHqHB=m*~W(Vbi~NMpDaeKMezY>q5eE;P@+tv{rz+3 zpMQ8)YFPf|T+jAgTZY%)8ajos5hL17uRLo|_I|epS$p<9LD%5ax*U(L0bBSGe^Pt6 zE_Pr$ctDGi$Ha%6L0b|(v^rXZHAL1E-HXM3to@yG`#XSn514Sh5gc(|auTc&f37K4 zFb*8fm>3!3BaW%dT_4de;6Ip{_xL)u;W%tP=QZXAG2l8}kKg$dj07w5`H6)m28xS$ zMi7Ngnt9dSUdf>-b#{y`s`FI-X}Ug-v=Jr_8tQ@3%m?wXmE$E8f0r3e2+c)ket(p z&I`m7Sdwq$;o0-rU7o+;EFl- zg@38=o^yJ8$9CzKFV>q5XoP2QONB96dX8hIB);@* zHH_R!UE6g}p2R1z?Dk9d9Elz9Li~s$Ie}JJhY){i66;8;!OweN-tWq14)TBFa7;c= z?>ar-C+5Ac)FG~muR#c-#13*Aj?l082tLr4^PXNFKQ&$a`t+$aiSy&z>0kYzo3#H=0&37*@9%rzu{p7%4&3Za zeVB8Xt`!pV#C_&cYR8PRv7SAzzcr6DUc_!I`>nURc!}5?dHGDMnS$tD0$fa^kIYz9%+fv?;N3A>7o79=kf5FkNxz2&p`?^|& zH380{PT{=OBGn&YRX(?u=kZ-~Z?0Y9L+3lyB=`^CsYT@Psp+`CWmDs+?cgVHNnUa; z@(Vwra`_>KGq&)L|Rk%k%i-xjy2at8E83xN+vxrjEb!E7S@nfa`}={N8rZomn+Q0GrQa?2(2 zNWLMyVxo0u*Y{^#Z;rv=axSZxV6VzGfwTP{|~L*RVk5WC>j*63ZQ z&-=fVGZ{aa01wP%qe+8B`1kLSjF#P zfE>Xtu%H&hM(rf0&PFVM+U9#bFMX~zbxX#g_7EG4GZ^{2CrhqDfj)JK`M2kh_{T)wP z>O=5>|6mTTuO=am<=yUi#UAa{w%DZ>+jdd^-!PEeNxn4xoiAHo8MjSMg&mt4&vl<* zA?s$b1;1NEh$XRBXR#Kp&N}VAR4_`4J{KYt_k=E?g zo~$XIHQCzBG1MvE!+Xn@w><*;o;Tw~n-EKKUp`>ZIC!A>+{q8TH*>rvKWHER#kQ?U zqDL5a?3;R{nxB{^uF;p;uif;XBR+WQ4t#PpwF!FY)I_)Q@qW36808MovlglUsX1Z< zT;Si?c<$-DVuKt8kL3IG8{?RI1pD=geSEXmjXL(u-(<6idtxRp!BgR{)JoTUW-iQp zn6+x#ylYl!)6SRXO6R?5HMNI$O1{iFWbbcbJ9we)!V9bi{KtQ35%*)sIk~UVdmHZg zoO>*oAHC@ke2Pv4+l+~rsrm7dz9G)_M}0BZBQ`b){m_vzub7IfMGG0c^ znEA1OoqPw5Z*f*r*{iLvRi0C4(fho<;C z``~_#pmSj7z0Q5rr|!)V54Slm{wEg1X7aH9p1F4B8MVQdT+2O6^T5|W^)p95#V;|K z8u|Hoj)}jgHg26F53u*v!aJ=;`eEXZ>&Tl^m!uwbea%{APfem;kw@-dtp?fqaHxmp z_e`?pr52g%Af2mpPcYhJYSm(*dvuD2?88dk3La)2P8~8i7(C0r_=Gs?7xQQ4P3lSc z1b^AAHLWx5<(vFkJyMSCnmt&BBN!ubYaN<BKlj&p!P&R2|Nt~~Jp zHuNj;#XqM%^o#xcyvR9+n|T3Om=}y;=7u(!AKGf3V6%}qd)5YL4T5V}6LH?-zKVQSY5#xR`}1o3teScRu0i8~4g87E83SX& zSIMQWBN%)6p?Q?GfOF2z@%RdV;@jlZjW3+LW-eT>z#Q*CFiVczYxDS)h7Fxh_p>tC z#h(AO1{$;T`5Yh2B$kb*_4l{E_ck2siG`U9V1f9Q1G1j1K2?`QlYCx}r~ar(zyW$> zY7yqva0lEAS8#1`5oA=O;#)9{Z}8XD48(crxA_?=P1hB3*@M2Zmzo8K%RgW) zb-Zg$_!u-L&su-*t6EP!#kaPs+dGHcwRq|?`G)HRf5lvUwDmA!EZ6My)#OY0g!h9z z{C=||T(R{k^Om`Wir$#I)W_8!!Gdcdu0O&*a0J&754&!34$yq-UL*5qVkDf~wIuAV zKgzdgGV6+Z#NOx2&8<0;Yw#QSNld2iz%@9ecB zePKWSG#~rA{hOxadR$vg623XtM&_DRZ~-r%;nDN%6@T6@&pM=QP`n51P@C{O{-z#? z5qwJ=8AI;j6eH6wQ{(Y`ZKyuw9K=O#;N0>8Q|p~bqQlXV=X^& z&a+KT!27(8>$<_CbLOp&&0Txz5}qsuGuS<*@8jTIJ0DzEE;igJ!g?8cL`?!tZVesV z&!X(TpkNK2fOp^^@=ANOg)t?UI^QK$#EjTkU*y@;S?_~&PRP8!+9EpEb?Uc1+N1BA zx?}6za0__}eu+02GOp?nY{N$N9roi->hJbv)*7bo@H5BETCMNoqw2SlPxCooe1)I3 zkGi(1rqZ@kS78(Jfa$oFwlEIu2p;((& zbAmY!6f@l`j=k~>F)mJWj|2YboF{7t^;K#+zQVs^fLczTGZv75-|qSSti|BN#=H<- zPV4ijNwXHE)|)q6i>t~3_yb%fcj}K@EwSFniM;Q|i*Uu%BGDh!9MqOH*FF5Z$4q_V zr(+03J z^Fr**xr}v|@i`xH+_T3{UccM%zIF;m$ltfRI%|nF<<@&|&-t!81Fkrmb6|UF7i%fv z$T}2VyR``ZBOdAy?8Oi8BlVa(GKW|*@E*q7{fy?*-h)Lf%qL<_9N`A)3I0;wt3{$m za-T2ue71@6aK7N9nl*T+W;OoA7CVb!*UqyK?}lf1A();uRz1?ahne$XgTl*^}lOo_WIB~qE^GJV2!whU8CG4 z2QUVGG<(M;?>R=!FnJ(c=GsTrJyKU%+vK3cig$nhIj>Q!30J}~Pi%D^Q617=?NOa8 z*F1d`9AP)QVDby`g%8vs)D+f0{H*-k8_Z|cY~3r&XXmR+ct3L=&Ig{rni`f^B*Wrw ze1LW!x3z!gRqYoOvsdGjhhhXD-f~RLfE(%**CvL)X~fz_#Phr3?))J&X8zmbcpv8x zXL5}7MjXg5*i77`J@@A(=A4J)cn>%rS2!QGaxF#z{>f9gk{mu`kCt%VW3GeUKf9XG z19UwkG0(klYE0%obMFK=@LnF{a4uk8TAT6I^wIQ_zEaz%Gr;_;)6Q|gg_w#ziz#zS zP0#&!{5jk6b1C&PaWdDy6Mp6=waI%Nb4|hf7rkc@UVieazLBdqFFuJcqTR*Iv|Ieo z{yDF+<}hYP-Enh0>L9tQ7MV2#-lsY4xzCwgpUpiU){t3WP7QMY?Xgpb1V61c;gYVm z!X?B5+tFX-gPMNokWcqASd;V~C+{)8Zncpz`y+0)=Kq8; z544w@-#D)}2|vn_*<--_z>mBLu7ZSMK^_CkG<^LxjocPb)Sihc;whH!3;yu?k6c@G zoy1C)a`*+Bd!-|I}(6FUIa18T}3ph@)D>T9nVt zfB(K{zH<(3!deOQWZuWTn)O!B$+^J?_yfn-kG;f)JYdY=z#75l%0AWH@Iuy4tsmAC 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
Z6!}9dy3B$8DSML6wEC1|@V-Kf4 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 literal 0 HcmV?d00001 From b4ade73a8f5f6d2d4558be05714636baa3fbd009 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 17 Oct 2016 23:45:33 +0400 Subject: [PATCH 07/39] TODO de branche --- TODO.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 TODO.md diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..32dea67 --- /dev/null +++ b/TODO.md @@ -0,0 +1,3 @@ +# TODO + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file From b9dc5ca71e5de6bc92138a81a88ff9de883e88c1 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 19 Oct 2016 11:57:35 +0400 Subject: [PATCH 08/39] bug dans apacheconfig --- lib/ulib/apache.tools | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/ulib/apache.tools b/lib/ulib/apache.tools index a5b12d9..8445fcb 100644 --- a/lib/ulib/apache.tools +++ b/lib/ulib/apache.tools @@ -750,14 +750,15 @@ function apacheconfig_initvars() { function apacheconfig_loadconf() { local config modified - local destdir="$1" + local destdir="$1" autocreate __template_set_destdir destdir autocreate "$TEMPLATECTL_NAME" || return 1 setx config=templatectl_config "$destdir" modified= templatectl_loadvars "$config" && modified=1 - upvar config "$config" + upvars config "$config" modified "$modified" \ + destdir "$destdir" autocreate "$autocreate" } function apacheconfig_sysinfos() { From 3ccd4edc7d777d1dbd5dd05e7c94b813a94bbb6c Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 20 Oct 2016 10:28:06 +0400 Subject: [PATCH 09/39] =?UTF-8?q?apacheconfig:=20ne=20pas=20chercher=20?= =?UTF-8?q?=C3=A0=20utiliser=20site-certs.conf=20s'il=20n'y=20a=20pas=20le?= =?UTF-8?q?s=20variables=20@@{cert,key,ca}@@?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/apache.tools | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/ulib/apache.tools b/lib/ulib/apache.tools index 8445fcb..722b6e5 100644 --- a/lib/ulib/apache.tools +++ b/lib/ulib/apache.tools @@ -435,10 +435,12 @@ function apache_autoconf() { certsconf= if [ "${confname%.ssl.conf}" != "$confname" ]; then - if [ -d "$certsconfdir" ]; then - certsconf="${confname%.ssl.conf}-certs.conf" - else - ewarn "$conf: fichier ignoré parce que --certsconfdir n'a pas été spécifié" + if quietgrep -E '^[^#]*@@(cert|key|ca)@@' "$conf"; then + if [ -d "$certsconfdir" ]; then + certsconf="${confname%.ssl.conf}-certs.conf" + else + ewarn "$conf: fichier ignoré parce que --certsconfdir n'a pas été spécifié" + fi fi fi case "$confname" in From db24c7084fb22ff8943f998a7cc62952dd8a2f3d Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 21 Oct 2016 13:46:14 +0400 Subject: [PATCH 10/39] ulib: ajout de la fonction is_array() --- lib/ulib/base.array | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/ulib/base.array b/lib/ulib/base.array index 3940090..5dadff4 100644 --- a/lib/ulib/base.array +++ b/lib/ulib/base.array @@ -3,3 +3,10 @@ ##@cooked nocomments uprovide base.array urequire base.core + +function is_array() { +# tester si la variable $1 est un tableau + local -a arrays + eval "arrays=($(declare -p -a | while read dummy dummy var; do echo ${var%%=*}; done))" + array_contains arrays "$1" +} From 8d486dc4e3eb4d5e91292f7a17a504a767ec98ea Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 21 Oct 2016 15:19:53 +0400 Subject: [PATCH 11/39] ulib: ajouter la fonction strops --- lib/ulib/base.string | 54 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/lib/ulib/base.string b/lib/ulib/base.string index 288a867..4abcc07 100644 --- a/lib/ulib/base.string +++ b/lib/ulib/base.string @@ -116,6 +116,60 @@ function strrepl() { eval "$cmd" } +function strops() { +# Appliquer à une chaine de caractères une suite de traitements, e.g: +# 'strops var deref +suffix' est équivalent à 'echo "${var}suffix"' +# En commençant avec la valeur initiale $1, les arguments $2..* sont des +# opérations à appliquer dans l'ordre. +# Les opérations suivantes considèrent que la valeur courante est un nom de +# variable: +# :- := :? :+ deref dcount +# Toutes les autres opérations travaillent directement avec la valeur +# courante. Les opérations suivantes appliquent une transformation: +# # % / : ^ , +# -# +% -% + - +# Les opérations suivantes font un test sur la valeur et retournent +# immédiatement: +# = == != < > -eq -ne -lt -le -gt -ge -n -z +# La syntaxe des opérateurs standards de bash est reprise autant que possible, +# i.e si on a l'habitude d'écrire ${varOP} en bash, alors la syntaxe à utiliser +# à priori est 'strops var OP' ou 'strop var deref OP' suivant les opérateurs. +# Autres opérateurs: +# deref indirection +# dcount nombre d'éléments du tableau +# +#STR ajouter un préfixe +# -#STR supprimer un préfixe +# +%STR ou +STR ajouter un suffixe +# -%STR ou -STR supprimer un suffixe + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + # l'argument est le nom de la variable + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + # l'argument est la valeur de la variable + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + esac + shift + done + echo "$__s_value" +} + function first_char() { # retourner le premier caractère de la chaine $* local str="$*" From d735b7b6955e3b9e4693a2e85ba100ea81585e37 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 21 Oct 2016 15:31:02 +0400 Subject: [PATCH 12/39] =?UTF-8?q?ulib:=20strops:=20ajout=20des=20op=C3=A9r?= =?UTF-8?q?ation=20mid=20et=20repl?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/base.string | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/ulib/base.string b/lib/ulib/base.string index 4abcc07..eb91a50 100644 --- a/lib/ulib/base.string +++ b/lib/ulib/base.string @@ -126,7 +126,7 @@ function strops() { # :- := :? :+ deref dcount # Toutes les autres opérations travaillent directement avec la valeur # courante. Les opérations suivantes appliquent une transformation: -# # % / : ^ , +# -# +% -% + - +# # % / : ^ , +# -# +% -% + - mid repl # Les opérations suivantes font un test sur la valeur et retournent # immédiatement: # = == != < > -eq -ne -lt -le -gt -ge -n -z @@ -140,6 +140,8 @@ function strops() { # -#STR supprimer un préfixe # +%STR ou +STR ajouter un suffixe # -%STR ou -STR supprimer un suffixe +# mid RANGE traiter la chaine avec strmid() +# repl FROM TO traiter la chaine avec strrepl() local -a __s_tmp local __s_value="$1"; shift while [ $# -gt 0 ]; do @@ -164,6 +166,9 @@ function strops() { +*) eval '__s_value="$__s_value"'"${1#+}";; -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; esac shift done From 4a4094c4932e1d6e44cc815fd3a8b70a321721b4 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 1 Nov 2016 00:03:25 +0400 Subject: [PATCH 13/39] =?UTF-8?q?d=C3=A9placement=20de=20is=5Fdefined()=20?= =?UTF-8?q?et=20is=5Farray()=20dans=20base.core?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/base.array | 7 ------- lib/ulib/base.core | 10 ++++++++++ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/ulib/base.array b/lib/ulib/base.array index 5dadff4..3940090 100644 --- a/lib/ulib/base.array +++ b/lib/ulib/base.array @@ -3,10 +3,3 @@ ##@cooked nocomments uprovide base.array urequire base.core - -function is_array() { -# tester si la variable $1 est un tableau - local -a arrays - eval "arrays=($(declare -p -a | while read dummy dummy var; do echo ${var%%=*}; done))" - array_contains arrays "$1" -} diff --git a/lib/ulib/base.core b/lib/ulib/base.core index 9a540f5..d3c4484 100644 --- a/lib/ulib/base.core +++ b/lib/ulib/base.core @@ -377,6 +377,16 @@ function err2out() { "$@" 2>&1 } +function is_defined() { +# tester si la variable $1 est définie + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { +# tester si la variable $1 est un tableau + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + # cf http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference function upvar() { # Assign variable one scope above the caller From 190519d968cc64a8a6a7829f4ec652b97d3e09b5 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sat, 19 Nov 2016 10:28:26 +0400 Subject: [PATCH 14/39] =?UTF-8?q?pxs=20ne=20requi=C3=A8re=20plus=20la=20pr?= =?UTF-8?q?=C3=A9sence=20du=20remote=20origin?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- uproject | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/uproject b/uproject index 9d0e14d..61f5a53 100755 --- a/uproject +++ b/uproject @@ -241,7 +241,12 @@ elif array_contains VCS_CMDS "$CMD"; then elif array_contains GITANNEX_CMDS "$CMD"; then function xsync() { if ! git_have_annex; then - git_commit -Al "Maj des fichiers" && git pull && git_push + setyesval offline "$UTOOLS_VCS_OFFLINE" + if git_commit -Al "Maj des fichiers"; then + [ -n "$offline" ] && return 0 + git_have_remote || return 0 + git pull && git_push + fi elif is_yes "$(git config --get annex.direct)"; then git annex add && git annex sync && From 2980d15769e1903cb28647b5eefa682df2c35811 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Tue, 22 Nov 2016 15:28:28 +0400 Subject: [PATCH 15/39] pver supporte la gestion des versions maven --- lib/pyulib/src/ulib/ext/xpath/README.rst | 2 +- lib/pyulib/src/ulib/ext/xpath/__init__.py | 149 ++- lib/pyulib/src/ulib/ext/xpath/exceptions.py | 2 +- lib/pyulib/src/ulib/ext/xpath/expr.py | 19 +- lib/pyulib/src/ulib/ext/xpath/parser.g | 252 ----- lib/pyulib/src/ulib/ext/xpath/parser.py | 52 +- lib/pyulib/src/ulib/ext/xpath/yappsrt.py | 10 +- lib/uinst/conf | 5 + lib/ulib/ptools | 573 +++++++---- lib/ulib/semver | 106 +- lib/ulib/support/python/__init__.py | 0 .../support/python/xpath/__init__.py} | 49 +- lib/ulib/support/python/xpath/exceptions.py | 49 + lib/ulib/support/python/xpath/expr.py | 903 ++++++++++++++++++ lib/ulib/support/python/xpath/parser.py | 416 ++++++++ lib/ulib/support/python/xpath/yappsrt.py | 174 ++++ lib/ulib/support/xpathtool.py | 158 +++ lib/ulib/vcs | 2 +- lib/ulib/xmlsupport | 8 + prel | 61 +- pver | 159 ++- 21 files changed, 2599 insertions(+), 550 deletions(-) delete mode 100644 lib/pyulib/src/ulib/ext/xpath/parser.g create mode 100644 lib/ulib/support/python/__init__.py rename lib/{pyulib/src/ulib/ext/xpath/_xpath.py => ulib/support/python/xpath/__init__.py} (75%) create mode 100644 lib/ulib/support/python/xpath/exceptions.py create mode 100644 lib/ulib/support/python/xpath/expr.py create mode 100644 lib/ulib/support/python/xpath/parser.py create mode 100644 lib/ulib/support/python/xpath/yappsrt.py create mode 100755 lib/ulib/support/xpathtool.py create mode 100644 lib/ulib/xmlsupport diff --git a/lib/pyulib/src/ulib/ext/xpath/README.rst b/lib/pyulib/src/ulib/ext/xpath/README.rst index 6b260ef..0425286 100644 --- a/lib/pyulib/src/ulib/ext/xpath/README.rst +++ b/lib/pyulib/src/ulib/ext/xpath/README.rst @@ -217,7 +217,7 @@ Expression Context Objects The context contains the following attributes and methods: .. attribute:: default_namespace - + The default namespace URI. .. attribute:: namespaces diff --git a/lib/pyulib/src/ulib/ext/xpath/__init__.py b/lib/pyulib/src/ulib/ext/xpath/__init__.py index 6cfabb2..0741f64 100644 --- a/lib/pyulib/src/ulib/ext/xpath/__init__.py +++ b/lib/pyulib/src/ulib/ext/xpath/__init__.py @@ -1,10 +1,149 @@ -import exceptions +from xpath.exceptions import * +import xpath.exceptions +import xpath.expr +import xpath.parser +import xpath.yappsrt -from _xpath import api, XPathContext, XPath -from exceptions import * +__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath'] +__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_'))) -__all__ = ['find', 'findnode', 'findvalue', 'findvalues', 'XPathContext', 'XPath'] -__all__.extend((x for x in dir(exceptions) if not x.startswith('_'))) +def api(f): + """Decorator for functions and methods that are part of the external + module API and that can throw XPathError exceptions. + + The call stack for these exceptions can be very large, and not very + interesting to the user. This decorator rethrows XPathErrors to + trim the stack. + + """ + def api_function(*args, **kwargs): + try: + return f(*args, **kwargs) + except XPathError, e: + raise e + api_function.__name__ = f.__name__ + api_function.__doc__ = f.__doc__ + return api_function + +class XPathContext(object): + def __init__(self, document=None, **kwargs): + self.default_namespace = None + self.namespaces = {} + self.variables = {} + + if document is not None: + if document.nodeType != document.DOCUMENT_NODE: + document = document.ownerDocument + if document.documentElement is not None: + attrs = document.documentElement.attributes + for attr in (attrs.item(i) for i in xrange(attrs.length)): + if attr.name == 'xmlns': + self.default_namespace = attr.value + elif attr.name.startswith('xmlns:'): + self.namespaces[attr.name[6:]] = attr.value + + self.update(**kwargs) + + def clone(self): + dup = XPathContext() + dup.default_namespace = self.default_namespace + dup.namespaces.update(self.namespaces) + dup.variables.update(self.variables) + return dup + + def update(self, default_namespace=None, namespaces=None, + variables=None, **kwargs): + if default_namespace is not None: + self.default_namespace = default_namespace + if namespaces is not None: + self.namespaces = namespaces + if variables is not None: + self.variables = variables + self.variables.update(kwargs) + + @api + def find(self, expr, node, **kwargs): + return xpath.find(expr, node, context=self, **kwargs) + + @api + def findnode(self, expr, node, **kwargs): + return xpath.findnode(expr, node, context=self, **kwargs) + + @api + def findvalue(self, expr, node, **kwargs): + return xpath.findvalue(expr, node, context=self, **kwargs) + + @api + def findvalues(self, expr, node, **kwargs): + return xpath.findvalues(expr, node, context=self, **kwargs) + +class XPath(): + _max_cache = 100 + _cache = {} + + def __init__(self, expr): + """Init docs. + """ + try: + parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr))) + self.expr = parser.XPath() + except xpath.yappsrt.SyntaxError, e: + raise XPathParseError(str(expr), e.pos, e.msg) + + @classmethod + def get(cls, s): + if isinstance(s, cls): + return s + try: + return cls._cache[s] + except KeyError: + if len(cls._cache) > cls._max_cache: + cls._cache.clear() + expr = cls(s) + cls._cache[s] = expr + return expr + + @api + def find(self, node, context=None, **kwargs): + if context is None: + context = XPathContext(node, **kwargs) + elif kwargs: + context = context.clone() + context.update(**kwargs) + return self.expr.evaluate(node, 1, 1, context) + + @api + def findnode(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if not xpath.expr.nodesetp(result): + raise XPathTypeError("expression is not a node-set") + if len(result) == 0: + return None + return result[0] + + @api + def findvalue(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if xpath.expr.nodesetp(result): + if len(result) == 0: + return None + result = xpath.expr.string(result) + return result + + @api + def findvalues(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if not xpath.expr.nodesetp(result): + raise XPathTypeError("expression is not a node-set") + return [xpath.expr.string_value(x) for x in result] + + def __repr__(self): + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__name__, + repr(str(self.expr))) + + def __str__(self): + return str(self.expr) @api def find(expr, node, **kwargs): diff --git a/lib/pyulib/src/ulib/ext/xpath/exceptions.py b/lib/pyulib/src/ulib/ext/xpath/exceptions.py index 1597670..7ce017a 100644 --- a/lib/pyulib/src/ulib/ext/xpath/exceptions.py +++ b/lib/pyulib/src/ulib/ext/xpath/exceptions.py @@ -15,7 +15,7 @@ class XPathParseError(XPathError): XPathError.__init__(self) self.expr = expr self.pos = pos - self.message = message + self.err = message def __str__(self): return ("Syntax error:\n" + diff --git a/lib/pyulib/src/ulib/ext/xpath/expr.py b/lib/pyulib/src/ulib/ext/xpath/expr.py index b681a0d..4199813 100644 --- a/lib/pyulib/src/ulib/ext/xpath/expr.py +++ b/lib/pyulib/src/ulib/ext/xpath/expr.py @@ -6,7 +6,8 @@ import re import xml.dom import weakref -from exceptions import * +from xpath.exceptions import * +import xpath # @@ -21,6 +22,8 @@ def string_value(node): for n in axes['descendant'](node): if n.nodeType == n.TEXT_NODE: s += n.data + elif n.nodeType == n.CDATA_SECTION_NODE: + s += n.nodeValue return s elif node.nodeType == node.ATTRIBUTE_NODE: @@ -31,9 +34,12 @@ def string_value(node): node.nodeType == node.TEXT_NODE): return node.data + elif node.nodeType == node.CDATA_SECTION_NODE: + return node.nodeValue + def document_order(node): """Compute a document order value for the node. - + cmp(document_order(a), document_order(b)) will return -1, 0, or 1 if a is before, identical to, or after b in the document respectively. @@ -100,10 +106,10 @@ def string(v): return u'Infinity' elif v == float('-inf'): return u'-Infinity' - elif int(v) == v and v <= 0xffffffff: - v = int(v) elif str(v) == 'nan': return u'NaN' + elif int(v) == v and v <= 0xffffffff: + v = int(v) return unicode(v) elif booleanp(v): return u'true' if v else u'false' @@ -747,7 +753,7 @@ class PathExpr(Expr): class PredicateList(Expr): """A list of predicates. - + Predicates are handled as an expression wrapping the expression filtered by the predicates. @@ -883,7 +889,8 @@ class CommentTest(object): class TextTest(object): def match(self, node, axis, context): - return node.nodeType == node.TEXT_NODE + return (node.nodeType == node.TEXT_NODE or + node.nodeType == node.CDATA_SECTION_NODE) def __str__(self): return 'text()' diff --git a/lib/pyulib/src/ulib/ext/xpath/parser.g b/lib/pyulib/src/ulib/ext/xpath/parser.g deleted file mode 100644 index df75bb1..0000000 --- a/lib/pyulib/src/ulib/ext/xpath/parser.g +++ /dev/null @@ -1,252 +0,0 @@ -import expr as X -from yappsrt import * - -%% - -parser XPath: - option: 'no-support-module' - - ignore: r'\s+' - token END: r'$' - - token FORWARD_AXIS_NAME: - r'child|descendant-or-self|attribute|self|descendant|following-sibling|following|namespace' - token REVERSE_AXIS_NAME: - r'parent|preceding-sibling|preceding|ancestor-or-self|ancestor' - - # Dire hack here, since yapps2 has only one token of lookahead: NCNAME - # does not match when followed by a open paren. - token NCNAME: r'[a-zA-Z_][a-zA-Z0-9_\-\.\w]*(?!\()' - token FUNCNAME: r'[a-zA-Z_][a-zA-Z0-9_\-\.\w]*' - - token DQUOTE: r'\"(?:[^\"])*\"' - token SQUOTE: r"\'(?:[^\'])*\'" - token NUMBER: r'((\.[0-9]+)|([0-9]+(\.[0-9]*)?))([eE][\+\-]?[0-9]+)?' - token EQ_COMP: r'\!?\=' - token REL_COMP: r'[\<\>]\=?' - token ADD_COMP: r'[\+\-]' - token MUL_COMP: r'\*|div|mod' - - rule XPath: - Expr END {{ return Expr }} - - rule Expr: - OrExpr {{ return OrExpr }} - - rule OrExpr: - AndExpr {{ Expr = AndExpr }} - ( - r'or' AndExpr - {{ Expr = X.OrExpr('or', Expr, AndExpr) }} - )* {{ return Expr }} - - rule AndExpr: - EqualityExpr {{ Expr = EqualityExpr }} - ( - r'and' EqualityExpr - {{ Expr = X.AndExpr('and', Expr, EqualityExpr) }} - )* {{ return Expr }} - - rule EqualityExpr: - RelationalExpr {{ Expr = RelationalExpr }} - ( - EQ_COMP - RelationalExpr - {{ Expr = X.EqualityExpr(EQ_COMP, Expr, RelationalExpr) }} - )* {{ return Expr }} - - rule RelationalExpr: - AdditiveExpr {{ Expr = AdditiveExpr }} - ( - REL_COMP - AdditiveExpr - {{ Expr = X.EqualityExpr(REL_COMP, Expr, AdditiveExpr) }} - )* {{ return Expr }} - - rule AdditiveExpr: - MultiplicativeExpr {{ Expr = MultiplicativeExpr }} - ( - ADD_COMP - MultiplicativeExpr - {{ Expr = X.ArithmeticalExpr(ADD_COMP, Expr, MultiplicativeExpr) }} - )* {{ return Expr }} - - rule MultiplicativeExpr: - UnionExpr {{ Expr = UnionExpr }} - ( - MUL_COMP - UnionExpr - {{ Expr = X.ArithmeticalExpr(MUL_COMP, Expr, UnionExpr) }} - )* {{ return Expr }} - - rule UnionExpr: - UnaryExpr {{ Expr = UnaryExpr }} - ( - '\|' UnaryExpr - {{ Expr = X.UnionExpr('|', Expr, UnaryExpr) }} - )* {{ return Expr }} - - rule UnaryExpr: - r'\-' ValueExpr {{ return X.NegationExpr(ValueExpr) }} - | ValueExpr {{ return ValueExpr }} - - rule ValueExpr: - PathExpr {{ return PathExpr }} - - rule PathExpr: - r'\/' {{ path = None }} - [ - RelativePathExpr {{ path = RelativePathExpr }} - ] {{ return X.AbsolutePathExpr(path) }} - | r'\/\/' RelativePathExpr - {{ step = X.AxisStep('descendant-or-self') }} - {{ RelativePathExpr.steps.insert(0, step) }} - {{ return X.AbsolutePathExpr(RelativePathExpr) }} - | RelativePathExpr {{ return RelativePathExpr }} - - rule RelativePathExpr: - StepExpr {{ steps = [StepExpr] }} - ( - ( - r'\/' - | r'\/\/' - {{ steps.append(X.AxisStep('descendant-or-self')) }} - ) - StepExpr {{ steps.append(StepExpr) }} - )* - {{ return X.PathExpr(steps) }} - - rule StepExpr: - AxisStep {{ return AxisStep }} - | FilterExpr {{ return FilterExpr }} - - rule AxisStep: - ( - ForwardStep {{ step = ForwardStep }} - | ReverseStep {{ step = ReverseStep }} - ) {{ expr = X.AxisStep(*step) }} - [ - PredicateList - {{ expr = X.PredicateList(expr, PredicateList, step[0]) }} - ] - {{ return expr }} - - rule ForwardStep: - ForwardAxis NodeTest {{ return [ForwardAxis, NodeTest] }} - | AbbrevForwardStep {{ return AbbrevForwardStep }} - - rule ForwardAxis: - FORWARD_AXIS_NAME r'::' {{ return FORWARD_AXIS_NAME }} - - rule AbbrevForwardStep: - {{ axis = 'child' }} - [ - r'@' {{ axis = 'attribute' }} - ] - NodeTest {{ return [axis, NodeTest] }} - - rule ReverseStep: - ReverseAxis NodeTest {{ return [ReverseAxis, NodeTest] }} - | AbbrevReverseStep {{ return AbbrevReverseStep }} - - rule ReverseAxis: - REVERSE_AXIS_NAME r'::' {{ return REVERSE_AXIS_NAME }} - - rule AbbrevReverseStep: - r'\.\.' {{ return ['parent', None] }} - - rule NodeTest: - KindTest {{ return KindTest }} - | NameTest {{ return NameTest }} - - rule NameTest: - # We also support the XPath 2.0 :*. - {{ prefix = None }} - WildcardOrNCName {{ localpart = WildcardOrNCName }} - [ - r':' WildcardOrNCName {{ prefix = localpart }} - {{ localpart = WildcardOrNCName }} - ] - {{ return X.NameTest(prefix, localpart) }} - - rule WildcardOrNCName: - r'\*' {{ return '*' }} - | NCNAME {{ return NCNAME }} - - rule FilterExpr: - PrimaryExpr - [ - PredicateList - {{ PrimaryExpr = X.PredicateList(PrimaryExpr,PredicateList) }} - ] {{ return PrimaryExpr }} - - rule PredicateList: - Predicate {{ predicates = [Predicate] }} - ( - Predicate {{ predicates.append(Predicate) }} - )* {{ return predicates }} - - rule Predicate: - r'\[' Expr r'\]' {{ return Expr }} - - rule PrimaryExpr: - Literal {{ return X.LiteralExpr(Literal) }} - | VariableReference {{ return VariableReference }} - | r'\(' Expr r'\)' {{ return Expr }} - | ContextItemExpr {{ return ContextItemExpr }} - | FunctionCall {{ return FunctionCall }} - - rule VariableReference: - r'\$' QName - {{ return X.VariableReference(*QName) }} - - rule ContextItemExpr: - r'\.' {{ return X.AxisStep('self') }} - - rule FunctionCall: - FUNCNAME r'\(' {{ args = [] }} - [ - Expr {{ args.append(Expr) }} - ( - r'\,' Expr {{ args.append(Expr) }} - )* - ] r'\)' {{ return X.Function(FUNCNAME, args) }} - - rule KindTest: - PITest {{ return PITest }} - | CommentTest {{ return CommentTest }} - | TextTest {{ return TextTest }} - | AnyKindTest {{ return AnyKindTest }} - - rule PITest: - r'processing-instruction' {{ name = None }} - r'\(' [ - NCNAME {{ name = NCNAME }} - | StringLiteral {{ name = StringLiteral }} - ] r'\)' {{ return X.PITest(name) }} - - rule CommentTest: - r'comment' r'\(' r'\)' {{ return X.CommentTest() }} - - rule TextTest: - r'text' r'\(' r'\)' {{ return X.TextTest() }} - - rule AnyKindTest: - r'node' r'\(' r'\)' {{ return X.AnyKindTest() }} - - rule Literal: - NumericLiteral {{ return NumericLiteral }} - | StringLiteral {{ return StringLiteral }} - - rule NumericLiteral: - NUMBER {{ return float(NUMBER) }} - - rule StringLiteral: - DQUOTE {{ return DQUOTE[1:-1] }} - | SQUOTE {{ return SQUOTE[1:-1] }} - - rule QName: - NCNAME {{ name = NCNAME }} - [ - r'\:' NCNAME {{ return (name, NCNAME) }} - ] {{ return (None, name) }} diff --git a/lib/pyulib/src/ulib/ext/xpath/parser.py b/lib/pyulib/src/ulib/ext/xpath/parser.py index bb673f9..504de78 100644 --- a/lib/pyulib/src/ulib/ext/xpath/parser.py +++ b/lib/pyulib/src/ulib/ext/xpath/parser.py @@ -1,5 +1,5 @@ -import expr as X -from yappsrt import * +import xpath.expr as X +from xpath.yappsrt import * from string import * @@ -8,10 +8,10 @@ import re class XPathScanner(Scanner): patterns = [ ("r'\\:'", re.compile('\\:')), - ("r'node'", re.compile('node')), - ("r'text'", re.compile('text')), - ("r'comment'", re.compile('comment')), - ("r'processing-instruction'", re.compile('processing-instruction')), + ("r'node\\s*\\('", re.compile('node\\s*\\(')), + ("r'text\\s*\\('", re.compile('text\\s*\\(')), + ("r'comment\\s*\\('", re.compile('comment\\s*\\(')), + ("r'processing-instruction\\s*\\('", re.compile('processing-instruction\\s*\\(')), ("r'\\,'", re.compile('\\,')), ("r'\\.'", re.compile('\\.')), ("r'\\$'", re.compile('\\$')), @@ -121,7 +121,7 @@ class XPath(Parser): return Expr def UnaryExpr(self): - _token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ == "r'\\-'": self._scan("r'\\-'") ValueExpr = self.ValueExpr() @@ -135,11 +135,11 @@ class XPath(Parser): return PathExpr def PathExpr(self): - _token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ == "r'\\/'": self._scan("r'\\/'") path = None - if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]: + if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]: RelativePathExpr = self.RelativePathExpr() path = RelativePathExpr return X.AbsolutePathExpr(path) @@ -168,7 +168,7 @@ class XPath(Parser): return X.PathExpr(steps) def StepExpr(self): - _token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE']: AxisStep = self.AxisStep() return AxisStep @@ -177,7 +177,7 @@ class XPath(Parser): return FilterExpr def AxisStep(self): - _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ not in ['REVERSE_AXIS_NAME', "r'\\.\\.'"]: ForwardStep = self.ForwardStep() step = ForwardStep @@ -191,7 +191,7 @@ class XPath(Parser): return expr def ForwardStep(self): - _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ == 'FORWARD_AXIS_NAME': ForwardAxis = self.ForwardAxis() NodeTest = self.NodeTest() @@ -207,7 +207,7 @@ class XPath(Parser): def AbbrevForwardStep(self): axis = 'child' - if self._peek("r'@'", "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') == "r'@'": + if self._peek("r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') == "r'@'": self._scan("r'@'") axis = 'attribute' NodeTest = self.NodeTest() @@ -233,7 +233,7 @@ class XPath(Parser): return ['parent', None] def NodeTest(self): - _token_ = self._peek("r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') + _token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') if _token_ not in ["r'\\*'", 'NCNAME']: KindTest = self.KindTest() return KindTest @@ -315,7 +315,7 @@ class XPath(Parser): FUNCNAME = self._scan('FUNCNAME') self._scan("r'\\('") args = [] - if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction'", "r'comment'", "r'text'", "r'node'", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]: + if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]: Expr = self.Expr() args.append(Expr) while self._peek("r'\\,'", "r'\\)'") == "r'\\,'": @@ -326,24 +326,23 @@ class XPath(Parser): return X.Function(FUNCNAME, args) def KindTest(self): - _token_ = self._peek("r'processing-instruction'", "r'comment'", "r'text'", "r'node'") - if _token_ == "r'processing-instruction'": + _token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('") + if _token_ == "r'processing-instruction\\s*\\('": PITest = self.PITest() return PITest - elif _token_ == "r'comment'": + elif _token_ == "r'comment\\s*\\('": CommentTest = self.CommentTest() return CommentTest - elif _token_ == "r'text'": + elif _token_ == "r'text\\s*\\('": TextTest = self.TextTest() return TextTest - else:# == "r'node'" + else:# == "r'node\\s*\\('" AnyKindTest = self.AnyKindTest() return AnyKindTest def PITest(self): - self._scan("r'processing-instruction'") + self._scan("r'processing-instruction\\s*\\('") name = None - self._scan("r'\\('") if self._peek('NCNAME', "r'\\)'", 'DQUOTE', 'SQUOTE') != "r'\\)'": _token_ = self._peek('NCNAME', 'DQUOTE', 'SQUOTE') if _token_ == 'NCNAME': @@ -356,20 +355,17 @@ class XPath(Parser): return X.PITest(name) def CommentTest(self): - self._scan("r'comment'") - self._scan("r'\\('") + self._scan("r'comment\\s*\\('") self._scan("r'\\)'") return X.CommentTest() def TextTest(self): - self._scan("r'text'") - self._scan("r'\\('") + self._scan("r'text\\s*\\('") self._scan("r'\\)'") return X.TextTest() def AnyKindTest(self): - self._scan("r'node'") - self._scan("r'\\('") + self._scan("r'node\\s*\\('") self._scan("r'\\)'") return X.AnyKindTest() diff --git a/lib/pyulib/src/ulib/ext/xpath/yappsrt.py b/lib/pyulib/src/ulib/ext/xpath/yappsrt.py index 6dff734..c8d8933 100644 --- a/lib/pyulib/src/ulib/ext/xpath/yappsrt.py +++ b/lib/pyulib/src/ulib/ext/xpath/yappsrt.py @@ -51,14 +51,14 @@ class Scanner: raise NotImplementedError("Unimplemented: restriction set changed") return self.tokens[i] raise NoMoreTokens() - + def __repr__(self): """Print the last 10 tokens that have been scanned in""" output = '' for t in self.tokens[-10:]: output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3])) return output - + def scan(self, restrict): """Should scan another token and add it to the list, self.tokens, and add the restriction to self.restrictions""" @@ -77,7 +77,7 @@ class Scanner: # We got a match that's better than the previous one best_pat = p best_match = len(m.group(0)) - + # If we didn't find anything, raise an error if best_pat == '(error)' and best_match < 0: msg = "Bad Token" @@ -105,13 +105,13 @@ class Parser: def __init__(self, scanner): self._scanner = scanner self._pos = 0 - + def _peek(self, *types): """Returns the token type for lookahead; if there are any args then the list of args is the set of token types to allow""" tok = self._scanner.token(self._pos, types) return tok[2] - + def _scan(self, type): """Returns the matched text, and moves to the next token""" tok = self._scanner.token(self._pos, [type]) diff --git a/lib/uinst/conf b/lib/uinst/conf index b0a5061..510fc38 100644 --- a/lib/uinst/conf +++ b/lib/uinst/conf @@ -14,6 +14,10 @@ rm -f .nutools-devel # supprimer fichiers de développement rm -rf lib/pyulib/{build,devel,migrate,test} +# compiler les modules python de support +estep "Compilation des modules python" +python -m compileall lib/ulib/support/python + # liens pour les scripts python for i in plver plbck uencdetect urandomize umail uxpath wofixsql; do ln -s lib/pywrapper "$i" @@ -21,6 +25,7 @@ done ln -s lib/ulib/support/cgiupload.py ln -s lib/ulib/support/cgiparams.py ln -s lib/ulib/support/cgilsxml.py +ln -s lib/ulib/support/xpathtool.py # liens pour les scripts shell for i in cg cgs; do diff --git a/lib/ulib/ptools b/lib/ulib/ptools index 2205179..0206f92 100644 --- a/lib/ulib/ptools +++ b/lib/ulib/ptools @@ -3,8 +3,9 @@ ##@cooked nocomments ##@include vcs ##@include semver +##@include xmlsupport uprovide ptools -urequire vcs semver +urequire vcs semver xmlsupport function is_any_branch() { local branch="$1"; shift @@ -99,43 +100,93 @@ function list_feature_branches() { ################################################################################ # Outils de haut niveau -function __pom_get_version() { - # obtenir la version dans le pom $1(=pom.xml) - local pom="${1:-pom.xml}" - awk <"$pom" '/^[ \t]*/ { - sub(/^.*/, "") - sub(/<\/version>.*$/, "") - print - exit -}' +__pver_perror() { + local r="$1"; shift + [ $# -gt 0 ] || set "Une erreur s'est produite" + local m=$r + [ $m -gt $# ] && m=$# + [ $m -gt 0 ] && eerror "${!m}" + return $r } -function __pom_set_version() { - # modifier la version du le fichier $1(=pom.xml) à la valeur - # $2(=1.0.0-SNAPSHOT) + +__pver_unless() { + local r m + if [[ "$1" == -* ]]; then + [ "${@:1:2}" ] 2>/dev/null; r=$? + shift; shift + else + [ "${@:1:3}" ] 2>/dev/null; r=$? + shift; shift; shift + fi + __pver_perror $r "$@" +} + +function __pver_get_prel_version() { + # retourner la version correspondant à la branche courante si c'est une + # branche de release. retourner 1 si la branche courante n'est pas une + # branche de release, 2 si on n'est pas dans un dépôt git + local branch + branch="$(git_get_branch)" || return 2 + if [[ "$branch" == release-* ]]; then + echo "${branch#release-}" + return 0 + else + return 1 + fi +} +function __pver_pom_get_vpath() { + # pour le fichier $1(=pom.xml), retourner le chemin dans lequel se trouve la + # version du projet + local pom="${1:-pom.xml}" + if xpathtool -tf "$pom" /project/version 2>/dev/null; then + echo /project/version + elif xpathtool -tf "$pom" /project/parent/version 2>/dev/null; then + echo /project/parent/version + else + echo /project/version + fi +} + +function __pver_pom_get_version() { + # obtenir la version dans le pom $1(=pom.xml) à partir du chemin $2, qui est + # déterminé automatiquement s'il n'est pas spécifié. + local pom="${1:-pom.xml}" + local vpath="$2" + local version + if [ -n "$vpath" ]; then + xpathtool -f "$pom" -g "$vpath" + return + fi + version="$(xpathtool -f "$pom" -g /project/version 2>/dev/null)" + if [ -n "$version" ]; then + echo "$version" + return + fi + version="$(xpathtool -f "$pom" -g /project/parent/version 2>/dev/null)" + if [ -n "$version" ]; then + echo "$version" + return + fi +} + +function __pver_pom_set_version() { + # modifier la version dans le pom $1(=pom.xml) à la valeur + # $2(=1.0.0-SNAPSHOT) en utilisant le chemin xpath $3, qui est déterminé + # automatiquement s'il n'est pas spécifié. + # retourner 0 si la version a été mise à jour dans le chemin /project/version + # retourner 1 si la version a été mise à jour dans le chemin /project/parent/version + # retourner 2 si la version a été mise à jour dans un autre chemin + # retourner 3 si une erreur s'est produite local pom="${1:-pom.xml}" local version="${2:-1.0.0-SNAPSHOT}" - local tmpfile; ac_set_tmpfile tmpfile - awk <"$pom" >"$tmpfile" ' -BEGIN { - version = '"$(qawk "$version")"' - found = 0 -} -!found && $0 ~ /^[ \t]*/ { - prefix = "" - if (match($0, /^.*/)) { - prefix = substr($0, RSTART, RLENGTH) - } - suffix = "" - if (match($0, /<\/version>.*$/)) { - suffix = substr($0, RSTART, RLENGTH) - } - print prefix version suffix - found = 1 - next -} -{ print }' - cat "$tmpfile" >"$pom" - ac_clean "$tmpfile" + local vpath="$3" + [ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$pom")" + xpathtool -f "$pom" -s "$vpath" "$version" || return 3 + case "$vpath" in + /project/version) return 0;; + /project/parent/version) return 1;; + *) return 2;; + esac } function pver() { @@ -145,92 +196,170 @@ function pver() { local -a args local action=auto local source=auto - local file= - local git= - local version= - local allow_empty= - local convert=auto - local operator= - local oversion= - local setversion= - local incversion= - local setprelease= - local setalpha= - local setbeta= - local setrc= - local setrelease= - local setmetadata= addmetadata= - local vcsmetadata= + local file git version operator oversion + local setversion incversion + local setpr setprelease setsnapshot setalpha setbeta setrc setrelease + local setmd resetmetadata setmetadata addmetadata vcsmetadata + local vpath setmapfile mapfile allow_empty convert=auto maven_update parse_opts "${PRETTYOPTS[@]}" \ --help '$exit_with pver_display_help' \ - -f:,--file: '$set@ file; source=file' \ - -e:,--maven:,--pom: '$set@ file; source=pom' \ - -F:,--file-string: '$set@ file; source=file-string' \ - -g:,--git-string: '$set@ git; source=git-string' \ - -s:,--string: '$set@ version; source=string' \ + -w:,--w:,--fw:,--auto-file: '$set@ file; source=auto-file' \ + --sw:,--auto-string: '$set@ file; source=auto-string' \ + --gw:,--auto-git-string: '$set@ git; source=auto-git-string' \ + -e:,--e:,--pom:,--maven: '$set@ file; source=pom' \ + -E:,--E:,--pom-string:,--maven-string: '$set@ file; source=pom-string' \ + -f:,--f:,--file: '$set@ file; source=file' \ + -F:,--F:,--file-string: '$set@ file; source=file-string' \ + -g:,--g:,--git-file-string:,--git-string: '$set@ git; source=git-file-string' \ + -G:,--G:,--git-pom-string: '$set@ git; source=git-pom-string' \ + --git-prel-string source=git-prel-string \ + -s:,--s:,--string: '$set@ version; source=string' \ --show action=show \ - --allow-empty allow_empty=1 \ + --show-source action=show-source \ --check action=check \ - --convert convert=1 \ - --no-convert convert= \ --eq: '$action=compare; operator=eq; set@ oversion' \ --ne: '$action=compare; operator=ne; set@ oversion' \ --lt: '$action=compare; operator=lt; set@ oversion' \ --le: '$action=compare; operator=le; set@ oversion' \ --gt: '$action=compare; operator=gt; set@ oversion' \ --ge: '$action=compare; operator=ge; set@ oversion' \ - -v:,--set-version: '$action=update; set@ setversion; incversion=' \ - --prel '$action=update; setversion=prel; incversion=' \ -u,--update '$action=update; [ -z "$incversion" ] && incversion=auto' \ --menu '$action=update; incversion=menu' \ + -v:,--set-version: '$action=update; set@ setversion; incversion=' \ + --prel '$action=update; setversion=prel; incversion=' \ -x,--major '$action=update; incversion=major' \ -z,--minor '$action=update; incversion=minor' \ -p,--patchlevel '$action=update; incversion=patchlevel' \ - -l:,--prelease:,--prerelease: '$action=update; set@ setprelease; [ -z "$setprelease" ] && { setalpha=; setbeta=; setrc=; setrelease=1; }' \ - -a,--alpha '$action=update; setalpha=1; setbeta=; setrc=; setrelease=' \ - -b,--beta '$action=update; setalpha=; setbeta=1; setrc=; setrelease=' \ - -r,--rc '$action=update; setalpha=; setbeta=; setrc=1; setrelease=' \ - -R,--release,--final '$action=update; setalpha=; setbeta=; setrc=; setrelease=1' \ - -m:,--metadata:,--set-metadata: '$action=update; set@ setmetadata' \ - --add-metadata: '$action=update; set@ addmetadata' \ - -M,--vcs-metadata '$action=update; vcsmetadata=1' \ + -k,--keep '$action=update; incversion=' \ + -l:,--prelease:,--prerelease: '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=; setrelease=; set@ setprelease; [ -z "$setprelease" ] && setrelease=1' \ + -S,--snapshot '$action=update; setpr=1; setsnapshot=1; setalpha=; setbeta=; setrc=; setrelease=' \ + -a,--alpha '$action=update; setpr=1; setsnapshot=; setalpha=1; setbeta=; setrc=; setrelease=' \ + -b,--beta '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=1; setrc=; setrelease=' \ + -r,--rc '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=1; setrelease=' \ + -R,--release,--final '$action=update; setpr=1; setsnapshot=; setalpha=; setbeta=; setrc=; setrelease=1' \ + -m:,--metadata:,--set-metadata: '$action=update; setmd=1; resetmetadata=1; set@ setmetadata' \ + --add-metadata: '$action=update; setmd=1; set@ addmetadata' \ + -M,--vcs-metadata '$action=update; setmd=1; vcsmetadata=1' \ + --vpath: vpath= \ + --map: '$setmapfile=1; set@ mapfile' \ + --allow-empty allow_empty=1 \ + --convert convert=1 \ + --no-convert convert= \ + -t,--maven-update maven_update=1 \ @ args -- "$@" && set -- "${args[@]}" || { eerror "$args"; return 1; } # Calculer la source + if [ "$source" == auto-file -o "$source" == auto-string ]; then + [ -n "$file" ] || file=. + if [ -d "$file" ]; then + if [ -f "$file/$DEFAULT_POM" ]; then + file="$file/$DEFAULT_POM" + elif [ -f "$file/$DEFAULT_FILE" ]; then + file="$file/$DEFAULT_FILE" + elif [ -f "$file/version.txt" ]; then + file="$file/version.txt" + else + file="$file/$DEFAULT_FILE" + fi + fi + if [ "$source" == auto-file ]; then + if [[ "$file" == *.xml ]]; then source=pom + else source=file + fi + elif [ "$source" == auto-string ]; then + if [[ "$file" == *.xml ]]; then source=pom-string + else source=file-string + fi + fi + edebug "Auto-sélection de $(ppath "$file")" + + elif [ "$source" == auto-git-string ]; then + git_check_gitvcs || return 2 + splitfsep2 "$git" : branch name + [ -n "$branch" ] || branch=master + if [ "$(git cat-file -t "$branch:$name" 2>/dev/null)" == tree ]; then + if git rev-parse --verify --quiet "$branch:${name:+$name/}$DEFAULT_POM" >/dev/null; then + name="${name:+$name/}$DEFAULT_POM" + elif git rev-parse --verify --quiet "$branch:${name:+$name/}$DEFAULT_FILE" >/dev/null; then + name="${name:+$name/}$DEFAULT_FILE" + elif git rev-parse --verify --quiet "$branch:${name:+$name/}version.txt" >/dev/null; then + name="${name:+$name/}version.txt" + fi + fi + if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then + if [[ "$name" == *.xml ]]; then source=git-pom-string + else source=git-file-string + fi + git="$branch:$name" + edebug "Auto-sélection de $git" + else + eerror "$name: fichier introuvable sur la branche $branch" + return 1 + fi + fi + if [ "$source" == auto ]; then source=file - for i in "$DEFAULT_FILE" version.txt "$DEFAULT_POM"; do + for i in "$DEFAULT_POM" "$DEFAULT_FILE" version.txt; do if [ -f "$i" ]; then case "$i" in - "$DEFAULT_POM") - source=pom - file="$i" - break - ;; - *) - source=file - file="$i" - break - ;; + "$DEFAULT_POM") source=pom; file="$i"; break;; + *) source=file; file="$i"; break;; esac fi done - elif [ "$source" == file ]; then + elif [ "$source" == file -o "$source" == pom ]; then [ "$action" == auto ] && action=update fi [ "$source" == file -a -z "$file" ] && file="$DEFAULT_FILE" [ "$source" == pom -a -z "$file" ] && file="$DEFAULT_POM" [ "$action" == auto ] && action=show + if [[ "$source" == auto* ]]; then + eerror "bug: impossible de déterminer la source" + return 1 + fi + + if [ "$action" == show-source ]; then + echo "$source" + return 0 + fi + # Lire la version + local -a maprules + if [ "$source" == file -o "$source" == pom ]; then + local mapdir="$(dirname -- "$file")" + if [ -z "$setmapfile" ]; then + local tmpfile + tmpfile="$mapdir/.pver-map" + [ -f "$tmpfile" ] && mapfile="$tmpfile" + fi + if [ -n "$mapfile" ]; then + __pver_unless -f "$mapfile" "$mapfile: fichier introuvable" || return + + mapdir="$(dirname -- "$mapfile")" + array_from_lines maprules "$(<"$mapfile" filter_conf)" + local maprule filespec filevpath filename + local -a filenames + for maprule in "${maprules[@]}"; do + splitpair "$maprule" filespec filevpath + [ "$filevpath" != - ] || continue + array_lsfiles filenames "$mapdir" "$filespec" + [ ${#filenames[*]} -gt 0 ] || continue + file="${filenames[0]}" + if [[ "$file" == *.xml ]]; then source=pom + else source=file + fi + edebug "Sélection de $file comme fichier de référence" + break + done + fi + fi + [ "$source" == pom ] && maven_update=1 + if [ "$source" == file ]; then [ -f "$file" ] && version="$(<"$file")" - elif [ "$source" == pom ]; then - [ -f "$file" ] || { - eerror "$file: fichier introuvable" - return 1 - } - version="$(__pom_get_version "$file")" + elif [ "$source" == file-string ]; then if [ -z "$file" ]; then file="$DEFAULT_FILE" @@ -239,14 +368,51 @@ function pver() { fi [ -f "$file" ] && version="$(<"$file")" file= - elif [ "$source" == git-string ]; then + + elif [ "$source" == git-file-string ]; then + git_check_gitvcs || return 2 splitfsep2 "$git" : branch name [ -n "$branch" ] || branch=master [ -n "$name" ] || name="$DEFAULT_FILE" if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then version="$(git cat-file blob "$branch:$name" 2>/dev/null)" fi + + elif [ "$source" == pom ]; then + __pver_unless -f "$file" "$file: fichier introuvable" || return + [ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$file")" + version="$(__pver_pom_get_version "$file" "$vpath")" + + elif [ "$source" == pom-string ]; then + if [ -z "$file" ]; then file="$DEFAULT_POM" + elif [ -d "$file" ]; then file="$file/$DEFAULT_POM" + fi + __pver_unless -f "$file" "$file: fichier introuvable" || return + + [ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$file")" + version="$(__pver_pom_get_version "$file" "$vpath")" + file= + + elif [ "$source" == git-pom-string ]; then + git_check_gitvcs || return 2 + splitfsep2 "$git" : branch name + [ -n "$branch" ] || branch=master + [ -n "$name" ] || name="$DEFAULT_POM" + if git rev-parse --verify --quiet "$branch:$name" >/dev/null; then + local tmpfile; ac_set_tmpfile tmpfile + git cat-file blob "$branch:$name" >"$tmpfile" 2>/dev/null + [ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$tmpfile")" + version="$(__pver_pom_get_version "$tmpfile" "$vpath")" + ac_clean "$tmpfile" + else + eerror "$name: fichier introuvable sur la branche $branch" + return 1 + fi + + elif [ "$source" == git-prel-string ]; then + version="$(__pver_get_prel_version)" || return fi + [ -n "$version" -o -n "$allow_empty" ] || version=0.0.0 # Conversion éventuelle du numéro de version @@ -285,7 +451,7 @@ BEGIN { version = version ".0" } # afficher la version migrée au format semver - if (metadata != "") print version "+" metadata + if (metadata != "") print version "+r" metadata else print version ### maven, pom.xml @@ -339,7 +505,7 @@ BEGIN { fi if [ "$action" == check ]; then - [ -n "$valid" ] || { eerror "Numéro de version invalide: $version"; return 1; } + __pver_unless -n "$valid" "Numéro de version invalide: $version" || return elif [ "$action" == compare ]; then psemver_parse "$oversion" o @@ -423,94 +589,100 @@ BEGIN { elif [ "$action" == update ]; then [ -z "$version" -a -n "$allow_empty" ] && return 1 - [ -n "$valid" ] || { eerror "Numéro de version invalide: $version"; return 1; } + __pver_unless -n "$valid" "Numéro de version invalide: $version" || return if [ -n "$file" ]; then if [ -f "$file" ]; then - if isatty; then - estepi "La version actuelle est $version" - fi + isatty && estepi "La version actuelle est $version" elif [ "$source" == pom ]; then eerror "$file: fichier introuvable" return 1 - else - if isatty; then - ask_yesno "Le fichier $(ppath "$file") n'existe pas. Faut-il le créer?" O || return 1 - fi + elif isatty; then + ask_yesno "Le fichier $(ppath "$file") n'existe pas. Faut-il le créer?" O || return 1 fi fi # forcer le numéro de version if [ -n "$setversion" ]; then if [ "$setversion" == prel ]; then - local branch; branch="$(git_get_branch)" || return 2 - if [[ "$branch" == release-* ]]; then - setversion="${branch#release-}" - else - eerror "$branch: n'est pas une release branch" - return 1 - fi + setversion="$(__pver_get_prel_version)" + __pver_perror $? \ + "$(git_get_branch): n'est pas une release branch" \ + "Dépôt git introuvable" || return fi - psemver_setversion "$setversion" "" || { eerror "Numéro de version invalide: $setversion"; return 1; } + psemver_setversion "$setversion" "" || { + eerror "Numéro de version invalide: $setversion" + return 1 + } fi # Calculer metadata if [ -n "$vcsmetadata" ]; then + resetmetadata=1 setmetadata="$(git rev-parse --short HEAD)" || return 1 fi # incrémenter les numéros de version - if [ "$incversion" == auto ]; then - if [ -n "$setrelease" -o -n "$setprelease" -o -n "$setmetadata" -o -n "$addmetadata" ]; then - incversion= - else - incversion=menu + if [ -n "$maven_update" -a -n "$incversion" ]; then + # il y a des règles particulières pour maven + if [ -n "$setrelease" -a -z "$prelease" ]; then + # on est déjà en release, faire le traitement normal + maven_update= + elif [ -n "$setsnapshot" -a "$prelease" == SNAPSHOT ]; then + # on est déjà en snapshot, faire le traitement normal, mais garder le préfixe SNAPSHOT + maven_update= + if [ "$incversion" == auto ]; then + [ -n "$setmd" ] && incversion= || incversion=menu + fi + elif [ -z "$prelease" ]; then + # on est en release, passer en snapshot + setsnapshot=1 + setrelease= + if [ "$incversion" == auto ]; then + [ -n "$setmd" ] && incversion= || incversion=menu + fi + elif [ "$prelease" == SNAPSHOT ]; then + # on est en snapshot, passer en release + setsnapshot= + setrelease=1 + if [ "$incversion" == auto ]; then + [ -n "$setmd" ] && incversion= || incversion=menu + fi fi fi + if [ "$incversion" == auto ]; then + [ -n "$setpr" -o -n "$setmd" ] && incversion= || incversion=menu + fi + if [ -z "$incversion" -a -n "$maven_update" ]; then + # on doit incrémenter avec les règles maven, mais aucune des options + # -[xzp] n'est spécifiée + if [ -n "$setrelease" ]; then + incversion=patchlevel + elif [ -n "$setsnapshot" ]; then + if [ "$patchlevel" -eq 0 ]; then + incversion=minor + else + incversion=patchlevel + fi + fi + fi + + if [ -n "$setrelease" ]; then setprelease= + elif [ -n "$setsnapshot" ]; then setprelease=SNAPSHOT + fi if [ "$incversion" == menu ]; then - psemver_copy x; { - psemver_incmajor x - psemver_setprelease "$setprelease" x - if [ -n "$addmetadata" ]; then - [ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" x - psemver_addmetadata "$addmetadata" x - else - psemver_setmetadata "$setmetadata" x - fi - psemver_setvar versionx x - } - psemver_copy z; { - psemver_incminor z - psemver_setprelease "$setprelease" z - if [ -n "$addmetadata" ]; then - [ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" z - psemver_addmetadata "$addmetadata" z - else - psemver_setmetadata "$setmetadata" z - fi - psemver_setvar versionz z - } - psemver_copy p; { - psemver_incpatchlevel p - psemver_setprelease "$setprelease" p - if [ -n "$addmetadata" ]; then - [ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" p - psemver_addmetadata "$addmetadata" p - else - psemver_setmetadata "$setmetadata" p - fi - psemver_setvar versionp p - } - psemver_copy k; { - psemver_setprelease "$setprelease" k - if [ -n "$addmetadata" ]; then - [ -n "$setmetadata" ] && psemver_setmetadata "$setmetadata" k - psemver_addmetadata "$addmetadata" k - else - psemver_setmetadata "$setmetadata" k - fi - psemver_setvar versionk k - } + psemver_copy x + psemver_incsetprmd major x "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update" + psemver_setvar versionx x + psemver_copy z + psemver_incsetprmd minor z "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update" + psemver_setvar versionz z + psemver_copy p + psemver_incsetprmd patchlevel p "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update" + psemver_setvar versionp p + psemver_copy k + psemver_incsetprmd "" k "$setprelease" "$resetmetadata" "$setmetadata" "$addmetadata" "$maven_update" + psemver_setvar versionk k nextvs=( "$versionx : maj incompatibles de l'API (-x)" "$versionz : maj compatibles de l'API (-z)" @@ -528,49 +700,74 @@ BEGIN { *) incversion=;; esac fi + + local r if [ -n "$incversion" ]; then - case "$incversion" in - major) psemver_incmajor;; - minor) psemver_incminor;; - patchlevel) psemver_incpatchlevel;; - esac - # Quand on incrémente, réinitialiser la valeur de prérelease et metadata - psemver_setprelease - [ -z "$addmetadata" ] && psemver_setmetadata - fi - - # spécifier prerelease - if [ -n "$setrelease" ]; then - psemver_setprelease "" - elif [ -n "$setprelease" ]; then - psemver_setprelease "$setprelease" || { eerror "Identifiant de pre-release invalide: $setprelease"; return 1; } - fi - if [ -n "$setalpha" ]; then - : #XXX - elif [ -n "$setbeta" ]; then - : #XXX - elif [ -n "$setrc" ]; then - : #XXX - fi - - # spécifier metadata - if [ -n "$setmetadata" ]; then - psemver_setmetadata "$setmetadata" || { eerror "Identifiant de build invalide: $setmetadata"; return 1; } - fi - if [ -n "$addmetadata" ]; then - psemver_addmetadata "$addmetadata" || { eerror "Identifiant de build invalide: $addmetadata"; return 1; } + psemver_incsetprmd "$incversion" "" "$setprelease" 1 "$setmetadata" "$addmetadata" "$maven_update" + r=$? + else + psemver_setprmd "" "$setprelease" "$resetmetadata" "$setmetadata" "$addmetadata" + r=$? fi + case $r in + 2) eerror "Identifiant de pre-release invalide: $setprelease"; return 1;; + 3) eerror "Identifiant de build invalide: $setmetadata"; return 1;; + 4) eerror "Identifiant de build invalide: $addmetadata"; return 1;; + esac # afficher le résultat final + local -a depfiles psemver_setvar version if [ -n "$file" ]; then - case "$source" in - file) echo "$version" >"$file";; - pom) __pom_set_version "$file" "$version";; - esac + if [ "$source" == file ]; then + echo "$version" >"$file" + elif [ "$source" == pom ]; then + __pver_pom_set_version "$file" "$version" + [ $? -eq 3 ] && return 1 + fi + if [ ${#maprules[*]} -gt 0 ]; then + # mettre à jour les fichiers mentionnés dans mapfile + local -a donefiles + for maprule in "${maprules[@]}"; do + splitpair "$maprule" filespec filevpath + array_lsfiles filenames "$mapdir" "$filespec" + for file in "${filenames[@]}"; do + file="$(abspath "$file")" + edebug "Examen de $file" + # si c'est un wildcard, ne traiter que si le fichier n'a + # pas encore été traité + case "$filespec" in *\**|*\?*) array_contains donefiles "$file" && break;; esac + if [ "$filevpath" != - ]; then + if [[ "$file" == *.xml ]]; then + edebug "Maj version de $file au chemin XPATH ${filevpath:-par défaut}" + __pver_pom_set_version "$file" "$version" "$filevpath" + case $? in + 0|1) :;; + 2) array_addu depfiles "$file";; + *) return 1;; + esac + else + edebug "Maj version de $file" + echo "$version" >"$file" + fi + else + edebug "Fichier $file ignoré" + fi + array_addu donefiles "$file" + done + done + fi fi if isatty; then estepn "La nouvelle version est $version" + if [ ${#depfiles[*]} -gt 0 ]; then + local msg="Les fichiers suivants ont été modifiés, et leur version doit être mise à jour:" + for file in "${depfiles[@]}"; do + msg="$msg + $(qvals pver -uzw "$file" -R --menu)" + done + eimportant "$msg" + fi else echo "$version" fi diff --git a/lib/ulib/semver b/lib/ulib/semver index 94c63e0..eaa69aa 100644 --- a/lib/ulib/semver +++ b/lib/ulib/semver @@ -10,6 +10,7 @@ function __semver_check_prelease() { [ -z "${1//[a-zA-Z0-9.-]/}" ]; } function __semver_check_metadata() { [ -z "${1//[a-zA-Z0-9.-]/}" ]; } function semver_parse() { + # args: version majorV minorV patchlevelV preleaseV metadataV validV local __p_ver="$1" local __p_ma="${2:-major}" __p_mi="${3:-minor}" __p_pl="${4:-patchlevel}" local __p_pr="${5:-prelease}" __p_md="${6:-metadata}" __p_va="${7:-valid}" @@ -84,34 +85,79 @@ function semver_parse() { } function semver_incmajor() { - setv "$1" $((${!1} + 1)) - setv "$2" 0 - setv "$3" 0 - array_new "$4" + # args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease + if [ -z "$6" ]; then + setv "$1" $((${!1} + 1)) + setv "$2" 0 + setv "$3" 0 + array_new "$4" + elif [ -z "$7" ]; then + # maven snapshot --> release + if [ ${!2} -ne 0 -o ${!3} -ne 0 ]; then + setv "$1" $((${!1} + 1)) + setv "$2" 0 + setv "$3" 0 + fi + array_new "$4" + else + # maven release --> snapshot + setv "$2" $((${!2} + 1)) + setv "$3" 0 + array_split "$4" "$setprelease" . + fi } function semver_incminor() { - setv "$2" $((${!2} + 1)) - setv "$3" 0 - array_new "$4" + # args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease + if [ -z "$6" ]; then + setv "$2" $((${!2} + 1)) + setv "$3" 0 + array_new "$4" + elif [ -z "$7" ]; then + # maven snapshot --> release + if [ ${!3} -ne 0 ]; then + setv "$2" $((${!2} + 1)) + setv "$3" 0 + fi + array_new "$4" + else + # maven release --> snapshot + setv "$2" $((${!2} + 1)) + setv "$3" 0 + array_split "$4" "$setprelease" . + fi } function semver_incpatchlevel() { - setv "$3" $((${!3} + 1)) - array_new "$4" + # args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease + if [ -z "$6" ]; then + setv "$3" $((${!3} + 1)) + array_new "$4" + elif [ -z "$7" ]; then + # maven snapshot --> release + : + else + # maven release --> snapshot + setv "$3" $((${!3} + 1)) + array_split "$4" "$setprelease" . + fi } function semver_setversion() { + # args: version majorV minorV patchlevelV preleaseV metadataV local __sv_ma __sv_mi __sv_svl __sv_svr __sv_md __sv_va semver_parse "$1" __sv_ma __sv_mi __sv_pl __sv_pr __sv_md __sv_va [ -n "$__sv_va" ] || return 1 setv "$2" "$__sv_ma" setv "$3" "$__sv_mi" setv "$4" "$__sv_pl" + array_copy "$5" __sv_pr + array_copy "$6" __sv_md return 0 } function semver_setprelease() { + # args: setprelease majorV minorV patchlevelV preleaseV metadataV if [ -n "$1" ]; then __semver_check_prelease "$1" || return 1 array_split "$5" "$1" . @@ -122,6 +168,7 @@ function semver_setprelease() { } function semver_compare_prelease() { + # args: prelease1 prelease2 local -a __cp_pr1 __cp_pr2 __cp_len i __cp_v1 __cp_v2 array_copy __cp_pr1 "$1" array_copy __cp_pr2 "$2" @@ -179,6 +226,7 @@ function semver_compare_prelease() { } function semver_setmetadata() { + # args: setmetadata majorV minorV patchlevelV preleaseV metadataV if [ -n "$1" ]; then __semver_check_metadata "$1" || return 1 array_split "$6" "$1" . @@ -189,6 +237,7 @@ function semver_setmetadata() { } function semver_addmetadata() { + # args: addmetadata majorV minorV patchlevelV preleaseV metadataV if [ -n "$1" ]; then __semver_check_metadata "$1" || return 1 local -a __sam_metadata @@ -199,11 +248,13 @@ function semver_addmetadata() { } function semver_compare_metadata() { + # args: metadata1 metadata2 # même algo que pour prelease semver_compare_prelease "$@" } function semver_copy() { + # args: majorDV minorDV patchlevelDV preleaseDV metadataDV majorSV minorSV patchlevelSV preleaseSV metadataSV setv "$1" "${!6}" setv "$2" "${!7}" setv "$3" "${!8}" @@ -212,6 +263,7 @@ function semver_copy() { } function semver_build() { + # args: majorV minorV patchlevelV preleaseV metadataV echo_ "${!1}.${!2}.${!3}" array_isempty "$4" || recho_ "-$(array_join "$4" .)" array_isempty "$5" || recho_ "+$(array_join "$5" .)" @@ -219,6 +271,7 @@ function semver_build() { } function semver_setvar() { + # args: versionV majorV minorV patchlevelV preleaseV metadataV setv "$1" "$(semver_build "$2" "$3" "$4" "$5" "$6")" } @@ -228,9 +281,9 @@ function semver_setvar() { # uniquement un préfixe pour les noms de variable function psemver_parse() { semver_parse "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata" "${2}valid"; } -function psemver_incmajor() { semver_incmajor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; } -function psemver_incminor() { semver_incminor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; } -function psemver_incpatchlevel() { semver_incpatchlevel "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; } +function psemver_incmajor() { semver_incmajor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; } +function psemver_incminor() { semver_incminor "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; } +function psemver_incpatchlevel() { semver_incpatchlevel "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${2}metadata" "$2" "$3"; } function psemver_setversion() { semver_setversion "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; } function psemver_setprelease() { semver_setprelease "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; } function psemver_compare_prelease() { semver_compare_prelease "${1}prelease" "${2}prelease"; } @@ -240,3 +293,32 @@ function psemver_compare_metadata() { semver_compare_metadata "${1}metadata" "${ function psemver_copy() { semver_copy "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; } function psemver_build() { semver_build "${1}major" "${1}minor" "${1}patchlevel" "${1}prelease" "${1}metadata"; } function psemver_setvar() { semver_setvar "$1" "${2}major" "${2}minor" "${2}patchlevel" "${2}prelease" "${2}metadata"; } + +function psemver_setprmd() { + local setprelease="$2" resetmetadata="$3" setmetadata="$4" addmetadata="$5" + + psemver_setprelease "$setprelease" "$1" || return 2 + # XXX pas encore implémenté + local setalpha setbeta setrc + if [ -n "$setalpha" ]; then : + elif [ -n "$setbeta" ]; then : + elif [ -n "$setrc" ]; then : + fi + + [ -n "$resetmetadata" ] && psemver_setmetadata "" "$1" + if [ -n "$setmetadata" ]; then + psemver_setmetadata "$setmetadata" "$1" || return 3 + fi + if [ -n "$addmetadata" ]; then + psemver_addmetadata "$addmetadata" "$1" || return 4 + fi + + return 0 +} + +function psemver_incsetprmd() { + local setprelease="$3" resetmetadata="$4" setmetadata="$5" addmetadata="$6" maven_update="$7" + [ -n "$1" ] && "psemver_inc$1" "$2" "$maven_update" "$setprelease" + shift + psemver_setprmd "$@" +} diff --git a/lib/ulib/support/python/__init__.py b/lib/ulib/support/python/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/pyulib/src/ulib/ext/xpath/_xpath.py b/lib/ulib/support/python/xpath/__init__.py similarity index 75% rename from lib/pyulib/src/ulib/ext/xpath/_xpath.py rename to lib/ulib/support/python/xpath/__init__.py index d1c48bc..0741f64 100644 --- a/lib/pyulib/src/ulib/ext/xpath/_xpath.py +++ b/lib/ulib/support/python/xpath/__init__.py @@ -1,8 +1,11 @@ -import expr as E -import parser as P -import yappsrt as Y +from xpath.exceptions import * +import xpath.exceptions +import xpath.expr +import xpath.parser +import xpath.yappsrt -from exceptions import * +__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath'] +__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_'))) def api(f): """Decorator for functions and methods that are part of the external @@ -60,19 +63,19 @@ class XPathContext(object): @api def find(self, expr, node, **kwargs): - return XPath.get(expr).find(node, context=self, **kwargs) + return xpath.find(expr, node, context=self, **kwargs) @api def findnode(self, expr, node, **kwargs): - return XPath.get(expr).findnode(node, context=self, **kwargs) + return xpath.findnode(expr, node, context=self, **kwargs) @api def findvalue(self, expr, node, **kwargs): - return XPath.get(expr).findvalue(node, context=self, **kwargs) + return xpath.findvalue(expr, node, context=self, **kwargs) @api def findvalues(self, expr, node, **kwargs): - return XPath.get(expr).findvalues(node, context=self, **kwargs) + return xpath.findvalues(expr, node, context=self, **kwargs) class XPath(): _max_cache = 100 @@ -82,9 +85,9 @@ class XPath(): """Init docs. """ try: - parser = P.XPath(P.XPathScanner(str(expr))) + parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr))) self.expr = parser.XPath() - except Y.SyntaxError, e: + except xpath.yappsrt.SyntaxError, e: raise XPathParseError(str(expr), e.pos, e.msg) @classmethod @@ -112,7 +115,7 @@ class XPath(): @api def findnode(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) - if not E.nodesetp(result): + if not xpath.expr.nodesetp(result): raise XPathTypeError("expression is not a node-set") if len(result) == 0: return None @@ -121,18 +124,18 @@ class XPath(): @api def findvalue(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) - if E.nodesetp(result): + if xpath.expr.nodesetp(result): if len(result) == 0: return None - result = E.string(result) + result = xpath.expr.string(result) return result @api def findvalues(self, node, context=None, **kwargs): result = self.find(node, context, **kwargs) - if not E.nodesetp(result): + if not xpath.expr.nodesetp(result): raise XPathTypeError("expression is not a node-set") - return [E.string_value(x) for x in result] + return [xpath.expr.string_value(x) for x in result] def __repr__(self): return '%s.%s(%s)' % (self.__class__.__module__, @@ -141,3 +144,19 @@ class XPath(): def __str__(self): return str(self.expr) + +@api +def find(expr, node, **kwargs): + return XPath.get(expr).find(node, **kwargs) + +@api +def findnode(expr, node, **kwargs): + return XPath.get(expr).findnode(node, **kwargs) + +@api +def findvalue(expr, node, **kwargs): + return XPath.get(expr).findvalue(node, **kwargs) + +@api +def findvalues(expr, node, **kwargs): + return XPath.get(expr).findvalues(node, **kwargs) diff --git a/lib/ulib/support/python/xpath/exceptions.py b/lib/ulib/support/python/xpath/exceptions.py new file mode 100644 index 0000000..7ce017a --- /dev/null +++ b/lib/ulib/support/python/xpath/exceptions.py @@ -0,0 +1,49 @@ + +class XPathError(Exception): + """Base exception class used for all XPath exceptions.""" + +class XPathNotImplementedError(XPathError): + """Raised when an XPath expression contains a feature of XPath which + has not been implemented. + + """ + +class XPathParseError(XPathError): + """Raised when an XPath expression could not be parsed.""" + + def __init__(self, expr, pos, message): + XPathError.__init__(self) + self.expr = expr + self.pos = pos + self.err = message + + def __str__(self): + return ("Syntax error:\n" + + self.expr.replace("\n", " ") + "\n" + + ("-" * self.pos) + "^") + +class XPathTypeError(XPathError): + """Raised when an XPath expression is found to contain a type error. + For example, the expression "string()/node()" contains a type error + because the "string()" function does not return a node-set. + + """ + +class XPathUnknownFunctionError(XPathError): + """Raised when an XPath expression contains a function that has no + binding in the expression context. + + """ + +class XPathUnknownPrefixError(XPathError): + """Raised when an XPath expression contains a QName with a namespace + prefix that has no corresponding namespace declaration in the expression + context. + + """ + +class XPathUnknownVariableError(XPathError): + """Raised when an XPath expression contains a variable that has no + binding in the expression context. + + """ diff --git a/lib/ulib/support/python/xpath/expr.py b/lib/ulib/support/python/xpath/expr.py new file mode 100644 index 0000000..4199813 --- /dev/null +++ b/lib/ulib/support/python/xpath/expr.py @@ -0,0 +1,903 @@ +from __future__ import division +from itertools import * +import math +import operator +import re +import xml.dom +import weakref + +from xpath.exceptions import * +import xpath + + +# +# Data model functions. +# + +def string_value(node): + """Compute the string-value of a node.""" + if (node.nodeType == node.DOCUMENT_NODE or + node.nodeType == node.ELEMENT_NODE): + s = u'' + for n in axes['descendant'](node): + if n.nodeType == n.TEXT_NODE: + s += n.data + elif n.nodeType == n.CDATA_SECTION_NODE: + s += n.nodeValue + return s + + elif node.nodeType == node.ATTRIBUTE_NODE: + return node.value + + elif (node.nodeType == node.PROCESSING_INSTRUCTION_NODE or + node.nodeType == node.COMMENT_NODE or + node.nodeType == node.TEXT_NODE): + return node.data + + elif node.nodeType == node.CDATA_SECTION_NODE: + return node.nodeValue + +def document_order(node): + """Compute a document order value for the node. + + cmp(document_order(a), document_order(b)) will return -1, 0, or 1 if + a is before, identical to, or after b in the document respectively. + + We represent document order as a list of sibling indexes. That is, + the third child of the document node has an order of [2]. The first + child of that node has an order of [2,0]. + + Attributes have a sibling index of -1 (coming before all children of + their node) and are further ordered by name--e.g., [2,0,-1,'href']. + + """ + + # Attributes: parent-order + [-1, attribute-name] + if node.nodeType == node.ATTRIBUTE_NODE: + order = document_order(node.ownerElement) + order.extend((-1, node.name)) + return order + + # The document root (hopefully): [] + if node.parentNode is None: + return [] + + # Determine which child this is of its parent. + sibpos = 0 + sib = node + while sib.previousSibling is not None: + sibpos += 1 + sib = sib.previousSibling + + # Order: parent-order + [sibling-position] + order = document_order(node.parentNode) + order.append(sibpos) + return order + +# +# Type functions, operating on the various XPath types. +# +# Internally, we use the following representations: +# nodeset - list of DOM tree nodes in document order +# string - str or unicode +# boolean - bool +# number - int or float +# + +def nodeset(v): + """Convert a value to a nodeset.""" + if not nodesetp(v): + raise XPathTypeError, "value is not a node-set" + return v + +def nodesetp(v): + """Return true iff 'v' is a node-set.""" + if isinstance(v, list): + return True + +def string(v): + """Convert a value to a string.""" + if nodesetp(v): + if not v: + return u'' + return string_value(v[0]) + elif numberp(v): + if v == float('inf'): + return u'Infinity' + elif v == float('-inf'): + return u'-Infinity' + elif str(v) == 'nan': + return u'NaN' + elif int(v) == v and v <= 0xffffffff: + v = int(v) + return unicode(v) + elif booleanp(v): + return u'true' if v else u'false' + return v + +def stringp(v): + """Return true iff 'v' is a string.""" + return isinstance(v, basestring) + +def boolean(v): + """Convert a value to a boolean.""" + if nodesetp(v): + return len(v) > 0 + elif numberp(v): + if v == 0 or v != v: + return False + return True + elif stringp(v): + return v != '' + return v + +def booleanp(v): + """Return true iff 'v' is a boolean.""" + return isinstance(v, bool) + +def number(v): + """Convert a value to a number.""" + if nodesetp(v): + v = string(v) + try: + return float(v) + except ValueError: + return float('NaN') + +def numberp(v): + """Return true iff 'v' is a number.""" + return (not(isinstance(v, bool)) and + (isinstance(v, int) or isinstance(v, float))) + +class Expr(object): + """Abstract base class for XPath expressions.""" + + def evaluate(self, node, pos, size, context): + """Evaluate the expression. + + The context node, context position, and context size are passed as + arguments. + + Returns an XPath value: a nodeset, string, boolean, or number. + + """ + +class BinaryOperatorExpr(Expr): + """Base class for all binary operators.""" + + def __init__(self, op, left, right): + self.op = op + self.left = left + self.right = right + + def evaluate(self, node, pos, size, context): + # Subclasses either override evaluate() or implement operate(). + return self.operate(self.left.evaluate(node, pos, size, context), + self.right.evaluate(node, pos, size, context)) + + def __str__(self): + return '(%s %s %s)' % (self.left, self.op, self.right) + +class AndExpr(BinaryOperatorExpr): + """ and """ + + def evaluate(self, node, pos, size, context): + # Note that XPath boolean operations short-circuit. + return (boolean(self.left.evaluate(node, pos, size, context) and + boolean(self.right.evaluate(node, pos, size, context)))) + +class OrExpr(BinaryOperatorExpr): + """ or """ + + def evaluate(self, node, pos, size, context): + # Note that XPath boolean operations short-circuit. + return (boolean(self.left.evaluate(node, pos, size, context) or + boolean(self.right.evaluate(node, pos, size, context)))) + +class EqualityExpr(BinaryOperatorExpr): + """ = , != , etc.""" + + operators = { + '=' : operator.eq, + '!=' : operator.ne, + '<=' : operator.le, + '<' : operator.lt, + '>=' : operator.ge, + '>' : operator.gt, + } + + def operate(self, a, b): + if nodesetp(a): + for node in a: + if self.operate(string_value(node), b): + return True + return False + + if nodesetp(b): + for node in b: + if self.operate(a, string_value(node)): + return True + return False + + if self.op in ('=', '!='): + if booleanp(a) or booleanp(b): + convert = boolean + elif numberp(a) or numberp(b): + convert = number + else: + convert = string + else: + convert = number + + a, b = convert(a), convert(b) + return self.operators[self.op](a, b) + +def divop(x, y): + try: + return x / y + except ZeroDivisionError: + if x == 0 and y == 0: + return float('nan') + if x < 0: + return float('-inf') + return float('inf') + +class ArithmeticalExpr(BinaryOperatorExpr): + """ + , - , etc.""" + + # Note that we must use math.fmod for the correct modulo semantics. + operators = { + '+' : operator.add, + '-' : operator.sub, + '*' : operator.mul, + 'div' : divop, + 'mod' : math.fmod + } + + def operate(self, a, b): + return self.operators[self.op](number(a), number(b)) + +class UnionExpr(BinaryOperatorExpr): + """ | """ + + def operate(self, a, b): + if not nodesetp(a) or not nodesetp(b): + raise XPathTypeError("union operand is not a node-set") + + # Need to sort the result to preserve document order. + return sorted(set(chain(a, b)), key=document_order) + +class NegationExpr(Expr): + """- """ + + def __init__(self, expr): + self.expr = expr + + def evaluate(self, node, pos, size, context): + return -number(self.expr.evaluate(node, pos, size, context)) + + def __str__(self): + return '(-%s)' % self.expr + +class LiteralExpr(Expr): + """Literals--either numbers or strings.""" + + def __init__(self, literal): + self.literal = literal + + def evaluate(self, node, pos, size, context): + return self.literal + + def __str__(self): + if stringp(self.literal): + if "'" in self.literal: + return '"%s"' % self.literal + else: + return "'%s'" % self.literal + return string(self.literal) + +class VariableReference(Expr): + """Variable references.""" + + def __init__(self, prefix, name): + self.prefix = prefix + self.name = name + + def evaluate(self, node, pos, size, context): + try: + if self.prefix is not None: + try: + namespaceURI = context.namespaces[self.prefix] + except KeyError: + raise XPathUnknownPrefixError(self.prefix) + return context.variables[(namespaceURI, self.name)] + else: + return context.variables[self.name] + except KeyError: + raise XPathUnknownVariableError(str(self)) + + def __str__(self): + if self.prefix is None: + return '$%s' % self.name + else: + return '$%s:%s' % (self.prefix, self.name) + +class Function(Expr): + """Functions.""" + + def __init__(self, name, args): + self.name = name + self.args = args + self.evaluate = getattr(self, 'f_%s' % name.replace('-', '_'), None) + if self.evaluate is None: + raise XPathUnknownFunctionError, 'unknown function "%s()"' % name + + if len(self.args) < self.evaluate.minargs: + raise XPathTypeError, 'too few arguments for "%s()"' % name + if (self.evaluate.maxargs is not None and + len(self.args) > self.evaluate.maxargs): + raise XPathTypeError, 'too many arguments for "%s()"' % name + + # + # XPath functions are implemented by methods of the Function class. + # + # A method implementing an XPath function is decorated with the function + # decorator, and receives the evaluated function arguments as positional + # parameters. + # + + def function(minargs, maxargs, implicit=False, first=False, convert=None): + """Function decorator. + + minargs -- Minimum number of arguments taken by the function. + maxargs -- Maximum number of arguments taken by the function. + implicit -- True for functions which operate on a nodeset consisting + of the current context node when passed no argument. + (e.g., string() and number().) + convert -- When non-None, a function used to filter function arguments. + """ + def decorator(f): + def new_f(self, node, pos, size, context): + if implicit and len(self.args) == 0: + args = [[node]] + else: + args = [x.evaluate(node, pos, size, context) + for x in self.args] + if first: + args[0] = nodeset(args[0]) + if len(args[0]) > 0: + args[0] = args[0][0] + else: + args[0] = None + if convert is not None: + args = [convert(x) for x in args] + return f(self, node, pos, size, context, *args) + + new_f.minargs = minargs + new_f.maxargs = maxargs + new_f.__name__ = f.__name__ + new_f.__doc__ = f.__doc__ + return new_f + return decorator + + # Node Set Functions + + @function(0, 0) + def f_last(self, node, pos, size, context): + return size + + @function(0, 0) + def f_position(self, node, pos, size, context): + return pos + + @function(1, 1, convert=nodeset) + def f_count(self, node, pos, size, context, nodes): + return len(nodes) + + @function(1, 1) + def f_id(self, node, pos, size, context, arg): + if nodesetp(arg): + ids = (string_value(x) for x in arg) + else: + ids = [string(arg)] + if node.nodeType != node.DOCUMENT_NODE: + node = node.ownerDocument + return list(filter(None, (node.getElementById(id) for id in ids))) + + @function(0, 1, implicit=True, first=True) + def f_local_name(self, node, pos, size, context, argnode): + if argnode is None: + return '' + if (argnode.nodeType == argnode.ELEMENT_NODE or + argnode.nodeType == argnode.ATTRIBUTE_NODE): + return argnode.localName + elif argnode.nodeType == argnode.PROCESSING_INSTRUCTION_NODE: + return argnode.target + return '' + + @function(0, 1, implicit=True, first=True) + def f_namespace_uri(self, node, pos, size, context, argnode): + if argnode is None: + return '' + return argnode.namespaceURI + + @function(0, 1, implicit=True, first=True) + def f_name(self, node, pos, size, context, argnode): + if argnode is None: + return '' + if argnode.nodeType == argnode.ELEMENT_NODE: + return argnode.tagName + elif argnode.nodeType == argnode.ATTRIBUTE_NODE: + return argnode.name + elif argnode.nodeType == argnode.PROCESSING_INSTRUCTION_NODE: + return argnode.target + return '' + + # String Functions + + @function(0, 1, implicit=True, convert=string) + def f_string(self, node, pos, size, context, arg): + return arg + + @function(2, None, convert=string) + def f_concat(self, node, pos, size, context, *args): + return ''.join((x for x in args)) + + @function(2, 2, convert=string) + def f_starts_with(self, node, pos, size, context, a, b): + return a.startswith(b) + + @function(2, 2, convert=string) + def f_contains(self, node, pos, size, context, a, b): + return b in a + + @function(2, 2, convert=string) + def f_substring_before(self, node, pos, size, context, a, b): + try: + return a[0:a.index(b)] + except ValueError: + return '' + + @function(2, 2, convert=string) + def f_substring_after(self, node, pos, size, context, a, b): + try: + return a[a.index(b)+len(b):] + except ValueError: + return '' + + @function(2, 3) + def f_substring(self, node, pos, size, context, s, start, count=None): + s = string(s) + start = round(number(start)) + if start != start: + # Catch NaN + return '' + + if count is None: + end = len(s) + 1 + else: + end = start + round(number(count)) + if end != end: + # Catch NaN + return '' + if end > len(s): + end = len(s)+1 + + if start < 1: + start = 1 + if start > len(s): + return '' + if end <= start: + return '' + return s[int(start)-1:int(end)-1] + + @function(0, 1, implicit=True, convert=string) + def f_string_length(self, node, pos, size, context, s): + return len(s) + + @function(0, 1, implicit=True, convert=string) + def f_normalize_space(self, node, pos, size, context, s): + return re.sub(r'\s+', ' ', s.strip()) + + @function(3, 3, convert=lambda x: unicode(string(x))) + def f_translate(self, node, pos, size, context, s, source, target): + # str.translate() and unicode.translate() are completely different. + # The translate() arguments are coerced to unicode. + table = {} + for schar, tchar in izip(source, target): + schar = ord(schar) + if schar not in table: + table[schar] = tchar + if len(source) > len(target): + for schar in source[len(target):]: + schar = ord(schar) + if schar not in table: + table[schar] = None + return s.translate(table) + + # Boolean functions + + @function(1, 1, convert=boolean) + def f_boolean(self, node, pos, size, context, b): + return b + + @function(1, 1, convert=boolean) + def f_not(self, node, pos, size, context, b): + return not b + + @function(0, 0) + def f_true(self, node, pos, size, context): + return True + + @function(0, 0) + def f_false(self, node, pos, size, context): + return False + + @function(1, 1, convert=string) + def f_lang(self, node, pos, size, context, s): + s = s.lower() + for n in axes['ancestor-or-self'](node): + if n.nodeType == n.ELEMENT_NODE and n.hasAttribute('xml:lang'): + lang = n.getAttribute('xml:lang').lower() + if s == lang or lang.startswith(s + u'-'): + return True + break + return False + + # Number functions + + @function(0, 1, implicit=True, convert=number) + def f_number(self, node, pos, size, context, n): + return n + + @function(1, 1, convert=nodeset) + def f_sum(self, node, pos, size, context, nodes): + return sum((number(string_value(x)) for x in nodes)) + + @function(1, 1, convert=number) + def f_floor(self, node, pos, size, context, n): + return math.floor(n) + + @function(1, 1, convert=number) + def f_ceiling(self, node, pos, size, context, n): + return math.ceil(n) + + @function(1, 1, convert=number) + def f_round(self, node, pos, size, context, n): + # XXX round(-0.0) should be -0.0, not 0.0. + # XXX round(-1.5) should be -1.0, not -2.0. + return round(n) + + def __str__(self): + return '%s(%s)' % (self.name, ', '.join((str(x) for x in self.args))) + +# +# XPath axes. +# + +# Dictionary of all axis functions. +axes = {} + +def axisfn(reverse=False, principal_node_type=xml.dom.Node.ELEMENT_NODE): + """Axis function decorator. + + An axis function will take a node as an argument and return a sequence + over the nodes along an XPath axis. Axis functions have two extra + attributes indicating the axis direction and principal node type. + """ + def decorate(f): + f.__name__ = f.__name__.replace('_', '-') + f.reverse = reverse + f.principal_node_type = principal_node_type + return f + return decorate + +def make_axes(): + """Define functions to walk each of the possible XPath axes.""" + + @axisfn() + def child(node): + return node.childNodes + + @axisfn() + def descendant(node): + for child in node.childNodes: + for node in descendant_or_self(child): + yield node + + @axisfn() + def parent(node): + if node.parentNode is not None: + yield node.parentNode + + @axisfn(reverse=True) + def ancestor(node): + while node.parentNode is not None: + node = node.parentNode + yield node + + @axisfn() + def following_sibling(node): + while node.nextSibling is not None: + node = node.nextSibling + yield node + + @axisfn(reverse=True) + def preceding_sibling(node): + while node.previousSibling is not None: + node = node.previousSibling + yield node + + @axisfn() + def following(node): + while node is not None: + while node.nextSibling is not None: + node = node.nextSibling + for n in descendant_or_self(node): + yield n + node = node.parentNode + + @axisfn(reverse=True) + def preceding(node): + while node is not None: + while node.previousSibling is not None: + node = node.previousSibling + # Could be more efficient here. + for n in reversed(list(descendant_or_self(node))): + yield n + node = node.parentNode + + @axisfn(principal_node_type=xml.dom.Node.ATTRIBUTE_NODE) + def attribute(node): + if node.attributes is not None: + return (node.attributes.item(i) + for i in xrange(node.attributes.length)) + return () + + @axisfn() + def namespace(node): + raise XPathNotImplementedError("namespace axis is not implemented") + + @axisfn() + def self(node): + yield node + + @axisfn() + def descendant_or_self(node): + yield node + for child in node.childNodes: + for node in descendant_or_self(child): + yield node + + @axisfn(reverse=True) + def ancestor_or_self(node): + return chain([node], ancestor(node)) + + # Place each axis function defined here into the 'axes' dict. + for axis in locals().values(): + axes[axis.__name__] = axis + +make_axes() + +def merge_into_nodeset(target, source): + """Place all the nodes from the source node-set into the target + node-set, preserving document order. Both node-sets must be in + document order to begin with. + + """ + if len(target) == 0: + target.extend(source) + return + + source = [n for n in source if n not in target] + if len(source) == 0: + return + + # If the last node in the target set comes before the first node in the + # source set, then we can just concatenate the sets. Otherwise, we + # will need to sort. (We could also check to see if the last node in + # the source set comes before the first node in the target set, but this + # situation is very unlikely in practice.) + if document_order(target[-1]) < document_order(source[0]): + target.extend(source) + else: + target.extend(source) + target.sort(key=document_order) + +class AbsolutePathExpr(Expr): + """Absolute location paths.""" + + def __init__(self, path): + self.path = path + + def evaluate(self, node, pos, size, context): + if node.nodeType != node.DOCUMENT_NODE: + node = node.ownerDocument + if self.path is None: + return [node] + return self.path.evaluate(node, 1, 1, context) + + def __str__(self): + return '/%s' % (self.path or '') + +class PathExpr(Expr): + """Location path expressions.""" + + def __init__(self, steps): + self.steps = steps + + def evaluate(self, node, pos, size, context): + # The first step in the path is evaluated in the current context. + # If this is the only step in the path, the return value is + # unimportant. If there are other steps, however, it must be a + # node-set. + result = self.steps[0].evaluate(node, pos, size, context) + if len(self.steps) > 1 and not nodesetp(result): + raise XPathTypeError("path step is not a node-set") + + # Subsequent steps are evaluated for each node in the node-set + # resulting from the previous step. + for step in self.steps[1:]: + aggregate = [] + for i in xrange(len(result)): + nodes = step.evaluate(result[i], i+1, len(result), context) + if not nodesetp(nodes): + raise XPathTypeError("path step is not a node-set") + merge_into_nodeset(aggregate, nodes) + result = aggregate + + return result + + def __str__(self): + return '/'.join((str(s) for s in self.steps)) + +class PredicateList(Expr): + """A list of predicates. + + Predicates are handled as an expression wrapping the expression + filtered by the predicates. + + """ + def __init__(self, expr, predicates, axis='child'): + self.predicates = predicates + self.expr = expr + self.axis = axes[axis] + + def evaluate(self, node, pos, size, context): + result = self.expr.evaluate(node, pos, size, context) + if not nodesetp(result): + raise XPathTypeError("predicate input is not a node-set") + + if self.axis.reverse: + result.reverse() + + for pred in self.predicates: + match = [] + for i, node in izip(count(1), result): + r = pred.evaluate(node, i, len(result), context) + + # If a predicate evaluates to a number, select the node + # with that position. Otherwise, select nodes for which + # the boolean value of the predicate is true. + if numberp(r): + if r == i: + match.append(node) + elif boolean(r): + match.append(node) + result = match + + if self.axis.reverse: + result.reverse() + + return result + + def __str__(self): + s = str(self.expr) + if '/' in s: + s = '(%s)' % s + return s + ''.join(('[%s]' % x for x in self.predicates)) + +class AxisStep(Expr): + """One step in a location path expression.""" + + def __init__(self, axis, test=None, predicates=None): + if test is None: + test = AnyKindTest() + self.axis = axes[axis] + self.test = test + + def evaluate(self, node, pos, size, context): + match = [] + for n in self.axis(node): + if self.test.match(n, self.axis, context): + match.append(n) + + if self.axis.reverse: + match.reverse() + + return match + + def __str__(self): + return '%s::%s' % (self.axis.__name__, self.test) + +# +# Node tests. +# + +class Test(object): + """Abstract base class for node tests.""" + + def match(self, node, axis, context): + """Return True if 'node' matches the test along 'axis'.""" + +class NameTest(object): + def __init__(self, prefix, localpart): + self.prefix = prefix + self.localName = localpart + if self.prefix == None and self.localName == '*': + self.prefix = '*' + + def match(self, node, axis, context): + if node.nodeType != axis.principal_node_type: + return False + + if self.prefix != '*': + namespaceURI = None + if self.prefix is not None: + try: + namespaceURI = context.namespaces[self.prefix] + except KeyError: + raise XPathUnknownPrefixError(self.prefix) + elif axis.principal_node_type == node.ELEMENT_NODE: + namespaceURI = context.default_namespace + if namespaceURI != node.namespaceURI: + return False + if self.localName != '*': + if self.localName != node.localName: + return False + return True + + def __str__(self): + if self.prefix is not None: + return '%s:%s' % (self.prefix, self.localName) + else: + return self.localName + +class PITest(object): + def __init__(self, name=None): + self.name = name + + def match(self, node, axis, context): + return (node.nodeType == node.PROCESSING_INSTRUCTION_NODE and + (self.name is None or node.target == self.name)) + + def __str__(self): + if self.name is None: + name = '' + elif "'" in self.name: + name = '"%s"' % self.name + else: + name = "'%s'" % self.name + return 'processing-instruction(%s)' % name + +class CommentTest(object): + def match(self, node, axis, context): + return node.nodeType == node.COMMENT_NODE + + def __str__(self): + return 'comment()' + +class TextTest(object): + def match(self, node, axis, context): + return (node.nodeType == node.TEXT_NODE or + node.nodeType == node.CDATA_SECTION_NODE) + + def __str__(self): + return 'text()' + +class AnyKindTest(object): + def match(self, node, axis, context): + return True + + def __str__(self): + return 'node()' diff --git a/lib/ulib/support/python/xpath/parser.py b/lib/ulib/support/python/xpath/parser.py new file mode 100644 index 0000000..504de78 --- /dev/null +++ b/lib/ulib/support/python/xpath/parser.py @@ -0,0 +1,416 @@ +import xpath.expr as X +from xpath.yappsrt import * + + +from string import * +import re + +class XPathScanner(Scanner): + patterns = [ + ("r'\\:'", re.compile('\\:')), + ("r'node\\s*\\('", re.compile('node\\s*\\(')), + ("r'text\\s*\\('", re.compile('text\\s*\\(')), + ("r'comment\\s*\\('", re.compile('comment\\s*\\(')), + ("r'processing-instruction\\s*\\('", re.compile('processing-instruction\\s*\\(')), + ("r'\\,'", re.compile('\\,')), + ("r'\\.'", re.compile('\\.')), + ("r'\\$'", re.compile('\\$')), + ("r'\\)'", re.compile('\\)')), + ("r'\\('", re.compile('\\(')), + ("r'\\]'", re.compile('\\]')), + ("r'\\['", re.compile('\\[')), + ("r'\\*'", re.compile('\\*')), + ("r':'", re.compile(':')), + ("r'\\.\\.'", re.compile('\\.\\.')), + ("r'@'", re.compile('@')), + ("r'::'", re.compile('::')), + ("r'\\/\\/'", re.compile('\\/\\/')), + ("r'\\/'", re.compile('\\/')), + ("r'\\-'", re.compile('\\-')), + ("'\\|'", re.compile('\\|')), + ("r'and'", re.compile('and')), + ("r'or'", re.compile('or')), + ('\\s+', re.compile('\\s+')), + ('END', re.compile('$')), + ('FORWARD_AXIS_NAME', re.compile('child|descendant-or-self|attribute|self|descendant|following-sibling|following|namespace')), + ('REVERSE_AXIS_NAME', re.compile('parent|preceding-sibling|preceding|ancestor-or-self|ancestor')), + ('NCNAME', re.compile('[a-zA-Z_][a-zA-Z0-9_\\-\\.\\w]*(?!\\()')), + ('FUNCNAME', re.compile('[a-zA-Z_][a-zA-Z0-9_\\-\\.\\w]*')), + ('DQUOTE', re.compile('\\"(?:[^\\"])*\\"')), + ('SQUOTE', re.compile("\\'(?:[^\\'])*\\'")), + ('NUMBER', re.compile('((\\.[0-9]+)|([0-9]+(\\.[0-9]*)?))([eE][\\+\\-]?[0-9]+)?')), + ('EQ_COMP', re.compile('\\!?\\=')), + ('REL_COMP', re.compile('[\\<\\>]\\=?')), + ('ADD_COMP', re.compile('[\\+\\-]')), + ('MUL_COMP', re.compile('\\*|div|mod')), + ] + def __init__(self, str): + Scanner.__init__(self,None,['\\s+'],str) + +class XPath(Parser): + def XPath(self): + Expr = self.Expr() + END = self._scan('END') + return Expr + + def Expr(self): + OrExpr = self.OrExpr() + return OrExpr + + def OrExpr(self): + AndExpr = self.AndExpr() + Expr = AndExpr + while self._peek("r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'or'": + self._scan("r'or'") + AndExpr = self.AndExpr() + Expr = X.OrExpr('or', Expr, AndExpr) + return Expr + + def AndExpr(self): + EqualityExpr = self.EqualityExpr() + Expr = EqualityExpr + while self._peek("r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'and'": + self._scan("r'and'") + EqualityExpr = self.EqualityExpr() + Expr = X.AndExpr('and', Expr, EqualityExpr) + return Expr + + def EqualityExpr(self): + RelationalExpr = self.RelationalExpr() + Expr = RelationalExpr + while self._peek('EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'EQ_COMP': + EQ_COMP = self._scan('EQ_COMP') + RelationalExpr = self.RelationalExpr() + Expr = X.EqualityExpr(EQ_COMP, Expr, RelationalExpr) + return Expr + + def RelationalExpr(self): + AdditiveExpr = self.AdditiveExpr() + Expr = AdditiveExpr + while self._peek('REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'REL_COMP': + REL_COMP = self._scan('REL_COMP') + AdditiveExpr = self.AdditiveExpr() + Expr = X.EqualityExpr(REL_COMP, Expr, AdditiveExpr) + return Expr + + def AdditiveExpr(self): + MultiplicativeExpr = self.MultiplicativeExpr() + Expr = MultiplicativeExpr + while self._peek('ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'ADD_COMP': + ADD_COMP = self._scan('ADD_COMP') + MultiplicativeExpr = self.MultiplicativeExpr() + Expr = X.ArithmeticalExpr(ADD_COMP, Expr, MultiplicativeExpr) + return Expr + + def MultiplicativeExpr(self): + UnionExpr = self.UnionExpr() + Expr = UnionExpr + while self._peek('MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == 'MUL_COMP': + MUL_COMP = self._scan('MUL_COMP') + UnionExpr = self.UnionExpr() + Expr = X.ArithmeticalExpr(MUL_COMP, Expr, UnionExpr) + return Expr + + def UnionExpr(self): + UnaryExpr = self.UnaryExpr() + Expr = UnaryExpr + while self._peek("'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "'\\|'": + self._scan("'\\|'") + UnaryExpr = self.UnaryExpr() + Expr = X.UnionExpr('|', Expr, UnaryExpr) + return Expr + + def UnaryExpr(self): + _token_ = self._peek("r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ == "r'\\-'": + self._scan("r'\\-'") + ValueExpr = self.ValueExpr() + return X.NegationExpr(ValueExpr) + else: + ValueExpr = self.ValueExpr() + return ValueExpr + + def ValueExpr(self): + PathExpr = self.PathExpr() + return PathExpr + + def PathExpr(self): + _token_ = self._peek("r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ == "r'\\/'": + self._scan("r'\\/'") + path = None + if self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME', "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") not in ["'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'"]: + RelativePathExpr = self.RelativePathExpr() + path = RelativePathExpr + return X.AbsolutePathExpr(path) + elif _token_ == "r'\\/\\/'": + self._scan("r'\\/\\/'") + RelativePathExpr = self.RelativePathExpr() + step = X.AxisStep('descendant-or-self') + RelativePathExpr.steps.insert(0, step) + return X.AbsolutePathExpr(RelativePathExpr) + else: + RelativePathExpr = self.RelativePathExpr() + return RelativePathExpr + + def RelativePathExpr(self): + StepExpr = self.StepExpr() + steps = [StepExpr] + while self._peek("r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") in ["r'\\/'", "r'\\/\\/'"]: + _token_ = self._peek("r'\\/'", "r'\\/\\/'") + if _token_ == "r'\\/'": + self._scan("r'\\/'") + else:# == "r'\\/\\/'" + self._scan("r'\\/\\/'") + steps.append(X.AxisStep('descendant-or-self')) + StepExpr = self.StepExpr() + steps.append(StepExpr) + return X.PathExpr(steps) + + def StepExpr(self): + _token_ = self._peek("r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE']: + AxisStep = self.AxisStep() + return AxisStep + else: + FilterExpr = self.FilterExpr() + return FilterExpr + + def AxisStep(self): + _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ not in ['REVERSE_AXIS_NAME', "r'\\.\\.'"]: + ForwardStep = self.ForwardStep() + step = ForwardStep + else:# in ['REVERSE_AXIS_NAME', "r'\\.\\.'"] + ReverseStep = self.ReverseStep() + step = ReverseStep + expr = X.AxisStep(*step) + if self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['": + PredicateList = self.PredicateList() + expr = X.PredicateList(expr, PredicateList, step[0]) + return expr + + def ForwardStep(self): + _token_ = self._peek('FORWARD_AXIS_NAME', "r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ == 'FORWARD_AXIS_NAME': + ForwardAxis = self.ForwardAxis() + NodeTest = self.NodeTest() + return [ForwardAxis, NodeTest] + else: + AbbrevForwardStep = self.AbbrevForwardStep() + return AbbrevForwardStep + + def ForwardAxis(self): + FORWARD_AXIS_NAME = self._scan('FORWARD_AXIS_NAME') + self._scan("r'::'") + return FORWARD_AXIS_NAME + + def AbbrevForwardStep(self): + axis = 'child' + if self._peek("r'@'", "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') == "r'@'": + self._scan("r'@'") + axis = 'attribute' + NodeTest = self.NodeTest() + return [axis, NodeTest] + + def ReverseStep(self): + _token_ = self._peek('REVERSE_AXIS_NAME', "r'\\.\\.'") + if _token_ == 'REVERSE_AXIS_NAME': + ReverseAxis = self.ReverseAxis() + NodeTest = self.NodeTest() + return [ReverseAxis, NodeTest] + else:# == "r'\\.\\.'" + AbbrevReverseStep = self.AbbrevReverseStep() + return AbbrevReverseStep + + def ReverseAxis(self): + REVERSE_AXIS_NAME = self._scan('REVERSE_AXIS_NAME') + self._scan("r'::'") + return REVERSE_AXIS_NAME + + def AbbrevReverseStep(self): + self._scan("r'\\.\\.'") + return ['parent', None] + + def NodeTest(self): + _token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') + if _token_ not in ["r'\\*'", 'NCNAME']: + KindTest = self.KindTest() + return KindTest + else:# in ["r'\\*'", 'NCNAME'] + NameTest = self.NameTest() + return NameTest + + def NameTest(self): + prefix = None + WildcardOrNCName = self.WildcardOrNCName() + localpart = WildcardOrNCName + if self._peek("r':'", "r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r':'": + self._scan("r':'") + WildcardOrNCName = self.WildcardOrNCName() + prefix = localpart + localpart = WildcardOrNCName + return X.NameTest(prefix, localpart) + + def WildcardOrNCName(self): + _token_ = self._peek("r'\\*'", 'NCNAME') + if _token_ == "r'\\*'": + self._scan("r'\\*'") + return '*' + else:# == 'NCNAME' + NCNAME = self._scan('NCNAME') + return NCNAME + + def FilterExpr(self): + PrimaryExpr = self.PrimaryExpr() + if self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['": + PredicateList = self.PredicateList() + PrimaryExpr = X.PredicateList(PrimaryExpr,PredicateList) + return PrimaryExpr + + def PredicateList(self): + Predicate = self.Predicate() + predicates = [Predicate] + while self._peek("r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\['": + Predicate = self.Predicate() + predicates.append(Predicate) + return predicates + + def Predicate(self): + self._scan("r'\\['") + Expr = self.Expr() + self._scan("r'\\]'") + return Expr + + def PrimaryExpr(self): + _token_ = self._peek("r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE') + if _token_ not in ["r'\\('", "r'\\$'", "r'\\.'", 'FUNCNAME']: + Literal = self.Literal() + return X.LiteralExpr(Literal) + elif _token_ == "r'\\$'": + VariableReference = self.VariableReference() + return VariableReference + elif _token_ == "r'\\('": + self._scan("r'\\('") + Expr = self.Expr() + self._scan("r'\\)'") + return Expr + elif _token_ == "r'\\.'": + ContextItemExpr = self.ContextItemExpr() + return ContextItemExpr + else:# == 'FUNCNAME' + FunctionCall = self.FunctionCall() + return FunctionCall + + def VariableReference(self): + self._scan("r'\\$'") + QName = self.QName() + return X.VariableReference(*QName) + + def ContextItemExpr(self): + self._scan("r'\\.'") + return X.AxisStep('self') + + def FunctionCall(self): + FUNCNAME = self._scan('FUNCNAME') + self._scan("r'\\('") + args = [] + if self._peek("r'\\,'", "r'\\)'", "r'\\-'", "r'\\/'", "r'\\/\\/'", "r'\\('", 'FORWARD_AXIS_NAME', "r'@'", 'REVERSE_AXIS_NAME', "r'\\.\\.'", "r'\\$'", "r'\\.'", 'FUNCNAME', 'NUMBER', 'DQUOTE', 'SQUOTE', "r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('", "r'\\*'", 'NCNAME') not in ["r'\\,'", "r'\\)'"]: + Expr = self.Expr() + args.append(Expr) + while self._peek("r'\\,'", "r'\\)'") == "r'\\,'": + self._scan("r'\\,'") + Expr = self.Expr() + args.append(Expr) + self._scan("r'\\)'") + return X.Function(FUNCNAME, args) + + def KindTest(self): + _token_ = self._peek("r'processing-instruction\\s*\\('", "r'comment\\s*\\('", "r'text\\s*\\('", "r'node\\s*\\('") + if _token_ == "r'processing-instruction\\s*\\('": + PITest = self.PITest() + return PITest + elif _token_ == "r'comment\\s*\\('": + CommentTest = self.CommentTest() + return CommentTest + elif _token_ == "r'text\\s*\\('": + TextTest = self.TextTest() + return TextTest + else:# == "r'node\\s*\\('" + AnyKindTest = self.AnyKindTest() + return AnyKindTest + + def PITest(self): + self._scan("r'processing-instruction\\s*\\('") + name = None + if self._peek('NCNAME', "r'\\)'", 'DQUOTE', 'SQUOTE') != "r'\\)'": + _token_ = self._peek('NCNAME', 'DQUOTE', 'SQUOTE') + if _token_ == 'NCNAME': + NCNAME = self._scan('NCNAME') + name = NCNAME + else:# in ['DQUOTE', 'SQUOTE'] + StringLiteral = self.StringLiteral() + name = StringLiteral + self._scan("r'\\)'") + return X.PITest(name) + + def CommentTest(self): + self._scan("r'comment\\s*\\('") + self._scan("r'\\)'") + return X.CommentTest() + + def TextTest(self): + self._scan("r'text\\s*\\('") + self._scan("r'\\)'") + return X.TextTest() + + def AnyKindTest(self): + self._scan("r'node\\s*\\('") + self._scan("r'\\)'") + return X.AnyKindTest() + + def Literal(self): + _token_ = self._peek('NUMBER', 'DQUOTE', 'SQUOTE') + if _token_ == 'NUMBER': + NumericLiteral = self.NumericLiteral() + return NumericLiteral + else:# in ['DQUOTE', 'SQUOTE'] + StringLiteral = self.StringLiteral() + return StringLiteral + + def NumericLiteral(self): + NUMBER = self._scan('NUMBER') + return float(NUMBER) + + def StringLiteral(self): + _token_ = self._peek('DQUOTE', 'SQUOTE') + if _token_ == 'DQUOTE': + DQUOTE = self._scan('DQUOTE') + return DQUOTE[1:-1] + else:# == 'SQUOTE' + SQUOTE = self._scan('SQUOTE') + return SQUOTE[1:-1] + + def QName(self): + NCNAME = self._scan('NCNAME') + name = NCNAME + if self._peek("r'\\:'", "r'\\['", "r'\\/'", "r'\\/\\/'", "'\\|'", 'MUL_COMP', 'ADD_COMP', 'REL_COMP', 'EQ_COMP', "r'and'", "r'or'", 'END', "r'\\]'", "r'\\)'", "r'\\,'") == "r'\\:'": + self._scan("r'\\:'") + NCNAME = self._scan('NCNAME') + return (name, NCNAME) + return (None, name) + + +def parse(rule, text): + P = XPath(XPathScanner(text)) + return wrap_error_reporter(P, rule) + +if __name__ == '__main__': + from sys import argv, stdin + if len(argv) >= 2: + if len(argv) >= 3: + f = open(argv[2],'r') + else: + f = stdin + print parse(argv[1], f.read()) + else: print 'Args: []' diff --git a/lib/ulib/support/python/xpath/yappsrt.py b/lib/ulib/support/python/xpath/yappsrt.py new file mode 100644 index 0000000..c8d8933 --- /dev/null +++ b/lib/ulib/support/python/xpath/yappsrt.py @@ -0,0 +1,174 @@ +# Yapps 2.0 Runtime +# +# This module is needed to run generated parsers. + +from string import join, count, find, rfind +import re + +class SyntaxError(Exception): + """When we run into an unexpected token, this is the exception to use""" + def __init__(self, pos=-1, msg="Bad Token"): + Exception.__init__(self) + self.pos = pos + self.msg = msg + def __repr__(self): + if self.pos < 0: return "#" + else: return "SyntaxError[@ char %s: %s]" % (repr(self.pos), self.msg) + +class NoMoreTokens(Exception): + """Another exception object, for when we run out of tokens""" + pass + +class Scanner: + def __init__(self, patterns, ignore, input): + """Patterns is [(terminal,regex)...] + Ignore is [terminal,...]; + Input is a string""" + self.tokens = [] + self.restrictions = [] + self.input = input + self.pos = 0 + self.ignore = ignore + # The stored patterns are a pair (compiled regex,source + # regex). If the patterns variable passed in to the + # constructor is None, we assume that the class already has a + # proper .patterns list constructed + if patterns is not None: + self.patterns = [] + for k, r in patterns: + self.patterns.append( (k, re.compile(r)) ) + + def token(self, i, restrict=0): + """Get the i'th token, and if i is one past the end, then scan + for another token; restrict is a list of tokens that + are allowed, or 0 for any token.""" + if i == len(self.tokens): self.scan(restrict) + if i < len(self.tokens): + # Make sure the restriction is more restricted + if restrict and self.restrictions[i]: + for r in restrict: + if r not in self.restrictions[i]: + raise NotImplementedError("Unimplemented: restriction set changed") + return self.tokens[i] + raise NoMoreTokens() + + def __repr__(self): + """Print the last 10 tokens that have been scanned in""" + output = '' + for t in self.tokens[-10:]: + output = '%s\n (@%s) %s = %s' % (output,t[0],t[2],repr(t[3])) + return output + + def scan(self, restrict): + """Should scan another token and add it to the list, self.tokens, + and add the restriction to self.restrictions""" + # Keep looking for a token, ignoring any in self.ignore + while 1: + # Search the patterns for the longest match, with earlier + # tokens in the list having preference + best_match = -1 + best_pat = '(error)' + for p, regexp in self.patterns: + # First check to see if we're ignoring this token + if restrict and p not in restrict and p not in self.ignore: + continue + m = regexp.match(self.input, self.pos) + if m and len(m.group(0)) > best_match: + # We got a match that's better than the previous one + best_pat = p + best_match = len(m.group(0)) + + # If we didn't find anything, raise an error + if best_pat == '(error)' and best_match < 0: + msg = "Bad Token" + if restrict: + msg = "Trying to find one of "+join(restrict,", ") + raise SyntaxError(self.pos, msg) + + # If we found something that isn't to be ignored, return it + if best_pat not in self.ignore: + # Create a token with this data + token = (self.pos, self.pos+best_match, best_pat, + self.input[self.pos:self.pos+best_match]) + self.pos = self.pos + best_match + # Only add this token if it's not in the list + # (to prevent looping) + if not self.tokens or token != self.tokens[-1]: + self.tokens.append(token) + self.restrictions.append(restrict) + return + else: + # This token should be ignored .. + self.pos = self.pos + best_match + +class Parser: + def __init__(self, scanner): + self._scanner = scanner + self._pos = 0 + + def _peek(self, *types): + """Returns the token type for lookahead; if there are any args + then the list of args is the set of token types to allow""" + tok = self._scanner.token(self._pos, types) + return tok[2] + + def _scan(self, type): + """Returns the matched text, and moves to the next token""" + tok = self._scanner.token(self._pos, [type]) + if tok[2] != type: + raise SyntaxError(tok[0], 'Trying to find '+type) + self._pos = 1+self._pos + return tok[3] + + + +def print_error(input, err, scanner): + """This is a really dumb long function to print error messages nicely.""" + p = err.pos + # Figure out the line number + line = count(input[:p], '\n') + print err.msg+" on line "+repr(line+1)+":" + # Now try printing part of the line + text = input[max(p-80, 0):p+80] + p = p - max(p-80, 0) + + # Strip to the left + i = rfind(text[:p], '\n') + j = rfind(text[:p], '\r') + if i < 0 or (0 <= j < i): i = j + if 0 <= i < p: + p = p - i - 1 + text = text[i+1:] + + # Strip to the right + i = find(text,'\n', p) + j = find(text,'\r', p) + if i < 0 or (0 <= j < i): i = j + if i >= 0: + text = text[:i] + + # Now shorten the text + while len(text) > 70 and p > 60: + # Cut off 10 chars + text = "..." + text[10:] + p = p - 7 + + # Now print the string, along with an indicator + print '> ',text + print '> ',' '*p + '^' + print 'List of nearby tokens:', scanner + +def wrap_error_reporter(parser, rule): + return_value = None + try: + return_value = getattr(parser, rule)() + except SyntaxError, s: + input = parser._scanner.input + try: + print_error(input, s, parser._scanner) + except ImportError: + print 'Syntax Error',s.msg,'on line',1+count(input[:s.pos], '\n') + except NoMoreTokens: + print 'Could not complete parsing; stopped around here:' + print parser._scanner + return return_value diff --git a/lib/ulib/support/xpathtool.py b/lib/ulib/support/xpathtool.py new file mode 100755 index 0000000..ce2039e --- /dev/null +++ b/lib/ulib/support/xpathtool.py @@ -0,0 +1,158 @@ +#!/usr/bin/env python +# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +u"""Ce script permet d'obtenir ou de modifier un élément identifié par une expression XPATH +""" + +import os, sys, re, types, tempfile, codecs, shutil +from os import path +sys.path.insert(0, path.join(path.dirname(__file__), 'python')) +from xml.dom import Node, minidom +import xpath + +def get_text(node, as_xml=False): + if isinstance(node, Node): + if node.nodeType == Node.ELEMENT_NODE: + if as_xml: return node.toxml("utf-8") + else: return xpath.expr.string_value(node).encode("utf-8") + elif node.nodeType == Node.ATTRIBUTE_NODE: + return node.value.encode("utf-8") + elif node.nodeType == Node.TEXT_NODE or node.nodeType == Node.CDATA_SECTION_NODE: + return node.data.encode("utf-8") + elif type(node) is types.UnicodeType: + return node.encode("utf-8") + else: + return str(node) + +def set_text(node, value, doc): + if not isinstance(node, Node): + raise ValueError("L'expression ne désigne pas un noeud XML") + if node.nodeType == Node.ELEMENT_NODE: + firstChild = node.firstChild + if value is not None: + textNode = doc.createTextNode(value) + if firstChild is None: + node.appendChild(textNode) + elif firstChild.nodeType == Node.TEXT_NODE or firstChild.nodeType == Node.CDATA_SECTION_NODE: + node.replaceChild(textNode, firstChild) + else: + node.insertBefore(textNode, firstChild) + elif firstChild is not None: + if firstChild.nodeType == Node.TEXT_NODE or firstChild.nodeType == Node.CDATA_SECTION_NODE: + node.removeChild(firstChild) + elif node.nodeType == Node.ATTRIBUTE_NODE: + if value is not None: node.value = value + else: pass # impossible d'accéder au parent d'un attribut + elif node.nodeType == Node.TEXT_NODE or node.nodeType == Node.CDATA_SECTION_NODE: + if value is not None: node.data = value + else: node.parentNode.removeChild(node) + else: + raise ValueError("Type de noeud non supporté: %s" % node.nodeType) + +RE_PARENT0 = re.compile(r'(^|/)parent\[') +RE_PARENT1 = re.compile(r'(^|/)parent(?=/|$)') +def py_dom_xpath_compat(expr): + expr = RE_PARENT0.sub(r"\1*[local-name()='parent' and ", expr) + expr = RE_PARENT1.sub(r"\1*[local-name()='parent']", expr) + return expr + +def run_xpathtool(): + from optparse import OptionParser + OP = OptionParser(usage=u"\n\t%prog -g XPATH [INPUT [OUTPUT]]\n\t%prog -s XPATH VALUE [INPUT [OUTPUT]]", description=__doc__) + OP.add_option('-f', '--input', dest='inf', + help=u"Spécifier le fichier en entrée") + OP.add_option('-o', '--output', dest='outf', + help=u"Spécifier le fichier en sortie") + OP.add_option('-g', '--get', dest='mode', action='store_const', const='get', + help=u"Forcer l'affichage de la valeur. " + + u"Par défaut, ce mode est sélectionné s'il n'y a aucun argument après XPATH") + OP.add_option('-t', '--exist', dest='mode', action='store_const', const='exist', + help=u"Tester l'existence du chemin spécifié.") + OP.add_option('-s', '--set', dest='mode', action='store_const', const='set', + help=u"Forcer la modification de la valeur. " + + u"Par défaut, ce mode est sélectionné s'il y a un argument VALUE après XPATH") + OP.add_option('-x', '--as-xml', dest='as_xml', action='store_true', + help=u"Retourner le résultat de l'expression en XML") + OP.add_option('--no-compat', dest='compat', action='store_false', default=True, + help=u"Ne pas transfomer certaines expression en un équivalent compatible avec py-dom-xpath. " + + u"Par exemple, par défaut \"/parent\" est transformé en \"/*[local-name='parent']\". " + + u"Cette option désactive ce comportement.") + o, args = OP.parse_args() + inf = o.inf + outf = o.outf + mode = o.mode + as_xml = o.as_xml + compat = o.compat + + count = len(args) + if count == 0: raise ValueError("Vous devez spécifier l'expression XPATH") + expr = args[0] + if compat: expr = py_dom_xpath_compat(expr) + value = None + + args = args[1:] + count = len(args) + if mode is None: + if count == 0: # xpathtool.py XPATH + mode = 'get' + elif inf is None: + if count == 1: # xpathtool.py XPATH INPUT + mode = 'get' + inf = args[0] + elif count >= 2: # xpathtool.py XPATH VALUE INPUT [OUTPUT] + mode = 'set' + value = args[0] + inf = args[1] + if count > 2: outf = args[2] + elif inf is not None: # xpathtool.py XPATH VALUE -f INPUT + mode = 'set' + value = args[0] + elif mode == 'get': + if inf is None: # xpathtool.py -g XPATH [INPUT] + if count > 0: inf = args[0] + elif mode == 'set': + if count > 0: value = args[0] + if inf is None: # xpathtool.py -s XPATH VALUE [INPUT [OUTPUT]] + if count > 1: inf = args[1] + if count > 2: outf = args[2] + + if inf == '-': inf = None + if outf == '-': outf = sys.stdout + if inf is None: + inf = sys.stdin + if outf is None: outf = sys.stdout + elif outf is None: + outf = inf + + doc = minidom.parse(inf) + if mode == 'get' or mode == 'exist': + #print "search %r from %r" % (expr, inf) #DEBUG + nodes = xpath.find(expr, doc) + if mode == 'get': + for node in nodes: + print get_text(node, as_xml) + if nodes: r = 0 + else: r = 1 + sys.exit(r) + elif mode == 'set': + if value is not None and not type(value) is types.UnicodeType: + value = unicode(value, "utf-8") + #print "search %r in %r, replace with %r then write in %r" % (expr, inf, value, outf) #DEBUG + for node in xpath.find(expr, doc): + set_text(node, value, doc) + #print "writing to %r" % outf #DEBUG + if type(outf) in types.StringTypes: + fd, tmpf = tempfile.mkstemp() + try: + os.close(fd) + out = codecs.open(tmpf, "w", "utf-8") + doc.writexml(out, encoding="utf-8") + out.close() + shutil.copyfile(tmpf, outf) + finally: + os.remove(tmpf) + else: + doc.writexml(outf, encoding="utf-8") + +if __name__ == '__main__': + run_xpathtool() diff --git a/lib/ulib/vcs b/lib/ulib/vcs index e9923dd..7fe12be 100644 --- a/lib/ulib/vcs +++ b/lib/ulib/vcs @@ -610,7 +610,7 @@ __VCS_GIT_ADVANCED=( git_is_merged ) function git_check_gitvcs() { - [ "$(_vcs_get_type "$(_vcs_get_dir)")" == git ] + git rev-parse --show-toplevel >&/dev/null } function git_ensure_gitvcs() { git_check_gitvcs || die "Ce n'est pas un dépôt git" diff --git a/lib/ulib/xmlsupport b/lib/ulib/xmlsupport new file mode 100644 index 0000000..1235be7 --- /dev/null +++ b/lib/ulib/xmlsupport @@ -0,0 +1,8 @@ +##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +## Fonction de support pour gérer des fichiers XML +##@cooked nocomments +##@require ulib +uprovide xmlsupport +urequire ulib + +function xpathtool() { "$ULIBDIR/support/xpathtool.py" "$@"; } diff --git a/prel b/prel index ac9a7ff..c5f616a 100755 --- a/prel +++ b/prel @@ -94,7 +94,7 @@ OPTIONS -m, --merge Si la branche actuelle est une branche de release, ou s'il existe une branche de release, la merger dans master, puis dans develop, puis la - supprimer. Puis basculer sur la branche master. + supprimer. A l'issu de cette opération, rester sur la branche develop. S'il n'existe pas de branche de release, proposer de fusionner les modifications de la branche develop dans la branche master, sans préparer de branche de release au préalable. @@ -114,13 +114,18 @@ OPTIONS release par rapport à develop. -d, --diff Afficher les modifications actuellement effectuée dans la branche de - release par rapport à develop, sous forme de diff." + release par rapport à develop, sous forme de diff. + +OPTIONS AVANCEES + --uc, --upgrade-changes + Convertir un fichier CHANGES.txt en CHANGES.md" } function show_summary() { git log --oneline --graph "$@" | grep -vF '|\' | grep -vF '|/' | sed 's/\* //; s/^ /+ /' | - grep -v "Intégration de la branche release-" + grep -v "Intégration de la branche release-" | + grep -v "Branche develop en version .*-SNAPSHOT" } function format_md() { @@ -143,6 +148,21 @@ $1 == "|" { ' } +function __get_newver_from_release() { + local relver filever + is_release_branch "$release" && relver="${release#release-}" + setx filever=pver --sw "" --allow-empty + if [ -n "$relver" -a "$filever" != "$relver" ]; then + newver="$filever" + ewarn "La version de la branche de release est différente de la version dans le fichier" + enote "La version effectivement sélectionnée est $newver" + elif [ -n "$filever" ]; then + newver="$filever" + elif [ -n "$relver" ]; then + newver="$relver" + fi +} + projdir= origin=origin action=auto @@ -208,6 +228,9 @@ fi git_ensure_gitvcs +setx vertype=pver --sw "" --show-source +[[ "$vertype" == pom* ]] && maven_update=1 || maven_update= + push_branches=() push_tags=() push_deferred= @@ -216,14 +239,20 @@ push_deferred= setx branch=git_get_branch +update_opt= if [ "$action" == update ]; then - setx oldver=pver -g "" + if [ -n "$maven_update" ]; then + setx oldver=pver --gw develop: + enote "La version de la branche develop est $oldver" + else + setx oldver=pver --gw master: + fi newver= if [ "$incversion" == auto ]; then if [ ${#pver_opts[*]} -gt 0 ]; then # des options ont été spécifiées, les honorer - setx newver=pver -s "$oldver" "${pver_opts[@]}" + setx newver=pver -s "$oldver" ${maven_update:+--maven-update -R} "${pver_opts[@]}" release="release-$newver" else # sinon, prendre une décision en fonction des branches de release @@ -246,6 +275,7 @@ if [ "$action" == update ]; then fi fi fi + pver_opts=(${maven_update:+--maven-update -R} "${pver_opts[@]}") case "$incversion" in menu) setx majorv=pver -s "$oldver" -ux "${pver_opts[@]}" @@ -257,18 +287,28 @@ if [ "$action" == update ]; then -t "Basculer vers une nouvelle branche de release" \ -m "Veuillez choisir la branche à créer" [ "$release" != master ] && newver="${release#release-}" + if [ "$release" == "release-$majorv" ]; then + update_opt=-x + elif [ "$release" == "release-$minorv" ]; then + update_opt=-z + elif [ "$release" == "release-$patchlevelv" ]; then + update_opt=-p + fi ;; major) setx newver=pver -s "$oldver" -ux "${pver_opts[@]}" release="release-$newver" + update_opt=-x ;; minor) setx newver=pver -s "$oldver" -uz "${pver_opts[@]}" release="release-$newver" + update_opt=-z ;; patchlevel) setx newver=pver -s "$oldver" -up "${pver_opts[@]}" release="release-$newver" + update_opt=-p ;; esac @@ -331,7 +371,7 @@ Vous allez créer la nouvelle branche de release ${COULEUR_VERTE}$release${COULE if [ -z "$newver" ]; then # le cas échéant, tenter de calculer la version en fonction de la release - is_release_branch "$release" && newver="${release#release-}" + __get_newver_from_release fi if [ "$r" -eq 0 -a -n "$write" ]; then @@ -445,7 +485,7 @@ if [ "$action" == merge ]; then if [ -z "$newver" ]; then # le cas échéant, tenter de calculer la version en fonction de la release - is_release_branch "$release" && newver="${release#release-}" + __get_newver_from_release fi if [ -n "$newver" ]; then @@ -469,6 +509,13 @@ ou celle-ci pour pour pousser TOUS les tags: estepn "Intégration ${COULEUR_VERTE}$release${COULEUR_NORMALE} --> ${COULEUR_BLEUE}develop${COULEUR_NORMALE}" git checkout develop git merge "$release" -m "Intégration de la branche $release" --no-ff || die + + if [ -n "$maven_update" ]; then + [ -n "$update_opt" ] || update_opt=-z + pver -u $update_opt -S + git add -A + git commit -m "Branche develop en version $(pver --show)" || die + fi fi # mettre à jour la branche sur laquelle on se trouve diff --git a/pver b/pver index cb10543..33fc3db 100755 --- a/pver +++ b/pver @@ -1,7 +1,7 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 source "$(dirname "$0")/lib/ulib/ulib" || exit 1 -urequire DEFAULTS ptools +urequire DEFAULTS ptools xmlsupport function pver_display_help() { uecho "$scriptname: gérer des numéros de version selon les règles du versionage sémantique v2.0.0 (http://semver.org/) @@ -10,38 +10,55 @@ USAGE $scriptname [options] OPTIONS + -w, --auto-file DIR + Gérer le numéro de version du répertoire spécifié. Si un fichier pom.xml + existe dans ce répertoire, alors c'est l'option '-e DIR/pom.xml' qui est + activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est + l'option '-f DIR/VERSION.txt' qui est activée. Sinon, un nouveau fichier + VERSION.txt est créé dans ce répertoire. C'est l'option par défaut. + --sw, --auto-string DIR + Prendre pour valeur de départ la version du répertoire spécifié. Si un + fichier pom.xml existe dans ce répertoire, alors c'est l'option -E qui + est activé. Si un fichier VERSION.txt existe dans ce répertoire, alors + c'est l'option -F qui est activée. + --gw, --auto-git-string [BRANCH:]DIR + Prendre pour valeur de départ la version du répertoire spécifié dans la + branche BRANCH (qui vaut par défaut master) du dépôt git. Si un fichier + pom.xml existe dans ce répertoire, alors c'est l'option -g qui est + activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est + l'option -G qui est activée. + -e, --pom POMFILE + Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié. + Le fichier DOIT exister. Implique --maven-update + -E, --pom-string POMFILE + Prendre pour valeur de départ la version contenue dans le fichier + pom.xml spécifié. Le fichier DOIT exister. Implique --maven-update -f, --file VERSIONFILE Gérer le numéro de version se trouvant dans le fichier spécifié. Le - fichier est créé si nécessaire. C'est l'option par défaut si un fichier - nommé VERSION.txt se trouve dans le répertoire courant. - -e, --maven POMFILE - Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié. - Le fichier DOIT exister. C'est l'option par défaut si un fichier nommé - pom.xml se trouve dans le répertoire courant. + fichier est créé si nécessaire. -F, --file-string VERSIONFILE - Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui - vaut par défaut VERSION.txt) - -g, --git-string [branch:]VERSIONFILE + Prendre pour valeur de départ le contenu du fichier VERSIONFILE. + -g, --git-file-string [BRANCH:]VERSIONFILE Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui vaut par défaut VERSION.txt) dans la branche BRANCH (qui vaut par défaut - master) du dépôt git situé dans le répertoire courant. + master) du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt + git. + -G, --git-pom-string [BRANCH:]POMFILE + Prendre pour valeur de départ la version du fichier POMFILE (qui vaut + par défaut pom.xml) dans la branche BRANCH (qui vaut par défaut master) + du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt git. + --git-prel-string + Prendre pour valeur de départ le numéro de version correspondant à la + branche de release courante. Retourner 1 si la branche courante n'est + pas une branche de release, 2 si on n'est pas situé dans un dépôt git. -s, --string VERSION Prendre pour valeur de départ le numéro de version spécifié --show Afficher le numéro de version. C'est l'action par défaut - --allow-empty - Supporter que la version puisse ne pas être spécifiée ni trouvée. Dans - ce cas, ne pas assumer que la version effective est 0.0.0 - Avec --show et --update, ne rien afficher si la version est vide. --check Vérifier que le numéro de version est conforme aux règles du versionage sémantique - --convert - --no-convert - Activer (resp. désactiver) la conversion automatique. Par défaut, si la - version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est - convertie automatiquement au format sémantique x.z.p+rYYYYMMDD --eq VERSION --ne VERSION --lt VERSION @@ -54,6 +71,10 @@ OPTIONS --le, --gt, et --ge ignorent l'identifiant de build (comme le demande la règle du versionage sémantique). Les opérateurs --same et --diff comparent aussi les identifiants de build. + -u, --update + Mettre à jour le numéro de version. + +Les options suivantes impliquent --update: -v, --set-version VERSION Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Cette option ne devrait pas être utilisée en temps normal parce que cela @@ -62,18 +83,20 @@ OPTIONS Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Le numéro de version est obtenu à partir du nom de la branche git courante, qui doit être de la forme release-VERSION - -u, --update - Mettre à jour le numéro de version. - --menu Afficher un menu permettant de choisir le composant de la version à - incrémenter + incrémenter. -x, --major - Augmenter le numéro de version majeure + Augmenter le numéro de version majeure. -z, --minor Augmenter le numéro de version mineure. C'est la valeur par défaut. -p, --patchlevel - Augmenter le numéro de patch + Augmenter le numéro de patch. + -k, --keep + Ne pas augmenter le numéro de version. Cette option est surtout utile + pour *convertir* un numéro de version existant et mettre à jour le + fichier correspondant. Elle est assumée si aucune option -[xzp] n'est + spécifiée et qu'une des options -[labrSRmM] est utilisée. -l, --prelease ID Spécifier un identifiant de pré-release, à ajouter au numéro de version. @@ -85,17 +108,95 @@ OPTIONS de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3, rc1 devient rc2 XXX ces fonctions ne sont pas encore implémentées + -S, --snapshot + Ajouter l'identifiant SNAPSHOT, utilisé par Maven -R, --final, --release - Supprimer l'identifiant de prérelease + Supprimer l'identifiant de prérelease, utilisé par Maven -m, --metadata ID Spécifier un identifiant de build, à ajouter au numéro de version. -M, --vcs-metadata - Spécifier l'identifiant à partir de la révision actuelle dans le + Calculer l'identifiant de build à partir de la révision actuelle dans le gestionnaire de version. Note: pour le moment, seul git est supporté. --add-metadata ID Ajouter l'identifiant spécifié à la valeur actuelle, au lieu de la - remplacer. Séparer l'identifiant de la valeur précédente avec un '.'" + remplacer. Séparer l'identifiant de la valeur précédente avec un '.' + +OPTIONS AVANCEES + --show-source + Afficher le type de source qui sera traité, i.e. pom, file, pom-string, + file-string, git-pom-string, git-file-string + --vpath VPATH + Pour les options -e et -E, spécifier le chemin XPATH du tag qui contient + le numéro de version. + --map MAPFILE + Cette option permet de spécifier un fichier de règles qui indique les + fichiers pom.xml et VERSION.txt qui doivent être mis à jour dans un + projet multi-modules pour lequel les versions doivent être mises à jour + en même temps. + Par défaut, si un fichier nommé .pver-map existe dans le répertoire de + {POM,VERSION}FILE, cette option est automatiquement activée. Ainsi, on + n'aura besoin d'utiliser cette option que si l'on désire charger un + fichier alternatif ou ignorer le fichier par défaut. + Si une valeur vide est fournie, seul le fichier {POM,VERSION}FILE est + traité. Sinon, {POM,VERSION}FILE est utilisé uniquement pour chercher le + fichier .pver-map et seuls les fichiers mentionnés dans MAPFILE sont + traités. + Le fichier MAPFILE est constitué d'un ensemble de lignes de la forme + FILESPEC:VPATH + FILESPEC est requis et prend la forme d'une spécification de chemin + relatif au répertoire de MAPFILE et identifiant un ensemble de fichiers + de version. Si FILESPEC contient des wildcards, alors les fichiers + identifiés par ce chemin sont ignorés s'ils ont déjà été traités par une + règle précédente. Si FILESPEC ne contient pas de wildcards, alors le + fichier est systématiquement traité. + VPATH désigne le chemin XPATH vers le numéro de version qu'il faut + mettre à jour dans les fichiers pom.xml. Si VPATH vaut -, alors le + fichier pom.xml correspondant n'est pas modifié (il est ignoré). Si + VPATH est vide alors le chemin par défaut est utilisé pour ce + fichier. Pour les fichiers VERSION.txt, VPATH doit être vide + Le fichier de version correspondant au premier fichier de la première + ligne de MAPFILE contient la version de référence, qui est dupliquée + dans tous les autres fichiers. + --allow-empty + Supporter que la version puisse ne pas être spécifiée ni trouvée. Sans + cette option, on assume que la version effective est 0.0.0 si elle n'est + pas spécifiée ni trouvée. + Avec --show et --update, ne rien afficher si la version est vide. + --convert + --no-convert + Activer (resp. désactiver) la conversion automatique. Par défaut, si la + version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est + convertie automatiquement au format sémantique x.z.p+rYYYYMMDD + -t, --maven-update + Mettre à jour le numéro de version selons les règles de Maven. Cette + option est automatiquement activée si -e est sélectionné. Elle n'est + prise en compte qu'avec l'option -u + Si les options -R et -S ne sont pas spécifiée, alors une release est + transformée en snapshot et une snapshot en release. Avec -R, une + snapshot est transformée en release, et une release est traitée / + incrémentée normalement. Avec -S une release est transformée en + snapshot, et un snapshot est traité / incrémentée normalement. + Si l'une des options -x, -z, -p est utilisée, alors la partie spécifiée + est incrémentée selon les règles suivantes. Dans les exemples suivants, + A est un nombre quelconque, B = A + 1, et x, y et z sont des nombres + quelconques. + Avec l'option -x: + A.0.0-SNAPSHOT --> A.0.0 + A.x.y-SNAPSHOT --> B.0.0 + x.A.y --> x.B.0-SNAPSHOT + Avec l'option -z: + x.A.0-SNAPSHOT --> x.A.0 + x.A.y-SNAPSHOT --> x.B.0 + x.A.y --> x.B.0-SNAPSHOT + Avec l'option -p: + x.y.z-SNAPSHOT --> x.y.z + x.y.A --> x.y.B-SNAPSHOT + Si aucune des options -x, -z, -p n'est utilisée, alors les parties sont + incrémentées selon les règles suivantes: + x.y.z-SNAPSHOT --> x.y.z (comme -p) + x.A.0 --> x.B.0-SNAPSHOT (comme -z) + x.y.A --> x.y.B-SNAPSHOT (comme -p)" } pver "$@" From e37cc8fad38c618ce4e12636f9fb11169572e70f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 25 Nov 2016 11:01:25 +0400 Subject: [PATCH 16/39] =?UTF-8?q?pver=20et=20prel=20utilisent=20-p=20par?= =?UTF-8?q?=20d=C3=A9faut=20pour=20le=20support=20maven?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/ptools | 24 ++++++++++++++---------- prel | 10 ++++++++-- pver | 13 +++++-------- 3 files changed, 27 insertions(+), 20 deletions(-) diff --git a/lib/ulib/ptools b/lib/ulib/ptools index 0206f92..b32a9fb 100644 --- a/lib/ulib/ptools +++ b/lib/ulib/ptools @@ -659,11 +659,7 @@ BEGIN { if [ -n "$setrelease" ]; then incversion=patchlevel elif [ -n "$setsnapshot" ]; then - if [ "$patchlevel" -eq 0 ]; then - incversion=minor - else - incversion=patchlevel - fi + incversion=patchlevel fi fi @@ -689,7 +685,11 @@ BEGIN { "$versionp : correction de bugs (-p)" "$versionk : ne pas incrémenter la version" ) - nextv="${nextvs[1]}" + if [ -z "$maven_update" ]; then + nextv="${nextvs[1]}" + else + nextv="${nextvs[2]}" + fi simple_menu nextv nextvs \ -t "Incrémenter le numéro de version" \ -m "Veuillez choisir la prochaine version" @@ -716,7 +716,7 @@ BEGIN { esac # afficher le résultat final - local -a depfiles + local -a depfiles regfiles psemver_setvar version if [ -n "$file" ]; then if [ "$source" == file ]; then @@ -742,7 +742,7 @@ BEGIN { edebug "Maj version de $file au chemin XPATH ${filevpath:-par défaut}" __pver_pom_set_version "$file" "$version" "$filevpath" case $? in - 0|1) :;; + 0|1) array_addu regfiles "$file";; 2) array_addu depfiles "$file";; *) return 1;; esac @@ -761,12 +761,16 @@ BEGIN { if isatty; then estepn "La nouvelle version est $version" if [ ${#depfiles[*]} -gt 0 ]; then - local msg="Les fichiers suivants ont été modifiés, et leur version doit être mise à jour:" + local msg for file in "${depfiles[@]}"; do + array_contains regfiles "$file" && continue + if [ -z "$msg" ]; then + msg="Les fichiers suivants ont été modifiés, et leur version doit être mise à jour:" + fi msg="$msg $(qvals pver -uzw "$file" -R --menu)" done - eimportant "$msg" + [ -n "$msg" ] && eimportant "$msg" fi else echo "$version" diff --git a/prel b/prel index c5f616a..f634ab3 100755 --- a/prel +++ b/prel @@ -241,6 +241,8 @@ setx branch=git_get_branch update_opt= if [ "$action" == update ]; then + git_ensure_cleancheckout + if [ -n "$maven_update" ]; then setx oldver=pver --gw develop: enote "La version de la branche develop est $oldver" @@ -281,7 +283,11 @@ if [ "$action" == update ]; then setx majorv=pver -s "$oldver" -ux "${pver_opts[@]}" setx minorv=pver -s "$oldver" -uz "${pver_opts[@]}" setx patchlevelv=pver -s "$oldver" -up "${pver_opts[@]}" - release="release-$minorv" + if [ -z "$maven_update" ]; then + release="release-$minorv" + else + release="release-$patchlevelv" + fi branches=("release-$majorv" "release-$minorv" "release-$patchlevelv" master) simple_menu release branches \ -t "Basculer vers une nouvelle branche de release" \ @@ -511,7 +517,7 @@ ou celle-ci pour pour pousser TOUS les tags: git merge "$release" -m "Intégration de la branche $release" --no-ff || die if [ -n "$maven_update" ]; then - [ -n "$update_opt" ] || update_opt=-z + [ -n "$update_opt" ] || update_opt=-p pver -u $update_opt -S git add -A git commit -m "Branche develop en version $(pver --show)" || die diff --git a/pver b/pver index 33fc3db..ededd1c 100755 --- a/pver +++ b/pver @@ -89,7 +89,9 @@ Les options suivantes impliquent --update: -x, --major Augmenter le numéro de version majeure. -z, --minor - Augmenter le numéro de version mineure. C'est la valeur par défaut. + Augmenter le numéro de version mineure. C'est la valeur par défaut, sauf + avec --maven-update où c'est l'option -p qui est sélectionnée par + défaut. -p, --patchlevel Augmenter le numéro de patch. -k, --keep @@ -189,14 +191,9 @@ OPTIONS AVANCEES x.A.0-SNAPSHOT --> x.A.0 x.A.y-SNAPSHOT --> x.B.0 x.A.y --> x.B.0-SNAPSHOT - Avec l'option -p: + Avec l'option -p, qui est celle sélectionnée par défaut: x.y.z-SNAPSHOT --> x.y.z - x.y.A --> x.y.B-SNAPSHOT - Si aucune des options -x, -z, -p n'est utilisée, alors les parties sont - incrémentées selon les règles suivantes: - x.y.z-SNAPSHOT --> x.y.z (comme -p) - x.A.0 --> x.B.0-SNAPSHOT (comme -z) - x.y.A --> x.y.B-SNAPSHOT (comme -p)" + x.y.A --> x.y.B-SNAPSHOT" } pver "$@" From 9983e499a4b5a903257ce9075d01f68b6acbe0e8 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 25 Nov 2016 13:58:29 +0400 Subject: [PATCH 17/39] pver: support des aliases D et P pour .pver-map --- lib/ulib/ptools | 4 ++++ pver | 13 +++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/lib/ulib/ptools b/lib/ulib/ptools index b32a9fb..4f17183 100644 --- a/lib/ulib/ptools +++ b/lib/ulib/ptools @@ -180,6 +180,10 @@ function __pver_pom_set_version() { local pom="${1:-pom.xml}" local version="${2:-1.0.0-SNAPSHOT}" local vpath="$3" + case "$vpath" in + D) vpath=/project/version;; + P) vpath=/project/parent/version;; + esac [ -n "$vpath" ] || vpath="$(__pver_pom_get_vpath "$pom")" xpathtool -f "$pom" -s "$vpath" "$version" || return 3 case "$vpath" in diff --git a/pver b/pver index ededd1c..8173ff3 100755 --- a/pver +++ b/pver @@ -153,10 +153,15 @@ OPTIONS AVANCEES règle précédente. Si FILESPEC ne contient pas de wildcards, alors le fichier est systématiquement traité. VPATH désigne le chemin XPATH vers le numéro de version qu'il faut - mettre à jour dans les fichiers pom.xml. Si VPATH vaut -, alors le - fichier pom.xml correspondant n'est pas modifié (il est ignoré). Si - VPATH est vide alors le chemin par défaut est utilisé pour ce - fichier. Pour les fichiers VERSION.txt, VPATH doit être vide + mettre à jour dans les fichiers pom.xml. Certaines valeurs spéciales + pour VPATH sont supportées: + (vide) le chemin par défaut est utilisé pour ce fichier, c'est à + dire /project/version ou /project/parent/version en fonction + du contenu du fichier + - le fichier pom.xml n'est pas modifié (il est ignoré) + D alias pour le chemin XPATH /project/version + P alias pour le chemin XPATH /project/parent/version + Pour les fichiers VERSION.txt, VPATH doit être vide Le fichier de version correspondant au premier fichier de la première ligne de MAPFILE contient la version de référence, qui est dupliquée dans tous les autres fichiers. From b278b175e394467299b1a4acb74c3d80e174ef28 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 25 Nov 2016 19:08:50 +0400 Subject: [PATCH 18/39] =?UTF-8?q?prel:=20mise=20=C3=A0=20jour=20de=20la=20?= =?UTF-8?q?commande=20par=20d=C3=A9faut=20pour=20les=20d=C3=A9pendances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/ptools | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ulib/ptools b/lib/ulib/ptools index 4f17183..4f9d74b 100644 --- a/lib/ulib/ptools +++ b/lib/ulib/ptools @@ -772,7 +772,7 @@ BEGIN { msg="Les fichiers suivants ont été modifiés, et leur version doit être mise à jour:" fi msg="$msg - $(qvals pver -uzw "$file" -R --menu)" + $(qvals pver -uw "$file" -R -p)" done [ -n "$msg" ] && eimportant "$msg" fi From 96b1866049be1243399187bef6c73da8647001fe Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 28 Nov 2016 08:35:29 +0400 Subject: [PATCH 19/39] prel: support du fichier .prel-noauto pour interdire les releases automatiques --- prel | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/prel b/prel index f634ab3..f2ed446 100755 --- a/prel +++ b/prel @@ -228,6 +228,10 @@ fi git_ensure_gitvcs +if [ -f .prel-noauto -a -n "$update" -a -n "$merge" ]; then + die "Vous ne pouvez pas faire de release automatique sur ce dépôt. Vous devez utiliser -u et -m séparement." +fi + setx vertype=pver --sw "" --show-source [[ "$vertype" == pom* ]] && maven_update=1 || maven_update= From 2425cbe0f8ee1515553e35b97e551db1f8663880 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 28 Nov 2016 09:16:41 +0400 Subject: [PATCH 20/39] maj de l'arborescence de la doc --- doc/DefaultTiddlers.twp | 25 - doc/EnsureVM.twp | 16 - doc/Main.twp | 46 - doc/MainMenu.twp | 25 - doc/SKvm.twp | 40 - doc/SVirtualBox.twp | 29 - doc/SiteSubtitle.twp | 25 - doc/SiteTitle.twp | 25 - doc/SiteUrl.twp | 25 - doc/_root.twp | 12 - doc/apacheconfig.twp | 69 - doc/authftp.twp | 39 - doc/caturl.twp | 20 - doc/compileAndGo.twp | 11 - doc/cssh.twp | 18 - doc/doinplace.twp | 35 - doc/dumpclients.twp | 30 - doc/em.twp | 95 -- doc/fconv.twp | 64 - doc/fnconv.twp | 58 - doc/geturl.twp | 14 - doc/index.md | 18 +- doc/mkRewriteRules.twp | 86 -- doc/mkiso.twp | 25 - doc/mkurl.twp | 20 - doc/mkusfx.twp | 35 - doc/mocifs.twp | 30 - doc/modav.twp | 44 - doc/moiso.twp | 24 - doc/mossh.twp | 33 - doc/mysqlcsv.twp | 69 - doc/mysqlloadcsv.twp | 112 -- doc/noerr.twp | 11 - doc/noerror.twp | 11 - doc/noout.twp | 11 - doc/nutools.twp | 38 - doc/openurl.twp | 14 - doc/pdev.twp | 90 -- doc/prel.twp | 105 -- doc/pver.md | 98 -- doc/pver.twp | 102 -- doc/pz.twp | 24 - doc/reptyr.cgo.twp | 20 - doc/rmtildes.twp | 16 - doc/rruns.twp | 78 - doc/ruinst.twp | 52 - doc/rumount.twp | 18 - doc/runs.twp | 62 - doc/runsconfig.twp | 37 - doc/runsmod.twp | 70 - doc/rwoinst.twp | 38 - doc/sqlcsv.twp | 98 -- doc/{ => tools}/EnsureVM.md | 0 doc/{ => tools}/SKvm.md | 0 doc/{ => tools}/SVirtualBox.md | 0 doc/{ => tools}/_root.md | 0 doc/{ => tools}/apacheconfig.md | 0 doc/{ => tools}/authftp.md | 0 doc/{ => tools}/caturl.md | 0 doc/tools/chrono.py.md | 28 + doc/{ => tools}/compileAndGo.md | 0 doc/{ => tools}/cssh.md | 0 doc/{ => tools}/doinplace.md | 0 doc/{ => tools}/dumpclients.md | 0 doc/{ => tools}/em.md | 15 +- doc/{ => tools}/fconv.md | 0 doc/{ => tools}/fnconv.md | 0 doc/tools/fndate.md | 69 + doc/tools/foreach.md | 67 + doc/{ => tools}/geturl.md | 0 doc/{ => tools}/mkRewriteRules.md | 0 doc/{ => tools}/mkiso.md | 0 doc/{ => tools}/mkurl.md | 0 doc/{ => tools}/mkusfx.md | 0 doc/{ => tools}/mocifs.md | 0 doc/{ => tools}/modav.md | 0 doc/{ => tools}/moiso.md | 0 doc/{ => tools}/mossh.md | 0 doc/{ => tools}/mysqlcsv.md | 0 doc/{ => tools}/mysqlloadcsv.md | 0 doc/{ => tools}/noerr.md | 0 doc/{ => tools}/noerror.md | 0 doc/{ => tools}/noout.md | 0 doc/{ => tools}/nutools.md | 0 doc/{ => tools}/openurl.md | 0 doc/{ => tools}/pdev.md | 0 doc/{ => tools}/prel.md | 16 +- doc/tools/pver.md | 201 +++ doc/{ => tools}/pz.md | 0 doc/{ => tools}/reptyr.cgo.md | 0 doc/{ => tools}/rmtildes.md | 0 doc/{ => tools}/rruns.md | 0 doc/{ => tools}/ruinst.md | 0 doc/{ => tools}/rumount.md | 0 doc/{ => tools}/runs.md | 2 +- doc/{ => tools}/runsconfig.md | 0 doc/{ => tools}/runsmod.md | 8 +- doc/{ => tools}/rwoinst.md | 0 doc/{ => tools}/sqlcsv.md | 0 doc/{ => tools}/twsync.md | 0 doc/{ => tools}/uawk.md | 0 doc/{ => tools}/ubackup.md | 0 doc/{ => tools}/ucalc.md | 0 doc/{ => tools}/uconf.md | 0 doc/{ => tools}/ucrontab.md | 0 doc/{ => tools}/udaemon.cgo.md | 0 doc/{ => tools}/udir.md | 0 doc/{ => tools}/udist.md | 0 doc/{ => tools}/uenv.md | 0 doc/{ => tools}/ufixmod.md | 0 doc/{ => tools}/ugenpass.md | 0 doc/{ => tools}/uinc.md | 0 doc/{ => tools}/uinc.sh.md | 0 doc/{ => tools}/uinst.md | 0 doc/{ => tools}/uinst.sh.md | 0 doc/{ => tools}/ujava.md | 0 doc/{ => tools}/uldap.md | 0 doc/tools/ulib.md | 63 + doc/{ => tools}/ulink.md | 0 doc/{ => tools}/umatch.md | 0 doc/{ => tools}/umirror.md | 0 doc/tools/umountr.md | 18 + doc/{ => tools}/upassword.md | 0 doc/{ => tools}/update-nutools.md | 0 doc/{ => tools}/uprefix.md | 0 doc/{ => tools}/uproject.md | 5 +- doc/{ => tools}/uscrontab.md | 0 doc/{ => tools}/ussh.md | 0 doc/{ => tools}/usysinfos.md | 0 doc/{ => tools}/utempl.md | 0 doc/{ => tools}/utrigger.md | 0 doc/{ => tools}/uwatch.md | 0 doc/{ => tools}/vzusage.md | 0 doc/{ => tools}/woArchive.md | 0 doc/{ => tools}/woSwitch.md | 0 doc/{ => tools}/woctl.md | 0 doc/{ => tools}/woinst.md | 0 doc/{ => tools}/wosign.md | 0 doc/twsync.twp | 39 - doc/uawk.twp | 17 - doc/ubackup.twp | 24 - doc/ucalc.twp | 35 - doc/uconf.twp | 56 - doc/ucrontab.twp | 90 -- doc/udaemon.cgo.twp | 18 - doc/udir.twp | 43 - doc/udist.twp | 107 -- doc/uenv.twp | 41 - doc/ufixmod.twp | 21 - doc/ugenpass.twp | 25 - doc/uinc.sh.twp | 47 - doc/uinc.twp | 47 - doc/uinst.sh.twp | 61 - doc/uinst.twp | 61 - doc/ujava.twp | 30 - doc/uldap.twp | 227 --- doc/ulib.md | 131 +- doc/ulib.twp | 74 - doc/{ulib_DEFAULTS.md => ulib/DEFAULTS.md} | 0 .../PREFIXES-DEFAULTS.md} | 0 doc/{ulib_apache.md => ulib/apache.md} | 2 + .../apache.tools.md} | 5 + doc/{ulib_auto.md => ulib/auto.md} | 0 doc/{ulib_awk.md => ulib/awk.md} | 1 + doc/{ulib_base.args.md => ulib/base.args.md} | 0 .../base.array.md} | 0 doc/{ulib_base.bool.md => ulib/base.bool.md} | 0 .../base.compat.md} | 0 doc/{ulib_base.core.md => ulib/base.core.md} | 49 + doc/{ulib_base.init.md => ulib/base.init.md} | 0 doc/{ulib_base.md => ulib/base.md} | 19 + doc/{ulib_base.num.md => ulib/base.num.md} | 0 .../base.quote.md} | 5 + .../base.split.md} | 0 .../base.string.md} | 28 + .../base.tools.md} | 0 doc/{ulib_base.ulib.md => ulib/base.ulib.md} | 0 doc/{ulib_bash.md => ulib/bash.md} | 0 doc/{ulib_cgi.md => ulib/cgi.md} | 0 .../cgisupport.md} | 0 doc/{ulib_compat.md => ulib/compat.md} | 0 doc/{ulib_conf.md => ulib/conf.md} | 0 doc/{ulib_crontab.md => ulib/crontab.md} | 0 doc/{ulib_debian.md => ulib/debian.md} | 0 doc/{ulib_install.md => ulib/install.md} | 0 doc/{ulib_ipcalc.md => ulib/ipcalc.md} | 0 doc/{ulib_java.md => ulib/java.md} | 0 .../javaproperties.md} | 0 doc/{ulib_json.md => ulib/json.md} | 0 doc/{ulib_ldap.md => ulib/ldap.md} | 0 doc/{ulib_ldif.md => ulib/ldif.md} | 4 + doc/{ulib_legacy.md => ulib/legacy.md} | 0 doc/{ulib_macosx.md => ulib/macosx.md} | 0 doc/{ulib_mkcrypt.md => ulib/mkcrypt.md} | 0 doc/{ulib_modeline.md => ulib/modeline.md} | 0 .../network-manager-service.md} | 0 doc/{ulib_password.md => ulib/password.md} | 0 doc/{ulib_prefixes.md => ulib/prefixes.md} | 0 doc/{ulib_pretty.md => ulib/pretty.md} | 0 doc/{ulib_ptools.md => ulib/ptools.md} | 0 doc/{ulib_redhat.md => ulib/redhat.md} | 0 doc/{ulib_runs.md => ulib/runs.md} | 2 + .../runsmod.defaults.md} | 0 doc/{ulib_runsmod.md => ulib/runsmod.md} | 2 + doc/ulib/semver.md | 72 + doc/{ulib_service.md => ulib/service.md} | 0 doc/{ulib_sysinfos.md => ulib/sysinfos.md} | 0 doc/{ulib_template.md => ulib/template.md} | 0 .../tiddlywiki.md} | 0 doc/{ulib_udir.md => ulib/udir.md} | 0 doc/{ulib_uenv.md => ulib/uenv.md} | 0 .../uenv_update.md} | 0 doc/{ulib_uinc.md => ulib/uinc.md} | 0 doc/{ulib_uinst.md => ulib/uinst.md} | 0 doc/{ulib_ulib.md => ulib/ulib.md} | 0 doc/{ulib_ulibsh.md => ulib/ulibsh.md} | 0 doc/{ulib_vcs.md => ulib/vcs.md} | 1 + doc/{ulib_virsh.md => ulib/virsh.md} | 0 .../webobjects.md} | 0 doc/{ulib_woinst.md => ulib/woinst.md} | 0 .../wondermonitor.md} | 0 doc/{ulib_wosign.md => ulib/wosign.md} | 0 doc/{ulib_wotaskd.md => ulib/wotaskd.md} | 0 doc/ulib/xmlsupport.md | 5 + doc/ulib_DEFAULTS.twp | 8 - doc/ulib_PREFIXES-DEFAULTS.twp | 10 - doc/ulib_apache.tools.twp | 17 - doc/ulib_apache.twp | 34 - doc/ulib_auto.twp | 8 - doc/ulib_awk.twp | 47 - doc/ulib_base.args.twp | 64 - doc/ulib_base.array.twp | 8 - doc/ulib_base.bool.twp | 38 - doc/ulib_base.compat.twp | 22 - doc/ulib_base.core.twp | 150 -- doc/ulib_base.init.twp | 8 - doc/ulib_base.num.twp | 21 - doc/ulib_base.quote.twp | 25 - doc/ulib_base.split.twp | 79 - doc/ulib_base.string.twp | 102 -- doc/ulib_base.tools.twp | 28 - doc/ulib_base.twp | 1276 ----------------- doc/ulib_base.ulib.twp | 8 - doc/ulib_bash.twp | 8 - doc/ulib_cgi.twp | 54 - doc/ulib_cgisupport.twp | 11 - doc/ulib_compat.twp | 8 - doc/ulib_conf.twp | 196 --- doc/ulib_crontab.twp | 21 - doc/ulib_debian.twp | 179 --- doc/ulib_install.twp | 46 - doc/ulib_ipcalc.twp | 72 - doc/ulib_java.twp | 41 - doc/ulib_javaproperties.twp | 35 - doc/ulib_json.twp | 14 - doc/ulib_ldap.twp | 80 -- doc/ulib_ldif.twp | 68 - doc/ulib_legacy.twp | 50 - doc/ulib_macosx.twp | 24 - doc/ulib_mkcrypt.twp | 9 - doc/ulib_modeline.twp | 10 - doc/ulib_network-manager-service.twp | 18 - doc/ulib_password.twp | 24 - doc/ulib_pkg.twp | 20 - doc/ulib_prefixes.twp | 14 - doc/ulib_pretty.twp | 21 - doc/ulib_ptools.twp | 18 - doc/ulib_redhat.twp | 44 - doc/ulib_runs.twp | 242 ---- doc/ulib_runsmod.defaults.twp | 8 - doc/ulib_runsmod.twp | 34 - doc/ulib_semver.md | 33 - doc/ulib_semver.twp | 37 - doc/ulib_service.twp | 33 - doc/ulib_sysinfos.twp | 66 - doc/ulib_template.twp | 101 -- doc/ulib_tiddlywiki.twp | 115 -- doc/ulib_udir.twp | 61 - doc/ulib_uenv.twp | 8 - doc/ulib_uenv_update.twp | 29 - doc/ulib_uinc.twp | 9 - doc/ulib_uinst.twp | 19 - doc/ulib_ulib.twp | 41 - doc/ulib_ulibsh.twp | 17 - doc/ulib_vcs.twp | 161 --- doc/ulib_virsh.twp | 22 - doc/ulib_webobjects.twp | 180 --- doc/ulib_woinst.twp | 12 - doc/ulib_wondermonitor.twp | 219 --- doc/ulib_wosign.twp | 19 - doc/ulib_wotaskd.twp | 12 - doc/ulibshell.twp | 23 - doc/ulibsync.twp | 18 - doc/ulink.twp | 24 - doc/umatch.twp | 55 - doc/umirror.twp | 18 - doc/upassword.twp | 70 - doc/update-md | 9 +- doc/update-nutools.twp | 11 - doc/update-twp | 156 -- doc/uprefix.twp | 22 - doc/uproject.twp | 125 -- doc/uscrontab.twp | 292 ---- doc/ussh.twp | 80 -- doc/usysinfos.twp | 19 - doc/utempl.twp | 28 - doc/utrigger.twp | 53 - doc/uwatch.twp | 35 - doc/vzusage.twp | 25 - doc/woArchive.twp | 21 - doc/woSwitch.twp | 33 - doc/woctl.twp | 60 - doc/woinst.twp | 29 - doc/wosign.twp | 24 - 314 files changed, 752 insertions(+), 9085 deletions(-) delete mode 100644 doc/DefaultTiddlers.twp delete mode 100644 doc/EnsureVM.twp delete mode 100644 doc/Main.twp delete mode 100644 doc/MainMenu.twp delete mode 100644 doc/SKvm.twp delete mode 100644 doc/SVirtualBox.twp delete mode 100644 doc/SiteSubtitle.twp delete mode 100644 doc/SiteTitle.twp delete mode 100644 doc/SiteUrl.twp delete mode 100644 doc/_root.twp delete mode 100644 doc/apacheconfig.twp delete mode 100644 doc/authftp.twp delete mode 100644 doc/caturl.twp delete mode 100644 doc/compileAndGo.twp delete mode 100644 doc/cssh.twp delete mode 100644 doc/doinplace.twp delete mode 100644 doc/dumpclients.twp delete mode 100644 doc/em.twp delete mode 100644 doc/fconv.twp delete mode 100644 doc/fnconv.twp delete mode 100644 doc/geturl.twp delete mode 100644 doc/mkRewriteRules.twp delete mode 100644 doc/mkiso.twp delete mode 100644 doc/mkurl.twp delete mode 100644 doc/mkusfx.twp delete mode 100644 doc/mocifs.twp delete mode 100644 doc/modav.twp delete mode 100644 doc/moiso.twp delete mode 100644 doc/mossh.twp delete mode 100644 doc/mysqlcsv.twp delete mode 100644 doc/mysqlloadcsv.twp delete mode 100644 doc/noerr.twp delete mode 100644 doc/noerror.twp delete mode 100644 doc/noout.twp delete mode 100644 doc/nutools.twp delete mode 100644 doc/openurl.twp delete mode 100644 doc/pdev.twp delete mode 100644 doc/prel.twp delete mode 100644 doc/pver.md delete mode 100644 doc/pver.twp delete mode 100644 doc/pz.twp delete mode 100644 doc/reptyr.cgo.twp delete mode 100644 doc/rmtildes.twp delete mode 100644 doc/rruns.twp delete mode 100644 doc/ruinst.twp delete mode 100644 doc/rumount.twp delete mode 100644 doc/runs.twp delete mode 100644 doc/runsconfig.twp delete mode 100644 doc/runsmod.twp delete mode 100644 doc/rwoinst.twp delete mode 100644 doc/sqlcsv.twp rename doc/{ => tools}/EnsureVM.md (100%) rename doc/{ => tools}/SKvm.md (100%) rename doc/{ => tools}/SVirtualBox.md (100%) rename doc/{ => tools}/_root.md (100%) rename doc/{ => tools}/apacheconfig.md (100%) rename doc/{ => tools}/authftp.md (100%) rename doc/{ => tools}/caturl.md (100%) create mode 100644 doc/tools/chrono.py.md rename doc/{ => tools}/compileAndGo.md (100%) rename doc/{ => tools}/cssh.md (100%) rename doc/{ => tools}/doinplace.md (100%) rename doc/{ => tools}/dumpclients.md (100%) rename doc/{ => tools}/em.md (87%) rename doc/{ => tools}/fconv.md (100%) rename doc/{ => tools}/fnconv.md (100%) create mode 100644 doc/tools/fndate.md create mode 100644 doc/tools/foreach.md rename doc/{ => tools}/geturl.md (100%) rename doc/{ => tools}/mkRewriteRules.md (100%) rename doc/{ => tools}/mkiso.md (100%) rename doc/{ => tools}/mkurl.md (100%) rename doc/{ => tools}/mkusfx.md (100%) rename doc/{ => tools}/mocifs.md (100%) rename doc/{ => tools}/modav.md (100%) rename doc/{ => tools}/moiso.md (100%) rename doc/{ => tools}/mossh.md (100%) rename doc/{ => tools}/mysqlcsv.md (100%) rename doc/{ => tools}/mysqlloadcsv.md (100%) rename doc/{ => tools}/noerr.md (100%) rename doc/{ => tools}/noerror.md (100%) rename doc/{ => tools}/noout.md (100%) rename doc/{ => tools}/nutools.md (100%) rename doc/{ => tools}/openurl.md (100%) rename doc/{ => tools}/pdev.md (100%) rename doc/{ => tools}/prel.md (88%) create mode 100644 doc/tools/pver.md rename doc/{ => tools}/pz.md (100%) rename doc/{ => tools}/reptyr.cgo.md (100%) rename doc/{ => tools}/rmtildes.md (100%) rename doc/{ => tools}/rruns.md (100%) rename doc/{ => tools}/ruinst.md (100%) rename doc/{ => tools}/rumount.md (100%) rename doc/{ => tools}/runs.md (96%) rename doc/{ => tools}/runsconfig.md (100%) rename doc/{ => tools}/runsmod.md (89%) rename doc/{ => tools}/rwoinst.md (100%) rename doc/{ => tools}/sqlcsv.md (100%) rename doc/{ => tools}/twsync.md (100%) rename doc/{ => tools}/uawk.md (100%) rename doc/{ => tools}/ubackup.md (100%) rename doc/{ => tools}/ucalc.md (100%) rename doc/{ => tools}/uconf.md (100%) rename doc/{ => tools}/ucrontab.md (100%) rename doc/{ => tools}/udaemon.cgo.md (100%) rename doc/{ => tools}/udir.md (100%) rename doc/{ => tools}/udist.md (100%) rename doc/{ => tools}/uenv.md (100%) rename doc/{ => tools}/ufixmod.md (100%) rename doc/{ => tools}/ugenpass.md (100%) rename doc/{ => tools}/uinc.md (100%) rename doc/{ => tools}/uinc.sh.md (100%) rename doc/{ => tools}/uinst.md (100%) rename doc/{ => tools}/uinst.sh.md (100%) rename doc/{ => tools}/ujava.md (100%) rename doc/{ => tools}/uldap.md (100%) create mode 100644 doc/tools/ulib.md rename doc/{ => tools}/ulink.md (100%) rename doc/{ => tools}/umatch.md (100%) rename doc/{ => tools}/umirror.md (100%) create mode 100644 doc/tools/umountr.md rename doc/{ => tools}/upassword.md (100%) rename doc/{ => tools}/update-nutools.md (100%) rename doc/{ => tools}/uprefix.md (100%) rename doc/{ => tools}/uproject.md (95%) rename doc/{ => tools}/uscrontab.md (100%) rename doc/{ => tools}/ussh.md (100%) rename doc/{ => tools}/usysinfos.md (100%) rename doc/{ => tools}/utempl.md (100%) rename doc/{ => tools}/utrigger.md (100%) rename doc/{ => tools}/uwatch.md (100%) rename doc/{ => tools}/vzusage.md (100%) rename doc/{ => tools}/woArchive.md (100%) rename doc/{ => tools}/woSwitch.md (100%) rename doc/{ => tools}/woctl.md (100%) rename doc/{ => tools}/woinst.md (100%) rename doc/{ => tools}/wosign.md (100%) delete mode 100644 doc/twsync.twp delete mode 100644 doc/uawk.twp delete mode 100644 doc/ubackup.twp delete mode 100644 doc/ucalc.twp delete mode 100644 doc/uconf.twp delete mode 100644 doc/ucrontab.twp delete mode 100644 doc/udaemon.cgo.twp delete mode 100644 doc/udir.twp delete mode 100644 doc/udist.twp delete mode 100644 doc/uenv.twp delete mode 100644 doc/ufixmod.twp delete mode 100644 doc/ugenpass.twp delete mode 100644 doc/uinc.sh.twp delete mode 100644 doc/uinc.twp delete mode 100644 doc/uinst.sh.twp delete mode 100644 doc/uinst.twp delete mode 100644 doc/ujava.twp delete mode 100644 doc/uldap.twp delete mode 100644 doc/ulib.twp rename doc/{ulib_DEFAULTS.md => ulib/DEFAULTS.md} (100%) rename doc/{ulib_PREFIXES-DEFAULTS.md => ulib/PREFIXES-DEFAULTS.md} (100%) rename doc/{ulib_apache.md => ulib/apache.md} (91%) rename doc/{ulib_apache.tools.md => ulib/apache.tools.md} (71%) rename doc/{ulib_auto.md => ulib/auto.md} (100%) rename doc/{ulib_awk.md => ulib/awk.md} (95%) rename doc/{ulib_base.args.md => ulib/base.args.md} (100%) rename doc/{ulib_base.array.md => ulib/base.array.md} (100%) rename doc/{ulib_base.bool.md => ulib/base.bool.md} (100%) rename doc/{ulib_base.compat.md => ulib/base.compat.md} (100%) rename doc/{ulib_base.core.md => ulib/base.core.md} (75%) rename doc/{ulib_base.init.md => ulib/base.init.md} (100%) rename doc/{ulib_base.md => ulib/base.md} (97%) rename doc/{ulib_base.num.md => ulib/base.num.md} (100%) rename doc/{ulib_base.quote.md => ulib/base.quote.md} (74%) rename doc/{ulib_base.split.md => ulib/base.split.md} (100%) rename doc/{ulib_base.string.md => ulib/base.string.md} (61%) rename doc/{ulib_base.tools.md => ulib/base.tools.md} (100%) rename doc/{ulib_base.ulib.md => ulib/base.ulib.md} (100%) rename doc/{ulib_bash.md => ulib/bash.md} (100%) rename doc/{ulib_cgi.md => ulib/cgi.md} (100%) rename doc/{ulib_cgisupport.md => ulib/cgisupport.md} (100%) rename doc/{ulib_compat.md => ulib/compat.md} (100%) rename doc/{ulib_conf.md => ulib/conf.md} (100%) rename doc/{ulib_crontab.md => ulib/crontab.md} (100%) rename doc/{ulib_debian.md => ulib/debian.md} (100%) rename doc/{ulib_install.md => ulib/install.md} (100%) rename doc/{ulib_ipcalc.md => ulib/ipcalc.md} (100%) rename doc/{ulib_java.md => ulib/java.md} (100%) rename doc/{ulib_javaproperties.md => ulib/javaproperties.md} (100%) rename doc/{ulib_json.md => ulib/json.md} (100%) rename doc/{ulib_ldap.md => ulib/ldap.md} (100%) rename doc/{ulib_ldif.md => ulib/ldif.md} (91%) rename doc/{ulib_legacy.md => ulib/legacy.md} (100%) rename doc/{ulib_macosx.md => ulib/macosx.md} (100%) rename doc/{ulib_mkcrypt.md => ulib/mkcrypt.md} (100%) rename doc/{ulib_modeline.md => ulib/modeline.md} (100%) rename doc/{ulib_network-manager-service.md => ulib/network-manager-service.md} (100%) rename doc/{ulib_password.md => ulib/password.md} (100%) rename doc/{ulib_prefixes.md => ulib/prefixes.md} (100%) rename doc/{ulib_pretty.md => ulib/pretty.md} (100%) rename doc/{ulib_ptools.md => ulib/ptools.md} (100%) rename doc/{ulib_redhat.md => ulib/redhat.md} (100%) rename doc/{ulib_runs.md => ulib/runs.md} (99%) rename doc/{ulib_runsmod.defaults.md => ulib/runsmod.defaults.md} (100%) rename doc/{ulib_runsmod.md => ulib/runsmod.md} (92%) create mode 100644 doc/ulib/semver.md rename doc/{ulib_service.md => ulib/service.md} (100%) rename doc/{ulib_sysinfos.md => ulib/sysinfos.md} (100%) rename doc/{ulib_template.md => ulib/template.md} (100%) rename doc/{ulib_tiddlywiki.md => ulib/tiddlywiki.md} (100%) rename doc/{ulib_udir.md => ulib/udir.md} (100%) rename doc/{ulib_uenv.md => ulib/uenv.md} (100%) rename doc/{ulib_uenv_update.md => ulib/uenv_update.md} (100%) rename doc/{ulib_uinc.md => ulib/uinc.md} (100%) rename doc/{ulib_uinst.md => ulib/uinst.md} (100%) rename doc/{ulib_ulib.md => ulib/ulib.md} (100%) rename doc/{ulib_ulibsh.md => ulib/ulibsh.md} (100%) rename doc/{ulib_vcs.md => ulib/vcs.md} (98%) rename doc/{ulib_virsh.md => ulib/virsh.md} (100%) rename doc/{ulib_webobjects.md => ulib/webobjects.md} (100%) rename doc/{ulib_woinst.md => ulib/woinst.md} (100%) rename doc/{ulib_wondermonitor.md => ulib/wondermonitor.md} (100%) rename doc/{ulib_wosign.md => ulib/wosign.md} (100%) rename doc/{ulib_wotaskd.md => ulib/wotaskd.md} (100%) create mode 100644 doc/ulib/xmlsupport.md delete mode 100644 doc/ulib_DEFAULTS.twp delete mode 100644 doc/ulib_PREFIXES-DEFAULTS.twp delete mode 100644 doc/ulib_apache.tools.twp delete mode 100644 doc/ulib_apache.twp delete mode 100644 doc/ulib_auto.twp delete mode 100644 doc/ulib_awk.twp delete mode 100644 doc/ulib_base.args.twp delete mode 100644 doc/ulib_base.array.twp delete mode 100644 doc/ulib_base.bool.twp delete mode 100644 doc/ulib_base.compat.twp delete mode 100644 doc/ulib_base.core.twp delete mode 100644 doc/ulib_base.init.twp delete mode 100644 doc/ulib_base.num.twp delete mode 100644 doc/ulib_base.quote.twp delete mode 100644 doc/ulib_base.split.twp delete mode 100644 doc/ulib_base.string.twp delete mode 100644 doc/ulib_base.tools.twp delete mode 100644 doc/ulib_base.twp delete mode 100644 doc/ulib_base.ulib.twp delete mode 100644 doc/ulib_bash.twp delete mode 100644 doc/ulib_cgi.twp delete mode 100644 doc/ulib_cgisupport.twp delete mode 100644 doc/ulib_compat.twp delete mode 100644 doc/ulib_conf.twp delete mode 100644 doc/ulib_crontab.twp delete mode 100644 doc/ulib_debian.twp delete mode 100644 doc/ulib_install.twp delete mode 100644 doc/ulib_ipcalc.twp delete mode 100644 doc/ulib_java.twp delete mode 100644 doc/ulib_javaproperties.twp delete mode 100644 doc/ulib_json.twp delete mode 100644 doc/ulib_ldap.twp delete mode 100644 doc/ulib_ldif.twp delete mode 100644 doc/ulib_legacy.twp delete mode 100644 doc/ulib_macosx.twp delete mode 100644 doc/ulib_mkcrypt.twp delete mode 100644 doc/ulib_modeline.twp delete mode 100644 doc/ulib_network-manager-service.twp delete mode 100644 doc/ulib_password.twp delete mode 100644 doc/ulib_pkg.twp delete mode 100644 doc/ulib_prefixes.twp delete mode 100644 doc/ulib_pretty.twp delete mode 100644 doc/ulib_ptools.twp delete mode 100644 doc/ulib_redhat.twp delete mode 100644 doc/ulib_runs.twp delete mode 100644 doc/ulib_runsmod.defaults.twp delete mode 100644 doc/ulib_runsmod.twp delete mode 100644 doc/ulib_semver.md delete mode 100644 doc/ulib_semver.twp delete mode 100644 doc/ulib_service.twp delete mode 100644 doc/ulib_sysinfos.twp delete mode 100644 doc/ulib_template.twp delete mode 100644 doc/ulib_tiddlywiki.twp delete mode 100644 doc/ulib_udir.twp delete mode 100644 doc/ulib_uenv.twp delete mode 100644 doc/ulib_uenv_update.twp delete mode 100644 doc/ulib_uinc.twp delete mode 100644 doc/ulib_uinst.twp delete mode 100644 doc/ulib_ulib.twp delete mode 100644 doc/ulib_ulibsh.twp delete mode 100644 doc/ulib_vcs.twp delete mode 100644 doc/ulib_virsh.twp delete mode 100644 doc/ulib_webobjects.twp delete mode 100644 doc/ulib_woinst.twp delete mode 100644 doc/ulib_wondermonitor.twp delete mode 100644 doc/ulib_wosign.twp delete mode 100644 doc/ulib_wotaskd.twp delete mode 100644 doc/ulibshell.twp delete mode 100644 doc/ulibsync.twp delete mode 100644 doc/ulink.twp delete mode 100644 doc/umatch.twp delete mode 100644 doc/umirror.twp delete mode 100644 doc/upassword.twp delete mode 100644 doc/update-nutools.twp delete mode 100755 doc/update-twp delete mode 100644 doc/uprefix.twp delete mode 100644 doc/uproject.twp delete mode 100644 doc/uscrontab.twp delete mode 100644 doc/ussh.twp delete mode 100644 doc/usysinfos.twp delete mode 100644 doc/utempl.twp delete mode 100644 doc/utrigger.twp delete mode 100644 doc/uwatch.twp delete mode 100644 doc/vzusage.twp delete mode 100644 doc/woArchive.twp delete mode 100644 doc/woSwitch.twp delete mode 100644 doc/woctl.twp delete mode 100644 doc/woinst.twp delete mode 100644 doc/wosign.twp diff --git a/doc/DefaultTiddlers.twp b/doc/DefaultTiddlers.twp deleted file mode 100644 index cac184d..0000000 --- a/doc/DefaultTiddlers.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: DefaultTiddlers - -[[Main]] diff --git a/doc/EnsureVM.twp b/doc/EnsureVM.twp deleted file mode 100644 index cda2f7d..0000000 --- a/doc/EnsureVM.twp +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: EnsureVM - -{{{ -EnsureVM: s'assurer que les services sont lancés pour un type de virtualisation - -USAGE - EnsureVM type - -Les types supportés sont virtualbox et kvm (par défaut) -}}} diff --git a/doc/Main.twp b/doc/Main.twp deleted file mode 100644 index 470bda7..0000000 --- a/doc/Main.twp +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: Main - -!Présentation -nutools est un ensemble d'utilitaires pour faciliter l'utililisation des Unixes, en particulier Linux, mais aussi MacOS X et Cygwin. -C'est aussi une librairie de scripts shell réutilisables ([[ulib]]) et une librairie de modules python réutilisables (pyulib) - -!Prérequis -Python >= 2.3 et GNU Awk sont requis pour que toutes les fonctionnalités soient supportées. -* Sous Linux, lors de l'installation du package, les meilleurs efforts sont fait pour que ces packages soient installés. -* Sous MacOSX, il faut installer manuellement Fink, DarwinPorts ou Homebrew - -! Outils -Chaque outil contient une aide intégrée. Il suffit de lancer l'outil avec l'argument {{{--help}}} pour avoir une aide détaillée. -* Déploiement d'un répertoire ou d'une archive -** [[uinst]]: Déploiement local -** [[mkusfx]]: Faire une archive auto-installable avec uinst -** [[ruinst]]: Déploiement distant avec uinst -** [[runs]]: Lancer un script avec le protocole RUNS -** [[rruns]]: Déploiement distant avec runs -* Librairie réutilisable de scripts shell -** [[uinc]]: Dépliage des inclusions dans un fichier -** [[ulibsync]]: Faire une copie locale pour un projet de ulib et/ou pyulib -* Autres outils -** [[udir]]: Gestion des paramètres d'un répertoire. Ces paramètres sont entre autres utilisés par uinst et uinc. diff --git a/doc/MainMenu.twp b/doc/MainMenu.twp deleted file mode 100644 index 935032c..0000000 --- a/doc/MainMenu.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: MainMenu - -[[GettingStarted]] diff --git a/doc/SKvm.twp b/doc/SKvm.twp deleted file mode 100644 index 8d66b66..0000000 --- a/doc/SKvm.twp +++ /dev/null @@ -1,40 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: SKvm - -{{{ -SKvm: lancer une machine virtuelle kvm - -USAGE - SKvm [options] vmName - SKvm {-l|-A|-g} - -OPTIONS - -n, --check - Ne rien faire excepté s'assurer que les modules kvm sont chargés - -u, --user - Lancer l'opération avec les droits de l'utilisateur courant. Par défaut, - ce script tente d'acquérir les droits de root. - -l, --list - Lister les machines virtuelles - -s, --start - Démarrer la machine virtuelle (par défaut) - Si le nom de la machine virtuelle n'est pas spécifiée, un menu est - affiché - -k, --stop - Arrêter la machine virtuelle - -H, --destroy - Arrêter sauvagement la machine virtuelle - -r, --restart - Redémarrer la machine virtuelle - -S, --managed-save - Enregistrer l'état de la machine virtuelle - -A, --stop-all - Arrêter toutes les machines virtuelles qui tournent actuellement - -g, --gui - Afficher le gestionnaire de machines virtuelle -}}} diff --git a/doc/SVirtualBox.twp b/doc/SVirtualBox.twp deleted file mode 100644 index b07371d..0000000 --- a/doc/SVirtualBox.twp +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: SVirtualBox - -{{{ -SVirtualBox: lancer une machine virtuelle VirtualBox - -USAGE - SVirtualBox [options] vmName - -OPTIONS - -n Ne rien faire excepté s'assurer que les modules VirtualBox sont chargés - -l Lister les machines virtuelles - -s Démarrer la machine virtuelle (par défaut) - Si le nom de la machine virtuelle n'est pas spécifiée, un menu est - affiché - -b Démarrer la VM sans interface graphique. Cette option n'est valide - qu'avec -s - -k Arrêter la machine virtuelle (par ACPI) - -p Mettre en veille la machine virtuelle (par ACPI) - -H Arrêter sauvagement la machine virtuelle - -R Redémarrer sauvagement la machine virtuelle - -S Enregistrer l'état de la machine virtuelle - -g Afficher le gestionnaire de machines virtuelle -}}} diff --git a/doc/SiteSubtitle.twp b/doc/SiteSubtitle.twp deleted file mode 100644 index 54da2b3..0000000 --- a/doc/SiteSubtitle.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: SiteSubtitle - -Outils divers pour linux/macosx, et librairies pour bash diff --git a/doc/SiteTitle.twp b/doc/SiteTitle.twp deleted file mode 100644 index ed1c759..0000000 --- a/doc/SiteTitle.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: SiteTitle - -nutools diff --git a/doc/SiteUrl.twp b/doc/SiteUrl.twp deleted file mode 100644 index bef6314..0000000 --- a/doc/SiteUrl.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# Pense-bête: -# ''bold'' ==striked== __underline__ //italic// ^^super^^ ~~sub~~ -# @@highlight@@ @@color:red;background-color:white; rouge sur noir@@ -# ~NotAWikiWord [[force wikiword]] [[friendly name|WikiWord]] -# [[external|http://site.com]] ---- {{monospace}} -# !h1 !!h2 !!!h3 !!!!h4 !!!!!h5 -# * dotlist ** sublist # numlist ## sublist -# {{{ |caption|c [img[title|filename]] -# pre text |!header|!header|h [img[filename]] -# }}} |cell|cell| [img[title|filename][link]] -# <<< |>|colspan| [img[filename][link]] -# blockquote |rowspan|one| [img[filename]] -# >quote1 |left| right| -# >>quote2 |>| center | -# >>>quote3 -##@creator: jclain -##@created: 09/03/2012 05:08 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: SiteUrl - - diff --git a/doc/_root.twp b/doc/_root.twp deleted file mode 100644 index 8d9dad4..0000000 --- a/doc/_root.twp +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: _root - -{{{ -_root: devenir l'utilisateur root, avec 'sudo' si possible, ou 'su' si -'sudo' n'est pas installé -}}} diff --git a/doc/apacheconfig.twp b/doc/apacheconfig.twp deleted file mode 100644 index e5f2c89..0000000 --- a/doc/apacheconfig.twp +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: apacheconfig - -{{{ -apacheconfig: Gérer la configuration d'un serveur web apache - -USAGE - apacheconfig -c - apacheconfig -t -- args... - -OPTIONS - -c, --create - Créer un nouveau répertoire de configuration pour un hôte - -d, --destdir DESTDIR[=apacheconfig] - Nom du répertoire local de configuration. - - -t, --template [OPT] - Gérer les fichiers du répertoire local avec templatectl. La valeur de - cette option est utilisée comme argument court pour l'invocation de - templatectl, e.g - apacheconfig -tm args - est équivalent à - templatectl -m args - Les arguments qui restent sont passés tels quels à templatectl - Les options courantes de templatectl -l, -v, -m, -L sont disponibles - directement - --help-template - Afficher l'aide concernent la gestion des templates. - Equivalent à -t -- --help - -h, --host HOST - Spécifier l'hôte. Equivalent à -v host=HOST - --sysname SYSNAME - --sysdist SYSDIST - -s, --sysver SYSVER - Spécifier la distribution pour laquelle synchroniser le template. Par - défaut, choisir les valeurs correspondantes au système courant. - Les options -7 et -8 sont des aliases respectivement pour -s wheezy et - -s jessie, parce que les fichiers par défaut ont changé à partir de - debian jessie. - - -u, --update, --deploy - Mettre à jour la configuration système à partir du répertoire local. - Lors du déploiement de la configuration, les valeurs des variables - dynamiques sont remplacées dans les fichiers destination. - Les arguments qui restent sont passés tels quels à apache_autoconf - -r, --certsdir CERTSDIR - Spécifier le cas échéant le répertoire contenant les certificats à - déployer. Cet argument est requis si le répertoire certsconf/ existe. - - --localhosts - Créer dans le fichier /etc/hosts tous les noms d'hôte ayant un suffixe - .local mentionnés dans les fichiers de site. Cette option est utile pour - le développement et les tests. - -C, --one-conf CONF - Ne déployer que le fichier de configuration spécifié. Cette option est - utilisée avec --deploy et est utile pour le développement et les tests. - -M, --one-module MODULE - Ne déployer que le fichier de module spécifié. Cette option est utilisée - avec --deploy et est utile pour le développement et les tests. - -S, --one-site SITE - Ne déployer que le fichier de site spécifié. Cette option est utilisée - avec --deploy ou --localhosts et est utile pour le développement et les - tests. -}}} diff --git a/doc/authftp.twp b/doc/authftp.twp deleted file mode 100644 index f132efe..0000000 --- a/doc/authftp.twp +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: authftp - -{{{ -authftp: Se connecter sur un site FTP authentifié -Ce script nécessite ncftp. Il est conçu pour faciliter l'accès à des sites FTP -s'il est requis d'y accéder par un proxy FTP pour les connexion authentifiées. - -USAGE - authftp [options] host login password [path] - -OPTIONS - -p, --proxy - -n, --noproxy - Forcer l'utilisation, resp. ne pas utiliser, le proxy FTP (i.e. faire la - connexion directement) - -l, --lftp - Se connecter avec lftp au lieu de ncftp. Le fonctionnement n'est pas - garanti si l'on utilise un proxy FTP. - -o OPTION - Ajouter une option à la commande lancée. Si l'option prend un argument, - il faut doubler l'option -o, e.g. - authftp -l -o -e -o 'mirror remote local' host login pass - Dans cet exemple, l'option -e de lftp est utilisée pour faire un miroir - local du répertoire remote. - --tls - Indiquer que la connexion se fera en TLS. Implique --lftp puisque ncftp - ne le supporte pas. - -note: A cause d'une limitation de lftp, ce n'est pas possible de se connecter -automatiquement avec lftp si le mot de passe contient une virgule. A cause de la -façon dont le proxy ftp est configuré, il n'est pas possible de se connecter -avec un mot de passe qui contient le caractère @ -}}} diff --git a/doc/caturl.twp b/doc/caturl.twp deleted file mode 100644 index 9d677f2..0000000 --- a/doc/caturl.twp +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 13/05/2016 09:36 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: caturl - -{{{ -caturl: Afficher une url - -USAGE - caturl - -OPTIONS - -c, --check - Vérifier que le fichier est valide - -g, --get - Attaquer l'url spécifiée avec curl -}}} diff --git a/doc/compileAndGo.twp b/doc/compileAndGo.twp deleted file mode 100644 index 0a401cf..0000000 --- a/doc/compileAndGo.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: compileAndGo - -{{{ -compileAndGo: see http://Yost.com/computers/compileAndGo -}}} diff --git a/doc/cssh.twp b/doc/cssh.twp deleted file mode 100644 index 228e26e..0000000 --- a/doc/cssh.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: cssh - -{{{ -cssh: Faire une connexion ssh en lançant automatiquement un screen sur l'hôte distant - -USAGE - cssh [user@]host [options] - -En principe, hormis l'argument user@host, il ne faudrait spécifier que des -options. Dans le cas où d'autres arguments seraient spécifiés, les meilleurs -efforts sont faits pour lancer ces commandes avant screen. -}}} diff --git a/doc/doinplace.twp b/doc/doinplace.twp deleted file mode 100644 index 6d291d5..0000000 --- a/doc/doinplace.twp +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: doinplace - -{{{ -doinplace: filtrer en place un fichier à travers une suite de commandes - -USAGE - doinplace FILE COMMAND [ARGS...] - -Si on utilise une commande avec des options, penser à utliser '--' pour séparer -les options de ce script des options de la commande - -En plus des commandes systèmes, il est possible d'utiliser toute fonction de -nutools qui effectue des traitement sur un flux comme stripnl, filter_empty, -merge_contlines, filter_comment, filter_conf, etc. Les fonctions nl2lf, nl2crlf, -nl2cr, latin1compat et noaccents sont aussi disponibles par convenance. - -OPTIONS - -p, --evalp - Evaluer la commande avec evalp(), ce qui permet de chainer plusieurs - commandes en les séparant par //. Cette option est automatiquement - activée si ce script est lancé avec le nom doinplacex - -g, --ignore-error, --replace-always - Normalement, le fichier n'est pas remplacé si la commande retourne une - erreur. Avec cette option, le fichier est remplacé quel que soit le code - de retour de la commande. A utiliser par exemple avec des commandes - comme grep qui peuvent retourner FAUX s'ils ne trouvent pas le motif. - Cette option est automatiquement activée si ce script est lancé avec le - nom doinplacef -}}} diff --git a/doc/dumpclients.twp b/doc/dumpclients.twp deleted file mode 100644 index 6983b14..0000000 --- a/doc/dumpclients.twp +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: dumpclients - -{{{ -dumpclients: afficher les connexions TCP entrantes sur un port - -USAGE - dumpclients [options] port - -OPTIONS - -4, --only-tcp - -6, --only-tcp6 - Se limiter au protocole spécifié. Par défaut, afficher toutes les - connexions, qu'elles soient en IPv4 ou en IPv6 - -a, --all - Afficher tous les sockets, y compris les ports d'écoute. Par défaut, - seules les sockets ouvertes sont affichées. - -n, --numeric - Afficher uniquement les adresses IP au lieu du nom d'hôte. - -z, --allow-no-port - Ne pas exiger que le port soit spécifié - --show none,ip,port,state - Spécifier d'afficher comme informations supplémentaire: rien, l'adresse - ip, le port et/ou l'état. Par défaut, afficher le port et l'état. -}}} diff --git a/doc/em.twp b/doc/em.twp deleted file mode 100644 index e405ebf..0000000 --- a/doc/em.twp +++ /dev/null @@ -1,95 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: em - -{{{ -Usage: /usr/bin/emacs [OPTION-OR-FILENAME]... - -Run Emacs, the extensible, customizable, self-documenting real-time -display editor. The recommended way to start Emacs for normal editing -is with no options at all. - -Run M-x info RET m emacs RET m emacs invocation RET inside Emacs to -read the main documentation for these command-line arguments. - -Initialization options: - ---batch do not do interactive display; implies -q ---daemon start a server in the background ---debug-init enable Emacs Lisp debugger for init file ---display, -d DISPLAY use X server DISPLAY ---no-desktop do not load a saved desktop ---no-init-file, -q load neither ~/.emacs nor default.el ---no-shared-memory, -nl do not use shared memory ---no-site-file do not load site-start.el ---no-splash do not display a splash screen on startup ---no-window-system, -nw do not communicate with X, ignoring $DISPLAY ---quick, -Q equivalent to -q --no-site-file --no-splash ---script FILE run FILE as an Emacs Lisp script ---terminal, -t DEVICE use DEVICE for terminal I/O ---user, -u USER load ~USER/.emacs instead of your own - -Action options: - -FILE visit FILE using find-file -+LINE go to line LINE in next FILE -+LINE:COLUMN go to line LINE, column COLUMN, in next FILE ---directory, -L DIR add DIR to variable load-path ---eval EXPR evaluate Emacs Lisp expression EXPR ---execute EXPR evaluate Emacs Lisp expression EXPR ---file FILE visit FILE using find-file ---find-file FILE visit FILE using find-file ---funcall, -f FUNC call Emacs Lisp function FUNC with no arguments ---insert FILE insert contents of FILE into current buffer ---kill exit without asking for confirmation ---load, -l FILE load Emacs Lisp FILE using the load function ---visit FILE visit FILE using find-file - -Display options: - ---background-color, -bg COLOR window background color ---basic-display, -D disable many display features; - used for debugging Emacs ---border-color, -bd COLOR main border color ---border-width, -bw WIDTH width of main border ---color, --color=MODE override color mode for character terminals; - MODE defaults to `auto', and can also - be `never', `auto', `always', - or a mode name like `ansi8' ---cursor-color, -cr COLOR color of the Emacs cursor indicating point ---font, -fn FONT default font; must be fixed-width ---foreground-color, -fg COLOR window foreground color ---fullheight, -fh make the first frame high as the screen ---fullscreen, -fs make first frame fullscreen ---fullwidth, -fw make the first frame wide as the screen ---maximized, -mm make the first frame maximized ---geometry, -g GEOMETRY window geometry ---no-bitmap-icon, -nbi do not use picture of gnu for Emacs icon ---iconic start Emacs in iconified state ---internal-border, -ib WIDTH width between text and main border ---line-spacing, -lsp PIXELS additional space to put between lines ---mouse-color, -ms COLOR mouse cursor color in Emacs window ---name NAME title for initial Emacs frame ---no-blinking-cursor, -nbc disable blinking cursor ---reverse-video, -r, -rv switch foreground and background ---title, -T TITLE title for initial Emacs frame ---vertical-scroll-bars, -vb enable vertical scroll bars ---xrm XRESOURCES set additional X resources ---parent-id XID set parent window ---help display this help and exit ---version output version information and exit - -You can generally also specify long option names with a single -; for -example, -batch as well as --batch. You can use any unambiguous -abbreviation for a --option. - -Various environment variables and window system resources also affect -Emacs' operation. See the main documentation. - -Report bugs to bug-gnu-emacs@gnu.org. First, please see the Bugs -section of the Emacs manual or the file BUGS. -}}} diff --git a/doc/fconv.twp b/doc/fconv.twp deleted file mode 100644 index 5306686..0000000 --- a/doc/fconv.twp +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: fconv - -{{{ -fconv: convertir un fichier ou les fichiers d'un répertoire - -USAGE - fconv -f FILE [cmds...] - fconv FILE [cmds...] - -Une ou plusieurs commandes peuvent être spécifiées, séparées par // -La commande par défaut est 'lf' -Si des commandes utilisant des options sont utilisées, penser à séparer les -options de fconv avec -- - -OPTIONS - -N, --detect-always - Pour la commande conv, ne pas optimiser le calcul de l'encoding. Cette - option n'est valide que si src_enc n'est pas spécifié. On assume que - tous les fichiers n'ont pas le même encoding: l'encoding src_enc est - donc recalculé à chaque fois. - -r, --reverse - Pour la commande conv, inverser src_enc et dest_enc, qui doivent être - tous les deux spécifiés. - -f, --file FILE - Spécifier le fichier ou le répertoire concerné par la conversion. Les - aliases -d et --dir sont aussi reconnus. - Si cette option n'est pas spécifiée, le premier argument est considéré - comme le nom du fichier ou du répertoire à convertir. Par défaut, - convertir l'entrée standard. - Si un répertoire est spécifié, tous les fichiers de ce répertoire et de - ses sous-répertoires sont recherchés de façon récursive, sans limite de - profondeur. Ensuite, chacun de ces fichiers est converti. - --show-cmd - Afficher la commande qui serait exécutée - -COMMANDES - c, conv dest_enc [src_enc] - Convertir le fichier dans un autre encoding. - dest_enc est l'encoding destination. Il doit être spécifié. - src_enc est l'encoding source. S'il n'est pas spécifié ou vaut 'detect', - il est autodétecté. - U, utf8 [src_enc] - Equivalent à conv utf8 src_enc - L, latin1 [src_enc] - Equivalent à conv latin1 src_enc - lf - crlf - cr - Convertir respectivement les caractères de fin de ligne en LF, CR/LF ou CR - lc, latin1compat - Transformer certains caratères UTF-8 en équivalents qui existent en Latin1 - na, noaccents - Transformer les caractères accentués en caractères non accentués - sort [-u] - Trier le fichier avec la commande sort. Attention! Il ne faut utiliser - que les options de sort qui agissent sur un flux e.g. -u pour trier les - lignes de façon unique. -}}} diff --git a/doc/fnconv.twp b/doc/fnconv.twp deleted file mode 100644 index 7337bce..0000000 --- a/doc/fnconv.twp +++ /dev/null @@ -1,58 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: fnconv - -{{{ -fnconv: renommer un fichier ou les fichiers d'un répertoire - -USAGE - fnconv -f FILE [cmds...] - fnconv FILE [cmds...] - -Une ou plusieurs commandes peuvent être spécifiées, séparées // -La commande par défaut est 'fixcase' - -OPTIONS - -N, --detect-always - Pour la commande conv, ne pas optimiser le calcul de l'encoding. Cette - option n'est valide que si src_enc n'est pas spécifié. On assume que - tous les fichiers n'ont pas le même encoding: l'encoding src_enc est - donc recalculé à chaque fois. - -r, --reverse - Pour la commande conv, inverser src_enc et dest_enc, qui doivent être - tous les deux spécifiés. - -f, --file FILE - Spécifier le fichier ou le répertoire concerné par le renommage. Les - aliases -d et --dir sont aussi reconnus. - Si cette option n'est pas spécifiée, le premier argument est considéré - comme le nom du fichier ou du répertoire à renommer. - Si un répertoire est spécifié, le traitement est appliqué à tous les - fichiers et répertoires de façon récursive, sans limite de profondeur. - --show-cmd - Afficher la commande qui serait exécutée - -COMMANDES - C, conv dest_enc [src_enc] - Convertir le nom du fichier dans un autre encoding. - dest_enc est l'encoding destination. Il doit être spécifié. - src_enc est l'encoding source. S'il n'est pas spécifié ou vaut 'detect', - il est autodétecté. - U, utf8 [src_enc] - Equivalent à conv utf8 src_enc - L, latin1 [src_enc] - Equivalent à conv latin1 src_enc - lc, latin1compat - Transformer certains caratères UTF-8 en équivalents qui existent en Latin1 - na, noaccents - Transformer les caractères accentués en caractères non accentués - l, lowercase - Transfomer le nom en minuscule - u, uppercase - Transformer le nom en majuscule - f, fixcase - Transformer le nom en minuscule s'il est entièrement en majuscule -}}} diff --git a/doc/geturl.twp b/doc/geturl.twp deleted file mode 100644 index 1ec3d76..0000000 --- a/doc/geturl.twp +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: geturl - -{{{ -geturl: Télécharger un fichier avec wget ou curl - -USAGE - geturl [wget options] -}}} diff --git a/doc/index.md b/doc/index.md index 374a8f1..2762f05 100644 --- a/doc/index.md +++ b/doc/index.md @@ -20,16 +20,16 @@ Chaque outil contient une aide intégrée. Il suffit de lancer l'outil avec l'argument `--help` pour avoir une aide détaillée. * Déploiement d'un répertoire ou d'une archive - * [uinst](): Déploiement local - * [mkusfx](): Faire une archive auto-installable avec uinst - * [ruinst](): Déploiement distant avec uinst - * [runs](): Lancer un script avec le protocole RUNS - * [rruns](): Déploiement distant avec runs + * [uinst](tools/uinst): Déploiement local + * [mkusfx](tools/mkusfx): Faire une archive auto-installable avec uinst + * [ruinst](tools/ruinst): Déploiement distant avec uinst + * [runs](tools/runs): Lancer un script avec le protocole RUNS + * [rruns](tools/rruns): Déploiement distant avec runs * Librairie réutilisable de scripts shell - * [uinc](): Dépliage des inclusions dans un fichier - * [ulibsync](): Faire une copie locale pour un projet de ulib et/ou pyulib + * [uinc](tools/uinc): Dépliage des inclusions dans un fichier + * [ulibsync](tools/ulibsync): Faire une copie locale pour un projet de ulib et/ou pyulib * Autres outils - * [udir](): Gestion des paramètres d'un répertoire. Ces paramètres sont entre - autres utilisés par uinst et uinc. + * [udir](tools/udir): Gestion des paramètres d'un répertoire. Ces paramètres sont entre + autres utilisés par uinst et uinc. -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/mkRewriteRules.twp b/doc/mkRewriteRules.twp deleted file mode 100644 index 2b1c35c..0000000 --- a/doc/mkRewriteRules.twp +++ /dev/null @@ -1,86 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mkRewriteRules - -{{{ -mkRewriteRules: Créer un fichier de redirections pour Apache à partir d'un certain -nombre de règles - -USAGE - mkRewriteRules -f rewrite.rules [-o RewriteRules.conf] [-w RewriteRules.html] host - -OPTIONS - -p Générer les directives et tenir compte de proxy_acls - Par défaut, le champ proxy_acls est ignoré - -FORMAT des règles de mapping -============================ - -Les commentaires commencent par le signe "#" -Les règles sont de la forme: - src:dest:host:suffix:OPTS:prot:proxy_acls - ^prefix - =literal - -prot vaut par défaut http. Il peut valoir aussi https - -Si dest ou suffix se terminent par $, on est en mode NO_SLASH -En mode NO_SLASH, si src se termine par $, on est en mode NO_TRAIL - -* Si dest est de la forme Application.woa -En mode NO_SLASH, on génère - RewriteRule ^/src(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]$1 [L,OPTS] -En mode NO_SLASH+NO_TRAIL, on génère - RewriteRule ^/src [prot://host]/cgi-bin/WebObjects/dest[/suffix] [L,OPTS] -En mode normal, on génère - RewriteRule ^/src$ /src/ - RewriteRule ^/src/(.*) [prot://host]/cgi-bin/WebObjects/dest[/suffix]/$1 [L,OPTS] - -* Si dest n'est pas de la forme Application.woa -En mode NO_SLASH, on génère - RewriteRule ^/src(.*) [prot://host]/dest[/suffix]$1 [L,OPTS] -En mode NO_SLASH+NO_TRAIL, on génère - RewriteRule ^/src [prot://host]/dest[/suffix] [L,OPTS] -En mode normal, on génère - RewriteRule ^/src$ /src/ - RewriteRule ^/src/(.*) /dest[/suffix]/$1 [L,OPTS] - -Si une règle est précédée d'une ou plusieurs lignes de la forme "^prefix", -ces lignes sont copiées avant chacune des commandes RewriteRule générées -pour une règle. Ceci permet d'ajouter des conditions avec RewriteCond pour -une règle. e.g. - ^RewriteCond %{REMOTE_ADDR} 10\..* - src:dest.woa -qui génère: - RewriteCond %{REMOTE_ADDR} 10\..* - RewriteRule ^/src$ /src/ - RewriteCond %{REMOTE_ADDR} 10\..* - RewriteRule ^/src/(.*) /cgi-bin/WebObjects/dest.woa/$1 [L] - -Une ligne de la forme "=literal" est recopiée sans modifications (sans le "=") -dans le fichier de sortie. - -proxy_acls est utilisé si l'option -p est spécifiée et OPTS contient P (comme -proxy), ou si le mode de réécriture requière l'utilisation d'un proxy. - -* Avec la valeur "None", aucune directive n'est générée -* Si aucune valeur n'est spécifiée, la directive suivante est générée: - - AddDefaultCharset off - Order Deny,Allow - Allow from all - -* Si une valeur est spécifiée, la directive suivante est générée: - - AddDefaultCharset off - Order Allow,Deny - Allow from $proxy_acls - - -Dans les exemples donnés ci-dessus, $URL est l'url générée par la réécriture, -et $proxy_acls la valeur du champ proxy_acls spécifiée ci-dessus. -}}} diff --git a/doc/mkiso.twp b/doc/mkiso.twp deleted file mode 100644 index a42ac0c..0000000 --- a/doc/mkiso.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mkiso - -{{{ -mkiso: créer une image iso d'un répertoire - -USAGE - mkiso [options] srcdir [dest.iso] - -OPTIONS - -M, --hfs - créer une image hybride ISO/HFS - -V, --volume - Nom du volume. Par défaut, prendre le nom de base du répertoire - d'origine. La taille est de 32 caractères max. - -A, --application - Description de l'application qui est sur l'image créée. Par défaut, - prendre le nom de base du répertoire d'origine. La taille est de 128 - caractères max. -}}} diff --git a/doc/mkurl.twp b/doc/mkurl.twp deleted file mode 100644 index 660d3a3..0000000 --- a/doc/mkurl.twp +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mkurl - -{{{ -mkurl: Enregistrer une url dans un fichier raccourci - -USAGE - mkurl [output] - -OPTIONS -Par défaut, l'url est enregistrée dans un fichier homepage.url -Mais il est possible de spécifier un fichier avec l'extension .url pour un -raccourci utilisable aussi sous Windows, ou avec l'extension .desktop pour -compatibilité avec le standard XDG -}}} diff --git a/doc/mkusfx.twp b/doc/mkusfx.twp deleted file mode 100644 index 6da2c22..0000000 --- a/doc/mkusfx.twp +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mkusfx - -{{{ -mkusfx: Créer une archive auto-extractible qui installe son contenu avec uinst - -USAGE - mkusfx [options] [--bare] src cmd... - mkusfx [options] [--uinst] src - -OPTIONS - --bare - Installer le contenu de l'archive en lançant la commande 'cmd...' avec - le répertoire courant étant le contenu de l'archive. Typiquement, ce - sera une commande de forme './script', où script est un fichier situé à - la racine de l'archive - Dans ce mode d'installation, l'option --self-contained est ignorée. - --uinst - Installer le contenu de l'archive avec uinst (par défaut) - -o dest - Spécifier le fichier de sortie. Par défaut, il s'agit de - ${src}-installer.run - --tmp-archive - Spécifier qu'il s'agit d'une archive temporaire. Cette archive - s'auto-détruit après utilisation. - --self-contained - Spécifier que l'archive doit pouvoir s'installer même sur un système sur - lequel nutools n'est pas installé. Cette archive contiendra une copie - locale de ulib et uinst.sh -}}} diff --git a/doc/mocifs.twp b/doc/mocifs.twp deleted file mode 100644 index 7cea103..0000000 --- a/doc/mocifs.twp +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mocifs - -{{{ -mocifs: Monter un partage Windows/Samba/CIFS - -USAGE - mocifs [user@]host[/path] [mountpoint] - -Par défaut, le répertoire distant est montée sur un répertoire avec le même nom -de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. -Les options -M et -U permettent de modifier le comportement par défaut. - -OPTIONS - -M - Forcer le montage - -U - Forcer le démontage - -o OPTIONS - Ajouter les options spécifiées à la commande de montage mount.cifs - -u USERNAME - -p PASSWORD - -c USERNAME:PASSWORD - Spécifier les credentials à utiliser pour la connexion -}}} diff --git a/doc/modav.twp b/doc/modav.twp deleted file mode 100644 index 13556bc..0000000 --- a/doc/modav.twp +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: modav - -{{{ -modav: Monter un répertoire sur un hôte distant avec davfs - -USAGE - modav http[s]://host[/path] [mountpoint] - -Par défaut, le répertoire distant est montée sur un répertoire avec le même nom -de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. -Les options -M et -U permettent de modifier le comportement par défaut. - -OPTIONS - -M - Forcer le montage - -U - Forcer le démontage - -o OPTIONS - Ajouter les options spécifiées à la commande de montage - -u USERNAME - -p PASSWORD - -c USERNAME:PASSWORD - Si les credentials à utiliser ne sont pas configuré dans le fichier - /etc/davfs2/secrets, cette option permet de les spécifier. Si cette - option est utilisée, il est possible de rajouter - ask_auth 0 - dans le fichier /etc/davfs2/davfs2.conf pour éviter le conflit qui se - produit quand des informations sont demandées interactivement. C'est le - cas par exemple si une connexion en https est faite et que la chaine de - certification n'est pas configurée. Pour mémoire, cela se fait avec - servercert myCAs.pem - dans le fichier /etc/davfs2/davfs2.conf - Bien entendu, il est préférable de configurer les credentials dans le - fichier /etc/davfs2/secrets avec la syntaxe - url username password - ou - mountpoint username password -}}} diff --git a/doc/moiso.twp b/doc/moiso.twp deleted file mode 100644 index e8ec2be..0000000 --- a/doc/moiso.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: moiso - -{{{ -moiso: Monter une image ISO - -USAGE - moiso image.iso [mountpoint] - -Par défaut, l'image iso est montée sur un répertoire avec le même nom de base. -Si l'image est déjà montée, elle est démontée. Les options -m et -u permettent -de modifier le comportement par défaut. - -OPTIONS - -m - Forcer le montage - -u - Forcer le démontage -}}} diff --git a/doc/mossh.twp b/doc/mossh.twp deleted file mode 100644 index 0a64a06..0000000 --- a/doc/mossh.twp +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mossh - -{{{ -mossh: Monter un répertoire sur un hôte distant avec sshfs - -USAGE - mossh [user@]host[:/path] [mountpoint] - -Par défaut, le répertoire distant est montée sur un répertoire avec le même nom -de base que l'hôte. Si le répertoire distant est déjà monté, il est démonté. -Les options -M et -U permettent de modifier le comportement par défaut. - -OPTIONS - -M - Forcer le montage - -U - Forcer le démontage - -o OPTIONS - Ajouter les options spécifiées à la commande de montage - -s - Equivalent à -o allow_other ou -o allow_root selon que l'on est root ou - non - -u USER - Spécifier le user pour la connexion distante, s'il n'est pas possible de - le spécifier dans l'url. En cas de conflit, la valeur dans l'url est - prioritaire par rapport à cette option. -}}} diff --git a/doc/mysqlcsv.twp b/doc/mysqlcsv.twp deleted file mode 100644 index 24590c0..0000000 --- a/doc/mysqlcsv.twp +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mysqlcsv - -{{{ -mysqlcsv: Faire une requête MySQL et formater la sortie pour traitement avec awkcsv - -USAGE - mysqlcsv [query [db]] [-- mysql options] - -query est la requête sql à exécuter. Si query n'est pas spécifiée, la(les) - requête(s) sql sont prises sur l'entrée standard, ou depuis un fichier - si l'option -f est spécifiée. -db est le nom de la base de données. Cette argument n'est lu que si le nom - de la base de donnée n'est ni spécifié dans le fichier de configuration, - ni spécifié avec l'option -D - -OPTIONS - -h, --host HOST - -P, --port PORT - -u, --user USER - -pPASSWORD - -D, --database DATABASE - Informations de connexion à la base de données - -C, --config CONFIG - Prendre les informations de connexion depuis le fichier spécifié. - Le fichier doit être de la forme - host=HOST - #post=3306 - user=USER - password=PASS - #database=DB - #query=QUERY - Les variables port, database et query sont facultatives. - Les valeurs définies dans ce fichier sont prioritaires par rapport à - celles qui auraient été spécifiées sur la ligne de commande. - Utiliser password=--NOT-SET-- s'il faut se connecter sans mot de passe - Cette option peut être utilisée plusieurs fois, auquel cas les fichiers - sont chargés dans l'ordre. - --profile PROFILE - La variable $PROFILE est définie avec la valeur spécifiée avant de - sourcer les fichiers de configuration. Cela permet d'avoir des fichiers - de configuration qui calculent dynamiquement les paramètres en fonction - de la valeur du profil. - -N, --no-headers - Ne pas afficher les en-têtes - -c, --force - Continuer le traitement même en cas d'erreur - -r, --raw - Ne pas autoriser mysql à mettre en échappement certaines valeurs - retournées par le serveur. Par défaut, les transformations suivantes - sont effectuées: - newline --> \n - tab --> \t - nul --> \0 - \ --> \\ - -n, --nulls - Transformer dans le flux en sortie les valeurs NULL en chaines vides - -f, --input INPUT - Lire la requête depuis le fichier input au lieu de le lire depuis la - ligne de commande ou l'entrée standard. Ne pas spécifier cette option - ou utiliser '-' pour lire depuis l'entrée standard. - Cette option est ignorée si la requête est spécifiée parmi les - arguments. -}}} diff --git a/doc/mysqlloadcsv.twp b/doc/mysqlloadcsv.twp deleted file mode 100644 index d07938f..0000000 --- a/doc/mysqlloadcsv.twp +++ /dev/null @@ -1,112 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: mysqlloadcsv - -{{{ -mysqlloadcsv: Charger une table MySQL avec un fichier csv - -USAGE - mysqlloadcsv [db.]table [fields...] [-- mysql options] - -db est le nom de la base de données -table est le nom de la table à charger -fields est la liste des colonnes. Si cette valeur est spécifiée, il faudra - peut-être utiliser l'option -s pour ignorer le cas échéant la ligne des - en-têtes dans le fichier en entrée. Sinon, les colonnes à utiliser sont - calculées à partir du fichier en entrée. - -Dans les données en entrées, qui doivent être en UTF8, les conversions suivantes -sont effectuées par MySQL: - - \0 --> - \b --> - \n --> - \r --> - \t --> - \Z --> Ctrl+Z - \N --> NULL - -OPTIONS - -h, --host host - -P, --port port - -u, --user user - -ppassword - Informations de connexion à la base de données - -C, --config CONFIG - Prendre les informations de connexion depuis le fichier spécifié. - Le fichier doit être de la forme - host=HOST.TLD - #post=3306 - user=USER - password=PASS - #dbtable=DB.TABLE - #fields=(FIELDS...) - # Il est possible aussi de spécifier DB et TABLE séparément: - #database=DB - #table=TABLE - Les variables port, dbtable et fields sont facultatives. - Les valeurs définies dans ce fichier sont prioritaires par rapport à - celles qui auraient été spécifiées sur la ligne de commande. - Utiliser password=--NOT-SET-- s'il faut se connecter sans mot de passe - Cette option peut être utilisée plusieurs fois, auquel cas les fichiers - sont chargés dans l'ordre. - --profile PROFILE - La variable $PROFILE est définie avec la valeur spécifiée avant de - sourcer les fichiers de configuration. Cela permet d'avoir des fichiers - de configuration qui calculent dynamiquement les paramètres en fonction - de la valeur du profil. - -f, --input INPUT - Fichier en entrée. Ne pas spécifier cette option ou utiliser '-' pour - lire depuis l'entrée standard. - -d, --auto-dbtable DB - Spécifier la base de données avec laquelle se connecter. De plus, si le - nom de la table n'est pas spécifié, prendre par défaut le nom de base du - fichier spécifié avec l'option -f - -s, --skip-lines NBLINES - Nombre de lignes à sauter dans le fichier en entrée - -n, --fake - Ne pas effectuer l'opération. Afficher simplement la commande SQL. - --run - Forcer le lancement de l'opération. Utiliser cette option avec -A pour - créer la table avec les paramètres analysés. - -T, --truncate - Vider la table avant d'effectuer le chargement - -L, --load-data - Charger les données avec la commande 'load data local'. C'est l'option - par défaut. Le fichier est transmis tel quel à MySQL. - -I, --insert-data - Charger les données en générant des commandes 'insert into'. L'effet est - en principe le même avec l'option -L (sauf que certains formats de date - exotiques seront correctement importés). Cette option peut aussi être - utilisée avec l'option -n pour générer une liste de commande à corriger - et/ou adapter. - -U, -k, --update-data KEY - Au lieu de charger de nouvelles données, essayer de mettre à jour la - table. KEY est le nom de la colonne qui est utilisée comme clé. Toutes - les autres colonnes sont les nouvelles données à mettre à jour. Si - aucune ligne ne correspond à une clé donnée, la mise à jour pour cette - ligne est ignorée. - Note: utiliser les options -T et -U ensemble n'a pas de sens, mais -T - est quand même honoré. - -Z, --null-value VALUE - Avec les options -I et -U, considérer que NULL est représenté par la - chaine spécifiée. Par défaut, utiliser \N - -z, --null-is-empty - Avec les options -I et -U, considérer que NULL est représenté par la - chaine vide. Cette option est équivalente à -Z '' - -t, --types [DEFAULT_TYPE,]FIELD:TYPE,... - Spécifier pour chaque champ mentionné le type de donnée à forcer. Le - type 'auto' signifie que le type est autodétecté. C'est la valeur par - défaut. Les autres types valides sont 'str', 'int' et 'date' - Cette option est ignorée avec l'option -L - -A, --analyse - Analyser les données et afficher une requête pour créer une table qui - pourrait contenir ces données. - Cette option implique --fake et affiche simplement la commande SQL. - Pour lancer la commande SQL générée, il faut ajouter l'option --run - APRES cette option -}}} diff --git a/doc/noerr.twp b/doc/noerr.twp deleted file mode 100644 index ca9e012..0000000 --- a/doc/noerr.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: noerr - -{{{ -noerr: lancer une commande en supprimant la sortie d'erreur -}}} diff --git a/doc/noerror.twp b/doc/noerror.twp deleted file mode 100644 index 08abded..0000000 --- a/doc/noerror.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: noerror - -{{{ -noerror: lancer une commande en masquant son code de retour. le code de retour est toujours 0 -}}} diff --git a/doc/noout.twp b/doc/noout.twp deleted file mode 100644 index cc2b854..0000000 --- a/doc/noout.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: noout - -{{{ -noout: lancer une commande en supprimant la sortie standard -}}} diff --git a/doc/nutools.twp b/doc/nutools.twp deleted file mode 100644 index f0accb6..0000000 --- a/doc/nutools.twp +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: nutools - -{{{ -nutools: configurer ou afficher des informations sur nutools - -USAGE - nutools [VERSION] - -OPTIONS - -C, --configure - Faire la configuration pour l'utilisateur courant en appelant uenv -u - Avec cette option, l'option -l (ou --local-profiles) est aussi reconnue - et est passée directement à uenv - -v, --version - Afficher la version de nutools installée. C'est l'option par défaut - -c, --check - Calculer si la version installée correspond à la version spécifiée - -o, --oper OPERATOR - Spécifier l'opérateur à utiliser avec l'option --check (par défaut, - utiliser l'opérateur ge, qui permet de vérifier si la version minimum - spécifiée est installée) - --eq - --ne - --lt - --le - --gt - --ge - --same - --diff - Ces options sont des raccourcis. L'option '--OP' est équivalente à - '--check --op OP' -}}} diff --git a/doc/openurl.twp b/doc/openurl.twp deleted file mode 100644 index ec66ae0..0000000 --- a/doc/openurl.twp +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: openurl - -{{{ -openurl: Ouvrir une URL dans un navigateur - -USAGE - openurl -}}} diff --git a/doc/pdev.twp b/doc/pdev.twp deleted file mode 100644 index 5ad1858..0000000 --- a/doc/pdev.twp +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: pdev - -{{{ -pdev: basculer sur une branche de développement - -USAGE - pdev [FEATURE [SOURCE]] - pdev -m|-l|-d [FEATURE] - -- Vérifier l'existence de la branche develop. La créer si nécessaire en la - basant sur [origin/]master. -- Vérifier s'il n'y a pas de modifications locales. Sinon, proposer de faire un - commit ou un stash. -- Si FEATURE est spécifié, et si on n'est pas déjà sur cette branche, basculer - vers cette nouvelle branche. S'il s'agit d'une nouvelle branche, la baser sur - la branche SOURCE, qui vaut par défaut develop -- Si FEATURE n'est pas spécifié, basculer sur develop s'il s'agit de la seule - solution, sinon afficher un menu pour choisir la branche de destination. - -OPTIONS - -C, --projdir PROJDIR - Spécifier le répertoire de base du projet qui est dans git. Par défaut, - on travaille dans le répertoire courant et on laisse git trouver le - répertoire de base du projet. Avec cette option, le répertoire courant - est modifié avant de lancer les commandes git. - -O, --origin ORIGIN - Spécifier le nom de l'origine. Par défaut, utiliser 'origin' - -o, --offline - En cas de création d'une branche, ne pas pousser vers l'origine; ne pas - tenter le cas échéant de traquer la branche dans l'origine; ne pas - supprimer une branche dans l'origine. Cette option est automatiquement - activée si la variable UTOOLS_VCS_OFFLINE est définie. - --online - Annuler l'effet de la variable UTOOLS_VCS_OFFLINE: forcer le mode online - --sync - Faire un certain nombre d'opération pour 'corriger' le dépôt local: pour - chacune des branches distantes, vérifier qu'il existe une branche locale - qui la traque, et pour chaque feature branche locale, vérifier qu'il - existe une branche distante associée. Cette option nécessite --online - - -s, --squash COMMIT_MSG - Si la branche actuelle est une feature branch, la merger comme un seul - commit avec le message COMMIT_MSG dans develop puis la supprimer. Puis - basculer sur la branche develop. - Cette option ne devrait pas être utilisée avec -k, puisque bien que les - modifications soient mergées, la branche elle-même n'est pas considérée - comme mergée. Les résultats sont donc indéfinis si la branche est mergée - à plusieurs reprises. - -b, --rebase - Si la branche actuelle est une feature branch, lancer 'git rebase -i' - sur la feature branch. Cela permet de réordonner les commits pour - nettoyer l'historique avant de fusionner la branche avec -m - Cette option devrait le cas échéant être utilisée immédiatement avant -m - ou alors il faut forcer le push et communiquer avec l'équipe sur le fait - que la branche de feature a été rebasée. - -m, --merge - Si la branche actuelle est une feature branch, la merger dans develop - puis la supprimer. Puis basculer sur la branche develop. - --merge-log - Ajouter un résumé des modifications sur la feature branch en ajoutant le - log en une ligne de chaque commit dans le message du merge. Cette option - n'est en principe pas nécessaire puisque 'prel -um' intègre la liste des - commits dans CHANGES.txt - -k, --keep - Avec l'option -m, ne pas supprimer une feature branch après l'avoir - fusionnée dans develop. Cela permet d'intégrer les modifications petit à - petit. - --delete - Supprimer une feature branch, à condition qu'elle aie déjà été - entièrement fusionnée dans la branch develop - --force-delete - Supprimer une feature branch, même si elle n'a pas encore été fusionnée - dans la branche develop - - -l, --log - -d, --diff - Afficher les modifications entre deux branches. L'option --log affiche - les modifications dans l'ordre alors que --diff affiche les différences - sous forme de diff. Les deux options peuvent être combinées et ont - l'effet de 'git log -p' - La branche comparée, s'il elle n'est pas spécifiée, est par défaut la - branche courante. S'il s'agit d'une feature branch, elle est comparée à - develop. S'il s'agit de la branche develop, elle est comparée à master. -}}} diff --git a/doc/prel.twp b/doc/prel.twp deleted file mode 100644 index eb0f4cf..0000000 --- a/doc/prel.twp +++ /dev/null @@ -1,105 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: prel - -{{{ -prel: basculer sur une branche de release - -USAGE - prel -u [SOURCE] - prel -c [RELEASE [SOURCE]] - prel -m|-l|-d [RELEASE] - -- Vérifier s'il n'y a pas de modifications locales. Sinon, proposer de faire un - commit ou un stash. -- Avec l'option -c, s'il existe une branche de release, proposer de basculer - vers elle ou sur la branche master. Sinon, basculer sur la branche master. -- Avec l'option -u, proposer ou fixer une branche de release à créer. Si elle - existe déjà, basculer vers elle. Sinon, la créer en la basant sur SOURCE, qui - vaut par défaut develop - -OPTIONS - -C, --projdir PROJDIR - Spécifier le répertoire de base du projet qui est dans git. Par défaut, - on travaille dans le répertoire courant et on laisse git trouver le - répertoire de base du projet. Avec cette option, le répertoire courant - est modifié avant de lancer les commandes git. - -O, --origin ORIGIN - Spécifier le nom de l'origine. Par défaut, utiliser 'origin' - -o, --offline - En cas de création d'une branche, ne pas pousser vers l'origine; ne pas - tenter le cas échéant de traquer la branche dans l'origine; ne pas - supprimer une branche dans l'origine. Cette option est automatiquement - activée si la variable UTOOLS_VCS_OFFLINE est définie. - --online - Annuler l'effet de la variable UTOOLS_VCS_OFFLINE: forcer le mode online - - -c, --checkout - Basculer vers une branche de release existante. C'est l'option par - défaut. Si aucune branche de release n'existe, basculer vers master - -u, --update - Préparer une nouvelle release. Utiliser une des options -x, -z ou -p - pour spécifier le type de release à préparer. Si la branche qui serait - créée pour le type de release existe déjà, basculer vers cette branche. - S'il faut la créer, la baser sur la branche SOURCE, qui vaut par défaut - develop - --menu - -x, --major - -z, --minor - -p, --patchlevel - Utilisé avec l'option -u, soit afficher un menu pour choisir la version - de la nouvelle release (par défaut), soit préparer respectivement une - release majeure, mineure, ou pour correction de bug. - -v-OPT - Avec l'option -u, spécifier une option de pver permettant de choisir la - version de la nouvelle release. Les options supportées sont -v, -l, -a, - -b, -r et -R. Par exemple, si la version actuelle sur la branche master - est 0.2.3, les options '-uz -v-lbeta' permettent de préparer la release - 0.3.0-beta - En principe, cette option n'a pas à être utilisée, puisque dans une - branche de release, on peut faire vivre les versions de pré-release - jusqu'à la release finale. Ainsi, la branche de release est nommée - d'après la version finale, mais le projet peut recevoir une version de - pré-release incrémentale. - -w, --write - Si une nouvelle branche est créée avec -u, mettre à jour le fichier - VERSION.txt avec pver. C'est l'option par défaut. - -n, --no-write - Si une nouvelle branche est créée avec -u, NE PAS mettre à jour le - fichier VERSION.txt avec pver. Utiliser cette option si la mise à jour - du numéro de version doit être faite d'une manière particulière. - -e, --edit - Editer le fichier CHANGES.txt autogénéré par -u -w - Cette option est surtout utile si -m est utilisé avec -u, pour donner la - possibilité de corriger la liste des modifications avant leur - enregistrement définitif. - - -m, --merge - Si la branche actuelle est une branche de release, ou s'il existe une - branche de release, la merger dans master, puis dans develop, puis la - supprimer. Puis basculer sur la branche master. - S'il n'existe pas de branche de release, proposer de fusionner les - modifications de la branche develop dans la branche master, sans - préparer de branche de release au préalable. - --delete - Supprimer une branche de release, à condition qu'elle aie déjà été - entièrement fusionnée dans la branch master - --force-delete - Supprimer une branche de release, même si elle n'a pas encore été - fusionnée dans la branche master - - -s, --summary - Afficher la liste des différences entre la branche develop et la branche - master, comme elle serait générée par les options -u -w pour le fichier - CHANGES.txt - -l, --log - Afficher les modifications actuellement effectuée dans la branche de - release par rapport à develop. - -d, --diff - Afficher les modifications actuellement effectuée dans la branche de - release par rapport à develop, sous forme de diff. -}}} diff --git a/doc/pver.md b/doc/pver.md deleted file mode 100644 index b87a431..0000000 --- a/doc/pver.md +++ /dev/null @@ -1,98 +0,0 @@ -# pver - -~~~ -pver: gérer des numéros de version selon les règles du versionage sémantique v2.0.0 (http://semver.org/) - -USAGE - pver [options] - -OPTIONS - -f, --file VERSIONFILE - Gérer le numéro de version se trouvant dans le fichier spécifié. Le - fichier est créé si nécessaire. C'est l'option par défaut si un fichier - nommé VERSION.txt se trouve dans le répertoire courant. - -e, --maven POMFILE - Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié. - Le fichier DOIT exister. C'est l'option par défaut si un fichier nommé - pom.xml se trouve dans le répertoire courant. - -F, --file-string VERSIONFILE - Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui - vaut par défaut VERSION.txt) - -g, --git-string [branch:]VERSIONFILE - Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui - vaut par défaut VERSION.txt) dans la branche BRANCH (qui vaut par défaut - master) du dépôt git situé dans le répertoire courant. - -s, --string VERSION - Prendre pour valeur de départ le numéro de version spécifié - - --show - Afficher le numéro de version. C'est l'action par défaut - --allow-empty - Supporter que la version puisse ne pas être spécifiée ni trouvée. Dans - ce cas, ne pas assumer que la version effective est 0.0.0 - Avec --show et --update, ne rien afficher si la version est vide. - --check - Vérifier que le numéro de version est conforme aux règles du versionage - sémantique - --convert - --no-convert - Activer (resp. désactiver) la conversion automatique. Par défaut, si la - version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est - convertie automatiquement au format sémantique x.z.p+rYYYYMMDD - --eq VERSION - --ne VERSION - --lt VERSION - --le VERSION - --gt VERSION - --ge VERSION - --same VERSION - --diff VERSION - Comparer avec la version spécifiée. Les opérateurs --eq, --ne, --lt, - --le, --gt, et --ge ignorent l'identifiant de build (comme le demande la - règle du versionage sémantique). Les opérateurs --same et --diff - comparent aussi les identifiants de build. - -v, --set-version VERSION - Spécifier un nouveau numéro de version qui écrase la valeur actuelle. - Cette option ne devrait pas être utilisée en temps normal parce que cela - va contre les règles du versionage sémantique. - --prel - Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Le - numéro de version est obtenu à partir du nom de la branche git courante, - qui doit être de la forme release-VERSION - -u, --update - Mettre à jour le numéro de version. - - --menu - Afficher un menu permettant de choisir le composant de la version à - incrémenter - -x, --major - Augmenter le numéro de version majeure - -z, --minor - Augmenter le numéro de version mineure. C'est la valeur par défaut. - -p, --patchlevel - Augmenter le numéro de patch - - -l, --prelease ID - Spécifier un identifiant de pré-release, à ajouter au numéro de version. - -a, --alpha - -b, --beta - -r, --rc - Spécifier une pré-release de type alpha, beta, ou rc. Si la version est - déjà dans ce type, augmenter la dernière valeur numérique des composants - de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3, - rc1 devient rc2 - XXX ces fonctions ne sont pas encore implémentées - -R, --final, --release - Supprimer l'identifiant de prérelease - - -m, --metadata ID - Spécifier un identifiant de build, à ajouter au numéro de version. - -M, --vcs-metadata - Spécifier l'identifiant à partir de la révision actuelle dans le - gestionnaire de version. Note: pour le moment, seul git est supporté. - --add-metadata ID - Ajouter l'identifiant spécifié à la valeur actuelle, au lieu de la - remplacer. Séparer l'identifiant de la valeur précédente avec un '.' -~~~ - --*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/pver.twp b/doc/pver.twp deleted file mode 100644 index 243b6b1..0000000 --- a/doc/pver.twp +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: pver - -{{{ -pver: gérer des numéros de version selon les règles du versionage sémantique v2.0.0 (http://semver.org/) - -USAGE - pver [options] - -OPTIONS - -f, --file VERSIONFILE - Gérer le numéro de version se trouvant dans le fichier spécifié. Le - fichier est créé si nécessaire. C'est l'option par défaut si un fichier - nommé VERSION.txt se trouve dans le répertoire courant. - -e, --maven POMFILE - Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié. - Le fichier DOIT exister. C'est l'option par défaut si un fichier nommé - pom.xml se trouve dans le répertoire courant. - -F, --file-string VERSIONFILE - Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui - vaut par défaut VERSION.txt) - -g, --git-string [branch:]VERSIONFILE - Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui - vaut par défaut VERSION.txt) dans la branche BRANCH (qui vaut par défaut - master) du dépôt git situé dans le répertoire courant. - -s, --string VERSION - Prendre pour valeur de départ le numéro de version spécifié - - --show - Afficher le numéro de version. C'est l'action par défaut - --allow-empty - Supporter que la version puisse ne pas être spécifiée ni trouvée. Dans - ce cas, ne pas assumer que la version effective est 0.0.0 - Avec --show et --update, ne rien afficher si la version est vide. - --check - Vérifier que le numéro de version est conforme aux règles du versionage - sémantique - --convert - --no-convert - Activer (resp. désactiver) la conversion automatique. Par défaut, si la - version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est - convertie automatiquement au format sémantique x.z.p+rYYYYMMDD - --eq VERSION - --ne VERSION - --lt VERSION - --le VERSION - --gt VERSION - --ge VERSION - --same VERSION - --diff VERSION - Comparer avec la version spécifiée. Les opérateurs --eq, --ne, --lt, - --le, --gt, et --ge ignorent l'identifiant de build (comme le demande la - règle du versionage sémantique). Les opérateurs --same et --diff - comparent aussi les identifiants de build. - -v, --set-version VERSION - Spécifier un nouveau numéro de version qui écrase la valeur actuelle. - Cette option ne devrait pas être utilisée en temps normal parce que cela - va contre les règles du versionage sémantique. - --prel - Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Le - numéro de version est obtenu à partir du nom de la branche git courante, - qui doit être de la forme release-VERSION - -u, --update - Mettre à jour le numéro de version. - - --menu - Afficher un menu permettant de choisir le composant de la version à - incrémenter - -x, --major - Augmenter le numéro de version majeure - -z, --minor - Augmenter le numéro de version mineure. C'est la valeur par défaut. - -p, --patchlevel - Augmenter le numéro de patch - - -l, --prelease ID - Spécifier un identifiant de pré-release, à ajouter au numéro de version. - -a, --alpha - -b, --beta - -r, --rc - Spécifier une pré-release de type alpha, beta, ou rc. Si la version est - déjà dans ce type, augmenter la dernière valeur numérique des composants - de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3, - rc1 devient rc2 - XXX ces fonctions ne sont pas encore implémentées - -R, --final, --release - Supprimer l'identifiant de prérelease - - -m, --metadata ID - Spécifier un identifiant de build, à ajouter au numéro de version. - -M, --vcs-metadata - Spécifier l'identifiant à partir de la révision actuelle dans le - gestionnaire de version. Note: pour le moment, seul git est supporté. - --add-metadata ID - Ajouter l'identifiant spécifié à la valeur actuelle, au lieu de la - remplacer. Séparer l'identifiant de la valeur précédente avec un '.' -}}} diff --git a/doc/pz.twp b/doc/pz.twp deleted file mode 100644 index a49c742..0000000 --- a/doc/pz.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: pz - -{{{ -pz: faire une archive du projet - -USAGE - pz - -OPTIONS - -C, --projdir PROJDIR - Spécifier le répertoire de base du projet qui est dans git. Par défaut, - on travaille dans le répertoire courant et on laisse git trouver le - répertoire de base du projet. Avec cette option, le répertoire courant - est modifié avant de lancer les commandes git. - -d, --destdir DESTDIR - Spécifier le répertoire dans lequel générer l'archive. Par défaut, - prendre le répertoire parent du répertoire de base du dépôt. -}}} diff --git a/doc/reptyr.cgo.twp b/doc/reptyr.cgo.twp deleted file mode 100644 index f1b3003..0000000 --- a/doc/reptyr.cgo.twp +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: reptyr.cgo - -{{{ -Usage: reptyr [-s] PID - reptyr -l|-L [COMMAND [ARGS]] - -l Create a new pty pair and print the name of the slave. - if there are command-line arguments after -l - they are executed with REPTYR_PTY set to path of pty. - -L Like '-l', but also redirect the child's stdio to the slave. - -s Attach fds 0-2 on the target, even if it is not attached to a tty. - -h Print this help message and exit. - -v Print the version number and exit. - -V Print verbose debug output. -}}} diff --git a/doc/rmtildes.twp b/doc/rmtildes.twp deleted file mode 100644 index b9d56a5..0000000 --- a/doc/rmtildes.twp +++ /dev/null @@ -1,16 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: rmtildes - -{{{ -rmtildes: supprimer les fichiers *~ dans le répertoire courant - -USAGE - rmtildes [dir [glob]] - -Par défaut, dir==. et glob==*~ -}}} diff --git a/doc/rruns.twp b/doc/rruns.twp deleted file mode 100644 index a492593..0000000 --- a/doc/rruns.twp +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: rruns - -{{{ -rruns: Déploiement distant avec runs - -USAGE - rruns [-h hosts] [-T tmproot] rscriptname name=value... - rruns [-h hosts] [-T tmproot] @recipe name=value... - rruns [-h hosts] [-T tmproot] -f rscript name=value... - rruns [-h hosts] [-T tmproot] -r recipe name=value... - -Lancer ce script sans argument (hors options) est équivalent à le lancer avec -l'argument @default - -OPTIONS - -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé - sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir - se connecter par mot de passe pour configurer la connexion par clé. - Si l'on veut configurer la connexion par clé pour le user root, mais que - ce n'est pas possible de se connecter par mot de passe avec le user root - sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, - il est possible de faire la configuration avec '--configure root'. La - commande serait alors - rruns -h user@host --configure root - -T tmproot - Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple - /var/tmp. Cette option est utile pour les vservers, qui ont par défaut - un /tmp minuscule de 16 Mo. - -S ssh - Spécifier le programme à utiliser pour la connection par ssh. - -h host - -h @hostsfile - Spécifier un ou plusieurs hôtes sur lequels faire le déploiement. Pour - spécifier plusieurs hôtes, il est possible d'utiliser plusieurs fois - l'option -h, ou spécifier en une seule fois plusieurs hôtes en les - séparant par un espace ou le caractère ':', e.g. 'host1 host2' ou - 'host1:host2'. Si la spécification contient les caractères { et }, - l'expansion est effectuée, e.g - -h 'root@{host1,host2}.univ.run' - Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur - root. Il est possible de spécifier un autre utilisateur avec la syntaxe - user@host, e.g -h user@host - La forme @hostsfile permet de lire la liste des hôtes depuis le fichier - hostsfile, à raison d'un hôte par ligne. - Si cette option n'est pas spécifiée, et que le répertoire courant est - dans un des répertoires de $RUNSHOSTSPATH, sélectionner l'hôte - correspondant. Sinon, l'utilisateur doit saisir l'hôte distant de façon - interactive. - -f RSCRIPT - Lancer le script individuel spécifié au lieu de chercher dans les - répertoires $RUNS{SCRIPTS,HOSTS}PATH - -r RECIPE - Lancer les scripts spécifiés dans le fichier de recettes individuel - spécifié. - -z Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone - -o OUTPUT - Générer l'archive à lancer sur l'hôte distant au lieu de faire le - déploiement. Si plusieurs hôtes sont spécifiés, OUTPUT est considéré - comme un nom de base auquel est ajouté le nom de l'hôte sur lequel - l'archive doit être déployée. - --init - --no-init - Forcer (resp. empêcher) la création des répertoires d'hôte correspondant - aux hôtes spécifiés. Par défaut, la création des répertoires d'hôte est - effectuée uniquement si ce script est lancé sans argument. - --sysinfos - Après un déploiement réussi sur l'hôte distant, inscrire si ce n'est - déjà fait le résultat de la commande usysinfos dans le fichier - sysinfos.conf du répertoire d'hôte. - Cette option est automatiquement activée si ce script est lancé sans - argument (hors options). -}}} diff --git a/doc/ruinst.twp b/doc/ruinst.twp deleted file mode 100644 index bfd5161..0000000 --- a/doc/ruinst.twp +++ /dev/null @@ -1,52 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ruinst - -{{{ -ruinst: Déploiement distant avec uinst - -USAGE - ruinst [-h host] [-T tmproot] [-- options de uinst] - -note: à cause d'une limitation de makeself, les options de uinst ne devraient -pas contenir d'espaces ni de caractères spéciaux. L'échappement de ces -caractères n'est pas garanti. - -OPTIONS - -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé - sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir - se connecter par mot de passe pour configurer la connexion par clé. - Si l'on veut configurer la connexion par clé pour le user root, mais que - ce n'est pas possible de se connecter par mot de passe avec le user root - sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, - il est possible de faire la configuration avec '--configure root'. La - commande serait alors - ruinst -h user@host --configure root - Si l'hôte distant n'a pas sudo ou si sudo n'est pas configuré, il faut - rajouter l'option --uses-su, e.g: - ruinst -h user@host --configure root --uses-su - -T tmproot - Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple - /var/tmp. Cette option est utile pour les vservers, qui ont par défaut - un /tmp minuscule de 16 Mo. - -S, --ssh ssh - Spécifier le programme à utiliser pour la connection par ssh. - -h hosts - -h @hostsfile - Spécifier un ou plusieurs hôtes sur lequels faire le déploiement. Pour - spécifier plusieurs hôtes, il est possible d'utiliser plusieurs fois - l'option -h, ou spécifier en une seule fois plusieurs hôtes en les - séparant par un espace ou le caractère ':', e.g. 'host1 host2' ou - 'host1:host2'. Si la spécification contient les caractères { et }, - l'expansion est effectuée, e.g - -h 'root@{host1,host2}.univ.run' - Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur - root. Il est possible de spécifier un autre utilisateur avec la syntaxe - user@host, e.g -h user@host - La forme @hostsfile permet de lire la liste des hôtes depuis le fichier - hostsfile, à raison d'un hôte par ligne. -}}} diff --git a/doc/rumount.twp b/doc/rumount.twp deleted file mode 100644 index 0e3573f..0000000 --- a/doc/rumount.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:18 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: rumount - -{{{ -rumount: démonter un système de fichier récursivement - -USAGE - rumount mountpoint - -Démonter tous les systèmes de fichiers qui sont montés en-dessous de mountpoint -puis démonter mountpoint. Démonter aussi tous les systèmes de fichiers -bind-montés à partir d'un sous-répertoire de mountpoint. -}}} diff --git a/doc/runs.twp b/doc/runs.twp deleted file mode 100644 index 978e900..0000000 --- a/doc/runs.twp +++ /dev/null @@ -1,62 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: runs - -{{{ -runs: Lancer un script avec le protocole runs - -USAGE - runs [options] rscriptname name=value... - runs [options] @recipe name=value... - runs [options] -f rscript name=value... - runs [options] -r recipe name=value... - -OPTIONS -Configuration - --init - --verify - Vérifier le nom d'hôte et créer si nécessaire le répertoire d'hôte - correspondant à l'hôte courant ou à l'hôte spécifié avec l'option -h - Avec --verify, la création du répertoire d'hôte n'est pas effectuée. - --sysinfos DATA - Avec l'option --init, initialiser le fichier sysinfos.conf avec DATA, si - le fichier n'a pas déjà été provisionné. Sans cette option, un fichier - vide avec des commentaires est créé à la place. - --create RSCRIPT - Créer un modèle de script avec le nom donné. - Avec l'option -h, le script est créé dans le répertoire d'hôte - correspondant à l'hôte spécifié - -Gestion des scripts - -s Forcer l'exécution du script avec l'utilisateur root si ce n'est pas - déjà le cas - -f RSCRIPT - Lancer le script individuel spécifié au lieu de chercher dans les - répertoires de $RUNSSCRIPTSPATH - -r RECIPE - Lancer les scripts spécifiés dans le fichier de recettes individuel - spécifié. - -h HOSTNAME[.DOMAIN] - Spécifier que les scripts sont destinés à être lancés sur l'hôte - spécifié. Les scripts sont alors aussi cherchés dans les répertoires - {$RUNSHOSTSPATH}/$hostname.$domain (par défaut) et - {$RUNSHOSTSPATH}/$domain/$hostname (le cas échéant) - L'option --host est équivalente, sauf que son argument est facultatif et - que sa valeur par défaut est l'hôte courant, soit sulfure - --list - Afficher la liste des scripts qui sont disponibles. Avec l'option -h, - inclure aussi les scripts spécifiques à cet hôte. - Avec cette option, les arguments supplémentaires agissent comme des - filtres (regexp utilisée avec l'opérateur == de la commande [[). Les - noms des scripts doivent valider au moins un filtre. - --info - Afficher la la description du script et la valeur de chaque variable - définies - --desc-only - Afficher seulement la description du script - -z Forcer la réinstallation des scripts qui se basent sur shouldrun/setdone -}}} diff --git a/doc/runsconfig.twp b/doc/runsconfig.twp deleted file mode 100644 index 3cc4a10..0000000 --- a/doc/runsconfig.twp +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: runsconfig - -{{{ -runsconfig: Gérer un répertoire d'hôte de runs - -USAGE - runsconfig -c [host [destdir]] - runsconfig -t -- args... - -OPTIONS - -c, --create - Créer un nouveau répertoire de configuration pour un hôte - -d, --destdir DESTDIR[=runs] - Nom du répertoire local de configuration. - - -t, --template [OPT] - Gérer les fichiers du répertoire local avec templatectl. La valeur de - cette option est utilisée comme argument court pour l'invocation de - templatectl, e.g - runsconfig -tm args - est équivalent à - templatectl -m args - Les arguments qui restent sont passés tels quels à templatectl - Les options courantes de templatectl -l, -v, -m, -L sont disponibles - directement - --help-template - Afficher l'aide concernent la gestion des templates. - Equivalent à -t -- --help - -h, --host HOST - Spécifier l'hôte. Equivalent à -v host=HOST -}}} diff --git a/doc/runsmod.twp b/doc/runsmod.twp deleted file mode 100644 index 9d90c90..0000000 --- a/doc/runsmod.twp +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: runsmod - -{{{ -runsmod: récupérer des dépôts git à usage de runs - -USAGE - runsmod [options] [-h host] [modules...] - -Tous les dépôts spécifiés dans la configuration sont récupérés. Si des modules -sont spécifiés, les dépôts correspondants sont récupérés aussi. Avec l'option --h, des dépôts spécifiques à l'hôte peuvent éventuellement être récupérés en -plus. - -OPTIONS - -c, --config CONFIG - Spécifier un fichier de configuration à charger au lieu de la valeur par - défaut ~/etc/default/runs - --prod - --devel - Forcer un mode de sélection des urls. En mode production, préférer pour - le clonage les urls de production, qui sont en principe accessibles sans - authentification et en lecture seule. En mode développement, préférer - pour le clonage les urls de développement, qui sont en principe - accessibles par clé ssh et en lecture/écriture - --no-fetch - Ne rien récupérer. Utile avec --update-repolist - -N, --no-host - -A, --all-hosts - -H, -h, --host HOST - -T, --this-host - Options permettant de spécifier l'hôte pour la récupération de dépôts - spécifiques. - --no-host demande explicitement à ce qu'aucun hôte ne soit spécifié - --all-hosts sélectionne tous les dépôts spécifiques - --host récupère uniquement les dépôts pour l'hôte spécifié - --this-host équivaut à --host sulfure - L'option par défaut est --this-host en mode production et --all-hosts en - mode développement - --update-repolist - Forcer la mise à jour de la liste des dépôts. En principe, cette mise à - jour n'est pas faite plus d'une fois par période de 24 heures. - -0, --offline - -n, --no-pull - -u, --pull - Spécifier le mode opératoire pour la récupération des dépôts. - En mode --offline, ni clone ni pull ne sont autorisés. Le module doit - avoir déjà été cloné. - En mode --no-pull, seul le clonage est autorisé, e.g. le dépôt est - cloné si ce n'est pas déjà le cas. - En mode --pull, cloner le dépôt si ce n'est pas déjà le cas, ou le - mettre à jour le dépôt avant de l'utiliser s'il avait déjà été cloné. - Par défaut, utiliser --pull en mode production et --no-pull en mode - développement. - -i, --identity IDENTITY_FILE - Spécifier le fichier depuis lequel lire la clé privée pour les - connexions par ssh. - -o, --output OUTPUT - Spécifier un fichier dans lequel écrire des définitions de variables, - notamment REPODIRS qui reçoit la liste des chemins des dépôts qui ont - été récupérés. De plus, les variables RUNSSCRIPTSPATH, RUNSMODULESPATH - et RUNSHOSTSPATH sont définies. - -a, --append-output - Ajouter au fichier OUTPUT au lieu de l'écraser -}}} diff --git a/doc/rwoinst.twp b/doc/rwoinst.twp deleted file mode 100644 index 0415fac..0000000 --- a/doc/rwoinst.twp +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: rwoinst - -{{{ -rwoinst: Déploiement distant avec woinst - -USAGE - rwoinst [-H host] [-T tmproot] ... [-- options de woinst] - -OPTIONS - -C Ne pas faire le déploiement. Configurer uniquement la connexion par clé - sur les hôtes distants spécifiés pour le user spécifié. Il faut pouvoir - se connecter par mot de passe pour configurer la connexion par clé. - Si l'on veut configurer la connexion par clé pour le user root, mais que - ce n'est pas possible de se connecter par mot de passe avec le user root - sur l'hôte distant, et qu'il existe un user sudoer sur l'hôte distant, - il est possible de faire la configuration avec '--configure root'. La - commande serait alors - rwoinst -H user@host --configure root - -T tmproot - Spécifier le répertoire temporaire sur l'hôte distant, comme par exemple - /var/tmp. Cette option est utile pour les vservers, qui ont par défaut - un /tmp minuscule de 16 Mo. - -S ssh - Spécifier le programme à utiliser pour la connection par ssh. - -H host - Spécifier un hôte distant sur lequel faire le déploiement. Plusieurs - options -H peuvent être spécifiées, ou alors on peut séparer plusieurs - hôtes par ':', e.g. -H host1:host2 - Par défaut, la connexion sur l'hôte distant se fait avec l'utilisateur - root. Il est possible de spécifier un autre utilisateur avec la syntaxe - user@host, e.g -H user@host -}}} diff --git a/doc/sqlcsv.twp b/doc/sqlcsv.twp deleted file mode 100644 index fe91aba..0000000 --- a/doc/sqlcsv.twp +++ /dev/null @@ -1,98 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: sqlcsv - -{{{ -USAGE: - sqlcsv [query] - -query est la requête SQL à exécuter. Si query n'est pas spécifiée ou si elle - vaut '-', la requête SQL est lue sur l'entrée standard, ou depuis un - fichier si l'option -f est spécifiée. - -DEMARRAGE - -Au démarrage, les répertoires de configuration (utilisateur ~/.sqlcsv et système -/etc/sqlcsv) sont analysés. Les fichiers *.jar situés dans ces répertoires sont -ajoutés au CLASSPATH. La présence de certains fichiers est testée pour activer -éventuellement les logs détaillés. - -OPTIONS - -C, --config CONFIG - Prendre les informations de connexion depuis le fichier de propriété - spécifié. Pour l'identifiant CONN, la propriété 'CONN.url' doit exister - dans ce fichier avec la valeur de l'url jdbc de connexion. De plus, les - propriétés 'CONN.user' et 'CONN.password' contiennent respectivement si - nécessaire le nom et le mot de passe de connexion. La propriété - 'loglevel', si elle existe, est utilisée pour configurer le niveau - d'affichage des logs, comme avec l'option --loglevel - Si cette option n'est pas spécifiée, un fichier nommé sqlcsv.properties - est recherché dans l'ordre: dans le répertoire courant, dans le - répertoire de configuration utilisateur, puis dans le répertoire de - configuration système. Si le fichier est trouvé, il est chargé - automatiquement. - -l, --conn CONN - Spécifier l'identifiant (ou l'url) de connexion. Cette information est - obligatoire. Si cette option n'est pas fournie, il faut spécifier un - fichier de configuration avec l'option -C dans lequel *une seule* - propriété 'CONN.url' est définie. - -u, --user USER - -p, --password PASSWORD - Spécifier un nom de connexion et un mot de passe si l'url ne le fournit - pas. Ces valeurs ont la priorité sur les valeurs éventuellement déjà - présentes dans le fichier de propriété. - -f, --input INPUT - Lire la requête depuis le fichier INPUT au lieu de la lire depuis la - ligne de commande ou l'entrée standard. Ne pas spécifier cette option ou - utiliser '-' pour lire depuis l'entrée standard. Cette option est - ignorée si la requête est fournie sur la ligne de commande. - -o, --output OUTPUT - Ecrire le résultat dans le fichier OUTPUT. Utiliser '-' pour spécifier - la sortie standard (c'est la valeur par défaut). S'il y a plusieurs - requêtes et que le fichier de sortie n'est pas la sortie standard, - ajouter un numéro incrémental au nom du fichier en sortie pour chaque - requête. Sinon, il est possible de spécifier plusieurs fois cette option - pour nommer les fichiers correspondant à chaque requête. - -t, --autocommit - Activer le mode autocommit - -c, --ignore-io-error - Continuer le traitement même en cas d'erreur du système de fichiers. - Cependant le traitement s'arrête et la transaction est annulée si une - autre erreur se produit. - -y, --ignore-any-error - Continuer le traitement même en cas d'erreur quelconque. - -n, --no-headers - Ne JAMAIS inclure les en-têtes dans la sortie, même avec l'option -h - -a, --append - Ajouter le résultat au fichier OUTPUT au lieu de l'écraser. - -A, --auto-na - Activer les option -n -a si le fichier OUTPUT existe et qu'il est non - vide. Le test n'est effectué que pour le premier fichier spécifié. - -s, --same-output - Utiliser le même fichier pour écrire le résultat de toutes les requêtes. - Normalement, un numéro incrémental est ajouté au fichier en sortie si - plusieurs requêtes sont spécifiées. Si les en-têtes sont les mêmes, - ajouter le résultat au fichier directement à la suite. Sinon, sauter une - ligne blanche et afficher les nouveaux en-têtes. - -h, --force-headers - En cas d'écriture du résultat de plusieurs requêtes dans un même - fichier, ne pas tenter de concaténer les résultats même si les en-têtes - sont les mêmes. - --uc-output - Ajouter dans la sortie les résultat de toutes les requêtes, pas - seulement celles de type DQML - --loglevel LOGLEVEL - Spécifier le niveau de logs à afficher. Les valeurs valides sont à - choisir parmi ALL, FINEST, FINER, FINE, CONFIG, INFO, WARNING, ERROR - La présence de certains fichiers dans les répertoires de configuration - utilisateur ou système configure les logs avant que les options de la - ligne de commande ne soient analysés: un fichier DEBUG fait démarrer - l'application avec le niveau de log ALL ce qui permet de voir les logs - concernant le chargement des jar. Un fichier SQL_DEBUG permet d'activer - la trace de DriverManager. Exemple: - mkdir -p ~/.sqlcsv && touch ~/.sqlcsv/{DEBUG,SQL_DEBUG} -}}} diff --git a/doc/EnsureVM.md b/doc/tools/EnsureVM.md similarity index 100% rename from doc/EnsureVM.md rename to doc/tools/EnsureVM.md diff --git a/doc/SKvm.md b/doc/tools/SKvm.md similarity index 100% rename from doc/SKvm.md rename to doc/tools/SKvm.md diff --git a/doc/SVirtualBox.md b/doc/tools/SVirtualBox.md similarity index 100% rename from doc/SVirtualBox.md rename to doc/tools/SVirtualBox.md diff --git a/doc/_root.md b/doc/tools/_root.md similarity index 100% rename from doc/_root.md rename to doc/tools/_root.md diff --git a/doc/apacheconfig.md b/doc/tools/apacheconfig.md similarity index 100% rename from doc/apacheconfig.md rename to doc/tools/apacheconfig.md diff --git a/doc/authftp.md b/doc/tools/authftp.md similarity index 100% rename from doc/authftp.md rename to doc/tools/authftp.md diff --git a/doc/caturl.md b/doc/tools/caturl.md similarity index 100% rename from doc/caturl.md rename to doc/tools/caturl.md diff --git a/doc/tools/chrono.py.md b/doc/tools/chrono.py.md new file mode 100644 index 0000000..d3a9a0b --- /dev/null +++ b/doc/tools/chrono.py.md @@ -0,0 +1,28 @@ +# chrono.py + +~~~ +usage: chrono.py [options] [TIMEOUT] + +Afficher un chronomètre + +positional arguments: + TIMEOUT (valeur vide) + chronomètre qui démarre à 0:00 et ne s'arrête pas + H:M:S (heures:minutes:secondes) + ou M:S (minutes:secondes) + ou M (minutes) + minuteur qui démarre à H:M:S et fait un décompte jusqu'à 0:00. A la fin + du décompte, une sonnerie retentit. + @H[:M[:S]] + minuteur qui fonctionne comme précédemment, sauf qu'on spécifie l'heure + d'arrivée, et que la durée est calculée automatiquement + +optional arguments: + -h, --help show this help message and exit + -n, --no-autostart Ne pas démarrer automatiquement le décompte même si TIMEOUT est spécifié. + -s, --autostart Forcer le démarrage automatique du décompte, même si TIMEOUT n'est pas spécifié. + +Si TIMEOUT est spécifié, par défaut le décompte démarre automatiquement. +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/compileAndGo.md b/doc/tools/compileAndGo.md similarity index 100% rename from doc/compileAndGo.md rename to doc/tools/compileAndGo.md diff --git a/doc/cssh.md b/doc/tools/cssh.md similarity index 100% rename from doc/cssh.md rename to doc/tools/cssh.md diff --git a/doc/doinplace.md b/doc/tools/doinplace.md similarity index 100% rename from doc/doinplace.md rename to doc/tools/doinplace.md diff --git a/doc/dumpclients.md b/doc/tools/dumpclients.md similarity index 100% rename from doc/dumpclients.md rename to doc/tools/dumpclients.md diff --git a/doc/em.md b/doc/tools/em.md similarity index 87% rename from doc/em.md rename to doc/tools/em.md index 9f5b43e..bd3f8ae 100644 --- a/doc/em.md +++ b/doc/tools/em.md @@ -13,6 +13,7 @@ read the main documentation for these command-line arguments. Initialization options: --batch do not do interactive display; implies -q +--chdir DIR change to directory DIR --daemon start a server in the background --debug-init enable Emacs Lisp debugger for init file --display, -d DISPLAY use X server DISPLAY @@ -20,9 +21,11 @@ Initialization options: --no-init-file, -q load neither ~/.emacs nor default.el --no-shared-memory, -nl do not use shared memory --no-site-file do not load site-start.el +--no-site-lisp, -nsl do not add site-lisp directories to load-path --no-splash do not display a splash screen on startup --no-window-system, -nw do not communicate with X, ignoring $DISPLAY ---quick, -Q equivalent to -q --no-site-file --no-splash +--quick, -Q equivalent to: + -q --no-site-file --no-site-lisp --no-splash --script FILE run FILE as an Emacs Lisp script --terminal, -t DEVICE use DEVICE for terminal I/O --user, -u USER load ~USER/.emacs instead of your own @@ -32,7 +35,7 @@ Action options: FILE visit FILE using find-file +LINE go to line LINE in next FILE +LINE:COLUMN go to line LINE, column COLUMN, in next FILE ---directory, -L DIR add DIR to variable load-path +--directory, -L DIR prepend DIR to load-path (with :DIR, append DIR) --eval EXPR evaluate Emacs Lisp expression EXPR --execute EXPR evaluate Emacs Lisp expression EXPR --file FILE visit FILE using find-file @@ -51,14 +54,14 @@ Display options: --border-color, -bd COLOR main border color --border-width, -bw WIDTH width of main border --color, --color=MODE override color mode for character terminals; - MODE defaults to `auto', and can also - be `never', `auto', `always', + MODE defaults to `auto', and + can also be `never', `always', or a mode name like `ansi8' --cursor-color, -cr COLOR color of the Emacs cursor indicating point --font, -fn FONT default font; must be fixed-width --foreground-color, -fg COLOR window foreground color --fullheight, -fh make the first frame high as the screen ---fullscreen, -fs make first frame fullscreen +--fullscreen, -fs make the first frame fullscreen --fullwidth, -fw make the first frame wide as the screen --maximized, -mm make the first frame maximized --geometry, -g GEOMETRY window geometry @@ -82,7 +85,7 @@ example, -batch as well as --batch. You can use any unambiguous abbreviation for a --option. Various environment variables and window system resources also affect -Emacs' operation. See the main documentation. +the operation of Emacs. See the main documentation. Report bugs to bug-gnu-emacs@gnu.org. First, please see the Bugs section of the Emacs manual or the file BUGS. diff --git a/doc/fconv.md b/doc/tools/fconv.md similarity index 100% rename from doc/fconv.md rename to doc/tools/fconv.md diff --git a/doc/fnconv.md b/doc/tools/fnconv.md similarity index 100% rename from doc/fnconv.md rename to doc/tools/fnconv.md diff --git a/doc/tools/fndate.md b/doc/tools/fndate.md new file mode 100644 index 0000000..b73dcd6 --- /dev/null +++ b/doc/tools/fndate.md @@ -0,0 +1,69 @@ +# fndate + +~~~ +fndate: manipuler des noms de fichier pour les dater + +USAGE + fndate (FILE|DIR)s... + fndate -k SRCs... [DESTDIR] + fndate -m SRCs... [DESTDIR] + fndate -c COMMAND [ARGs...] + +OPTIONS + --create-or-rename + Créer un nouveau fichier (option -f) ou créer un nouveau répertoire + (option -d), ou renommer un fichier existant sans le changer de + répertoire. C'est l'option par défaut. + -f, --create-file + Créer un nouveau fichier. Cette option est ignorée si le fichier ou le + répertoire spécifié existe. C'est la valeur par défaut + -d, --create-dir + Créer un nouveau répertoire. Cette option est ignorée si le fichier ou + le répertoire spécifié existe. + -k, --copy + Copier le fichier SRC vers la destination DESTDIR qui vaut par défaut le + répertoire courant si un seul fichier source est spécifié. Si plusieurs + fichiers sources sont spécifiés, il faut absolument spécifier un + répertoire destination. Dans la destination, les fichiers sont nommés + avec la date en préfixe. + -m, --move + Déplacer le fichier SRC vers la destination DESTDIR qui vaut par défaut + le répertoire courant si un seul fichier source est spécifié. Si + plusieurs fichiers sources sont spécifiés, il faut absolument spécifier + un répertoire destination. Dans la destination, les fichiers sont nommés + avec la date en préfixe. + -c, --cmd + Les arguments sont une commande à lancer. Dans les argument, PLACEHOLDER + est remplacé par la date. Si le PLACEHOLDER n'est trouvé dans aucun des + arguments, alors ajouter la date à la fin de la commande. Par exemple, + voici comment émuler l'option -k + fndate -c cp src dest/~~ + --string + Remplacer dans chaque argument le placeholder par la date et afficher le + résultat. + -P, --placeholder PLACEHOLDER + Dans le nom spécifié, remplacer PLACEHOLDER par la date. Par défaut, + PLACEHOLDER vaut ~~. Si le nom spécifié ne contient pas le PLACEHOLDER, + il est placé au début. + -@, --force-date DATE + Dans le nom spécifié, si la date est déjà présente, forcer son + remplacement par la valeur spécifiée. Par défaut, le fichier n'est pas + modifié si la date est déjà présente. + -F, --format FORMAT + Spécifier le format de la date à insérer dans le nom du fichier. Par + défaut, FORMAT vaut 'YYMMDD-' + Les formats valides sont: + YYYY année sur 4 chiffres + YY année sur 2 chiffres + MM mois sur 2 chiffres + DD jour sur 2 chiffres + Tous les autres caractères sont pris tels-quels + -s, --short + Equivalent à -F YYMMDD --autof + -l, --long + Equivalent à -F YYYMMDD --autof + --autof + Option non (pas encore) documentée +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/tools/foreach.md b/doc/tools/foreach.md new file mode 100644 index 0000000..0e1e8ab --- /dev/null +++ b/doc/tools/foreach.md @@ -0,0 +1,67 @@ +# foreach + +~~~ +foreach: lancer une commande pour un ensemble d'arguments + +USAGE + foreach [options] [VAR=]expr cmd... + +La commande est lancée avec la variable 'VAR', qui vaut par défaut 'item', +définie à la valeur courante de l'énumération. De plus, la variable 'index' +reçoit une valeur incrémentale commençant à 0. + +En plus de VAR, les variables file, dir, name, basename, ext et dotext sont +définies. Elle valent respectivement le chemin absolu du fichier, le répertoire +absolu du fichier, le nom du fichier, le nom de base sans extension, l'extension +sans le point et l'extension avec le point, e.g pour item=dir/file.ext on a +file=/path/to/dir/file.ext, dir=/path/to/dir, name=file.ext, basename=file, +ext=ext, dotext=.ext + +Exemple: + foreach '*.c' cp %item dest/dir +Equivalent à: + foreach item='*.c' cp %item dest/dir + +OPTIONS + -b, --basedir BASEDIR + Chercher les expressions -d, -f, -a à partir de BASEDIR au lieu du + répertoire courant. + -d, --dir + Faire la correspondance de l'expression sur les répertoires uniquement + -f, --file + Faire la correspondance de l'expression sur les fichier uniquement + -a, --all + Faire la correspondance de l'expression sur les répertoires et les + fichiers + -p, --parent + Pour chaque fichier/répertoire, se placer dans le répertoire parent + avant de lancer la commande. item est aussi modifié pour ne plus + contenir que le nom de la cible (qui est désormais dans le répertoire + courant) + Cette option a la priorité sur --go + -g, --go + Si la cible est un répertoire, en faire le répertoire courant avant de + lancer la commande. Si la cible est un fichier, faire comme --parent et + se placer dans le répertoire du fichier avant de lancer la commande. + Dans les deux cas, item est modifié pour ne plus contenir que le nom de + la cible, soit '.' pour le répertoire courant, soit le nom du fichier + dans le répertoire courant. + Si cette option est mentionnée seule, elle implique --dir + -s, --string + Evaluer l'expression avec la fonction 'eval' du shell. + -x, --expand + -n, --no-expand + Reconnaitre et traiter (resp. ne pas reconnaitre) la syntaxe %var dans + les arguments. C'est le cas par défaut, ce qui permet de simplifier + l'écriture d'une ligne de commande. Pour écrire le caractère '%', il + suffit de le double e.g %%item + Si l'expansion est désactivée, il faut protéger le caractère $ pour + qu'il soit traité, e.g, avec les examples ci-dessus: + foreach -n '*.c' 'cp "$item" dest/dir' + -t, --title + --no-title + Afficher (resp. ne pas afficher) chaque correspondance avant de lancer + la commande. Par défaut, l'affichage est effectué. +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/geturl.md b/doc/tools/geturl.md similarity index 100% rename from doc/geturl.md rename to doc/tools/geturl.md diff --git a/doc/mkRewriteRules.md b/doc/tools/mkRewriteRules.md similarity index 100% rename from doc/mkRewriteRules.md rename to doc/tools/mkRewriteRules.md diff --git a/doc/mkiso.md b/doc/tools/mkiso.md similarity index 100% rename from doc/mkiso.md rename to doc/tools/mkiso.md diff --git a/doc/mkurl.md b/doc/tools/mkurl.md similarity index 100% rename from doc/mkurl.md rename to doc/tools/mkurl.md diff --git a/doc/mkusfx.md b/doc/tools/mkusfx.md similarity index 100% rename from doc/mkusfx.md rename to doc/tools/mkusfx.md diff --git a/doc/mocifs.md b/doc/tools/mocifs.md similarity index 100% rename from doc/mocifs.md rename to doc/tools/mocifs.md diff --git a/doc/modav.md b/doc/tools/modav.md similarity index 100% rename from doc/modav.md rename to doc/tools/modav.md diff --git a/doc/moiso.md b/doc/tools/moiso.md similarity index 100% rename from doc/moiso.md rename to doc/tools/moiso.md diff --git a/doc/mossh.md b/doc/tools/mossh.md similarity index 100% rename from doc/mossh.md rename to doc/tools/mossh.md diff --git a/doc/mysqlcsv.md b/doc/tools/mysqlcsv.md similarity index 100% rename from doc/mysqlcsv.md rename to doc/tools/mysqlcsv.md diff --git a/doc/mysqlloadcsv.md b/doc/tools/mysqlloadcsv.md similarity index 100% rename from doc/mysqlloadcsv.md rename to doc/tools/mysqlloadcsv.md diff --git a/doc/noerr.md b/doc/tools/noerr.md similarity index 100% rename from doc/noerr.md rename to doc/tools/noerr.md diff --git a/doc/noerror.md b/doc/tools/noerror.md similarity index 100% rename from doc/noerror.md rename to doc/tools/noerror.md diff --git a/doc/noout.md b/doc/tools/noout.md similarity index 100% rename from doc/noout.md rename to doc/tools/noout.md diff --git a/doc/nutools.md b/doc/tools/nutools.md similarity index 100% rename from doc/nutools.md rename to doc/tools/nutools.md diff --git a/doc/openurl.md b/doc/tools/openurl.md similarity index 100% rename from doc/openurl.md rename to doc/tools/openurl.md diff --git a/doc/pdev.md b/doc/tools/pdev.md similarity index 100% rename from doc/pdev.md rename to doc/tools/pdev.md diff --git a/doc/prel.md b/doc/tools/prel.md similarity index 88% rename from doc/prel.md rename to doc/tools/prel.md index 7e19a20..f9dd0bd 100644 --- a/doc/prel.md +++ b/doc/tools/prel.md @@ -67,15 +67,15 @@ OPTIONS fichier VERSION.txt avec pver. Utiliser cette option si la mise à jour du numéro de version doit être faite d'une manière particulière. -e, --edit - Editer le fichier CHANGES.txt autogénéré par -u -w - Cette option est surtout utile si -m est utilisé avec -u, pour donner la - possibilité de corriger la liste des modifications avant leur - enregistrement définitif. + Editer le fichier CHANGES.md autogénéré par les options -u -w ou un + fichier CHANGES.txt existant. Cette option est surtout utile si -m est + utilisé avec -u, pour donner la possibilité de corriger la liste des + modifications avant leur enregistrement définitif. -m, --merge Si la branche actuelle est une branche de release, ou s'il existe une branche de release, la merger dans master, puis dans develop, puis la - supprimer. Puis basculer sur la branche master. + supprimer. A l'issu de cette opération, rester sur la branche develop. S'il n'existe pas de branche de release, proposer de fusionner les modifications de la branche develop dans la branche master, sans préparer de branche de release au préalable. @@ -89,13 +89,17 @@ OPTIONS -s, --summary Afficher la liste des différences entre la branche develop et la branche master, comme elle serait générée par les options -u -w pour le fichier - CHANGES.txt + CHANGES.txt (la syntaxe pour CHANGES.md est légèrement différente) -l, --log Afficher les modifications actuellement effectuée dans la branche de release par rapport à develop. -d, --diff Afficher les modifications actuellement effectuée dans la branche de release par rapport à develop, sous forme de diff. + +OPTIONS AVANCEES + --uc, --upgrade-changes + Convertir un fichier CHANGES.txt en CHANGES.md ~~~ -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/tools/pver.md b/doc/tools/pver.md new file mode 100644 index 0000000..8db8c3b --- /dev/null +++ b/doc/tools/pver.md @@ -0,0 +1,201 @@ +# pver + +~~~ +pver: gérer des numéros de version selon les règles du versionage sémantique v2.0.0 (http://semver.org/) + +USAGE + pver [options] + +OPTIONS + -w, --auto-file DIR + Gérer le numéro de version du répertoire spécifié. Si un fichier pom.xml + existe dans ce répertoire, alors c'est l'option '-e DIR/pom.xml' qui est + activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est + l'option '-f DIR/VERSION.txt' qui est activée. Sinon, un nouveau fichier + VERSION.txt est créé dans ce répertoire. C'est l'option par défaut. + --sw, --auto-string DIR + Prendre pour valeur de départ la version du répertoire spécifié. Si un + fichier pom.xml existe dans ce répertoire, alors c'est l'option -E qui + est activé. Si un fichier VERSION.txt existe dans ce répertoire, alors + c'est l'option -F qui est activée. + --gw, --auto-git-string [BRANCH:]DIR + Prendre pour valeur de départ la version du répertoire spécifié dans la + branche BRANCH (qui vaut par défaut master) du dépôt git. Si un fichier + pom.xml existe dans ce répertoire, alors c'est l'option -g qui est + activé. Si un fichier VERSION.txt existe dans ce répertoire, alors c'est + l'option -G qui est activée. + -e, --pom POMFILE + Gérer le numéro de version se trouvant dans le fichier pom.xml spécifié. + Le fichier DOIT exister. Implique --maven-update + -E, --pom-string POMFILE + Prendre pour valeur de départ la version contenue dans le fichier + pom.xml spécifié. Le fichier DOIT exister. Implique --maven-update + -f, --file VERSIONFILE + Gérer le numéro de version se trouvant dans le fichier spécifié. Le + fichier est créé si nécessaire. + -F, --file-string VERSIONFILE + Prendre pour valeur de départ le contenu du fichier VERSIONFILE. + -g, --git-file-string [BRANCH:]VERSIONFILE + Prendre pour valeur de départ le contenu du fichier VERSIONFILE (qui + vaut par défaut VERSION.txt) dans la branche BRANCH (qui vaut par défaut + master) du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt + git. + -G, --git-pom-string [BRANCH:]POMFILE + Prendre pour valeur de départ la version du fichier POMFILE (qui vaut + par défaut pom.xml) dans la branche BRANCH (qui vaut par défaut master) + du dépôt git. Retourner 2 si on n'est pas situé dans un dépôt git. + --git-prel-string + Prendre pour valeur de départ le numéro de version correspondant à la + branche de release courante. Retourner 1 si la branche courante n'est + pas une branche de release, 2 si on n'est pas situé dans un dépôt git. + -s, --string VERSION + Prendre pour valeur de départ le numéro de version spécifié + + --show + Afficher le numéro de version. C'est l'action par défaut + --check + Vérifier que le numéro de version est conforme aux règles du versionage + sémantique + --eq VERSION + --ne VERSION + --lt VERSION + --le VERSION + --gt VERSION + --ge VERSION + --same VERSION + --diff VERSION + Comparer avec la version spécifiée. Les opérateurs --eq, --ne, --lt, + --le, --gt, et --ge ignorent l'identifiant de build (comme le demande la + règle du versionage sémantique). Les opérateurs --same et --diff + comparent aussi les identifiants de build. + -u, --update + Mettre à jour le numéro de version. + +Les options suivantes impliquent --update: + -v, --set-version VERSION + Spécifier un nouveau numéro de version qui écrase la valeur actuelle. + Cette option ne devrait pas être utilisée en temps normal parce que cela + va contre les règles du versionage sémantique. + --prel + Spécifier un nouveau numéro de version qui écrase la valeur actuelle. Le + numéro de version est obtenu à partir du nom de la branche git courante, + qui doit être de la forme release-VERSION + --menu + Afficher un menu permettant de choisir le composant de la version à + incrémenter. + -x, --major + Augmenter le numéro de version majeure. + -z, --minor + Augmenter le numéro de version mineure. C'est la valeur par défaut, sauf + avec --maven-update où c'est l'option -p qui est sélectionnée par + défaut. + -p, --patchlevel + Augmenter le numéro de patch. + -k, --keep + Ne pas augmenter le numéro de version. Cette option est surtout utile + pour *convertir* un numéro de version existant et mettre à jour le + fichier correspondant. Elle est assumée si aucune option -[xzp] n'est + spécifiée et qu'une des options -[labrSRmM] est utilisée. + + -l, --prelease ID + Spécifier un identifiant de pré-release, à ajouter au numéro de version. + -a, --alpha + -b, --beta + -r, --rc + Spécifier une pré-release de type alpha, beta, ou rc. Si la version est + déjà dans ce type, augmenter la dernière valeur numérique des composants + de l'identifiant, e.g. alpha deviant alpha.1, beta-1.2 devient beta-1.3, + rc1 devient rc2 + XXX ces fonctions ne sont pas encore implémentées + -S, --snapshot + Ajouter l'identifiant SNAPSHOT, utilisé par Maven + -R, --final, --release + Supprimer l'identifiant de prérelease, utilisé par Maven + + -m, --metadata ID + Spécifier un identifiant de build, à ajouter au numéro de version. + -M, --vcs-metadata + Calculer l'identifiant de build à partir de la révision actuelle dans le + gestionnaire de version. Note: pour le moment, seul git est supporté. + --add-metadata ID + Ajouter l'identifiant spécifié à la valeur actuelle, au lieu de la + remplacer. Séparer l'identifiant de la valeur précédente avec un '.' + +OPTIONS AVANCEES + --show-source + Afficher le type de source qui sera traité, i.e. pom, file, pom-string, + file-string, git-pom-string, git-file-string + --vpath VPATH + Pour les options -e et -E, spécifier le chemin XPATH du tag qui contient + le numéro de version. + --map MAPFILE + Cette option permet de spécifier un fichier de règles qui indique les + fichiers pom.xml et VERSION.txt qui doivent être mis à jour dans un + projet multi-modules pour lequel les versions doivent être mises à jour + en même temps. + Par défaut, si un fichier nommé .pver-map existe dans le répertoire de + {POM,VERSION}FILE, cette option est automatiquement activée. Ainsi, on + n'aura besoin d'utiliser cette option que si l'on désire charger un + fichier alternatif ou ignorer le fichier par défaut. + Si une valeur vide est fournie, seul le fichier {POM,VERSION}FILE est + traité. Sinon, {POM,VERSION}FILE est utilisé uniquement pour chercher le + fichier .pver-map et seuls les fichiers mentionnés dans MAPFILE sont + traités. + Le fichier MAPFILE est constitué d'un ensemble de lignes de la forme + FILESPEC:VPATH + FILESPEC est requis et prend la forme d'une spécification de chemin + relatif au répertoire de MAPFILE et identifiant un ensemble de fichiers + de version. Si FILESPEC contient des wildcards, alors les fichiers + identifiés par ce chemin sont ignorés s'ils ont déjà été traités par une + règle précédente. Si FILESPEC ne contient pas de wildcards, alors le + fichier est systématiquement traité. + VPATH désigne le chemin XPATH vers le numéro de version qu'il faut + mettre à jour dans les fichiers pom.xml. Certaines valeurs spéciales + pour VPATH sont supportées: + (vide) le chemin par défaut est utilisé pour ce fichier, c'est à + dire /project/version ou /project/parent/version en fonction + du contenu du fichier + - le fichier pom.xml n'est pas modifié (il est ignoré) + D alias pour le chemin XPATH /project/version + P alias pour le chemin XPATH /project/parent/version + Pour les fichiers VERSION.txt, VPATH doit être vide + Le fichier de version correspondant au premier fichier de la première + ligne de MAPFILE contient la version de référence, qui est dupliquée + dans tous les autres fichiers. + --allow-empty + Supporter que la version puisse ne pas être spécifiée ni trouvée. Sans + cette option, on assume que la version effective est 0.0.0 si elle n'est + pas spécifiée ni trouvée. + Avec --show et --update, ne rien afficher si la version est vide. + --convert + --no-convert + Activer (resp. désactiver) la conversion automatique. Par défaut, si la + version est au format classique 'x.z[.p]-rDD/MM/YYYY', elle est + convertie automatiquement au format sémantique x.z.p+rYYYYMMDD + -t, --maven-update + Mettre à jour le numéro de version selons les règles de Maven. Cette + option est automatiquement activée si -e est sélectionné. Elle n'est + prise en compte qu'avec l'option -u + Si les options -R et -S ne sont pas spécifiée, alors une release est + transformée en snapshot et une snapshot en release. Avec -R, une + snapshot est transformée en release, et une release est traitée / + incrémentée normalement. Avec -S une release est transformée en + snapshot, et un snapshot est traité / incrémentée normalement. + Si l'une des options -x, -z, -p est utilisée, alors la partie spécifiée + est incrémentée selon les règles suivantes. Dans les exemples suivants, + A est un nombre quelconque, B = A + 1, et x, y et z sont des nombres + quelconques. + Avec l'option -x: + A.0.0-SNAPSHOT --> A.0.0 + A.x.y-SNAPSHOT --> B.0.0 + x.A.y --> x.B.0-SNAPSHOT + Avec l'option -z: + x.A.0-SNAPSHOT --> x.A.0 + x.A.y-SNAPSHOT --> x.B.0 + x.A.y --> x.B.0-SNAPSHOT + Avec l'option -p, qui est celle sélectionnée par défaut: + x.y.z-SNAPSHOT --> x.y.z + x.y.A --> x.y.B-SNAPSHOT +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/pz.md b/doc/tools/pz.md similarity index 100% rename from doc/pz.md rename to doc/tools/pz.md diff --git a/doc/reptyr.cgo.md b/doc/tools/reptyr.cgo.md similarity index 100% rename from doc/reptyr.cgo.md rename to doc/tools/reptyr.cgo.md diff --git a/doc/rmtildes.md b/doc/tools/rmtildes.md similarity index 100% rename from doc/rmtildes.md rename to doc/tools/rmtildes.md diff --git a/doc/rruns.md b/doc/tools/rruns.md similarity index 100% rename from doc/rruns.md rename to doc/tools/rruns.md diff --git a/doc/ruinst.md b/doc/tools/ruinst.md similarity index 100% rename from doc/ruinst.md rename to doc/tools/ruinst.md diff --git a/doc/rumount.md b/doc/tools/rumount.md similarity index 100% rename from doc/rumount.md rename to doc/tools/rumount.md diff --git a/doc/runs.md b/doc/tools/runs.md similarity index 96% rename from doc/runs.md rename to doc/tools/runs.md index 3ecd61c..a648b04 100644 --- a/doc/runs.md +++ b/doc/tools/runs.md @@ -40,7 +40,7 @@ Gestion des scripts {$RUNSHOSTSPATH}/$hostname.$domain (par défaut) et {$RUNSHOSTSPATH}/$domain/$hostname (le cas échéant) L'option --host est équivalente, sauf que son argument est facultatif et - que sa valeur par défaut est l'hôte courant, soit sulfure + que sa valeur par défaut est l'hôte courant, soit natrix --list Afficher la liste des scripts qui sont disponibles. Avec l'option -h, inclure aussi les scripts spécifiques à cet hôte. diff --git a/doc/runsconfig.md b/doc/tools/runsconfig.md similarity index 100% rename from doc/runsconfig.md rename to doc/tools/runsconfig.md diff --git a/doc/runsmod.md b/doc/tools/runsmod.md similarity index 89% rename from doc/runsmod.md rename to doc/tools/runsmod.md index 8c5bf5e..b68b3f3 100644 --- a/doc/runsmod.md +++ b/doc/tools/runsmod.md @@ -11,6 +11,9 @@ sont spécifiés, les dépôts correspondants sont récupérés aussi. Avec l'op -h, des dépôts spécifiques à l'hôte peuvent éventuellement être récupérés en plus. +Il est possible de spécifier le module '*' pour récupérer tous les modules +disponibles. C'est utile sur un poste de développement. + OPTIONS -c, --config CONFIG Spécifier un fichier de configuration à charger au lieu de la valeur par @@ -33,12 +36,15 @@ OPTIONS --no-host demande explicitement à ce qu'aucun hôte ne soit spécifié --all-hosts sélectionne tous les dépôts spécifiques --host récupère uniquement les dépôts pour l'hôte spécifié - --this-host équivaut à --host sulfure + --this-host équivaut à --host natrix L'option par défaut est --this-host en mode production et --all-hosts en mode développement --update-repolist Forcer la mise à jour de la liste des dépôts. En principe, cette mise à jour n'est pas faite plus d'une fois par période de 24 heures. + --shallow-clone + Faire un clone avec une profondeur de 1. C'est la valeur par défaut pour + le mode production. -0, --offline -n, --no-pull -u, --pull diff --git a/doc/rwoinst.md b/doc/tools/rwoinst.md similarity index 100% rename from doc/rwoinst.md rename to doc/tools/rwoinst.md diff --git a/doc/sqlcsv.md b/doc/tools/sqlcsv.md similarity index 100% rename from doc/sqlcsv.md rename to doc/tools/sqlcsv.md diff --git a/doc/twsync.md b/doc/tools/twsync.md similarity index 100% rename from doc/twsync.md rename to doc/tools/twsync.md diff --git a/doc/uawk.md b/doc/tools/uawk.md similarity index 100% rename from doc/uawk.md rename to doc/tools/uawk.md diff --git a/doc/ubackup.md b/doc/tools/ubackup.md similarity index 100% rename from doc/ubackup.md rename to doc/tools/ubackup.md diff --git a/doc/ucalc.md b/doc/tools/ucalc.md similarity index 100% rename from doc/ucalc.md rename to doc/tools/ucalc.md diff --git a/doc/uconf.md b/doc/tools/uconf.md similarity index 100% rename from doc/uconf.md rename to doc/tools/uconf.md diff --git a/doc/ucrontab.md b/doc/tools/ucrontab.md similarity index 100% rename from doc/ucrontab.md rename to doc/tools/ucrontab.md diff --git a/doc/udaemon.cgo.md b/doc/tools/udaemon.cgo.md similarity index 100% rename from doc/udaemon.cgo.md rename to doc/tools/udaemon.cgo.md diff --git a/doc/udir.md b/doc/tools/udir.md similarity index 100% rename from doc/udir.md rename to doc/tools/udir.md diff --git a/doc/udist.md b/doc/tools/udist.md similarity index 100% rename from doc/udist.md rename to doc/tools/udist.md diff --git a/doc/uenv.md b/doc/tools/uenv.md similarity index 100% rename from doc/uenv.md rename to doc/tools/uenv.md diff --git a/doc/ufixmod.md b/doc/tools/ufixmod.md similarity index 100% rename from doc/ufixmod.md rename to doc/tools/ufixmod.md diff --git a/doc/ugenpass.md b/doc/tools/ugenpass.md similarity index 100% rename from doc/ugenpass.md rename to doc/tools/ugenpass.md diff --git a/doc/uinc.md b/doc/tools/uinc.md similarity index 100% rename from doc/uinc.md rename to doc/tools/uinc.md diff --git a/doc/uinc.sh.md b/doc/tools/uinc.sh.md similarity index 100% rename from doc/uinc.sh.md rename to doc/tools/uinc.sh.md diff --git a/doc/uinst.md b/doc/tools/uinst.md similarity index 100% rename from doc/uinst.md rename to doc/tools/uinst.md diff --git a/doc/uinst.sh.md b/doc/tools/uinst.sh.md similarity index 100% rename from doc/uinst.sh.md rename to doc/tools/uinst.sh.md diff --git a/doc/ujava.md b/doc/tools/ujava.md similarity index 100% rename from doc/ujava.md rename to doc/tools/ujava.md diff --git a/doc/uldap.md b/doc/tools/uldap.md similarity index 100% rename from doc/uldap.md rename to doc/tools/uldap.md diff --git a/doc/tools/ulib.md b/doc/tools/ulib.md new file mode 100644 index 0000000..281a9cf --- /dev/null +++ b/doc/tools/ulib.md @@ -0,0 +1,63 @@ +# ulib + +~~~ +ulib: Gestion des librairies ulib et pyulib + +USAGE + ulib [options] --destdir DESTDIR [options] + ulib [options] --version [options] + ulib [options] --shell [options] + +SYNCHRONISER ULIB/PYULIB + -S, --sync + Synchroniser ulib, pyulib et les scripts de support + -d, --destdir DESTDIR + Synchroniser ulib et/ou pyulib dans le répertoire destdir. + Les options -u et -p permettent de choisir ce qui est synchronisé + -u, --ulib + Copier/mettre à jour ulib (par défaut) + -p, --pyulib + Copier/mettre à jour pyulib + -s, --support + Copier/mettre à jour les scripts de support + +GERER LA VERSION DE ULIB + -v, --version + Gérer la version de ulib + -l, --show + Afficher la version de la librairie (par défaut) + -s, --system + Afficher aussi la version de la librairie système + -c, --check MIN_VERSION + Vérifier que la version de la librairie est au minimum MIN_VERSION, et + afficher un message d'information. Utiliser l'option -q si l'on veut + juste tester la version et ne pas afficher le message d'information. + -V, --set-version VERSION + Forcer la version de ulib + -u, --update + Incrémenter la version de ulib. Les options -x, -z, -p permettent de + choisir le numéro qui est incrémenté. + -x, --major + Augmenter le numéro de version majeure + -z, --minor + Augmenter le numéro de version mineure. C'est l'option par défaut + -p, --patchlevel + Augementer le numéro de patch + +LANCER UN SHELL (par défaut) + -s, --shell + Lancer un shell dans lequel les modules de ulib spécifiés sont chargés. + De plus, PATH est modifié pour que /home/jclain/wop/modules/nutools soit ajouté en premier. + Les arguments restants sont passés inchangés au shell. + -r, --require module + Spécifier un module à charger avec urequire. Plusieurs modules peuvent + être spécifiés en les séparant par ':' + Par défaut, seul le module DEFAULTS est chargé. + --force-reload + Forcer le (re)chargement des modules avec urequire. Cette option est + utile pour le développement. En effet, dans le shell, la commande + 'urequire module' (re)charge toujours le module, même s'il avait déjà + été chargé. +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulink.md b/doc/tools/ulink.md similarity index 100% rename from doc/ulink.md rename to doc/tools/ulink.md diff --git a/doc/umatch.md b/doc/tools/umatch.md similarity index 100% rename from doc/umatch.md rename to doc/tools/umatch.md diff --git a/doc/umirror.md b/doc/tools/umirror.md similarity index 100% rename from doc/umirror.md rename to doc/tools/umirror.md diff --git a/doc/tools/umountr.md b/doc/tools/umountr.md new file mode 100644 index 0000000..4caac66 --- /dev/null +++ b/doc/tools/umountr.md @@ -0,0 +1,18 @@ +# umountr + +~~~ +umountr: démonter un système de fichier récursivement + +USAGE + umountr mountpoint + +Démonter tous les systèmes de fichiers qui sont montés en-dessous de mountpoint +puis démonter mountpoint. Démonter aussi tous les systèmes de fichiers +bind-montés à partir d'un sous-répertoire de mountpoint. + +OPTION + -c, --continuous + Continuer même en cas d'erreur +~~~ + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/upassword.md b/doc/tools/upassword.md similarity index 100% rename from doc/upassword.md rename to doc/tools/upassword.md diff --git a/doc/update-nutools.md b/doc/tools/update-nutools.md similarity index 100% rename from doc/update-nutools.md rename to doc/tools/update-nutools.md diff --git a/doc/uprefix.md b/doc/tools/uprefix.md similarity index 100% rename from doc/uprefix.md rename to doc/tools/uprefix.md diff --git a/doc/uproject.md b/doc/tools/uproject.md similarity index 95% rename from doc/uproject.md rename to doc/tools/uproject.md index 15555c3..93b3e16 100644 --- a/doc/uproject.md +++ b/doc/tools/uproject.md @@ -56,8 +56,9 @@ COMMANDS -R Afficher les modifications effectuées depuis la dernière release. clone git@host:path/to/repo [destdir] - Cloner un dépôt distant. Initialiser git annex si le dépôt contient des - fichiers annexés. Récupérer aussi ces fichiers avec 'git annex get' + Cloner un dépôt distant. Basculer sur la branche develop si elle existe. + Initialiser git annex si le dépôt contient des fichiers annexés. + Récupérer aussi ces fichiers avec 'git annex get' crone git@host:path/to/repo [destdir] Créer un dépôt distant sur gitolite, puis le cloner diff --git a/doc/uscrontab.md b/doc/tools/uscrontab.md similarity index 100% rename from doc/uscrontab.md rename to doc/tools/uscrontab.md diff --git a/doc/ussh.md b/doc/tools/ussh.md similarity index 100% rename from doc/ussh.md rename to doc/tools/ussh.md diff --git a/doc/usysinfos.md b/doc/tools/usysinfos.md similarity index 100% rename from doc/usysinfos.md rename to doc/tools/usysinfos.md diff --git a/doc/utempl.md b/doc/tools/utempl.md similarity index 100% rename from doc/utempl.md rename to doc/tools/utempl.md diff --git a/doc/utrigger.md b/doc/tools/utrigger.md similarity index 100% rename from doc/utrigger.md rename to doc/tools/utrigger.md diff --git a/doc/uwatch.md b/doc/tools/uwatch.md similarity index 100% rename from doc/uwatch.md rename to doc/tools/uwatch.md diff --git a/doc/vzusage.md b/doc/tools/vzusage.md similarity index 100% rename from doc/vzusage.md rename to doc/tools/vzusage.md diff --git a/doc/woArchive.md b/doc/tools/woArchive.md similarity index 100% rename from doc/woArchive.md rename to doc/tools/woArchive.md diff --git a/doc/woSwitch.md b/doc/tools/woSwitch.md similarity index 100% rename from doc/woSwitch.md rename to doc/tools/woSwitch.md diff --git a/doc/woctl.md b/doc/tools/woctl.md similarity index 100% rename from doc/woctl.md rename to doc/tools/woctl.md diff --git a/doc/woinst.md b/doc/tools/woinst.md similarity index 100% rename from doc/woinst.md rename to doc/tools/woinst.md diff --git a/doc/wosign.md b/doc/tools/wosign.md similarity index 100% rename from doc/wosign.md rename to doc/tools/wosign.md diff --git a/doc/twsync.twp b/doc/twsync.twp deleted file mode 100644 index c867f77..0000000 --- a/doc/twsync.twp +++ /dev/null @@ -1,39 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: twsync - -{{{ -twsync: synchroniser un répertoire de wiki avec un tiddlywiki - -USAGE - twsync [options] - -Un répertoire de wiki est un répertoire où chaque page est contenu dans un -fichier avec l'extension .twp -Un tiddlywiki est un fichier html contenant le code de TiddlyWiki et les données -associées. - -OPTIONS - -d wikidir - -f wikifile - Spécifier le répertoire de wiki et le tiddlywiki à traiter. Par défaut, - il s'agit de wiki.html dans le répertoire courant. - -u Importer les pages de wikidir dans le tiddlywiki. Utiliser cette action - quand les pages de wikidir sont modifiées et qu'il faut mettre à jour le - tiddlywiki. - Il s'agit de l'action par défaut - --force - Forcer l'importation des pages même si les tiddlers correspondant sont - plus récents dans le tiddlywiki - Forcer aussi la regénération de wikifile même si aucune modification n'a - été détectée - -e Exporter les tiddlers du tiddlywiki vers wikidir. Utiliser cette action - quand le tiddlywiki a été modifié, et qu'il faut synchroniser wikidir - avec les dernières modifications. - -U Mettre à jour le fichier wikifile avec la dernière version de tiddlywiki - située dans ~/wop/modules/nutools/lib/tiddlywiki/empty.html -}}} diff --git a/doc/uawk.twp b/doc/uawk.twp deleted file mode 100644 index 494c2d8..0000000 --- a/doc/uawk.twp +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uawk - -{{{ -uawk: wrapper pour des outils implémentés en awk - -USAGE - uawk TOOL args... - -Les noms d'outils valides sont: awkrun awkcsv grepcsv awkfsv2csv mergecsv sortcsv dumpcsv printcsv -Utiliser l'option --help pour obtenir de l'aide sur chacun des outils -}}} diff --git a/doc/ubackup.twp b/doc/ubackup.twp deleted file mode 100644 index 90510bb..0000000 --- a/doc/ubackup.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ubackup - -{{{ -ubackup: faire une sauvegarde des fichiers - -USAGE - ubackup [options] - -OPTIONS - -l Lister les profils de sauvegarde disponibles. - -p Spécifier le profil de sauvegarde à effectuer. Par défaut, toutes les - sauvegardes sont effectuées. - -u USER - Faire le sauvegarde pour l'utilisateur $USER. Cette option n'est valide - que pour l'utilisateur root. - -n Afficher ce qui doit être fait plutôt que de le faire - -H Arrêter la machine après une sauvegarde REUSSIE. -}}} diff --git a/doc/ucalc.twp b/doc/ucalc.twp deleted file mode 100644 index 87c3bef..0000000 --- a/doc/ucalc.twp +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ucalc - -{{{ -ucalc: Afficher une valeur dans plusieurs unités - -USAGE - ucalc value - -Sans suffixe, la valeur est exprimée en octets. Sinon, elle peut être suffixée -pour spécifier l'unité dans laquelle est exprimée la valeur: - K,M,G,T -- Kibi (1024), Mibi (1024^2), Gibi (1024^3), Tebi (1024^4) - k,m,g,t -- Kilo (1000), Mega (1000^2), Giga (1000^3), Tera (1000^4) - s -- secteurs de 512 octets - S -- secteurs de 2048 octets - p -- pages de 4096 octets - c -- cylindres (si l'option -c est spécifiée) - b -- octets - -OPTIONS - -u UNIT - Spécifier l'unité de value. Le suffixe qui est éventuellement sur value - est ignoré. - -o UNIT - Spécifier l'unité en sortie. Par défaut, afficher la valeur dans toutes - les unités supportées. - -c VALUE - Taille d'un cylindre en octets, pour permettre l'affichage des valeurs - en cylindres -}}} diff --git a/doc/uconf.twp b/doc/uconf.twp deleted file mode 100644 index 14fa196..0000000 --- a/doc/uconf.twp +++ /dev/null @@ -1,56 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uconf - -{{{ -uconf: Activer ou désactiver un paramètre dans un fichier de configuration - -USAGE - uconf [options] config name[=value]... - -OPTIONS - -e - Activer le paramètre (par défaut). Si le paramètre existe, mais est - commenté, il est décommenté. Si une valeur est spécifiée pour le - paramètre, le paramètre est modifié dans le fichier en conséquence. - -q - Cette option s'utilise avec l'option -e et le type shell. Elle permet - de s'assurer que les valeurs ayant des espaces et/ou des caractères - spéciaux sont quotées - -d - Désactiver le paramètre. Le paramètre est commenté s'il existe dans le - fichier - -a - Ajouter une valeur à la variable, ou un paramètre avec cette valeur - (suivant le type de fichier de configuration) - -A - Ajouter une valeur au tableau, ou un paramètre avec cette valeur - (suivant le type de fichier de configuration) - -t TYPE - Type de fichier de configuration. TYPE peut être sh (par défaut), apache - ou mysql. - shell - les paramètres sont de la forme 'name=value', et les commentaires - débutent par '#'. Ce type peut être utilisé pour tous les fichiers - ayant ces caractéristiques, dont les fichiers de script shell - apache - les paramètres sont de la forme 'name value', et les commentaires - débutent par '#'. Ce type peut être utilisé pour tous les fichiers - ayant ces caractéristiques, dont les fichiers de configuration - apache - mysql, php - les paramètres sont dans des sections nommées de la forme [section], - sont de la forme 'name=value', et les commentaires débutent par '#' - ou ';' - Ce type peut être utilisé pour tous les fichiers ayant ces - caractéristiques, dont les fichiers de configuration de mysql et de - php. Avec ce type, la section est obligatoire. - -s SECTION - Avec le type mysql, préciser la section dans laquelle inscrire le - paramètre. Attention! La section DOIT exister, elle n'est pas créée - automatiquement. -}}} diff --git a/doc/ucrontab.twp b/doc/ucrontab.twp deleted file mode 100644 index a077fa1..0000000 --- a/doc/ucrontab.twp +++ /dev/null @@ -1,90 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ucrontab - -{{{ -ucrontab: Ajouter/Supprimer une ligne dans crontab - -USAGE - ucrontab [options] ctline - -OPTIONS - -a Ajouter la ligne dans le fichier crontab (par défaut) - -r Enlever la ligne dans le fichier crontab - -u user - Spécifier l'utilisateur pour lequel on modifie crontab. Par défaut, - modifier le crontab de root - -H host:hosts... - Modifier le crontab sur les hôtes distants spécifiés. Avec l'hôte '.' ou - 'localhost', la modification est faite sur l'hôte local - Si l'action est -a, un script 'undo-ucrontab.sh' est créé dans le - répertoire courant, qui annule la planification. Il est possible de - lancer ce script le lendemain pour enlever les planifications - installées. - ctline - Ligne de crontab de la forme 'minutes hours days months dows command' - Si la ligne est de la forme halt[@hh:mm], la commande 'shutdown -h now' - est planifiée pour le LENDEMAIN à hh:mm. si hh:mm n'est pas spécifié, - l'arrêt est planifié pour 7:00 - -t hh:mm - Ignorer la partie 'minutes hours days months dows' de ctline, et la - remplacer par une planification à hh:mm le LENDEMAIN. - --dom dayOfMonth - --month month - Spécifier respectivement le jour du mois et le mois (1-12) auquel faire - la planification. Par défaut, les planifications sont effectuées pour le - LENDEMAIN. Il est conseillé de spécifier les deux arguments si le jour - doit être fixé. - -s cmdfile - Spécifier un fichier, dont le contenu est utilisé pour générer le script - qui est planifié. Le fichier doit contenir l'ensemble des commandes à - exécuter. Le script est modifié pour s'autodétruire à la fin de son - exécution. Si ce comportement n'est pas souhaité, il faut rajouter la - commande 'exit 0' à la fin. - -S cmd - Comme -s, mais spécifier le contenu du fichier directement sur la ligne - de commande. - -f hostsfile - Spécifier un fichier qui contient la liste des hôtes sur lesquels faire - les planifications. - Les options -s, -S, -H, -d, -n sont ignorées. L'option --add est valide - jusqu'au premier @group du fichier. Voici le format de ce fichier: - - Un groupe d'hôte débute par une ligne de la forme '@group:adelay:gdelay' - - adelay est le nombre de minutes à laisser passer entre les hôtes du - groupe (par défaut 1) - - gdelay est le nombre de minutes à laisser passer après le traitement - du groupe (par défaut 15) - Les autres lignes sont des hôtes sur lequels planifier l'opération, de - la forme 'host:cmd' - - host est un nom d'hôte pleinement qualifié, sur lequel il est possible - de se connecter par clé. - - cmd est une description de la commande à lancer pour effectuer - l'opération planifiée. Utiliser la syntaxe ' - -OPTIONS - var=value - Spécifier la valeur d'une variable ou d'un préfixe, plutôt que de - laisser uprefix l'autodétecter. Utiliser 'uprefix -l' pour avoir une - liste de préfixes valides. Utiliser 'udir --help-vars' pour avoir une - liste de variables valides pour uinst.sh. - -d /path/to/destdir - Spécifier le répertoire destination, exactement comme si la valeur - destdir avait été spécifiée, i.e destdir="/path/to/destdir" - -h, --host [user@]host - Avec la méthode de déploiement uinst:rsync, permettre de spécifier un - hôte différent. Cette option est ignorée pour les autres méthodes de - déploiement. Si host vaut localhost, un déploiement local avec ssh est - effectué. Si host vaut '.', un déploiement local *sans passer par ssh* - est effectué, comme si seul le chemin avait été spécifié. - Cette option initialise la valeur destdir_override_userhost - -S, --ssh ssh - Avec la méthode de déploiement uinst:rsync, spécifier le programme à - utiliser pour la connection par ssh. Cette option initialise la valeur - destdir_ssh - --force-remote - Avec la méthode de déploiement uinst:rsync, si un hôte est spécifié dans - la valeur de destdir, forcer le déploiement distant avec ssh+rsync, même - si l'hôte et l'utilisateur correspondent aux valeurs courantes. Cette - option initialise la valeur destdir_force_remote - -a, --auto - Si la source n'est pas spécifiée, déterminer le répertoire à déployer - automatiquement (c'est la valeur par défaut) - --no-auto - Ne pas déterminer automatiquement le répertoire à déployer. - --prefix - Corriger les chemins srcdir et destdir qui commencent par des préfixes - valides (c'est la valeur par défaut). Utiliser 'uprefix -l' pour avoir - une liste de préfixes valides - --no-prefix - Ne jamais corriger un chemin. - --include-vcs - Inclure les fichiers de VCS dans les fichiers copiés. Par défaut, les - fichiers de VCS sont exclus. - -l, --local-profiles - Installer les profils locaux comme tels - --shared-profiles - Installer les profils locaux comme des profils partagés. C'est la valeur - par défaut pour compatibilité. - -C Configurer un répertoire pour le déploiement avec uinst - Ajouter l'option --force pour forcer la reconfiguration -}}} diff --git a/doc/uinst.twp b/doc/uinst.twp deleted file mode 100644 index e2f3338..0000000 --- a/doc/uinst.twp +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uinst - -{{{ -uinst: Déployer en local un fichier, une archive, ou un répertoire - -USAGE - uinst [options] - -OPTIONS - var=value - Spécifier la valeur d'une variable ou d'un préfixe, plutôt que de - laisser uprefix l'autodétecter. Utiliser 'uprefix -l' pour avoir une - liste de préfixes valides. Utiliser 'udir --help-vars' pour avoir une - liste de variables valides pour uinst. - -d /path/to/destdir - Spécifier le répertoire destination, exactement comme si la valeur - destdir avait été spécifiée, i.e destdir="/path/to/destdir" - -h, --host [user@]host - Avec la méthode de déploiement uinst:rsync, permettre de spécifier un - hôte différent. Cette option est ignorée pour les autres méthodes de - déploiement. Si host vaut localhost, un déploiement local avec ssh est - effectué. Si host vaut '.', un déploiement local *sans passer par ssh* - est effectué, comme si seul le chemin avait été spécifié. - Cette option initialise la valeur destdir_override_userhost - -S, --ssh ssh - Avec la méthode de déploiement uinst:rsync, spécifier le programme à - utiliser pour la connection par ssh. Cette option initialise la valeur - destdir_ssh - --force-remote - Avec la méthode de déploiement uinst:rsync, si un hôte est spécifié dans - la valeur de destdir, forcer le déploiement distant avec ssh+rsync, même - si l'hôte et l'utilisateur correspondent aux valeurs courantes. Cette - option initialise la valeur destdir_force_remote - -a, --auto - Si la source n'est pas spécifiée, déterminer le répertoire à déployer - automatiquement (c'est la valeur par défaut) - --no-auto - Ne pas déterminer automatiquement le répertoire à déployer. - --prefix - Corriger les chemins srcdir et destdir qui commencent par des préfixes - valides (c'est la valeur par défaut). Utiliser 'uprefix -l' pour avoir - une liste de préfixes valides - --no-prefix - Ne jamais corriger un chemin. - --include-vcs - Inclure les fichiers de VCS dans les fichiers copiés. Par défaut, les - fichiers de VCS sont exclus. - -l, --local-profiles - Installer les profils locaux comme tels - --shared-profiles - Installer les profils locaux comme des profils partagés. C'est la valeur - par défaut pour compatibilité. - -C Configurer un répertoire pour le déploiement avec uinst - Ajouter l'option --force pour forcer la reconfiguration -}}} diff --git a/doc/ujava.twp b/doc/ujava.twp deleted file mode 100644 index 828dec2..0000000 --- a/doc/ujava.twp +++ /dev/null @@ -1,30 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ujava - -{{{ -ujava: Lancer un script après avoir sélectionné une version de java - -USAGE - ujava [options] version [args...] - -OPTIONS - -b, --bits 32|64|auto - --32 - --64 - Sélectionner une version 32 ou 64 bits de java - -e, --exact - Sélectionner la version *exacte* de java demandée, au lieu de la version - minimum correspondant à la version demandée. - Si la version requise de java n'est pas trouvée, retourner avec le code - d'erreur 254. - -La version de java attendue peut-être exprimée de l'une des façons suivantes: -1.4 1.4+ 1.5 1.5+ 1.6 1.6+ 1.7 1.7+ -Si args n'est pas spécifié, un shell est lancé dans lequel les variables -JAVA_HOME, JAVA, JAVAC et PATH sont mis à jour. -}}} diff --git a/doc/uldap.twp b/doc/uldap.twp deleted file mode 100644 index 269fd8f..0000000 --- a/doc/uldap.twp +++ /dev/null @@ -1,227 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uldap - -{{{ -uldap: Shell pour accéder à un serveur ldap - -USAGE - uldap [options] - -OPTIONS - -C profile - Sélectionner un profil de connexion. Par défaut, si l'option -H n'est - pas spécifiée, le premier profil est sélectionné. - -x Ne pas tenter de faire une connexion sur le profil par défaut si aucun - profil n'est sélectionné. - -f script - Lire les commandes depuis le script spécifié. - -n Avec un script donné en ligne de commande ou lu depuis un fichier, ne pas - ajouter automatiquement la commande print à la fin - -i Si un script est spécifié, passer en mode interactif après l'exécution - du script. - -e Forcer l'arrêt du script si une erreur se produit. C'est l'option par - défaut pour un script spécifié avec -f. - -l input.ldif - Charger le fichier input.ldif comme espace de travail initial - -H ldapuri - -D binddn - -w password - -b searchbase - -v var=value - -COMMANDES - $ cmd - Passer directement une commande au shell. - set [options] [var=value...] - Changer des options ou des variables. set sans argument affiche la liste - des variables définies. - [set] plain - Passer en mode 'plain': indiquer que l'espace de travail contient des - données brutes. Les pré-traitements et post-traitements (uncut_on_load, - decode_on_load, encode_on_save, cut_on_save) ne sont pas appliqués sur - cet espace de travail - [set] ldif - Passer en mode 'ldif': indiquer que l'espace de travail contient des - données ldif. Les pré-traitements et post-traitements sont appliqués - normalement sur cet espace de travail - [set] append - Pour certaines opérations, spécifier si le résultat de la *prochaine* - opération remplace le contenu de l'espace de travail courant (par - défaut), ou si le résultat est ajouté à la fin. - last - Afficher en mode édition la dernière commande. Cette commande n'est - fonctionnelle qu'avec une version de bash >=4.x - profile name - Choisir le profil 'name'. Equivalent à 'set profile=name'. Sans - argument, afficher la liste des profils valides. - auth anonymous|binddn [password] - Spécifier le compte à utiliser pour se connecter. Equivalent à - 'set binddn=binddn; set password=password' - clear [-k] - Vider l'espace de travail et passer en mode 'plain'. - Avec l'option -k, supprimer aussi tout l'historique d'annulation. - load [-k] input - Charger un fichier dans l'espace de travail. Si l'extension du fichier - est .ldif, passer en mode 'ldif' - En mode append, rajouter le contenu du fichier à l'espace de travail, - puis repasser en mode replace. - Le code de retour est 0 si le fichier a été chargé, 1 sinon. - save [-a] output - Sauvegarder l'espace de travail dans un fichier. - Avec l'option -a, rajouter au fichier au lieu de l'écraser - print - Afficher l'espace de travail - alias a=rdn... - Définir un alias pour la commande cd. 'a' est l'alias, 'rdn' est le dn - correspondant, exprimé par rapport à $suffix. Sans argument, afficher - la liste des aliases définis. - cd rdn - Changer searchbase. Par défaut, il s'agit d'un rdn relatif à $searchbase - - Certains aliases sont supportés: .. pour l'objet parent, ~ pour - $suffix, / pour la racine. 'cd' sans argument équivaut à 'cd ~' - - Si le dn commence par '~/', il s'agit d'un rdn relatif à $suffix. - - Si le dn commence par /, searchbase reçoit la valeur rdn sans - modifications (sauf bien sûr enlever le '/' de tête si nécessaire). Il - faut alors que ce soit un dn absolu. - ls [-b searchbase] [filter [attrs...]] - search [-b searchbase] [filter [attrs...]] - Utiliser ldapsearch pour faire la recherche, et copier le résultat dans - l'espace de travail. 'ls' est équivalent à 'search -s one'. Si ce n'est - pas déjà le cas, passer en mode 'ldif'. - L'option -b prend une valeur avec la même syntaxe que la commande cd, - sauf que les alias ne sont pas supportés. En particulier, la valeur est - relative au $searchbase courant. Pour faire une recherche par rapport à - $suffix, il faut utiliser la syntaxe ~/searchbase. - En mode append, rajouter le résultat de la recherche à l'espace de - travail, puis repasser en mode replace. - Le code de retour est 1 si aucun enregistrement n'a été trouvé, sinon - le code de retour est celui de la commande ldapsearch. - cut Couper les lignes trop longues. Cette action est en principe effectuée - automatiquement lors de la sauvegarde. Il n'est pas conseillé - d'appliquer des méthodes de transformation après avoir utilisé cette - action. - uncut - Fusionner les lignes coupées. Cette action est en principe effectuée - automatiquement lors du chargement ou après la recherche. - encode [attrs...] - Encoder en base64 les valeurs des attributs mentionnés. - decode [attrs...] - Décoder les valeurs des attributs mentionnés si nécessaire (c'est à dire - s'ils sont encodés en base64) - keepattr attrs... - Garder uniquement les lignes des attributs mentionnés. Ensuite, - supprimer les objets ayant uniquement la ligne dn: (en d'autres termes, - keepattr sans argument supprime *tout* l'espace de travail) - keepval attr patterns... - Pour l'attribut attr, garder uniquement les lignes pour lesquelles les - valeurs correspondent aux expressions régulières. Les autres attributs - ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la - ligne dn: - exclude attrs... - Supprimer les lignes des attributs mentionnés. Ensuite, supprimer les - objets ayant uniquement la ligne dn: - excludeval attr patterns... - Pour l'attribut attr, supprimer les lignes pour lesquelles les - valeurs correspondent aux expressions régulières. Les autres attributs - ne sont pas modifiés. Ensuite, supprimer les objets ayant uniquement la - ligne dn: - keepvalentry attr patterns... - Pour l'attribut attr, vérifier si *au moins une* valeur correspond à - l'une des expressions régulières. Si c'est le cas, garder l'entrée - entière, sinon supprimer l'entrée. - excludevalentry attr patterns... - Pour l'attribut attr, vérifier si *aucune* des valeurs ne correspond à - l'une des expressions régulières. Si c'est le cas, garder l'entrée - entière, sinon supprimer l'entrée. - setval attr values... - Remplacer toutes les valeurs de l'attribut attr par les valeurs - spécifiées. - addval attr values... - Ajouter un nouvel attribut avec les valeurs spécifiées. Si l'attribut - existe déjà, les nouvelles valeurs sont ajoutées à la fin. - sed args - Modifier l'espace de travail avec le résultat de la commande sed. - note: aucun argument n'est filtré, mais il ne faut pas utiliser les - options de sed qui provoquent la modification en place du fichier, - comme par exemple l'option -i - awk args - Modifier l'espace de travail avec le résultat de la commande awk. - grep args - Modifier l'espace de travail avec le résultat de la commande grep. - format [options] attrs... - Formater l'espace de travail en données tabulaires, et passer en mode - 'plain'. - --show-headers - Afficher les en-têtes - -F FSEP - Spécifier le séparateur pour les attributs. Par défaut, il s'agit du - caractère de tabulation. - -R VSEP - Spécifier le séparateur pour les valeurs des attributs. Par défaut, il - s'agit du point-virgule ';' - -e Retourner les valeurs comme des variables shell. Les options -F et -R - sont ignorées. Les attributs multivalués sont écrits sous forme de - tableaux. Par exemple: - attributes=('mail' 'givenName') - index=0 - mail='user@domain.fr' - givenName=('peter' 'gabriel') - --bc - Dans le mode -e, spécifier une commande à insérer avant le premier - enregistrement. Quand cette commande est lancée, index==-1 - -c Dans le mode -e, spécifier une commande à insérer après chaque - enregistrement - --ec - Dans le mode -e, spécifier une commande à insérer après le dernier - enregistrement - sort [args] - Modifier l'espace de travail avec le résultat de la commande sort. - edit - Lancer un éditeur pour modifier l'espace de travail. - diff [options] - Afficher les différences entre l'espace de travail et la version - précédente - ifok cmd - iferror cmd - Si le dernier code de retour est 0 (resp. !=0), lancer la commande cmd - skip n - Sauter les n prochaines commandes. A utiliser avec ifok et iferror - undo - Annuler la dernière modification effectuée sur l'espace de travail - -Les directives suivantes prennent le contenu de l'espace de travail, et le -transforment en une suite de commandes de modifications pour ldapmodify: - - A Créer un objet de toutes pièces avec les attributs donnés et leurs - valeurs. - a Ajouter les valeurs spécifiée à l'attribut - r Remplacer les valeurs de l'attribut par celles spécifiées - d Supprimer les valeurs spécifiées de l'attribut - D Supprimer l'attribut - delentry - Supprimer l'objet - ldapmodify - Utiliser ldapmodify pour modifier les objets sur le serveur. Il faut - utiliser au préalable l'une des méthodes de transformation parmi A, a, - r, d, D, delentry. - Le code de retour est celui de la commande ldapmodify. - ldapadd - Utiliser ldapadd pour créer les objets situés dans l'espace de travail. - Le code de retour est celui de la commande ldapadd. - ldapdelete - Utiliser ldapdelete pour supprimer la liste des dns situés dans l'espace - de travail. - Le code de retour est celui de la commande ldapdelete. - -Notes: -- les expressions régulières sont celles reconnues par awk. -- pour spécifier plusieurs actions sur une même ligne, les séparer par // -- le code de retour est 0 si ok, 255 si une erreur s'est produite (erreur de - syntaxe, de connexion, de lecture/écriture de fichier, etc.). sinon, les - opérations ldap{search,modify,delete,add} ont leur code de retour respectifs -}}} diff --git a/doc/ulib.md b/doc/ulib.md index e55c67d..9952a7d 100644 --- a/doc/ulib.md +++ b/doc/ulib.md @@ -1,70 +1,71 @@ # ulib ## Liste des librairies de ulib -* [ulib/apache](ulib_apache) -* [ulib/apache.tools](ulib_apache.tools) -* [ulib/auto](ulib_auto) -* [ulib/awk](ulib_awk) -* [ulib/base](ulib_base) -* [ulib/base.args](ulib_base.args) -* [ulib/base.array](ulib_base.array) -* [ulib/base.bool](ulib_base.bool) -* [ulib/base.compat](ulib_base.compat) -* [ulib/base.core](ulib_base.core) -* [ulib/base.init](ulib_base.init) -* [ulib/base.num](ulib_base.num) -* [ulib/base.quote](ulib_base.quote) -* [ulib/base.split](ulib_base.split) -* [ulib/base.string](ulib_base.string) -* [ulib/base.tools](ulib_base.tools) -* [ulib/base.ulib](ulib_base.ulib) -* [ulib/bash](ulib_bash) -* [ulib/cgi](ulib_cgi) -* [ulib/cgisupport](ulib_cgisupport) -* [ulib/compat](ulib_compat) -* [ulib/conf](ulib_conf) -* [ulib/crontab](ulib_crontab) -* [ulib/debian](ulib_debian) -* [ulib/DEFAULTS](ulib_DEFAULTS) -* [ulib/install](ulib_install) -* [ulib/ipcalc](ulib_ipcalc) -* [ulib/java](ulib_java) -* [ulib/javaproperties](ulib_javaproperties) -* [ulib/json](ulib_json) -* [ulib/ldap](ulib_ldap) -* [ulib/ldif](ulib_ldif) -* [ulib/legacy](ulib_legacy) -* [ulib/macosx](ulib_macosx) -* [ulib/mkcrypt](ulib_mkcrypt) -* [ulib/modeline](ulib_modeline) -* [ulib/network-manager-service](ulib_network-manager-service) -* [ulib/password](ulib_password) -* [ulib/prefixes](ulib_prefixes) -* [ulib/PREFIXES-DEFAULTS](ulib_PREFIXES-DEFAULTS) -* [ulib/pretty](ulib_pretty) -* [ulib/ptools](ulib_ptools) -* [ulib/redhat](ulib_redhat) -* [ulib/runs](ulib_runs) -* [ulib/runsmod](ulib_runsmod) -* [ulib/runsmod.defaults](ulib_runsmod.defaults) -* [ulib/semver](ulib_semver) -* [ulib/service](ulib_service) -* [ulib/sysinfos](ulib_sysinfos) -* [ulib/template](ulib_template) -* [ulib/tiddlywiki](ulib_tiddlywiki) -* [ulib/udir](ulib_udir) -* [ulib/uenv](ulib_uenv) -* [ulib/uenv_update](ulib_uenv_update) -* [ulib/uinc](ulib_uinc) -* [ulib/uinst](ulib_uinst) -* [ulib/ulib](ulib_ulib) -* [ulib/ulibsh](ulib_ulibsh) -* [ulib/vcs](ulib_vcs) -* [ulib/virsh](ulib_virsh) -* [ulib/webobjects](ulib_webobjects) -* [ulib/woinst](ulib_woinst) -* [ulib/wondermonitor](ulib_wondermonitor) -* [ulib/wosign](ulib_wosign) -* [ulib/wotaskd](ulib_wotaskd) +* [ulib/apache]() +* [ulib/apache.tools]() +* [ulib/auto]() +* [ulib/awk]() +* [ulib/base]() +* [ulib/base.args]() +* [ulib/base.array]() +* [ulib/base.bool]() +* [ulib/base.compat]() +* [ulib/base.core]() +* [ulib/base.init]() +* [ulib/base.num]() +* [ulib/base.quote]() +* [ulib/base.split]() +* [ulib/base.string]() +* [ulib/base.tools]() +* [ulib/base.ulib]() +* [ulib/bash]() +* [ulib/cgi]() +* [ulib/cgisupport]() +* [ulib/compat]() +* [ulib/conf]() +* [ulib/crontab]() +* [ulib/debian]() +* [ulib/DEFAULTS]() +* [ulib/install]() +* [ulib/ipcalc]() +* [ulib/java]() +* [ulib/javaproperties]() +* [ulib/json]() +* [ulib/ldap]() +* [ulib/ldif]() +* [ulib/legacy]() +* [ulib/macosx]() +* [ulib/mkcrypt]() +* [ulib/modeline]() +* [ulib/network-manager-service]() +* [ulib/password]() +* [ulib/prefixes]() +* [ulib/PREFIXES-DEFAULTS]() +* [ulib/pretty]() +* [ulib/ptools]() +* [ulib/redhat]() +* [ulib/runs]() +* [ulib/runsmod]() +* [ulib/runsmod.defaults]() +* [ulib/semver]() +* [ulib/service]() +* [ulib/sysinfos]() +* [ulib/template]() +* [ulib/tiddlywiki]() +* [ulib/udir]() +* [ulib/uenv]() +* [ulib/uenv_update]() +* [ulib/uinc]() +* [ulib/uinst]() +* [ulib/ulib]() +* [ulib/ulibsh]() +* [ulib/vcs]() +* [ulib/virsh]() +* [ulib/webobjects]() +* [ulib/woinst]() +* [ulib/wondermonitor]() +* [ulib/wosign]() +* [ulib/wotaskd]() +* [ulib/xmlsupport]() -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib.twp b/doc/ulib.twp deleted file mode 100644 index 014e28a..0000000 --- a/doc/ulib.twp +++ /dev/null @@ -1,74 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib - -!Liste des librairies de ulib -* [[ulib/apache]] -* [[ulib/apache.tools]] -* [[ulib/auto]] -* [[ulib/awk]] -* [[ulib/base]] -* [[ulib/base.args]] -* [[ulib/base.array]] -* [[ulib/base.bool]] -* [[ulib/base.compat]] -* [[ulib/base.core]] -* [[ulib/base.init]] -* [[ulib/base.num]] -* [[ulib/base.quote]] -* [[ulib/base.split]] -* [[ulib/base.string]] -* [[ulib/base.tools]] -* [[ulib/base.ulib]] -* [[ulib/bash]] -* [[ulib/cgi]] -* [[ulib/cgisupport]] -* [[ulib/compat]] -* [[ulib/conf]] -* [[ulib/crontab]] -* [[ulib/debian]] -* [[ulib/DEFAULTS]] -* [[ulib/install]] -* [[ulib/ipcalc]] -* [[ulib/java]] -* [[ulib/javaproperties]] -* [[ulib/json]] -* [[ulib/ldap]] -* [[ulib/ldif]] -* [[ulib/legacy]] -* [[ulib/macosx]] -* [[ulib/mkcrypt]] -* [[ulib/modeline]] -* [[ulib/network-manager-service]] -* [[ulib/password]] -* [[ulib/prefixes]] -* [[ulib/PREFIXES-DEFAULTS]] -* [[ulib/pretty]] -* [[ulib/ptools]] -* [[ulib/redhat]] -* [[ulib/runs]] -* [[ulib/runsmod]] -* [[ulib/runsmod.defaults]] -* [[ulib/semver]] -* [[ulib/service]] -* [[ulib/sysinfos]] -* [[ulib/template]] -* [[ulib/tiddlywiki]] -* [[ulib/udir]] -* [[ulib/uenv]] -* [[ulib/uenv_update]] -* [[ulib/uinc]] -* [[ulib/uinst]] -* [[ulib/ulib]] -* [[ulib/ulibsh]] -* [[ulib/vcs]] -* [[ulib/virsh]] -* [[ulib/webobjects]] -* [[ulib/woinst]] -* [[ulib/wondermonitor]] -* [[ulib/wosign]] -* [[ulib/wotaskd]] diff --git a/doc/ulib_DEFAULTS.md b/doc/ulib/DEFAULTS.md similarity index 100% rename from doc/ulib_DEFAULTS.md rename to doc/ulib/DEFAULTS.md diff --git a/doc/ulib_PREFIXES-DEFAULTS.md b/doc/ulib/PREFIXES-DEFAULTS.md similarity index 100% rename from doc/ulib_PREFIXES-DEFAULTS.md rename to doc/ulib/PREFIXES-DEFAULTS.md diff --git a/doc/ulib_apache.md b/doc/ulib/apache.md similarity index 91% rename from doc/ulib_apache.md rename to doc/ulib/apache.md index e660ba3..a902121 100644 --- a/doc/ulib_apache.md +++ b/doc/ulib/apache.md @@ -10,6 +10,7 @@ ## `get_default_apacheconf_prefix` ## `get_default_apacheavsitesdir_prefix` ## `get_default_apachesitesdir_prefix` +## `get_default_htdocsbase_prefix` ## `get_default_htdocsdir_prefix` ## `get_default_cgibindir_prefix` ## `compute_apache_prefixes` @@ -24,6 +25,7 @@ ## `get_APACHECONF_prefix` ## `get_APACHEAVSITESDIR_prefix` ## `get_APACHESITESDIR_prefix` +## `get_HTDOCSBASE_prefix` ## `get_HTDOCSDIR_prefix` ## `get_CGIBINDIR_prefix` diff --git a/doc/ulib_apache.tools.md b/doc/ulib/apache.tools.md similarity index 71% rename from doc/ulib_apache.tools.md rename to doc/ulib/apache.tools.md index 7040a06..770632f 100644 --- a/doc/ulib_apache.tools.md +++ b/doc/ulib/apache.tools.md @@ -9,5 +9,10 @@ puis initialiser les variables $3(=cert), $4(=key) et $5(=ca) ## `apache_addcert` ## `apache_autoconf` ## `apache_autoconf_localhosts` +## `apacheconfig_initvars` +## `apacheconfig_loadconf` +## `apacheconfig_sysinfos` +## `apacheconfig_deploy` +## `apacheconfig_localhosts` -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_auto.md b/doc/ulib/auto.md similarity index 100% rename from doc/ulib_auto.md rename to doc/ulib/auto.md diff --git a/doc/ulib_awk.md b/doc/ulib/awk.md similarity index 95% rename from doc/ulib_awk.md rename to doc/ulib/awk.md index 39eba35..85ebfe3 100644 --- a/doc/ulib_awk.md +++ b/doc/ulib/awk.md @@ -34,6 +34,7 @@ caractères de long. ## `csortcsv` ## `sortcsv` ## `ldumpcsv` +## `init_fields` ## `cdumpcsv` ## `dumpcsv` ## `lprintcsv` diff --git a/doc/ulib_base.args.md b/doc/ulib/base.args.md similarity index 100% rename from doc/ulib_base.args.md rename to doc/ulib/base.args.md diff --git a/doc/ulib_base.array.md b/doc/ulib/base.array.md similarity index 100% rename from doc/ulib_base.array.md rename to doc/ulib/base.array.md diff --git a/doc/ulib_base.bool.md b/doc/ulib/base.bool.md similarity index 100% rename from doc/ulib_base.bool.md rename to doc/ulib/base.bool.md diff --git a/doc/ulib_base.compat.md b/doc/ulib/base.compat.md similarity index 100% rename from doc/ulib_base.compat.md rename to doc/ulib/base.compat.md diff --git a/doc/ulib_base.core.md b/doc/ulib/base.core.md similarity index 75% rename from doc/ulib_base.core.md rename to doc/ulib/base.core.md index 4e56d34..2112d67 100644 --- a/doc/ulib_base.core.md +++ b/doc/ulib/base.core.md @@ -142,5 +142,54 @@ testrp value == cmd1 // cmd2 ~~~ lancer la commande $@ en redirigeant la sortie d'erreur sur la sortie standard ~~~ +## `is_defined` +~~~ +tester si la variable $1 est définie +~~~ +## `is_array` +~~~ +tester si la variable $1 est un tableau +~~~ +## `upvar` +~~~ +Assign variable one scope above the caller +Usage: local "$1" && upvar $1 "value(s)" +Param: $1 Variable name to assign value to +Param: $* Value(s) to assign. If multiple values, an array is +assigned, otherwise a single value is assigned. +~~~ +## `array_upvar` +~~~ +Comme upvar() mais force la création d'un tableau +~~~ +## `upvars` +~~~ +Version modifiée par rapport à l'original. Il n'est plus nécessaire de +préfixer une variable scalaire avec -v, et -a peut être spécifié sans +argument. +Usage: +local varname [varname ...] && upvars [varname value] | [-aN varname [value ...]] ... +Options: +-a +assigns remaining values to varname as array +-aN +assigns next N values to varname as array. Returns 1 if wrong number of +options occurs +~~~ +## `array_buildcmd` +~~~ +Construire un tableau dont les éléments sont calculés à partir d'une chaine +dont les éléments sont séparés par des marqueurs ++X, e.g: +++ ou ++c[md] une chaine simple résultat de la commande +++s[split] un ensemble d'arguments résultat du split du résultat de la +commande sur espaces +++l[split] un ensemble d'arguments résultat du split du résultat de la +commande sur les lignes +++a[dd] un ensemble d'arguments donnés directement +Exemple: +array_buildcmd args echo Copie de ++ ppath "$src" ++a vers ++ ppath "$dest" +~~~ +## `buildcmd` +## `evalcmd` -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_base.init.md b/doc/ulib/base.init.md similarity index 100% rename from doc/ulib_base.init.md rename to doc/ulib/base.init.md diff --git a/doc/ulib_base.md b/doc/ulib/base.md similarity index 97% rename from doc/ulib_base.md rename to doc/ulib/base.md index cf27957..02ec387 100644 --- a/doc/ulib_base.md +++ b/doc/ulib/base.md @@ -1158,6 +1158,25 @@ on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées Si $2 vaut C, la valeur par défaut est N si on est interactif, O sinon Si $2 vaut X, la valeur par défaut est O si on est interactif, N sinon ~~~ +## `ask_any` +~~~ +Afficher le message $1 suivi du texte "[$2]" (qui vaut par défaut +Oq), puis +lire la réponse. Les lettres de la chaine de format $2 sont numérotées de 0 à +$((${#2} - 1)). Le code de retour est le numéro de la lettre qui a été +sélectionnée. Cette fonction est une généralisation de ask_yesno() pour +n'importe quel ensemble de lettres. +La première lettre en majuscule est la lettre sélectionnée par défaut. +La lettre O matche toutes les lettres qui signifient oui: o, y, 1, v, t +La lettre N matche toutes les lettres qui signifient non: n, f, 0 +Il y a des raccourcis: ++O --> On ++N --> oN ++C --> oN si on est en mode interactif, On sinon ++X --> On si on est en mode interactifn oN sinon +Si $1 est une option, elle est utilisée avec check_interaction pour savoir si +on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées +($2=message, $3=format) +~~~ ## `read_value` ~~~ Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non diff --git a/doc/ulib_base.num.md b/doc/ulib/base.num.md similarity index 100% rename from doc/ulib_base.num.md rename to doc/ulib/base.num.md diff --git a/doc/ulib_base.quote.md b/doc/ulib/base.quote.md similarity index 74% rename from doc/ulib_base.quote.md rename to doc/ulib/base.quote.md index c25e2cb..1caf8a4 100644 --- a/doc/ulib_base.quote.md +++ b/doc/ulib/base.quote.md @@ -17,5 +17,10 @@ Dans la chaine $* qui est de la forme "name=value", remplacer dans name et dans value '%' par '%25', '+' par '%2B', '&' par '%26', '=' par '%3D', ' ' par '+' ~~~ +## `qsql` +~~~ +Dans la chaine $*, remplacer ' par '' et afficher la chaine entourée de +quotes. Ceci est utile pour quoter des valeurs à insérer dans une requête SQL. +~~~ -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_base.split.md b/doc/ulib/base.split.md similarity index 100% rename from doc/ulib_base.split.md rename to doc/ulib/base.split.md diff --git a/doc/ulib_base.string.md b/doc/ulib/base.string.md similarity index 61% rename from doc/ulib_base.string.md rename to doc/ulib/base.string.md index 6c13f8d..b074c99 100644 --- a/doc/ulib_base.string.md +++ b/doc/ulib/base.string.md @@ -62,6 +62,34 @@ longueur de la chaine à partir de start Remplacer dans la valeur $3* le motif $1 par la chaine $2. $1 peut commencer par l'un des caractères /, #, % pour indiquer le type de recherche ~~~ +## `strops` +~~~ +Appliquer à une chaine de caractères une suite de traitements, e.g: +'strops var deref +suffix' est équivalent à 'echo "${var}suffix"' +En commençant avec la valeur initiale $1, les arguments $2..* sont des +opérations à appliquer dans l'ordre. +Les opérations suivantes considèrent que la valeur courante est un nom de +variable: +:- := :? :+ deref dcount +Toutes les autres opérations travaillent directement avec la valeur +courante. Les opérations suivantes appliquent une transformation: +# % / : ^ , +# -# +% -% + - mid repl +Les opérations suivantes font un test sur la valeur et retournent +immédiatement: += == != < > -eq -ne -lt -le -gt -ge -n -z +La syntaxe des opérateurs standards de bash est reprise autant que possible, +i.e si on a l'habitude d'écrire ${varOP} en bash, alors la syntaxe à utiliser +à priori est 'strops var OP' ou 'strop var deref OP' suivant les opérateurs. +Autres opérateurs: +deref indirection +dcount nombre d'éléments du tableau ++#STR ajouter un préfixe +-#STR supprimer un préfixe ++%STR ou +STR ajouter un suffixe +-%STR ou -STR supprimer un suffixe +mid RANGE traiter la chaine avec strmid() +repl FROM TO traiter la chaine avec strrepl() +~~~ ## `first_char` ~~~ retourner le premier caractère de la chaine $* diff --git a/doc/ulib_base.tools.md b/doc/ulib/base.tools.md similarity index 100% rename from doc/ulib_base.tools.md rename to doc/ulib/base.tools.md diff --git a/doc/ulib_base.ulib.md b/doc/ulib/base.ulib.md similarity index 100% rename from doc/ulib_base.ulib.md rename to doc/ulib/base.ulib.md diff --git a/doc/ulib_bash.md b/doc/ulib/bash.md similarity index 100% rename from doc/ulib_bash.md rename to doc/ulib/bash.md diff --git a/doc/ulib_cgi.md b/doc/ulib/cgi.md similarity index 100% rename from doc/ulib_cgi.md rename to doc/ulib/cgi.md diff --git a/doc/ulib_cgisupport.md b/doc/ulib/cgisupport.md similarity index 100% rename from doc/ulib_cgisupport.md rename to doc/ulib/cgisupport.md diff --git a/doc/ulib_compat.md b/doc/ulib/compat.md similarity index 100% rename from doc/ulib_compat.md rename to doc/ulib/compat.md diff --git a/doc/ulib_conf.md b/doc/ulib/conf.md similarity index 100% rename from doc/ulib_conf.md rename to doc/ulib/conf.md diff --git a/doc/ulib_crontab.md b/doc/ulib/crontab.md similarity index 100% rename from doc/ulib_crontab.md rename to doc/ulib/crontab.md diff --git a/doc/ulib_debian.md b/doc/ulib/debian.md similarity index 100% rename from doc/ulib_debian.md rename to doc/ulib/debian.md diff --git a/doc/ulib_install.md b/doc/ulib/install.md similarity index 100% rename from doc/ulib_install.md rename to doc/ulib/install.md diff --git a/doc/ulib_ipcalc.md b/doc/ulib/ipcalc.md similarity index 100% rename from doc/ulib_ipcalc.md rename to doc/ulib/ipcalc.md diff --git a/doc/ulib_java.md b/doc/ulib/java.md similarity index 100% rename from doc/ulib_java.md rename to doc/ulib/java.md diff --git a/doc/ulib_javaproperties.md b/doc/ulib/javaproperties.md similarity index 100% rename from doc/ulib_javaproperties.md rename to doc/ulib/javaproperties.md diff --git a/doc/ulib_json.md b/doc/ulib/json.md similarity index 100% rename from doc/ulib_json.md rename to doc/ulib/json.md diff --git a/doc/ulib_ldap.md b/doc/ulib/ldap.md similarity index 100% rename from doc/ulib_ldap.md rename to doc/ulib/ldap.md diff --git a/doc/ulib_ldif.md b/doc/ulib/ldif.md similarity index 91% rename from doc/ulib_ldif.md rename to doc/ulib/ldif.md index e8f5fdc..42b2ec4 100644 --- a/doc/ulib_ldif.md +++ b/doc/ulib/ldif.md @@ -24,6 +24,7 @@ Supprimer les objets marqués avec --DELETE--: ## `tl_modifyattr` ## `tl_deleteattr` ## `tl_deleteentry` +## `tl_modrdn` ## `tl_touchentry` ## `tl_keepattr` ## `tl_keepval` @@ -58,6 +59,9 @@ commandes. _T_cut_after: faut-il découper automatiquement les lignes *après* avoir lancé les commandes. +_T_suffix: +suffixe de la base de données pour les commandes qui prennent des RDN, +e.g. modrdn ~~~ ## `transform` diff --git a/doc/ulib_legacy.md b/doc/ulib/legacy.md similarity index 100% rename from doc/ulib_legacy.md rename to doc/ulib/legacy.md diff --git a/doc/ulib_macosx.md b/doc/ulib/macosx.md similarity index 100% rename from doc/ulib_macosx.md rename to doc/ulib/macosx.md diff --git a/doc/ulib_mkcrypt.md b/doc/ulib/mkcrypt.md similarity index 100% rename from doc/ulib_mkcrypt.md rename to doc/ulib/mkcrypt.md diff --git a/doc/ulib_modeline.md b/doc/ulib/modeline.md similarity index 100% rename from doc/ulib_modeline.md rename to doc/ulib/modeline.md diff --git a/doc/ulib_network-manager-service.md b/doc/ulib/network-manager-service.md similarity index 100% rename from doc/ulib_network-manager-service.md rename to doc/ulib/network-manager-service.md diff --git a/doc/ulib_password.md b/doc/ulib/password.md similarity index 100% rename from doc/ulib_password.md rename to doc/ulib/password.md diff --git a/doc/ulib_prefixes.md b/doc/ulib/prefixes.md similarity index 100% rename from doc/ulib_prefixes.md rename to doc/ulib/prefixes.md diff --git a/doc/ulib_pretty.md b/doc/ulib/pretty.md similarity index 100% rename from doc/ulib_pretty.md rename to doc/ulib/pretty.md diff --git a/doc/ulib_ptools.md b/doc/ulib/ptools.md similarity index 100% rename from doc/ulib_ptools.md rename to doc/ulib/ptools.md diff --git a/doc/ulib_redhat.md b/doc/ulib/redhat.md similarity index 100% rename from doc/ulib_redhat.md rename to doc/ulib/redhat.md diff --git a/doc/ulib_runs.md b/doc/ulib/runs.md similarity index 99% rename from doc/ulib_runs.md rename to doc/ulib/runs.md index 97e6775..2fca0f7 100644 --- a/doc/ulib_runs.md +++ b/doc/ulib/runs.md @@ -127,6 +127,8 @@ Lancer la recette $1. Le fichier de recette est cherché dans les répertoires de RUNSSCRIPTSPATH. Retourner 123 si le fichier de recette n'a pas été trouvé, sinon retourner la valeur de retour de runs_recipe() ~~~ +## `runs_before_parse_args` +## `runs_after_parse_args` ## `runs_init` ## `runs_initdomains` ~~~ diff --git a/doc/ulib_runsmod.defaults.md b/doc/ulib/runsmod.defaults.md similarity index 100% rename from doc/ulib_runsmod.defaults.md rename to doc/ulib/runsmod.defaults.md diff --git a/doc/ulib_runsmod.md b/doc/ulib/runsmod.md similarity index 92% rename from doc/ulib_runsmod.md rename to doc/ulib/runsmod.md index b848629..17f39eb 100644 --- a/doc/ulib_runsmod.md +++ b/doc/ulib/runsmod.md @@ -1,5 +1,6 @@ # ulib/runsmod +## `runsmod_autoconf` ## `runsmod_checkenv` ~~~ vérifier l'environement. créer les répertoires nécessaires. @@ -25,6 +26,7 @@ all, self ou one pour un hôte spécifique $2. Ajouter les chemins dans le tableau REPO_DIRS. Mettre à jour les tableaux SCRIPTS_DIRS, MODULES_DIRS et HOSTS_DIRS ~~~ +## `runsmod_update_vars` ## `runsmod_teardown_vars` -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib/semver.md b/doc/ulib/semver.md new file mode 100644 index 0000000..12c6b39 --- /dev/null +++ b/doc/ulib/semver.md @@ -0,0 +1,72 @@ +# ulib/semver + +## `semver_parse` +~~~ +args: version majorV minorV patchlevelV preleaseV metadataV validV +~~~ +## `semver_incmajor` +~~~ +args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease +~~~ +## `semver_incminor` +~~~ +args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease +~~~ +## `semver_incpatchlevel` +~~~ +args: majorV minorV patchlevelV preleaseV metadataV maven_update setprelease +~~~ +## `semver_setversion` +~~~ +args: version majorV minorV patchlevelV preleaseV metadataV +~~~ +## `semver_setprelease` +~~~ +args: setprelease majorV minorV patchlevelV preleaseV metadataV +~~~ +## `semver_compare_prelease` +~~~ +args: prelease1 prelease2 +~~~ +## `semver_setmetadata` +~~~ +args: setmetadata majorV minorV patchlevelV preleaseV metadataV +~~~ +## `semver_addmetadata` +~~~ +args: addmetadata majorV minorV patchlevelV preleaseV metadataV +~~~ +## `semver_compare_metadata` +~~~ +args: metadata1 metadata2 +même algo que pour prelease +~~~ +## `semver_copy` +~~~ +args: majorDV minorDV patchlevelDV preleaseDV metadataDV majorSV minorSV patchlevelSV preleaseSV metadataSV +~~~ +## `semver_build` +~~~ +args: majorV minorV patchlevelV preleaseV metadataV +~~~ +## `semver_setvar` +~~~ +args: versionV majorV minorV patchlevelV preleaseV metadataV +~~~ +## `psemver_parse` +## `psemver_incmajor` +## `psemver_incminor` +## `psemver_incpatchlevel` +## `psemver_setversion` +## `psemver_setprelease` +## `psemver_compare_prelease` +## `psemver_setmetadata` +## `psemver_addmetadata` +## `psemver_compare_metadata` +## `psemver_copy` +## `psemver_build` +## `psemver_setvar` +## `psemver_setprmd` +## `psemver_incsetprmd` + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_service.md b/doc/ulib/service.md similarity index 100% rename from doc/ulib_service.md rename to doc/ulib/service.md diff --git a/doc/ulib_sysinfos.md b/doc/ulib/sysinfos.md similarity index 100% rename from doc/ulib_sysinfos.md rename to doc/ulib/sysinfos.md diff --git a/doc/ulib_template.md b/doc/ulib/template.md similarity index 100% rename from doc/ulib_template.md rename to doc/ulib/template.md diff --git a/doc/ulib_tiddlywiki.md b/doc/ulib/tiddlywiki.md similarity index 100% rename from doc/ulib_tiddlywiki.md rename to doc/ulib/tiddlywiki.md diff --git a/doc/ulib_udir.md b/doc/ulib/udir.md similarity index 100% rename from doc/ulib_udir.md rename to doc/ulib/udir.md diff --git a/doc/ulib_uenv.md b/doc/ulib/uenv.md similarity index 100% rename from doc/ulib_uenv.md rename to doc/ulib/uenv.md diff --git a/doc/ulib_uenv_update.md b/doc/ulib/uenv_update.md similarity index 100% rename from doc/ulib_uenv_update.md rename to doc/ulib/uenv_update.md diff --git a/doc/ulib_uinc.md b/doc/ulib/uinc.md similarity index 100% rename from doc/ulib_uinc.md rename to doc/ulib/uinc.md diff --git a/doc/ulib_uinst.md b/doc/ulib/uinst.md similarity index 100% rename from doc/ulib_uinst.md rename to doc/ulib/uinst.md diff --git a/doc/ulib_ulib.md b/doc/ulib/ulib.md similarity index 100% rename from doc/ulib_ulib.md rename to doc/ulib/ulib.md diff --git a/doc/ulib_ulibsh.md b/doc/ulib/ulibsh.md similarity index 100% rename from doc/ulib_ulibsh.md rename to doc/ulib/ulibsh.md diff --git a/doc/ulib_vcs.md b/doc/ulib/vcs.md similarity index 98% rename from doc/ulib_vcs.md rename to doc/ulib/vcs.md index f65731a..fbc2009 100644 --- a/doc/ulib_vcs.md +++ b/doc/ulib/vcs.md @@ -120,6 +120,7 @@ nécessaire. la branche d'origine $2 vaut par défaut refs/remotes/origin/$1 vérifier que les branches $1 et $2 ont un ancêtre commun, et que la branche $1 a été complètement fusionnée dans la branche destination $2 ~~~ +## `git_annex_use_ssh_wrapper` ## `git_annex_initial` ~~~ sur le dépôt $1 fraichement cloné, vérifier s'il faut faire git annex diff --git a/doc/ulib_virsh.md b/doc/ulib/virsh.md similarity index 100% rename from doc/ulib_virsh.md rename to doc/ulib/virsh.md diff --git a/doc/ulib_webobjects.md b/doc/ulib/webobjects.md similarity index 100% rename from doc/ulib_webobjects.md rename to doc/ulib/webobjects.md diff --git a/doc/ulib_woinst.md b/doc/ulib/woinst.md similarity index 100% rename from doc/ulib_woinst.md rename to doc/ulib/woinst.md diff --git a/doc/ulib_wondermonitor.md b/doc/ulib/wondermonitor.md similarity index 100% rename from doc/ulib_wondermonitor.md rename to doc/ulib/wondermonitor.md diff --git a/doc/ulib_wosign.md b/doc/ulib/wosign.md similarity index 100% rename from doc/ulib_wosign.md rename to doc/ulib/wosign.md diff --git a/doc/ulib_wotaskd.md b/doc/ulib/wotaskd.md similarity index 100% rename from doc/ulib_wotaskd.md rename to doc/ulib/wotaskd.md diff --git a/doc/ulib/xmlsupport.md b/doc/ulib/xmlsupport.md new file mode 100644 index 0000000..d044c4a --- /dev/null +++ b/doc/ulib/xmlsupport.md @@ -0,0 +1,5 @@ +# ulib/xmlsupport + +## `xpathtool` + +-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_DEFAULTS.twp b/doc/ulib_DEFAULTS.twp deleted file mode 100644 index efe61bd..0000000 --- a/doc/ulib_DEFAULTS.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/DEFAULTS - diff --git a/doc/ulib_PREFIXES-DEFAULTS.twp b/doc/ulib_PREFIXES-DEFAULTS.twp deleted file mode 100644 index 4450490..0000000 --- a/doc/ulib_PREFIXES-DEFAULTS.twp +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/PREFIXES-DEFAULTS - -!! {{{compute_all_prefixes}}} -!! {{{recompute_all_prefixes}}} diff --git a/doc/ulib_apache.tools.twp b/doc/ulib_apache.tools.twp deleted file mode 100644 index e5ed841..0000000 --- a/doc/ulib_apache.tools.twp +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/apache.tools - -!! {{{apache_resolvecert}}} -{{{ -Calculer l'emplacement des certificats correspondant aux arguments $1 et -$2 (qui correspondent aux options --conf et --dir de apache_addcert()), -puis initialiser les variables $3(=cert), $4(=key) et $5(=ca) -}}} -!! {{{apache_addcert}}} -!! {{{apache_autoconf}}} -!! {{{apache_autoconf_localhosts}}} diff --git a/doc/ulib_apache.twp b/doc/ulib_apache.twp deleted file mode 100644 index 5d7e122..0000000 --- a/doc/ulib_apache.twp +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/apache - -!! {{{get_default_apachebin_prefix}}} -!! {{{get_default_apacheversion_prefix}}} -!! {{{get_default_apachectl_prefix}}} -!! {{{get_default_apachelogdir_prefix}}} -!! {{{get_default_apachesslcertsdir_prefix}}} -!! {{{get_default_apachesslkeysdir_prefix}}} -!! {{{get_default_apacheconfdir_prefix}}} -!! {{{get_default_apacheconf_prefix}}} -!! {{{get_default_apacheavsitesdir_prefix}}} -!! {{{get_default_apachesitesdir_prefix}}} -!! {{{get_default_htdocsdir_prefix}}} -!! {{{get_default_cgibindir_prefix}}} -!! {{{compute_apache_prefixes}}} -!! {{{recompute_apache_prefixes}}} -!! {{{get_APACHEBIN_prefix}}} -!! {{{get_APACHEVERSION_prefix}}} -!! {{{get_APACHECTL_prefix}}} -!! {{{get_APACHELOGDIR_prefix}}} -!! {{{get_APACHESSLCERTSDIR_prefix}}} -!! {{{get_APACHESSLKEYSDIR_prefix}}} -!! {{{get_APACHECONFDIR_prefix}}} -!! {{{get_APACHECONF_prefix}}} -!! {{{get_APACHEAVSITESDIR_prefix}}} -!! {{{get_APACHESITESDIR_prefix}}} -!! {{{get_HTDOCSDIR_prefix}}} -!! {{{get_CGIBINDIR_prefix}}} diff --git a/doc/ulib_auto.twp b/doc/ulib_auto.twp deleted file mode 100644 index c83c9d5..0000000 --- a/doc/ulib_auto.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/auto - diff --git a/doc/ulib_awk.twp b/doc/ulib_awk.twp deleted file mode 100644 index 9c144a0..0000000 --- a/doc/ulib_awk.twp +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/awk - -!! {{{parseheaders}}} -!! {{{printheaders}}} -!! {{{resetheaders}}} -!! {{{copyall}}} -!! {{{lawkcsv}}} -!! {{{cawkcsv}}} -!! {{{awkcsv}}} -!! {{{lgrepcsv}}} -!! {{{cgrepcsv}}} -!! {{{grepcsv}}} -!! {{{lawkfsv2csv}}} -!! {{{cawkfsv2csv}}} -!! {{{awkfsv2csv}}} -!! {{{lmergecsv}}} -{{{ -Fusionner sur la sortie standard les deux fichiers csv $1 et $2. La clé du -fichier $1 est spécifiée par l'option --lkey et vaut 1 par défaut. La clé -du fichier $2 est spécifiée par l'option --rkey et vaut 1 par défaut. Les -valeurs des clés ne doivent pas faire plus de 64 caractères de long. -}}} -!! {{{readleft}}} -!! {{{readright}}} -!! {{{right2left}}} -!! {{{cmergecsv}}} -!! {{{mergecsv}}} -!! {{{lsortcsv}}} -{{{ -Trier le fichier csv $1. La clé du tri est spécifiée par l'option -k et -vaut 1 par défaut. Les valeurs des clés ne doivent pas faire plus de 64 -caractères de long. -}}} -!! {{{csortcsv}}} -!! {{{sortcsv}}} -!! {{{ldumpcsv}}} -!! {{{cdumpcsv}}} -!! {{{dumpcsv}}} -!! {{{lprintcsv}}} -!! {{{cprintcsv}}} -!! {{{printcsv}}} diff --git a/doc/ulib_base.args.twp b/doc/ulib_base.args.twp deleted file mode 100644 index 10a399a..0000000 --- a/doc/ulib_base.args.twp +++ /dev/null @@ -1,64 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.args - -!! {{{parse_opts}}} -{{{ -Analyser des arguments. Cette fonction doit être appelée avec une description -des options à analyser, suivie des arguments proprement dits. En fonction des -options rencontrées, certaines variables sont mises à jour. -Les arguments de cette fonction sont donc de la forme 'optdescs -- args' -}}} -!! {{{parse_args_check}}} -{{{ -Simplifier l'utilisation de parse_opts(). En entrée, le tableau args doit être -initialisé avec la liste des options. En sortie, ce tableau contient la liste -des arguments restant sur la ligne de commande. En cas d'erreur, retourner 1. -Exemple d'utilisation: -args=(...) -parse_args_check "$@" || return; set -- "${args[@]}" -}}} -!! {{{parse_args}}} -{{{ -Simplifier l'utilisation de parse_opts(). En entrée, le tableau args doit être -initialisé avec la liste des options. En sortie, ce tableau contient la liste -des arguments restant sur la ligne de commande. En cas d'erreur, quitter le -script avec die() -Exemple d'utilisation: -args=(...) -parse_args_check "$@"; set -- "${args[@]}" -}}} -!! {{{genparse}}} -{{{ -Afficher une ligne de commande à évaluer pour simplifier l'utilisation de -parse_opts(). Une fonction display_help() par défaut est définie et les -options appropriées de parse_opts sont utilisées pour reconnaître les options -spécifiées par les arguments. -Cette fonction peut être utilisée de cette manière: -HELP_DESC=... -HELP_ARG_DESC=... # pour chaque arg -eval "$(genparse [args...])" -D'autres variables peuvent être définies: HELP_USAGE, HELP_OPTIONS, -HELP_ARG_OPTION. Consulter le source pour connaitre leur utilisation -Les arguments de cette fonction sont de la forme 'sansarg' pour une option -simple qui ne prend pas d'argument ou 'avecarg=[default-value]' pour une -option qui prend un argument. Les options générées sont des options -longues. En l'occurence, les options générées sont respectivement '--sansarg' -et '--avecarg:' -Les variables et les options sont toujours en minuscule. Pour les variables, -le caractère '-' est remplacé par '_'. Si une option contient une lettre en -majuscule, l'option courte correspondante à cette lettre sera aussi reconnue. -Par exemple, la commande suivante: -genparse Force enCoding=utf-8 input= long-Option= -affichera ceci: -function display_help() { -[ -n "$HELP_USAGE" ] || HELP_USAGE="USAGE -$scriptname [options]" -[ -n "$HELP_OPTIONS" ] || HELP_OPTIONS="OPTIONS -${HELP_FORCE_OPTION:- -f, --force${HELP_FORCE_DESC:+ -$HELP_FORCE_DESC}} -}}} diff --git a/doc/ulib_base.array.twp b/doc/ulib_base.array.twp deleted file mode 100644 index aa8e645..0000000 --- a/doc/ulib_base.array.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.array - diff --git a/doc/ulib_base.bool.twp b/doc/ulib_base.bool.twp deleted file mode 100644 index 4cbfbee..0000000 --- a/doc/ulib_base.bool.twp +++ /dev/null @@ -1,38 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.bool - -!! {{{is_yes}}} -{{{ -retourner vrai si $1 est une valeur "oui" -}}} -!! {{{is_no}}} -{{{ -retourner vrai si $1 est une valeur "non" -}}} -!! {{{yesval}}} -{{{ -normaliser une valeur vraie: si $1 est une valeur "oui", afficher 1, sinon -afficher une chaine vide -}}} -!! {{{setb}}} -{{{ -Lancer la commande $2..@ en supprimant la sortie standard. Si la commande -retourne vrai, assigner la valeur 1 à la variable $1. Sinon, lui assigner la -valeur "" -note: en principe, la syntaxe est 'setb var cmd args...'. cependant, la -syntaxe 'setb var=cmd args...' est supportée aussi -}}} -!! {{{evalb}}} -{{{ -Lancer la commande $@ avec evalx() en supprimant la sortie standard. Si la -commande retourne vrai, afficher 1. Sinon, afficher "" -}}} -!! {{{setxb}}} -{{{ -équivalent à setx $1 evalb $2..@ -}}} diff --git a/doc/ulib_base.compat.twp b/doc/ulib_base.compat.twp deleted file mode 100644 index cae5079..0000000 --- a/doc/ulib_base.compat.twp +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.compat - -!! {{{setx2}}} -!! {{{rawecho}}} -!! {{{rawecho_}}} -!! {{{quote_arg}}} -!! {{{quoted_arg}}} -!! {{{quoted_args}}} -!! {{{set_var}}} -!! {{{set_var_cmd}}} -!! {{{set_var_literal}}} -!! {{{quote_awk}}} -!! {{{quoted_awk}}} -!! {{{quote_seds}}} -!! {{{quote_form}}} -!! {{{quoted_form}}} diff --git a/doc/ulib_base.core.twp b/doc/ulib_base.core.twp deleted file mode 100644 index 36d82cb..0000000 --- a/doc/ulib_base.core.twp +++ /dev/null @@ -1,150 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.core - -!! {{{echo_}}} -{{{ -afficher la valeur $* sans passer à la ligne -}}} -!! {{{recho}}} -{{{ -afficher une valeur brute. contrairement à la commande echo, ne reconnaitre -aucune option (i.e. -e, -E, -n ne sont pas signifiants) -}}} -!! {{{recho_}}} -{{{ -afficher une valeur brute, sans passer à la ligne. contrairement à la commande -echo, ne reconnaitre aucune option (i.e. -e, -E, -n ne sont pas signifiants) -}}} -!! {{{should_quote}}} -{{{ -Tester si la chaine $* doit être mise entre quotes -}}} -!! {{{qval}}} -{{{ -Afficher la chaine $* quotée avec " -}}} -!! {{{qvalm}}} -{{{ -Afficher la chaine $* quotée si nécessaire avec " -}}} -!! {{{qvalr}}} -{{{ -Afficher la chaine $* quotée si nécessaire avec ", sauf si elle est vide -}}} -!! {{{qvals}}} -{{{ -Afficher chaque argument de cette fonction quotée le cas échéant avec " -Chaque valeur est séparée par un espace. -}}} -!! {{{qwc}}} -{{{ -Dans la chaine $*, remplacer \ par \\, " par \", $ par \$, ` par \`, puis -quoter la chaine avec ", sauf les wildcards * et ? -Cela permet de quoter une chaine permettant de glober des fichiers, e.g -eval "ls $(qwc "$value")" -Note: la protection de ! n'est pas effectuée, parce que le comportement du -shell est incohérent entre le shell interactif et les scripts. Pour une -version plus robuste, il est nécessaire d'utiliser un programme externe tel -que sed ou awk -}}} -!! {{{qlines}}} -{{{ -Traiter chaque ligne de l'entrée standard pour en faire des chaines quotées -avec ' -}}} -!! {{{setv}}} -{{{ -initialiser la variable $1 avec la valeur $2* -note: en principe, la syntaxe est 'setv var values...'. cependant, la -syntaxe 'setv var=values...' est supportée aussi -}}} -!! {{{echo_setv}}} -{{{ -Afficher la commande qui serait lancée par setv "$@" -}}} -!! {{{setx}}} -{{{ -syntaxe 1: setx var cmd -initialiser la variable $1 avec le résultat de la commande "$2..@" -note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la -syntaxe 'setx var=cmd args...' est supportée aussi -syntaxe 2: setx -a array cmd -initialiser le tableau $1 avec le résultat de la commande "$2..@", chaque -ligne du résultat étant un élément du tableau -note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la -syntaxe 'setx -a array=cmd args...' est supportée aussi -}}} -!! {{{evalx}}} -{{{ -Implémenter une syntaxe lisible et naturelle permettant d'enchainer des -traitements sur une valeur. Par exemple, la commande -evalx cmd1... // cmd2... // cmd3... -affiche le résultat de la commande "$(cmd3 $(cmd2 $(cmd1)))" -Retourner le dernier code d'erreur non nul, ou 0 si toutes les commandes se -sont exécutées sans erreur. -}}} -!! {{{setxx}}} -{{{ -équivalent à setx $1 evalx $2..@ -}}} -!! {{{evalp}}} -{{{ -Implémenter une syntaxe alternative permettant d'enchainer des traitements sur -un flux de données. Par exemple, la commande -evalp cmd1... // cmd2... // cmd3... -affiche le résultat de la commande "$(cmd1 | cmd2 | cmd3)" -Typiquement, cette fonction permet de faciliter la construction d'un -enchainement de commandes par programme, ou de faciliter l'utilisation de la -fonction setx() pour récupérer le résultat d'un enchainement. Dans les autres -cas, il est plus simple et naturel d'écrire les enchainements avec la syntaxe -de bash. -}}} -!! {{{setxp}}} -{{{ -équivalent à setx $1 evalp $2..@ -}}} -!! {{{testx}}} -{{{ -Faire un test unaire avec la commande [ sur une valeur calculée avec evalx. -Utiliser la syntaxe 'testx op cmds...' e.g. -testx -z cmd1 // cmd2 -}}} -!! {{{test2x}}} -{{{ -Faire une test binaire avec la commande [ entre une valeur spécifiée et une -valeur calculée avec evalx. Utiliser la syntaxe 'test2x value op cmds...' e.g. -test2x value == cmd1 // cmd2 -}}} -!! {{{testrx}}} -{{{ -Faire une test binaire avec la commande [[ entre une valeur spécifiée et une -valeur calculée avec evalx. Utiliser la syntaxe 'testrx value op cmds...' e.g. -testrx value == cmd1 // cmd2 -}}} -!! {{{testp}}} -{{{ -Faire un test unaire avec la commande [ sur une valeur calculée avec evalp. -Utiliser la syntaxe 'testp op cmds...' e.g. -testp -z cmd1 // cmd2 -}}} -!! {{{test2p}}} -{{{ -Faire une test binaire avec la commande [ entre une valeur spécifiée et une -valeur calculée avec evalp. Utiliser la syntaxe 'test2p value op cmds...' e.g. -test2p value == cmd1 // cmd2 -}}} -!! {{{testrp}}} -{{{ -Faire une test binaire avec la commande [[ entre une valeur spécifiée et une -valeur calculée avec evalp. Utiliser la syntaxe 'testrp value op cmds...' e.g. -testrp value == cmd1 // cmd2 -}}} -!! {{{err2out}}} -{{{ -lancer la commande $@ en redirigeant la sortie d'erreur sur la sortie standard -}}} diff --git a/doc/ulib_base.init.twp b/doc/ulib_base.init.twp deleted file mode 100644 index 6c03356..0000000 --- a/doc/ulib_base.init.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.init - diff --git a/doc/ulib_base.num.twp b/doc/ulib_base.num.twp deleted file mode 100644 index c1bd6b7..0000000 --- a/doc/ulib_base.num.twp +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.num - -!! {{{isnum}}} -{{{ -retourner vrai si $1 est une valeur numérique entière (positive ou négative) -}}} -!! {{{ispnum}}} -{{{ -retourner vrai si $1 est une valeur numérique entière positive -}}} -!! {{{isrnum}}} -{{{ -retourner vrai si $1 est une valeur numérique réelle (positive ou négative) -le séparateur décimal peut être . ou , -}}} diff --git a/doc/ulib_base.quote.twp b/doc/ulib_base.quote.twp deleted file mode 100644 index bbe1974..0000000 --- a/doc/ulib_base.quote.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.quote - -!! {{{qawk}}} -{{{ -Dans la chaine $*, remplacer \ par \\ et " par \" et afficher la chaine -entourée de guillemets. Ceci est utile pour quoter des valeur à insérer dans -un script awk -}}} -!! {{{qseds}}} -{{{ -Quoter la chaine $*, qui doit être utilisée comme chaine de recherche ou de -remplacement de grep, sed ou awk -}}} -!! {{{qform}}} -{{{ -Dans la chaine $* qui est de la forme "name=value", remplacer dans name et -dans value '%' par '%25', '+' par '%2B', '&' par '%26', '=' par '%3D', ' ' par -'+' -}}} diff --git a/doc/ulib_base.split.twp b/doc/ulib_base.split.twp deleted file mode 100644 index 6d9c395..0000000 --- a/doc/ulib_base.split.twp +++ /dev/null @@ -1,79 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.split - -!! {{{splitfsep}}} -{{{ -Découper $1 de la forme "first[SEPsecond]" entre first, qui est placé dans la -variable $3(=first) et second, qui est placée dans la variable $4(=second). $2 -est la valeur SEP. Le découpage est faite sur la *première* occurence de SEP. -}}} -!! {{{splitfsep2}}} -{{{ -Découper $1 de la forme "[firstSEP]second" entre first, qui est placé dans la -variable $3(=first) et second, qui est placée dans la variable $4(=second). $2 -est la valeur SEP. Le découpage est faite sur la *première* occurence de SEP. -}}} -!! {{{splitlsep}}} -{{{ -Découper $1 de la forme "first[SEPsecond]" entre first, qui est placé dans la -variable $3(=first) et second, qui est placée dans la variable $4(=second). $2 -est la valeur SEP. Le découpage est faite sur la *dernière* occurence de SEP. -}}} -!! {{{splitlsep2}}} -{{{ -Découper $1 de la forme "[firstSEP]second" entre first, qui est placé dans la -variable $3(=first) et second, qui est placée dans la variable $4(=second). $2 -est la valeur SEP. Le découpage est faite sur la *dernière* occurence de SEP. -}}} -!! {{{splitvar}}} -{{{ -Découper $1 de la forme name[=value] entre le nom, qui est placé dans la -variable $2(=name) et la valeur, qui est placée dans la variable $3(=value) -}}} -!! {{{splitpath}}} -{{{ -Découper $1 de la forme [dir/]name entre le répertoire, qui est placé dans la -variable $2(=dir), et le nom du fichier, qui est placé dans la variable -$3(=name) -}}} -!! {{{splitname}}} -{{{ -Découper $1 de la forme basename[.ext] entre le nom de base du fichier, qui -est placé dans la variable $2(=basename) et l'extension, qui est placée dans -la variable $3(=ext) -Attention, si $1 est un chemin, le résultat risque d'être faussé. Par exemple, -'splitname a.b/c' ne donne pas le résultat escompté. -}}} -!! {{{splithost}}} -{{{ -Découper $1 de la forme hostname[.domain] entre le nom d'hôte, qui est placé -dans la variable $2(=hostname) et le domaine, qui est placée dans la variable -$3(=domain) -}}} -!! {{{splituserhost}}} -{{{ -Découper $1 de la forme [user@]host entre le nom de l'utilisateur, qui est placé -dans la variable $2(=user) et le nom d'hôte, qui est placée dans la variable -$3(=host) -}}} -!! {{{splitpair}}} -{{{ -Découper $1 de la forme first[:second] entre la première valeur, qui est placé -dans la variable $2(=src) et la deuxième valeur, qui est placée dans la variable -$3(=dest) -}}} -!! {{{splitproxy}}} -{{{ -Découper $1 de la forme http://[user:password@]host[:port]/ entre les valeurs -$2(=host), $3(=port), $4(=user), $5(=password) -}}} -!! {{{spliturl}}} -{{{ -Découper $1 de la forme scheme://[user:password@]host[:port]/path entre les -valeurs $2(=scheme), $3(=user), $4(=password), $5(=host), $6(=port), $7(=path) -}}} diff --git a/doc/ulib_base.string.twp b/doc/ulib_base.string.twp deleted file mode 100644 index 855c53f..0000000 --- a/doc/ulib_base.string.twp +++ /dev/null @@ -1,102 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.string - -!! {{{straddp}}} -{{{ -ajouter le préfixe $1 à $2* -}}} -!! {{{strdelp}}} -{{{ -enlever le préfixe $1 à $2* -}}} -!! {{{strdelp2}}} -{{{ -enlever le préfixe $1 le plus long à $2* -}}} -!! {{{stradds}}} -{{{ -ajouter le suffixe $1 à $2* -}}} -!! {{{strdels}}} -{{{ -enlever le suffixe $1 à $2* -}}} -!! {{{strdels2}}} -{{{ -enlever le suffixe le plus long $1 à $2* -}}} -!! {{{strlower}}} -{{{ -afficher en minuscule la valeur $* -}}} -!! {{{strlower1}}} -{{{ -afficher la valeur $* après avoir converti la première lettre en minuscule -}}} -!! {{{strlowers}}} -{{{ -afficher les valeurs $1..* après avoir converti leur première lettre en -minuscule -}}} -!! {{{strupper}}} -{{{ -afficher en majuscule la valeur $* -}}} -!! {{{strupper1}}} -{{{ -afficher la valeur $* après avoir converti la première lettre en majuscule -}}} -!! {{{struppers}}} -{{{ -afficher les valeurs $1..* après avoir converti leur première lettre en -majuscule -}}} -!! {{{strmid}}} -{{{ -Afficher la plage $1 de la valeur $2*. La plage peut être d'une des formes -'start', '[start]:length'. Si start est négatif, le compte est effectué à -partir de la fin de la chaine. Si length est négatif, il est rajouté à la -longueur de la chaine à partir de start -}}} -!! {{{strrepl}}} -{{{ -Remplacer dans la valeur $3* le motif $1 par la chaine $2. $1 peut commencer -par l'un des caractères /, #, % pour indiquer le type de recherche -}}} -!! {{{first_char}}} -{{{ -retourner le premier caractère de la chaine $* -}}} -!! {{{last_char}}} -{{{ -retourner le dernier caractère de la chaine $* -}}} -!! {{{first_chars}}} -{{{ -retourner tous les caractères de la chaine $*, excepté le dernier -}}} -!! {{{last_chars}}} -{{{ -retourner tous les caractères de la chaine $*, excepté le premier -}}} -!! {{{first_char_is}}} -{{{ -Tester si le premier caractère de la chaine $1 est $2 -}}} -!! {{{last_char_is}}} -{{{ -Tester si le dernier caractère de la chaine $1 est $2 -}}} -!! {{{beginswith}}} -{{{ -Tester si la chaine $1 commence par le wildcard $2 -}}} -!! {{{endswith}}} -{{{ -Tester si la chaine $1 se termine par le wildcard $2 -}}} diff --git a/doc/ulib_base.tools.twp b/doc/ulib_base.tools.twp deleted file mode 100644 index 177fd56..0000000 --- a/doc/ulib_base.tools.twp +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.tools - -!! {{{base_umove}}} -{{{ -Outil de haut niveau pour déplacer un fichier ou un lien. Si c'est un lien qui -est déplacé, la destination du lien est mise à jour si elle est relative. -l'option '-d UPDATEDIR' permet de spécifier un répertoire dans lequel tous les -liens qui pointent vers le fichier déplacé sont mis à jour si le déplacement -du fichier se fait avec succès. -}}} -!! {{{base_udelete}}} -{{{ -Outil de haut niveau pour supprimer un fichier ou un lien. Si on doit -supprimer un fichier, et que l'option '-d UPDATEDIR' est spécifiée, et que des -liens du répertoire UPDATEDIR pointent vers le fichier supprimé, ces liens -sont supprimés aussi. -}}} -!! {{{base_ucopy}}} -{{{ -Outil de haut niveau pour copier un fichier ou un lien. Si c'est un lien qui -est copié, la destination du lien est mise à jour si elle est relative. -}}} diff --git a/doc/ulib_base.twp b/doc/ulib_base.twp deleted file mode 100644 index 9075f49..0000000 --- a/doc/ulib_base.twp +++ /dev/null @@ -1,1276 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base - -!! {{{setyesval}}} -{{{ -mettre la valeur normalisée de la valeur "oui" de $2 dans la variable $1 -}}} -!! {{{normyesval}}} -{{{ -remplacer la valeur de la variable $1 par la valeur normalisée de sa valeur "oui" -Si $2 est non vide, prendre cette valeur plutôt que la valeur de la variable $1 -}}} -!! {{{normyesvals}}} -{{{ -remplacer les valeur des variables $1..* par les valeurs normalisées -respectives de leur valeur "oui" -}}} -!! {{{quote_in}}} -{{{ -Comme quote_arg pour une chaine lue sur stdin -}}} -!! {{{quote_sin}}} -{{{ -Pour la chaine lue sur stdin, remplacer ' par '\''. Cela permet de protéger une -chaine à mettre entre quotes -}}} -!! {{{quote_sarg}}} -{{{ -Dans la chaine $1, remplacer ' par '\''. Cette fonction utilise quote_sin, -puisque le shell a des difficultés à faire le rechercher/remplacer approprié -}}} -!! {{{quoted_sarg}}} -{{{ -Dans la chaine $1, remplacer ' par '\'', et afficher la chaine entourée de -quotes -}}} -!! {{{quoted_sargs}}} -{{{ -Comme quoted_sarg, mais tous les arguments sont quotés et affichés entourés de -quotes, ce qui permet de construire des arguments d'une ligne de commande -}}} -!! {{{set_array_cmd}}} -{{{ -Afficher la commande permettant d'initialiser le tableau $1 avec les valeurs: -soit du tableau $2, soit de $3..$n si $2=="@" -S'il n'y a que l'argument $1, alors afficher la commande permettant de -recréer le tableau $1 -}}} -!! {{{set_array}}} -{{{ -Soit $1 un tableau à créer. Si $2=="@", créer le tableau $1 avec les valeurs -$3..$n. Sinon, créer le tableau $1 avec les valeurs du tableau $2. -Cette fonction n'existe que comme un pendant de set_var(), mais le véritable -intérêt est la fonction set_array_cmd(). cf array_copy() pour une version plus -efficace de la copie de tableaux -}}} -!! {{{array_count}}} -{{{ -retourner le nombre d'éléments du tableau $1 -}}} -!! {{{array_isempty}}} -{{{ -tester si le tableau $1 est vide -}}} -!! {{{array_new}}} -{{{ -créer un tableau vide dont le nom est $1 -}}} -!! {{{array_add}}} -{{{ -ajouter les valeurs $2..@ au tableau dont le nom est $1 -}}} -!! {{{array_ins}}} -{{{ -insérer les valeurs $2..@ au début du tableau dont le nom est $1 -}}} -!! {{{array_del}}} -{{{ -supprimer *les* valeurs $2 du tableau dont le nom est $1 -}}} -!! {{{array_addu}}} -{{{ -ajouter la valeur $2 au tableau dont le nom est $1, si la valeur n'y est pas -déjà. Retourner vrai si la valeur a été ajoutée -}}} -!! {{{array_set}}} -!! {{{array_insu}}} -{{{ -insérer la valeur $2 au début du tableau tableau dont le nom est $1, si la -valeur n'y est pas déjà. Retourner vrai si la valeur a été ajoutée. -}}} -!! {{{array_fillrange}}} -{{{ -Initialiser le tableau $1 avec les nombres de $2(=1) à $3(=10) avec un step de $4(=1) -}}} -!! {{{array_eq}}} -{{{ -tester l'égalité des tableaux $1 et $2 -}}} -!! {{{array_contains}}} -{{{ -tester si le tableau dont le nom est $1 contient la valeur $2 -}}} -!! {{{array_icontains}}} -{{{ -tester si le tableau dont le nom est $1 contient la valeur $2, sans tenir -compte de la casse -}}} -!! {{{array_find}}} -{{{ -si le tableau $1 contient la valeur $2, retourner l'index de la valeur. Si le -tableau $3 est spécifié, retourner la valeur à l'index dans ce tableau -}}} -!! {{{array_reverse}}} -{{{ -Inverser l'ordre des élément du tableau $1 -}}} -!! {{{array_replace}}} -{{{ -dans le tableau $1, remplacer toutes les occurences de $2 par $3..* -}}} -!! {{{array_each}}} -{{{ -Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les -arguments '$v $3..$n' -}}} -!! {{{array_map}}} -{{{ -Pour chacune des valeurs 'v' du tableau $1, appeler la fonction $2 avec les -arguments '$v $3..$n', et remplacer la valeur par le résultat de la fonction -}}} -!! {{{first_value}}} -{{{ -retourner la première valeur du tableau $1 -}}} -!! {{{last_value}}} -{{{ -retourner la dernière valeur du tableau $1 -}}} -!! {{{array_copy}}} -{{{ -copier le contenu du tableau $2 dans le tableau $1 -}}} -!! {{{array_copy_firsts}}} -{{{ -copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la dernière -}}} -!! {{{array_del_last}}} -!! {{{array_copy_lasts}}} -{{{ -copier tous les valeurs du tableau $2(=$1) dans le tableau $1, excepté la première -}}} -!! {{{array_del_first}}} -!! {{{array_extend}}} -{{{ -ajouter le contenu du tableau $2 au tableau $1 -}}} -!! {{{array_extendu}}} -{{{ -ajouter chacune des valeurs du tableau $2 au tableau $1, si ces valeurs n'y -sont pas déjà. Retourner vrai si au moins une valeur a été ajoutée -}}} -!! {{{array_extend_firsts}}} -{{{ -ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la dernière -}}} -!! {{{array_extend_lasts}}} -{{{ -ajouter toutes les valeurs du tableau $2 dans le tableau $1, excepté la première -}}} -!! {{{array_xsplit}}} -{{{ -créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés -par $3, qui vaut ':' par défaut). -}}} -!! {{{array_split}}} -{{{ -créer le tableau $1 avec chaque élément de $2 (un ensemble d'éléments séparés -par $3, qui vaut ':' par défaut). Les éléments vides sont ignorés. par exemple -"a::b" est équivalent à "a:b" -}}} -!! {{{array_from_path}}} -!! {{{array_from_xlines}}} -{{{ -créer le tableau $1 avec chaque ligne de $2. -}}} -!! {{{array_from_lines}}} -{{{ -créer le tableau $1 avec chaque ligne de $2. Les lignes vides sont ignorés. -}}} -!! {{{array_join}}} -{{{ -afficher le contenu du tableau dont le nom est $1 sous forme d'une liste de -valeurs séparées par $2 (par défaut, une virgule) -Si $1=="@", alors les éléments du tableaux sont les arguments de la fonction à -partir de $3 -Si $1!="@" et que le tableau est vide, afficher $3 -Si $1!="@", $4 et $5 sont des préfixes et suffixes à rajouter à chaque élément -}}} -!! {{{array_mapjoin}}} -{{{ -map le tableau $1 avec la fonction $2, puis afficher le résultat en séparant -chaque élément par $3. Les arguments et la sémantique sont les mêmes que pour -array_join en tenant compte de l'argument supplémentaire $2 qui est la -fonction pour array_map (les autres arguments sont décalés en conséquence) -}}} -!! {{{array_to_lines}}} -{{{ -afficher le tableau dont le nom est $1 sous forme de lignes -}}} -!! {{{array_to_path}}} -{{{ -afficher le tableau dont le nom est $1 sous forme d'une liste de chemins -séparés par ':') -}}} -!! {{{array_fix_paths}}} -{{{ -Corriger les valeurs du tableau $1. Les valeurs contenant le séparateur -$2(=':') sont séparées en plusieurs valeurs. Par exemple avec le tableau -input=(a b:c), le résultat est input=(a b c) -}}} -!! {{{get_date_rfc822}}} -!! {{{get_date_fr}}} -!! {{{get_time_fr}}} -!! {{{parse_date}}} -!! {{{udelpath}}} -{{{ -supprimer le chemin $1 de $2(=PATH) -}}} -!! {{{uaddpath}}} -{{{ -Ajouter le chemin $1 à la fin, dans $2(=PATH), s'il n'y existe pas déjà -}}} -!! {{{uinspathm}}} -{{{ -Ajouter le chemin $1 au début, dans $2(=PATH), s'il n'y existe pas déjà -}}} -!! {{{uinspath}}} -{{{ -S'assurer que le chemin $1 soit au début de $2(=PATH) -}}} -!! {{{withpath}}} -{{{ -tester si le chemin est relatif à . ou à .., ou est absolu. i.e 'withpath a/b' -renvoie faux alors que 'withpath ./a/b' renvoie vrai -}}} -!! {{{withext}}} -{{{ -tester si le fichier a une extension -}}} -!! {{{normpath}}} -{{{ -normaliser le chemin $1, qui est soit absolu, soit relatif à $2 (qui vaut -$(pwd) par défaut) -}}} -!! {{{abspath}}} -{{{ -Retourner un chemin absolu vers $1. Si $2 est non nul et si $1 est un chemin -relatif, alors $1 est exprimé par rapport à $2, sinon il est exprimé par -rapport au répertoire courant. -Si le chemin n'existe pas, il n'est PAS normalisé. Sinon, les meilleurs -efforts sont faits pour normaliser le chemin. -}}} -!! {{{parentdirs}}} -{{{ -Obtenir la liste de tous les parents du répertoire $2 dans le tableau $1, du -répertoire $2 vers la racine. Si $3 commence par 'r' (comme reverse), l'ordre -est inversé: le tableau contient les répertoire de la racine vers $2. -}}} -!! {{{ppath}}} -{{{ -Dans un chemin *absolu*, remplacer "$HOME" par "~" et "$(pwd)/" par "", afin -que le chemin soit plus facile à lire. Le répertoire courant est spécifié par -$2 ou $(pwd) si $2 est vide -}}} -!! {{{relpath}}} -{{{ -Afficher le chemin relatif de $1 par rapport à $2. Si $2 n'est pas spécifié, -on prend le répertoire courant. Si $1 ou $2 ne sont pas des chemins absolus, -il sont transformés en chemins absolus par rapport à $3. Si $1==$2, retourner -une chaine vide -}}} -!! {{{relpathx}}} -{{{ -Comme relpath, mais pour un chemin vers un exécutable qu'il faut lancer: -s'assurer qu'il y a une spécification de chemin, e.g. ./script -}}} -!! {{{withinpath}}} -{{{ -Tester si le chemin absolu $2 se trouve dans le chemin absolu "$1" (appelée -barrière). Soit un chemin P, on considère que P est dans P. Si ce comportement -n'est pas souhaité, $3(=N) doit valoir O, auquel cas P est dans Q implique que -P != Q. -}}} -!! {{{safe_abspath}}} -{{{ -Afficher le chemin absolu de $1, par rapport à $2, si et seulement si le -chemin résultat ne se trouve pas en dehors de la barrière $3. Si $2 n'est pas -spécifié, prendre le répertoire courant. S'il est relatif, l'exprimer par -rapport au répertoire courant. Si $3 est relatif, l'exprimer par rapport à $2. -Si le chemin résultat est sité en dehors de la barrière, ne rien afficher et -retourner un code d'erreur. -Si le chemin $1 n'existe pas, il n'est PAS normalisé. Sinon, les meilleurs -efforts sont faits pour normaliser le chemin résultat. -}}} -!! {{{safe_relpath}}} -{{{ -Afficher le chemin relatif de $1 par rapport à $2 si et seulement si le chemin -résultat ne se trouve pas en dehors de la barrière $3. Si $2 n'est pas -spécifié, prendre le répertoire courant. S'il est relatif, l'exprimer par -rapport au répertoire courant. Si $3 est relatif, l'exprimer par rapport à $2. -Si le chemin résultat est sité en dehors de la barrière, ne rien afficher et -retourner un code d'erreur. -}}} -!! {{{splitwcs}}} -{{{ -Découper un nom de chemin $1 entre la partie sans wildcards, qui est placée dans -la variables $2(=basedir), et la partie avec wildcards, qui est placée dans la -variable $3(=filespec) -}}} -!! {{{deref}}} -{{{ -Retourner un chemin absolu vers le fichier $1, dans lequel toutes les -composantes "lien symbolique" ont été supprimées. -DEPRECATED: Cette fonction est dépréciée. Utiliser à la place readlinkm() -}}} -!! {{{readlinka}}} -{{{ -Afficher un chemin absolu vers la destination du fichier $1. Si $1 n'est pas -un lien, afficher simplement le chemin du fichier -}}} -!! {{{readlinkm}}} -{{{ -Retourner un chemin absolu vers le fichier $1, dans lequel toutes les -composantes "lien symbolique" ont été supprimées. Il n'est pas requis que les -composantes du chemin existent. -}}} -!! {{{path_if_test}}} -{{{ -afficher un chemin si le fichier $2 existe (en utilisant l'opérateur $1) dans -l'un des chemins absolus $4..n. si $3==relative, afficher le chemin relatif, -sinon le chemin absolu. note: $3 peut être de la forme relative:path, auquel -cas le chemin affiché est exprimé relativement à path -}}} -!! {{{update_link}}} -{{{ -mettre à jour le lien $2 pour qu'il pointe vers le fichier $1 -}}} -!! {{{update_links}}} -{{{ -Mettre à jour les liens $2..@ pour qu'ils pointent vers la nouvelle -destination $1 -}}} -!! {{{move_link}}} -{{{ -Déplacer le lien $1 vers $2, et mettre à jour la destination du lien si -elle est exprimée de façon relative -Si $1 n'est pas un lien, le déplacer normalement avec mv -}}} -!! {{{copy_link}}} -{{{ -Copier le lien $1 vers $2, et mettre à jour la destination du lien si -elle est exprimée de façon relative -Si $1 n'est pas un lien, le copier normalement avec cp -}}} -!! {{{array_find_links}}} -{{{ -Chercher dans le répertoire $3 (qui est par défaut le répertoire courant) -les liens vers le fichier $2, et ajouter leurs chemins absolus dans le -tableau $1 -}}} -!! {{{list_links}}} -{{{ -Chercher dans le répertoire $2 les liens vers le fichier $1, et les -afficher, un par ligne. -}}} -!! {{{move_file}}} -{{{ -Déplacer le fichier $1 vers $2, et mettre à jour les liens $3..@ pour -qu'ils pointent vers la nouvelle destination -}}} -!! {{{get_nblines}}} -{{{ -Afficher le nombre de lignes d'un fichier -}}} -!! {{{mktempf}}} -{{{ -générer un fichier temporaire et retourner son nom -}}} -!! {{{mktempd}}} -{{{ -générer un répertoire temporaire et retourner son nom -}}} -!! {{{mkdirof}}} -{{{ -Créer le répertoire correspondant à un fichier -}}} -!! {{{cp_a}}} -{{{ -copier des fichiers en gardant le maximum de propriétés -}}} -!! {{{cp_R}}} -{{{ -copier des fichiers récursivement, en suivant les liens symboliques -}}} -!! {{{quietgrep}}} -{{{ -tester la présence d'un pattern dans un fichier -}}} -!! {{{quietdiff}}} -{{{ -tester si deux fichiers sont identiques -}}} -!! {{{testsame}}} -{{{ -tester si deux fichiers sont identiques/différents -}}} -!! {{{testdiff}}} -!! {{{testupdated}}} -{{{ -test si $2 n'existe pas ou si $1 est différent de $2 -}}} -!! {{{testnewer}}} -{{{ -test si $2 n'existe pas ou si $1 est plus récent que $2 -}}} -!! {{{ps_all}}} -{{{ -afficher tous les processus avec le maximum d'informations -}}} -!! {{{progexists}}} -{{{ -tester l'existence d'un programme dans le PATH -}}} -!! {{{has_python}}} -{{{ -tester la présence de python -}}} -!! {{{has_gawk}}} -{{{ -tester la présence de gnuawk -}}} -!! {{{is_root}}} -{{{ -tester si on est root -}}} -!! {{{source_ifexists}}} -{{{ -sourcer un fichier s'il existe -}}} -!! {{{is_running}}} -{{{ -tester si un programme dont on donne le PID tourne -}}} -!! {{{sedi}}} -{{{ -Lancer sed sur un fichier en le modifiant en place -}}} -!! {{{csort}}} -{{{ -Lancer sort avec LANG=C pour éviter les problèmes avec la locale. en effet, -avec LANG!=C, sort utilise les règles de la locale pour le tri, et par -exemple, avec LANG=fr_FR.UTF-8, la locale indique que les ponctuations doivent -être ignorées. -}}} -!! {{{lsort}}} -!! {{{cgrep}}} -{{{ -Lancer grep avec LANG=C pour éviter les problèmes avec la locale. cf csort -pour une explication. -}}} -!! {{{lgrep}}} -!! {{{csed}}} -{{{ -Lancer sed avec LANG=C pour éviter les problèmes avec la locale. cf csort pour -une explication. -}}} -!! {{{lsed}}} -!! {{{cawk}}} -{{{ -Lancer awk avec LANG=C pour éviter les problèmes avec la locale. cf csort pour -une explication. -}}} -!! {{{lawk}}} -!! {{{cdiff}}} -{{{ -Lancer diff avec LANG=C pour éviter les problèmes avec la locale. cf csort -pour une explication. -}}} -!! {{{ldiff}}} -!! {{{fix_mode}}} -{{{ -Si le fichier $1 n'est pas writable, le rendre writable temporairement. Si -nécessaire, le fichier est créé. -Cette fonction s'utilise de cette façon: -mode="$(fix_mode file)" -... -unfix_mode file "$mode" -}}} -!! {{{unfix_mode}}} -{{{ -Restaurer le mode $2 du fichier $1 traité par fix_mode -}}} -!! {{{get_mode}}} -{{{ -Obtenir le mode du fichier $1, en le créant si nécessaire. A utiliser avec -unfix_mode pour restaurer le mode d'un fichier qui a été traité avec un -fichier temporaire intermédiaire -}}} -!! {{{rm_maybe}}} -{{{ -Supprimer les fichiers dont on donne la liste. Si aucun fichier n'est -spécifié, cette fonction est un NOP -}}} -!! {{{cpdir}}} -{{{ -copier un fichier dans un répertoire, ou le contenu d'un répertoire dans un -autre répertoire, que le répertoire source soit un lien symbolique ou -non. Cette fonction existe parce que le comportement de "cp_a src dest" n'est -pas consistant selon les plateformes, surtout si src est un lien symbolique -sur un répertoire: parfois on copie le lien, parfois on copie le contenu du -répertoire, parfois on copie le répertoire... -La copie est faite avec rsync si possible. Les options du tableau -__CPDIR_RSYNC_ARGS sont rajoutées aux options standard de rsync. -}}} -!! {{{cpnovcs}}} -{{{ -copier le fichier/répertoire $1 *dans* le *répertoire* $2 avec rsync. Les -options du tableau __CPNOVCS_RSYNC_ARGS sont rajoutées aux options standard -de rsync. -Si $1 est un répertoire, la copie est faite en ignorant les sous-répertoires -de VCS (.svn, CVS). En ce qui concerne les répertoire de VCS, git aussi est -supporté, mais uniquement s'il est à la racine du transfert. -Si $1 se termine par un '/', c'est le contenu du répertoire qui est copié, pas -le répertoire lui-même. Si rsync n'est pas trouvé sur le système, alors on -fait une copie standard qui inclue les répertoires de VCS. -}}} -!! {{{cpvcs}}} -{{{ -comme cpnovcs, mais ne pas ignorer les répertoires de VCS -}}} -!! {{{cpdirnovcs}}} -{{{ -Le pendant de cpdir, mais en ignorant les sous-répertoires de VCS: copier le -contenu du répertoire $1 dans le répertoire $2 -}}} -!! {{{doinplace}}} -{{{ -Filtrer le fichier $1 à travers la commande $2..$*, puis remplacer le fichier -s'il n'y a pas eu d'erreur. Retourner le code d'erreur de la commande. Si $1 -n'est pas spécifié ou vaut -, filtrer l'entrée standard vers la sortie -standard. -La variante doinplacef remplace le fichier quelque soit le code de retour de -la commande. A utiliser avec des commandes comme grep qui peuvent retourner -FAUX s'ils ne trouvent pas le motif -}}} -!! {{{doinplacef}}} -!! {{{stripnl}}} -{{{ -Supprimer les caractères de fin de ligne de la chaine en entrée -}}} -!! {{{nl2lf}}} -!! {{{nl2crlf}}} -!! {{{nl2cr}}} -!! {{{list_all}}} -{{{ -Lister les fichiers ou répertoires du répertoire $1, un par ligne -Les répertoires . et .. sont enlevés de la liste -$1=un répertoire dont le contenu doit être listé -$2..@=un ensemble de patterns pour le listage -}}} -!! {{{list_files}}} -{{{ -Lister les fichiers du répertoire $1, un par ligne -$1=un répertoire dont le contenu doit être listé. -$2..@=un ensemble de patterns pour le listage -}}} -!! {{{list_dirs}}} -{{{ -Lister les répertoires du répertoire $1, un par ligne -Les répertoires . et .. sont enlevés de la liste -$1=un répertoire dont le contenu doit être listé. -$2..@=un ensemble de patterns pour le listage -}}} -!! {{{array_lsall}}} -{{{ -Lister les fichiers avec `list_all $2 $3...`, et les mettre dans le -tableau $1. Le tableau contient les chemins complets, par seulement les -noms comme avec list_all -}}} -!! {{{array_lsdirs}}} -{{{ -Lister les fichiers avec `list_dirs $2 $3...`, et les mettre dans le -tableau $1. Le tableau contient les chemins complets, par seulement les -noms comme avec list_dirs -}}} -!! {{{array_lsfiles}}} -{{{ -Lister les fichiers avec `list_files $2 $3...`, et les mettre dans le -tableau $1. Le tableau contient les chemins complets, par seulement les -noms comme avec list_files -}}} -!! {{{filter_empty}}} -{{{ -Filtrer l'entrée standard en enlevant les lignes vides -}}} -!! {{{filter_vcspath}}} -{{{ -L'entrée standard étant une liste de chemins, filtrer les fichiers et -répertoire qui ont un rapport avec subversion ou git -}}} -!! {{{merge_contlines}}} -{{{ -Avec les lignes lues sur stdin, fusionner celles qui se terminent par \ avec -les suivantes. -}}} -!! {{{filter_comment}}} -{{{ -Filtrer un fichier de configuration lu sur stdin en enlevant les commentaires -et les lignes vides. -Avec $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes -Comme filter_conf(), les commentaires doivent être sur une ligne à part. -Contrairement à filter_conf, il n'est pas nécessaire que le caractère '#' soit -en début de ligne: il peut apparaitre après des espaces et des tabulations. De -même, une ligne qui ne contient que des espaces et des tabulations est -considérée comme vide. -}}} -!! {{{filter_conf}}} -{{{ -filtrer un fichier de configuration lu sur stdin en enlevant les commentaires -et les lignes vides. Une ligne n'est considérée commentaire que si '#' est un -première position. Utiliser filter_comment() si les commentaire peuvent -commencer par des caractères espace et tabulation. -Si $1==-m, fusionner les lignes qui se terminent par \ avec les suivantes -}}} -!! {{{is_archive}}} -{{{ -tester si l'extension d'un fichier indique que c'est une archive -}}} -!! {{{extract_archive}}} -{{{ -Extraire le contenu de l'archive $1 dans le répertoire ${2:-.} -}}} -!! {{{get_archive_basename}}} -{{{ -Obtenir le nom de base de l'archive $1 -}}} -!! {{{get_archive_appname}}} -{{{ -Obtenir le nom probable de l'application ou du framework contenu dans -l'archive $1, e.g: -get_archive_versionsuffix app-0.1.tgz ---> app -}}} -!! {{{get_archive_versionsuffix}}} -{{{ -Obtenir la valeur probable de la version de l'application ou du framework -contenu dans l'archive $1, avec le caractère de séparation, e.g: -get_archive_versionsuffix app-0.1.tgz ---> -0.1 -}}} -!! {{{get_archive_version}}} -{{{ -Obtenir la valeur probable de la version de l'application ou du framework -contenu dans l'archive $1, e.g: -get_archive_versionsuffix app-0.1.tgz ---> 0.1 -}}} -!! {{{dump_usernames}}} -{{{ -Placer dans le tableau $1 la liste des utilisateurs du système -Cette implémentation consulte /etc/passwd et liste tous les utilisateurs dont -le homedir se trouve dans /home, et dont l'uid est >=500 -}}} -!! {{{resolv_ips}}} -{{{ -Placer dans le tableau $1(=ips) la liste des adresses ip correspondant à -l'hôte $2. La résolution est effectuée avec la commande host. -}}} -!! {{{resolv_hosts}}} -{{{ -Placer dans le tableau $1(=hosts) la liste des hôtes correspondant à -l'adresse ip $2. La résolution est effectuée avec la commande host. -}}} -!! {{{runscript_as}}} -{{{ -Utiliser bash pour lancer le script $2 avec les arguments $3..$n afin qu'il -tourne avec les droits d'un autre user $1(=root). Si $2=exec, utiliser exec -pour lancer le script et ses arguments qui commencent à partir de $3, ce qui -fait que cette fonction ne retourne pas. -Attention! cette fonction ne teste pas avec si on est déjà le user $1. Il y a -donc un risque de boucle infinie si on ne teste pas le user courant. -}}} -!! {{{runscript_as_root}}} -{{{ -Utiliser bash pour lancer le script $1 avec les arguments $2..$* avec les -droits de root. Si on est déjà en root, le script est simplement lancé. Sinon, -utiliser runscript_as pour lancer le script avec les droits de root. -}}} -!! {{{run_as}}} -{{{ -Relancer le script courant afin qu'il tourne avec les droits d'un autre user -$1(=root) -Attention! cette fonction ne teste pas avec si on est déjà ce user. Il y a -donc un risque de boucle infinie si on ne teste pas le user courant. -Il faut lancer cette fonction avec les arguments du script en cours. Par -exemple:: -run_as root "$@" -Si $2=--noexec, on n'utilise pas la fonction exec, ce qui fait que la fonction -retourne. Sinon, on peut considérer que cette fonction ne retourne jamais -}}} -!! {{{run_as_root}}} -{{{ -relancer le script courant afin qu'il tourne en root si on est pas en déjà -root. Sinon, cette fonction est un nop. -}}} -!! {{{check_user}}} -{{{ -Vérifier si le user courant est l'un des users $1..* -}}} -!! {{{ensure_user}}} -{{{ -Vérifier si le user courant est l'un des users $1..N où N est la position du -premier "--". Si ce n'est pas le cas et que l'on est root, relancer le script -avec ce user grâce à la fonction run_as() -Retourner 1 si ce n'était pas le bon user. Retourner 10 si ce n'était pas le -bon user et que l'on n'est pas root (donc impossible à priori de relancer le -script avec le bon user). Retourner 11 si l'utilisateur a choisi de ne pas -lancer le script avec le bon utilisateur -A utiliser de cette manière: -if ensure_user users... -- args; then -# ... on est avec le bon user; faire les opérations -else -# ... ce code n'est exécuté que si une erreur s'est produite, ou si ce -# n'était pas le bon user et que l'option --noexec est utilisée -fi -}}} -!! {{{check_hostname}}} -{{{ -Vérifier si le hostname courant est l'un des hôtes $1..* -localhost matche toujours -}}} -!! {{{check_userhostname}}} -{{{ -Vérifier si le hostname et éventuellement le user courant sont l'un des -arguments $1..* -Chaque argument est de la forme [user@]host, mais le test ne tient compte que -du nom de l'hôte, sans tenir compte du domaine. Si le user n'est pas spécifié, -le test ne porte que sur hostname. -}}} -!! {{{ensure_hostname}}} -{{{ -Vérifier si le hostname et le user courant sont l'un des arguments $1..* -Chaque argument est de la forme [user@]host, mais le test ne tient compte que -du nom de l'hôte, sans tenir compte du domaine. -Si user est spécifié: -- Si on est sur le bon hôte mais pas le bon user, ensure_user est lancé avec -l'argument approprié pour relancer le script -Si l'argument était de la forme userhost:path, le répertoire courant est -changé avant de lancer le script avec le bon utilisateur. -Sinon (si user n'est pas spécifié): -- Si on n'est pas sur le bon hôte, après confirmation le script est lancé avec -ssh sur l'hôte distant avec le user spécifié (qui vaut par défaut root). Ce -script DOIT exister sur l'hôte distant avec le même chemin. -Si l'argument était de la forme userhost:path, le répertoire courant distant -est changé avant de lancer le script -Si on est avec le bon user sur le bon hôte, le répertoire courant n'est jamais -changé. -Retourner 1 si ce n'était pas le bon user. Retourner 10 si ce n'était pas le -bon user et que l'on n'est pas root (donc impossible à priori de relancer le -script avec le bon user). Retourner 11 si l'utilisateur a choisi de ne pas -lancer le script sur l'hôte distant. Retourner 12 si une erreur s'est produite -avec ssh. -A utiliser de cette manière: -if ensure_hostname user@host... -- args; then -# ... on est [avec le bon user] sur le bon hôte; faire les opérations -else -# ... ce code n'est exécuté que si une erreur s'est produite, ou si ce -# n'était pas le bon user et que l'option --noexec est utilisée -fi -}}} -!! {{{sqvals}}} -!! {{{quoted_values}}} -!! {{{formatcsv}}} -!! {{{awkdef}}} -{{{ -Afficher un script à insérer au début d'un script awk. Ce script définit dans -une section BEGIN{} les variables donnés en arguments, et avec l'option -f, -des fonctions utiles. Si une valeur ne ressemble pas à une définition de -variable, l'analyse des variables s'arrête et le reste des arguments est -inséré tel quel. Cette fonction peut être utilisée de cette manière: -awk "$(awkdef -f var=value... 'script awk')" -Normalement, les variables définies sont scalaires, avec une syntaxe de la -forme var[:type]=value. type peut valoir str ou int, pour forcer le type de la -variable créée dans awk. -Il est possible d'utiliser la syntaxe awk_array[@]=bash_array ou array[@] (qui -est équivalente à array[@]=array) pour initialiser le tableau awk_array, qui -contiendra toute les valeurs du tableau nommé bash_array, avec les indices de -1 à N, N étant le nombre d'éléments du tableau bash_array. La variable -awk_array_count est aussi initialisée, et contient le nombre d'éléments du -tableau -La syntaxe "awk_array[@]=<\n..." permet de spécifier les valeurs du tableau, -une par ligne, e.g: -$'values[@]=<\nvalue1\nvalue2' -pour un tableau values qui contiendra deux valeurs: value1 et value2 -Avec l'option -f, des fonctions supplémentaires sont définies. Elles sont -décrites dans le module awk. -}}} -!! {{{lawkrun}}} -{{{ -wrapper pour lancer awk avec un script préparé par awkdef. Les définitions et -les arguments sont séparés par --, e.g. -awkrun var0=value0 var1=value1 script -- input0 input1 -}}} -!! {{{cawkrun}}} -!! {{{awkrun}}} -!! {{{lf_trylock}}} -{{{ -USAGE -lf_trylock [-h max_hours] /path/to/lockfile -OPTIONS -lockfile -fichier qui doit contenir le verrou --h max_hours -Nombre d'heures (par défaut 4) au bout duquel afficher stale -Sinon, afficher locked -Retourne 0 si le verrou a été placé correctement. Il ne faut pas oublier de -supprimer le fichier. Le mieux est de le faire supprimer automatiquement par -autoclean: -lockfile=... -case "$(lf_trylock "$lockfile")" in -locked) ...;; -stale) ...;; -esac -autoclean "$lockfile" -Sinon, retourner 1 et afficher l'une des deux valeurs suivantes: -- stale si le verrou a déjà été placé, depuis au moins max_hours heures -- locked si le verrou a déjà été placé -- retry si une erreur s'est produite pendant la pose du verrou ou sa -lecture. Cela peut se produire si les droits ne sont pas suffisants pour -écrire dans le répertoire destination, ou si le fichier a été supprimé -avant sa lecture (race-condition). Dans ce dernier cas, reessayer permettra -d'acquérir le verrou -}}} -!! {{{pidfile_set}}} -{{{ -USAGE -pidfile_set [-p pid] /path/to/pidfile -OPTIONS -pidfile -fichier qui doit contenir le pid du script --p pid -spécifier le pid. par défaut, utiliser $$ --r si pidfile existe mais que le processus ne tourne plus, faire -comme si le fichier n'existe pas. -Retourner 0 si le pid a été correctement écrit dans le fichier. Ce fichier -sera supprimé automatiquement en fin de script -Retourner 1 si le fichier existe déjà et que le processus est en train de -tourner. -Retourner 2 si le fichier existe déjà mais que le processus ne tourne plus. -Retourner 10 si autre erreur grave s'est produite (par exemple, s'il manque le -chemin vers pidfile, ou si le fichier n'est pas accessible en écriture.) -}}} -!! {{{pidfile_check}}} -{{{ -USAGE -pidfile_check /path/to/pidfile -OPTIONS -pidfile -fichier qui doit contenir le pid d'un processus -Cette fonction permet de vérifier si le processus associé à un fichier de pid -est en train de tourner. -Retourner 0 si le fichier de pid existe et que le process du pid spécifié est -en train de tourner. Retourner 1 sinon. -Retourner 10 si erreur grave s'est produite (par exemple, s'il manque le -chemin vers pidfile, ou si le fichier n'est pas accessible en lecture.) -}}} -!! {{{page_maybe}}} -{{{ -Utiliser less, si possible, pour afficher le flux en entrée. Si le terminal -n'est pas interactif ou si le nombre de lignes en entrée est inférieur au -nombre de lignes du terminal, afficher simplement le flux. -Les arguments de cette fonction sont passés à less -}}} -!! {{{utools_local}}} -{{{ -Afficher les commandes pour rendre locales certaines variables en fonction des -arguments: -- opts rend locale args, pour utiliser parse_opts() à l'intérieur d'une -fonction. -- verbosity et interaction rendent respectivement locales __verbosity et -__interaction. Ceci est utile pour pouvoir appeler sans risque de pollution -de l'environnement une fonction qui utilise parse_opts() avec les -définitions de PRETTYOPTS. -Si aucun arguments n'est fourni, toutes les définitions sont affichées. -}}} -!! {{{stdredir}}} -{{{ -Lancer la commande $4..@ en redirigeant stdin depuis $1, stdout vers $2, -stderr vers $3. Si $1 est vide ou vaut /dev/stdin, la redirection n'est -pas faite. Si $2 est vide ou vaut /dev/stdout, la redirection n'est pas -faite. Si $3 est vide ou vaut /dev/stderr, la redirection n'est pas faite. -Cette fonction existe parce que sur certaines versions de bash, il semble -que les redirections /dev/std* ne sont pas traitées de façon particulière. -De plus, sur des technologies telles que OpenVZ, les chemins /dev/std* ne -sont pas créés (parce que /proc/self/fd/* n'est pas accessible). Donc, -dans de rares cas où le script tourne sur OpenVZ avec une version de bash -qui est buggée, la redirection n'est pas faite correctement. -}}} -!! {{{isatty}}} -{{{ -tester si STDOUT n'est pas une redirection -}}} -!! {{{in_isatty}}} -{{{ -tester si STDIN n'est pas une redirection -}}} -!! {{{out_isatty}}} -{{{ -tester si STDOUT n'est pas une redirection -}}} -!! {{{err_isatty}}} -{{{ -tester si STDERR n'est pas une redirection -}}} -!! {{{die}}} -!! {{{die_unless}}} -{{{ -Afficher $-1 et quitter le script avec die() si la commande $1..-2 retourne -FAUX -}}} -!! {{{eerror_unless}}} -{{{ -Afficher $-1 avec eerror() et retourner $? si la commande $1..-2 retourne FAUX -}}} -!! {{{die_if}}} -{{{ -Afficher $-1 et quitter le script avec die() si la commande $1..-2 retourne -VRAI -}}} -!! {{{eerror_if}}} -{{{ -Afficher $-1 avec eerror() et retourner le code d'erreur 1 si la commande -$1..-2 retourne VRAI -}}} -!! {{{noerror}}} -{{{ -lancer la commande "$@" et masquer son code de retour -}}} -!! {{{noout}}} -{{{ -lancer la commande "$@" en supprimant sa sortie standard -}}} -!! {{{noerr}}} -{{{ -lancer la commande "$@" en supprimant sa sortie d'erreur -}}} -!! {{{tooenc}}} -{{{ -Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie -$3=($UTOOLS_OUTPUT_ENCODING) -}}} -!! {{{uecho}}} -!! {{{tooenc_}}} -{{{ -Transformer la valeur $1 de l'encoding $2(=$OENC) vers l'encoding de sortie -$3=($UTOOLS_OUTPUT_ENCODING) -}}} -!! {{{uecho_}}} -!! {{{toienc}}} -{{{ -Transformer la valeur $1 de $2(=$IENC) vers l'encoding d'entrée -$3(=$UTOOLS_INPUT_ENCODING) -}}} -!! {{{uread}}} -{{{ -Lire une valeur sur stdin et la placer dans la variable $1. On assume que la -valeur en entrée est encodée dans l'encoding d'entrée par défaut -}}} -!! {{{stooenc}}} -{{{ -Transformer la valeur lue sur stdin de $OENC vers l'encoding de sortie par -défaut ($UTOOLS_OUTPUT_ENCODING) -}}} -!! {{{stoienc}}} -{{{ -Transformer la valeur lue sur stdin de $IENC vers l'encoding d'entrée par -défaut ($UTOOLS_INPUT_ENCODING) -}}} -!! {{{elogto}}} -{{{ -Activer UTOOLS_EDATE et rediriger STDOUT et STDERR vers le fichier $1 -Si deux fichiers sont spécifiés, rediriger STDOUT vers $1 et STDERR vers $2 -Si aucun fichier n'est spécifié, ne pas faire de redirection -Si la redirection est activée, forcer l'utilisation de l'encoding UTF8 -Si UTOOLS_ELOG_OVERWRITE=1, alors le fichier en sortie est écrasé. Sinon, les -lignes en sortie lui sont ajoutées -}}} -!! {{{set_verbosity}}} -!! {{{set_interaction}}} -!! {{{show_error}}} -{{{ -tester respectivement si on doit afficher les messages d'erreur, -d'avertissement, d'information, de debug -}}} -!! {{{show_warn}}} -!! {{{show_info}}} -!! {{{show_verbose}}} -!! {{{show_debug}}} -!! {{{check_verbosity}}} -!! {{{get_verbosity_option}}} -!! {{{check_interaction}}} -!! {{{is_interaction}}} -!! {{{get_interaction_option}}} -!! {{{eflush}}} -{{{ -Afficher les messages en attente -}}} -!! {{{eclearp}}} -{{{ -Supprimer les message en attente -}}} -!! {{{eerror}}} -{{{ -Afficher un message d'erreur -}}} -!! {{{ewarn}}} -{{{ -Afficher un message d'avertissement -}}} -!! {{{enote}}} -{{{ -Afficher un message d'information de même niveau qu'un avertissement -}}} -!! {{{ebanner}}} -{{{ -Afficher un message très important encadré, puis attendre 5 secondes -}}} -!! {{{eimportant}}} -{{{ -Afficher un message très important -}}} -!! {{{eattention}}} -{{{ -Afficher un message important -}}} -!! {{{einfo}}} -{{{ -Afficher un message d'information -}}} -!! {{{eecho}}} -{{{ -Afficher un message d'information sans préfixe -}}} -!! {{{eecho_}}} -!! {{{edebug}}} -{{{ -Afficher un message de debug -}}} -!! {{{trace}}} -{{{ -Afficher la commande $1..@, la lancer, puis afficher son code d'erreur si une -erreur se produit -}}} -!! {{{trace_error}}} -{{{ -Lancer la commande $1..@, puis afficher son code d'erreur si une erreur se -produit. La différence avec trace() est que la commande n'est affichée que si -une erreur se produit. -}}} -!! {{{etitle}}} -{{{ -Afficher le titre $1, qui est le début éventuel d'une section. Les section -imbriquées sont affichées indentées. La section n'est pas terminée, et il faut -la terminer explicitement avec eend, sauf dans certains cas précis: -- Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le -contexte de la section. Puis, la section est automatiquement terminée sauf si -l'option -s est spécifiée, auquel cas la section reste ouverte. Si l'option -p -est spécifiée, eclearp() est appelé pour purger les messages en attente -- Dans le cas contraire, l'option -s est ignorée: la section doit toujours -être terminée explicitement. -La fonction etitled() est comme etitle(), mais le titre n'est pas affiché -immédiatement. L'affichage effectif est effectué dès qu'une fonction e* est -utilisée. Ceci permet, avec la fonction eclearp(), de ne pas afficher de titre -pour une section vide. -}}} -!! {{{etitled}}} -!! {{{estep}}} -{{{ -Afficher la description d'une opération. Cette fonction est particulièrement -appropriée dans le contexte d'un etitle. -Les variantes e (error), w (warning), n (note), i (info) permettent d'afficher -des couleurs différentes, mais toutes sont du niveau info. -}}} -!! {{{estepe}}} -!! {{{estepw}}} -!! {{{estepn}}} -!! {{{estepi}}} -!! {{{estep_}}} -!! {{{estepe_}}} -!! {{{estepw_}}} -!! {{{estepn_}}} -!! {{{estepi_}}} -!! {{{ebegin}}} -{{{ -Afficher le message $1, qui décrit le début d'une opération. Cette fonction -débute une section, qu'il faut terminer avec eend. -Si $2..$* est spécifié, c'est une commande. Lancer la commande dans le -contexte de la section. Puis, la section est terminée automatiquement, sauf si -l'option -s est spécifiée, auquel cas la section reste ouverte. -}}} -!! {{{edot}}} -{{{ -Afficher une étape d'une opération, matérialisée par un point '.' ou une -croix 'x' en cas de succès ou d'erreur. Cette fonction est particulièrement -appropriée dans le contexte d'un ebegin. -}}} -!! {{{edotw}}} -{{{ -Afficher un avertissement comme étape d'une opération, matérialisée par une -lettre 'w' (typiquement de couleur jaune). Cette fonction est particulièrement -appropriée dans le contexte d'un ebegin. -}}} -!! {{{ewait}}} -{{{ -Afficher les étapes d'une opération qui dure, matérialisées par des '+' toutes -les secondes tant que le processus $1 tourne. -A utiliser de cette manière: -ebegin "msg" -cmd & -ewait $! -eend -}}} -!! {{{eend}}} -{{{ -Terminer une section. -Avec l'option -c, remettre à zéro toutes les informations de section -Si la section en cours est un ebegin, afficher la fin de l'opération: [ok] ou -[error] en fonction du code de retour de la dernière commande (ou de $1 si -cette valeur est donnée) -Si la section en cours est un etitle, marquer la fin de la section concernée -par le titre. -}}} -!! {{{elinedots}}} -{{{ -Afficher un message comme avec ebegin "$1", puis afficher un point '.' pour -chaque ligne lue sur stdin. Cela permet de suivre une opération. En mode -DEBUG, afficher la ligne affichée plutôt qu'un point. -Si $2..$* sont spécifiés, lancer la commande et suivre sa sortie. Ainsi, -'elinedots msg cmd args' est un raccourci pour 'cmd args | elinedots msg' -}}} -!! {{{ask_yesno}}} -{{{ -Afficher le message $1 suivi de [oN] ou [On] suivant que $2 vaut O ou N, puis -lire la réponse. Retourner 0 si la réponse est vrai, 1 sinon. -Si $1 est une option, elle est utilisée avec check_interaction pour savoir si -on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées -($2=message, $3=default) -Si $2 vaut C, la valeur par défaut est N si on est interactif, O sinon -Si $2 vaut X, la valeur par défaut est O si on est interactif, N sinon -}}} -!! {{{read_value}}} -{{{ -Afficher le message $1 suivi de la valeur par défaut [$3] si elle est non -vide, puis lire la valeur donnée par l'utilisateur. Cette valeur doit être non -vide si $4(=O) est vrai. La valeur saisie est placée dans la variable -$2(=value) -Si $1 est une option, elle est utilisée avec check_interaction pour savoir si -on est en mode interactif ou non. A ce moment-là, les valeurs sont décalées -($2=message, $3=variable, $4=default, $5=required) -En mode non interactif, c'est la valeur par défaut qui est sélectionnée. Si -l'utilisateur requière que la valeur soit non vide et que la valeur par défaut -est vide, afficher un message d'erreur et retourner faux -read_password() est comme read_value(), mais la valeur saisie n'est pas -affichée, ce qui la rend appropriée pour la lecture d'un mot de passe. -}}} -!! {{{read_password}}} -!! {{{simple_menu}}} -{{{ -Afficher un menu simple dont les éléments sont les valeurs du tableau -$2(=options). L'option choisie est placée dans la variable $1(=option) --t TITLE: spécifier le titre du menu --m YOUR_CHOICE: spécifier le message d'invite pour la sélection de l'option --d DEFAULT: spécifier l'option par défaut. Par défaut, prendre la valeur -actuelle de la variable $1(=option) -}}} -!! {{{actions_menu}}} -{{{ -Afficher un menu dont les éléments sont les valeurs du tableau $4(=options), -et une liste d'actions tirées du tableau $3(=actions). L'option choisie est -placée dans la variable $2(=option). L'action choisie est placée dans la -variable $1(=action) -Un choix est saisi sous la forme [action]num_option --t TITLE: spécifier le titre du menu --m OPT_YOUR_CHOICE: spécifier le message d'invite pour la sélection de -l'action et de l'option --M ACT_YOUR_CHOICE: spécifier le message d'invite dans le cas où aucune option -n'est disponible. Dans ce cas, seules les actions vides sont possibles. --e VOID_ACTION: spécifier qu'une action est vide, c'est à dire qu'elle ne -requière pas d'être associée à une option. Par défaut, la dernière action -est classée dans cette catégorie puisque c'est l'action "quitter" --d DEFAULT_ACTION: choisir l'action par défaut. par défaut, c'est la première -action. --q QUIT_ACTION: choisir l'option "quitter" qui provoque la sortie du menu sans -choix. par défaut, c'est la dernière action. --o DEFAULT_OPTION: choisir l'option par défaut. par défaut, prendre la valeur -actuelle de la variable $2(=option) -}}} -!! {{{autoclean}}} -{{{ -Ajouter $1..$n à la liste des fichiers à supprimer à la fin du programme -}}} -!! {{{ac_cleanall}}} -{{{ -Supprimer *tous* les fichiers temporaires gérés par autoclean tout de suite. -}}} -!! {{{ac_clean}}} -{{{ -Supprimer les fichier temporaires $1..$* si et seulement s'ils ont été générés -par ac_set_tmpfile ou ac_set_tmpdir -}}} -!! {{{ac_set_tmpfile}}} -{{{ -Créer un fichier temporaire avec le motif $2, l'ajouter à la liste des -fichiers à supprimer en fin de programme, et mettre sa valeur dans la -variable $1 -En mode debug, si ($5 est vide ou ${!5} est une valeur vraie), et si $3 n'est -pas vide, prendre ce fichier au lieu de générer un nouveau fichier -temporaire. Si $4==keep, ne pas écraser le fichier $3 s'il existe. -}}} -!! {{{ac_set_tmpdir}}} -{{{ -Créer un répertoire temporaire avec le motif $2, l'ajouter à la liste des -fichiers à supprimer en fin de programme, et mettre sa valeur dans la -variable $1 -En mode debug, si ($4 est vide ou ${!4} est une valeur vraie), et si $3 n'est -pas vide, prendre ce nom de répertoire au lieu de créer un nouveau répertoire -temporaire -}}} -!! {{{debug_tee}}} -{{{ -En mode debug, passer le flux à travers la commande 'tee "$@"'. Sinon, le flux -est passé inchangé. -}}} -!! {{{get_user_defaults_file}}} -{{{ -Afficher le chemin vers le fichier utilisateur à éditer pour qu'il soit chargé -par 'set_defaults $1'. Ce fichier n'existe pas forcément; il faut peut-être le -créer. -}}} -!! {{{get_defaults_files}}} -{{{ -Initialiser le tableau $1(=defaults) avec la liste des fichiers qui seraient -chargés par la commande 'set_defaults $2..N' -}}} -!! {{{set_defaults}}} -{{{ -Pour chaque argument, sourcer /etc/default/$arg *et* (en priorité -~/etc/default.$HOSTNAME/$arg ou à défaut ~/etc/default/$arg) si ceux-ci -existent. *Sinon*, lire $scriptdir/lib/default/$arg si ce fichier existe -}}} -!! {{{myhost}}} -{{{ -Afficher le nom d'hôte pleinement qualifié, en faisant appel à la commande -hostname. Par comparaison, $MYHOST est fourni par bash. -}}} -!! {{{myhostname}}} -{{{ -Afficher le nom d'hôte sans domaine, en faisant appel à la commande -hostname. Par comparaison, $MYHOSTNAME est fourni par bash. -}}} diff --git a/doc/ulib_base.ulib.twp b/doc/ulib_base.ulib.twp deleted file mode 100644 index 064b5ad..0000000 --- a/doc/ulib_base.ulib.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/base.ulib - diff --git a/doc/ulib_bash.twp b/doc/ulib_bash.twp deleted file mode 100644 index a1e3fe7..0000000 --- a/doc/ulib_bash.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/bash - diff --git a/doc/ulib_cgi.twp b/doc/ulib_cgi.twp deleted file mode 100644 index e890043..0000000 --- a/doc/ulib_cgi.twp +++ /dev/null @@ -1,54 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/cgi - -!! {{{is_cgi}}} -{{{ -Tester si on est lancé comme un script CGI -}}} -!! {{{ctype_header}}} -{{{ -Générer une en-tête Content-Type: avec la valeur $1[=text/html] -}}} -!! {{{cdisp_header}}} -{{{ -Générer une en-tête Content-Disposition: avec le type $2[=attachment] et -le nom de fichier $1[=result] -}}} -!! {{{nocache_header}}} -{{{ -Générer des en-têtes qui désactivent la mise en cache du contenu -}}} -!! {{{cgicontent}}} -{{{ -Générer les en-têtes nécessaire avant de servir le contenu. -$1(=text/html) est le type de contenu. S'il faut servir le contenu avec -une disposition "attachment", $2 est le nom de fichier à proposer à -l'utilisateur. Si $3 est spécifié, c'est le chemin vers le fichier dont le -contenu doit être servi. -$4..* sont des en-têtes supplémentaires à rajouter -}}} -!! {{{cgicontent_nocache}}} -{{{ -Générer les en-têtes nécessaire avant de servir le contenu. Rajouter les -entêtes pour désactiver la mise en cache. -$1(=text/html) est le type de contenu. S'il faut servir le contenu avec -une disposition "attachment", $2 est le nom de fichier à proposer à -l'utilisateur. Si $3 est spécifié, c'est le chemin vers le fichier dont le -contenu doit être servi. -$4..* sont des en-têtes supplémentaires à rajouter -}}} -!! {{{cgierror}}} -{{{ -Afficher les en-têtes pour désactiver la mise en cache, puis afficher un -message d'erreur puis arrêter le script -}}} -!! {{{cgiredirect}}} -{{{ -Afficher les en-têtes pour rediriger le client vers la page $1 puis -arrêter le script -}}} diff --git a/doc/ulib_cgisupport.twp b/doc/ulib_cgisupport.twp deleted file mode 100644 index b2896cf..0000000 --- a/doc/ulib_cgisupport.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/cgisupport - -!! {{{cgiparams}}} -!! {{{cgilsxml}}} -!! {{{cgiupload}}} diff --git a/doc/ulib_compat.twp b/doc/ulib_compat.twp deleted file mode 100644 index aa9f721..0000000 --- a/doc/ulib_compat.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/compat - diff --git a/doc/ulib_conf.twp b/doc/ulib_conf.twp deleted file mode 100644 index 7befa96..0000000 --- a/doc/ulib_conf.twp +++ /dev/null @@ -1,196 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/conf - -!! {{{conf_enable}}} -{{{ -Dans le fichier de configuration $1, activer les paramètres $2..* -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Dans tous les cas, toutes les directives de ce nom sont recherchées et -décommentées. Si value est précisée, les directives sont mises à jour. Si -la directive ne figure pas dans le fichier, elle y est rajoutée à la fin -avec la valeur spécifiée. -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{conf_enableq}}} -{{{ -Comme conf_enable(), mais s'assure que les valeurs sont quotées dans le -fichier. Ceci permet de stocker des valeurs avec des espaces ou des -caractères spéciaux. -}}} -!! {{{conf_disable}}} -{{{ -Dans le fichier de configuration $1, désactiver les paramètres $2..* -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Toutes les directives de ce noms sont recherchées et commentées. La valeur -si elle est spécifiée, est ignorée. Si la directive ne figure pas dans le -fichier, c'est un NOP. -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{conf_append}}} -{{{ -Dans le fichier de configuration $1, augmenter les valeurs des variables -correspondant aux paramètres $2..* -Chaque argument de cette fonction correspond à une variable du fichier de -configuration, et doit être de la forme name=value -Une ligne 'name="${name:+$name:}$value"' est générée à la fin du fichier -de configuration. -Par défaut, le séparateur CONF_APPEND_SEP vaut ':', mais il est possible -de changer cette valeur, de façon globale -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{conf_array_append}}} -{{{ -Dans le fichier de configuration $1, augmenter les valeurs des variables -de tableau correspondant aux paramètres $2..* -Chaque argument de cette fonction correspond à une variable du fichier de -configuration, et doit être de la forme name=value -Une ligne name=("${name[@]}" "$value") est générée à la fin du fichier de -configuration -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{conf_check}}} -{{{ -Dans le fichier de configuration $1, tester si tous les paramètres $2..* -sont présents. -Chaque argument de cette fonction correspond à une variable du fichier de -configuration, et doit être de la forme name[=value] -Si une valeur est spécifiée, vérifier que le fichier contient la valeur -correspondante. Sinon, tester uniquement la présence de la directive. -}}} -!! {{{aconf_enable}}} -{{{ -Dans le fichier de configuration $1, activer les paramètres $2..* -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Toutes les directives de ce nom sont recherchées et décommentées, et la -valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y -est rajoutée à la fin. A cause du mode opératoire, cette fonction ne -convient pas pour les directives dont le nom peut apparaitre plusieurs -fois dans le fichier -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{aconf_disable}}} -{{{ -Dans le fichier de configuration $1, désactiver les paramètres $2..* -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Si la valeur est précisée, la directive correspondant à ce nom et cette -valeur est recherchée et commentée. Sinon, toutes les directives de ce -noms sont recherchées et commentées. Si la directive ne figure pas dans le -fichier, c'est un NOP. -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{aconf_append}}} -{{{ -Dans le fichier de configuration $1, ajouter des directives correspondant -aux paramètres $2..* -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name=value -Une ligne '$name $value' est ajoutée à la fin du fichier de configuration -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -}}} -!! {{{aconf_array_append}}} -!! {{{aconf_check}}} -{{{ -Dans le fichier de configuration $1, tester si tous les paramètres $2..* -sont présents. -Chaque argument de cette fonction correspond à une variable du fichier de -configuration, et doit être de la forme name[=value] -Si une valeur est spécifiée, vérifier que le fichier contient la valeur -correspondante. Sinon, tester uniquement la présence de la directive. -}}} -!! {{{mconf_enable}}} -{{{ -Dans le fichier de configuration $1, activer les paramètres $3..* de la -section $2 -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Toutes les directives de ce nom sont recherchées et décommentées, et la -valeur mise à jour. Si la directive ne figure pas dans le fichier, elle y -est rajoutée à la fin. A cause du mode opératoire, cette fonction ne -convient pas pour les directives dont le nom peut apparaitre plusieurs -fois dans le fichier -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -Cette fonction nécessite gawk et ignore la locale -}}} -!! {{{mconf_disable}}} -{{{ -Dans le fichier de configuration $1, désactiver les paramètres $3..* de la -section $2. -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name[=value] -Si la valeur est précisée, la directive correspondant à ce nom et cette -valeur est recherchée et commentée. Sinon, toutes les directives de ce -noms sont recherchées et commentées. Si la directive ne figure pas dans le -fichier, c'est un NOP. -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -Cette fonction nécessite gawk et ignore la locale -}}} -!! {{{mconf_append}}} -{{{ -Dans le fichier de configuration $1, ajouter des directives correspondant -aux paramètres $3..* dans la section $2 -Chaque argument de cette fonction correspond à une directive du fichier de -configuration et doit être de la forme name=value -Une ligne '$name = $value' est ajoutée à la fin de la section, qui est -créée si nécessaire à la fin du fichier de configuration -Retourner 0 si une modification a été faite dans le fichier, 1 sinon -Cette fonction nécessite gawk et ignore la locale -}}} -!! {{{mconf_array_append}}} -!! {{{mconf_check}}} -{{{ -Dans le fichier de configuration $1, tester si tous les paramètres $3..* -sont présents dans la section $2 -Chaque argument de cette fonction correspond à une variable du fichier de -configuration, et doit être de la forme name[=value] -Si une valeur est spécifiée, vérifier que le fichier contient la valeur -correspondante. Sinon, tester uniquement la présence de la directive. -Cette fonction nécessite gawk et ignore la locale -}}} -!! {{{gconf_addline}}} -{{{ -USAGE -gconf_addline configfile -a BEGIN -z END NEWLINE -Dans le fichier de configuration $1, ajouter la ligne NEWLINE entre les lignes -BEGIN et END. --a BEGIN -Spécifier une expression pour matcher une ligne de type BEGIN. Si -cette option n'est pas spécifiée, on considère que le début de fichier -matche la ligne BEGIN: la ligne NEWLINE est ajoutée dès que possible. -Les lignes sont matchées dans l'ordre, i.e. avec '-a 1 -a 2', il faut -d'abord trouver la ligne 1 puis la ligne 2, sinon, le test n'est pas -concluant. --t LINE -Si après avoir matché toutes les lignes BEGIN, la ligne LINE est -rencontrée, alors considérer que la ligne à rajouter existe déjà et -qu'il ne faut pas la rajouter de nouveau --r LINE -Si après avoir matché toutes les lignes BEGIN, la ligne LINE est -rencontrée, alors considérer que la ligne à rajouter existe et qu'il -faut la mettre à jour. Supprimer la ligne existante et la remplacer -par la nouvelle ligne. --z END -Spécifier une expression pour matcher la ligne de type END. Que cette -option soit ou non spécifiée, on considère toujours que la fin de -fichier matche la ligne END. Ainsi, si END n'est pas trouvée, la ligne -NEWLINE est ajoutée à la fin du fichier. -Dès que la ligne END est rencontrée, et si aucun des tests -t ou -r -n'est concluant, alors ajouter la nouvelle ligne avant celle-ci --n MAX[=1] -Ajouter au plus MAX occurences de NEWLINE. Après avoir matché END, le -cycle recommence, au plus MAX-1 fois. Utiliser MAX=-1 pour désactiver -la limite -Cette fonction nécessite gawk et ignore la locale -Retourner 0 si l'ajout s'est fait correctement. Retourner 1 si BEGIN n'a -pas été trouvé, et donc aucun ajout n'a été effectué. Retourner 2 si une -erreur quelconque s'est produite -}}} -!! {{{writelines_maybe}}} diff --git a/doc/ulib_crontab.twp b/doc/ulib_crontab.twp deleted file mode 100644 index e765538..0000000 --- a/doc/ulib_crontab.twp +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/crontab - -!! {{{add_to_crontab}}} -!! {{{remove_from_crontab}}} -!! {{{disable_in_crontab}}} -!! {{{enable_in_crontab}}} -!! {{{ctnow}}} -{{{ -date +"%-M %-H %-d %-m %u" -}}} -!! {{{ctresolve}}} -!! {{{get_default_crontabdir_prefix}}} -!! {{{compute_crontab_prefixes}}} -!! {{{recompute_crontab_prefixes}}} -!! {{{get_CRONTABDIR_prefix}}} diff --git a/doc/ulib_debian.twp b/doc/ulib_debian.twp deleted file mode 100644 index 76f13f0..0000000 --- a/doc/ulib_debian.twp +++ /dev/null @@ -1,179 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/debian - -!! {{{pkg_check}}} -{{{ -Vérifier que les packages sont installés sur le système -}}} -!! {{{pkg_update}}} -{{{ -Mettre à jour la liste des packages silencieusement sans confirmation -}}} -!! {{{pkg_upgrade}}} -{{{ -Mettre à jour la liste des packages silencieusement sans confirmation -}}} -!! {{{pkg_install}}} -{{{ -Installer les packages silencieusement et sans confirmation -}}} -!! {{{pkg_installm}}} -{{{ -Installer les packages silencieusement et sans confirmation -Retourner 0 si au moins un des packages a été installé. Sinon, les -packages n'ont pas été installés, soit parce qu'ils sont déjà installé, -soit parce qu'il y a eu une erreur. -}}} -!! {{{pkg_check_install}}} -{{{ -Si le programme $1 n'existe pas, alors installer les packages $2..$@ -S'il n'y a pas d'arguments $2..$@ utiliser $1 comme nom de package -Retourner 0 si au moins un des packages a été installé -}}} -!! {{{service_disable}}} -{{{ -Désactiver le service $1 pour qu'il ne se lance pas automatiquement au -démarrage -}}} -!! {{{service_enable}}} -{{{ -Activer le service $1 pour qu'il se lance automatiquement au démarrage -}}} -!! {{{network_parse_confbr}}} -{{{ -network_parse_confbr "$confbr" br ifaces -}}} -!! {{{network_format_confbr}}} -{{{ -network_format_confbr "$br" ifaces --> "br:ifaces" -}}} -!! {{{network_parse_confip}}} -{{{ -network_parse_confip "$confip" iface gateway ipsuffixes -}}} -!! {{{network_parse_ipsuffix}}} -{{{ -network_parse_ipsuffix "$ipsuffix" ip suffix -}}} -!! {{{network_format_confip}}} -{{{ -network_format_confip "$iface" "$gateway" ipsuffixes --> "iface//gateway:ipsuffixes" -}}} -!! {{{network_format_ipsuffix}}} -{{{ -network_format_ipsuffix "$ip" "$suffix" --> "ip/suffix" -}}} -!! {{{network_fix_confbrs}}} -{{{ -normaliser le tableau $1(=confbrs): fusionner les doublons -}}} -!! {{{network_fix_confips}}} -{{{ -normaliser le tableau $1(=confips): fusionner les doublons, spécifier le -suffixe /24 par défaut, etc. $2 est le cas échéant l'interface associée -aux adresses ip non qualifiées -}}} -!! {{{network_fix_mainiface}}} -{{{ -A partir des valeurs des tableaux $1(=confbrs) et $2(=confips), et de -l'interface principale $3, déterminer l'interface principale. Si $3 est -spécifié, c'est la valeur sélectionnée. Sinon, si un bridge existe, c'est -le premier bridge qui est sélectionné. Sinon, la première interface est -sélectionnée. Sinon, on prend eth0. -Ensuite, réorganiser les tableaux de façon que confips[0] devienne la -configuration ip de l'interface principale. -}}} -!! {{{network_fix_confs}}} -!! {{{network_set_confbrs}}} -{{{ -initialiser $1(=confbrs) avec l'état des bridges sur le système courant -}}} -!! {{{network_set_confips}}} -{{{ -initialiser le tableau $1(=confips) avec l'état des interfaces sur le -système courant -}}} -!! {{{network_interfaces_check_confbr}}} -{{{ -Vérifier que la configuration du bridge $1, dont les membres sont les -interfaces du tableau $2(=ifaces) est faite dans le fichier -$3(=/etc/network/interfaces) -}}} -!! {{{network_interfaces_check_confip}}} -{{{ -Vérifier que la configuration de l'interface $1, avec la passerelle $2, -avec les adresses IP du tabbleau $3(=ipsuffixes) est faite dans le fichier -$4(=/etc/network/interfaces) -}}} -!! {{{network_interfaces_remove_iface}}} -{{{ -Supprimer dans le fichier $2(=/etc/network/interfaces) toute la -configuration qui concerne l'interface $1 -}}} -!! {{{network_interfaces_remove_ifaces}}} -{{{ -Supprimer dans le fichier $2(=/etc/network/interfaces) toute la -configuration qui concerne les interfaces du tableau $1=(ifaces) -}}} -!! {{{network_interfaces_remove_confbr}}} -{{{ -Supprimer dans le fichier $3(=/etc/network/interfaces) toute la -configuration qui concerne le bridge $1, et dont les interfaces sont -listées dans le tableau $2(=ifaces) -}}} -!! {{{network_interfaces_add_confip}}} -{{{ -ajouter dans le fichier $4(=/etc/network/interfaces) la configuration pour -l'interface $1, avec éventuellement la passerelle $2, et les adresses ips -telles qu'elles sont définies dans le table $3(=ipsuffixes) -}}} -!! {{{network_interfaces_add_confbr}}} -{{{ -ajouter dans le fichier $4(=/etc/network/interfaces) la configuration pour -le bridge $1, avec la liste des interfaces dans le tableau $2(=ifaces) et -la liste des configurations des adresses des interfaces dans le tableau -$3(=confips) -}}} -!! {{{network_fix_hostname}}} -!! {{{network_fix_mailname}}} -!! {{{network_fix_exim4}}} -!! {{{network_fix_postfix}}} -!! {{{network_fix_hosts}}} -!! {{{network_config}}} -{{{ -(Re)configurer le réseau sur l'hôte courant. -$1 (host) est le nom d'hôte. -$2 (confips) est le nom d'un tableau contenant la configuration des -adresses ips pour les interfaces. -$3 (confbrs) est le nom d'un tableau contenant la configuration des -bridges à créer/mettre à jour. -$4 (mainiface) est le nom de l'interface principale, c'est à dire -l'interface qui est sélectionnée si une adresse ip n'est pas préfixée de -son interface. En principe, l'interface principale est le premier bridge -défini ou la première interface définie. -$5 (reset_interfaces) spécifie de ne pas chercher à mettre à jour le -fichier /etc/network/interfaces, mais de le recréer depuis zéro. -$6 (oldhost) est le nom d'hôte actuel, avant la modification -Si un des arguments n'est pas spécifié, il est ignoré. -Le tableau confips doit contenir des définitions d'une des formes -suivantes: -[[iface][//gateway]:]address[/suffix],... -[iface:]dhcp -La deuxième forme est pour spécifier qu'une interface est configurée par -DHCP. iface vaut par défaut eth0, sauf si une définition de bridge -existe, auquel cas il s'agit du premier bridge défini. Pour chaque -interface, seule la première spécification d'adresse IP tient compte de -l'argument gateway. Les autres spécifications définissent des adresses IP -supplémentaires pour l'interface. -Le tableau brs doit contenir des définitions de la forme suivante: -br:ifaces,... -br est le nom du bridge, e.g. br0. ifaces est une liste d'interfaces -séparées par une virgule. e.g. br0:eth0,eth1 -Bien que ce soit techniquement possible, ce script interdit que l'on -définisse une adresse IP pour une interface faisant partie d'un bridge. -}}} diff --git a/doc/ulib_install.twp b/doc/ulib_install.twp deleted file mode 100644 index e453709..0000000 --- a/doc/ulib_install.twp +++ /dev/null @@ -1,46 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/install - -!! {{{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 -}}} -!! {{{copy_replace}}} -{{{ -Copier de façon inconditionnelle le fichier $1 vers le fichier $2, en -réinitialisation les permissions à la valeur $3 -}}} -!! {{{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 -}}} -!! {{{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. -}}} -!! {{{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. -Retourner vrai si le fichier a été copié sans erreur. -}}} -!! {{{copy_tree}}} -{{{ -Copier de façon inconditionnelle l'arborescence $1 dans l'arborescence $2 -}}} -!! {{{link_new}}} -{{{ -Si $2 n'existe pas, créer le lien symbolique $2 pointant vers $1 -}}} diff --git a/doc/ulib_ipcalc.twp b/doc/ulib_ipcalc.twp deleted file mode 100644 index 0fee9e0..0000000 --- a/doc/ulib_ipcalc.twp +++ /dev/null @@ -1,72 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ipcalc - -!! {{{get_random_kvm_macaddr}}} -{{{ -Obtenir une adresse mac au hasard commençant par 52:54:00 pour KVM -}}} -!! {{{ipcalc_splitipmask}}} -{{{ -Découper $1 de la forme ip[/mask] entre l'adresse ip, qui est placé dans -la variable $2(=ip) et le masque, qui est placée dans la variable -$3(=mask) -}}} -!! {{{ipcalc_checkip}}} -{{{ -Vérifier l'adresse ip $1 pour voir si elle est valide. Si l'adresse est -valide, l'afficher. Sinon, retourner 1 -}}} -!! {{{ipcalc_checkmask}}} -{{{ -vérifier le masque de sous-réseau $1 pour voir si elle est valide. Si oui, -afficher le suffixe (0, 8, 16, 24, 32) associé. Sinon retourner 1 -}}} -!! {{{ipcalc_netmask}}} -{{{ -à partir d'un suffixe (0, 8, 16, 24, 32) ou d'un masque de sous-réseau, -afficher le masque de sous-réseau. si le suffixe ou le masque ne sont pas -reconnus, retourner 1 -}}} -!! {{{ipcalc_broadcast}}} -{{{ -Calculer l'adresse de broadcast correspondant à l'adresse ip $1. Le masque -de sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou -/x.x.x.x ou donné dans l'argument $2. Seuls les suffixes 0, 8, 16, 24, 32 -sont supportés. -Retourner 1 si un erreur s'est produite, par exemple si l'adresse ou le -suffixe sont invalides ou non supportés. -}}} -!! {{{ipcalc_gateway}}} -{{{ -Calculer l'adresse du gateway correspondant à l'adresse ip $1, en -considérant que le gateway est la première adresse du réseau. Le masque de -sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou -/x.x.x.x ou donné dans l'argument $2. Seuls les suffixes 0, 8, 16, 24, 32 -sont supportés. -Retourner 1 si un erreur s'est produite, par exemple si l'adresse ou le -suffixe sont invalides ou non supportés. -}}} -!! {{{ipcalc_match}}} -{{{ -Vérifier si l'adresse $1 correspond au modèle $2, e.g.: -ipcalc_match 10.75.0.23 10/8 --> TRUE -ipcalc_match 10.75.0.23 10.75.0.0/24 --> TRUE -ipcalc_match 10.75.0.23 10.75.0.28 --> FALSE -}}} -!! {{{ipcalc_fqdn}}} -{{{ -Calculer si possible le nom pleinement qualifié correspondant à l'hôte $1. -Dans tous les cas, afficher l'hôte, mais retourner 1 si la calcul n'a pas -pu être effectué. -}}} -!! {{{ipcalc_fqdn_maybe}}} -{{{ -Si $1 *semble* déjà être un nom d'hôte pleinement qualifié, l'afficher tel -quel. Sinon utiliser ipcalc_fqdn() pour afficher le nom d'hôte pleinement -qualifié correspondant. -}}} diff --git a/doc/ulib_java.twp b/doc/ulib_java.twp deleted file mode 100644 index 859037c..0000000 --- a/doc/ulib_java.twp +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 13/05/2016 09:36 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/java - -!! {{{select_java}}} -{{{ -sélectionner la version *minimum* de java correspondant à $1 -$1== 1.3|1.3+|1.4|1.4+|1.5|1.5+|1.6|1.6+|1.7|1.7+|1.8|1.8+ -Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la -version 32bits ou 64 bits -}}} -!! {{{select_java_exact}}} -{{{ -sélectionner la version *exacte* de java correspondant à $1 -$1== 1.3|1.4|1.5|1.6|1.7|1.8 pour une correspondance exacte -$1== 1.3+|1.4+|1.5+|1.6+|1.7+|1.8+ pour une version minimum -Si $2 est défini, il peut s'agit de 32 ou 64 selon que l'on requière la -version 32bits ou 64 bits -}}} -!! {{{select_java_any}}} -{{{ -Sélectionner la version exacte de java correspondant aux arguments, dans -l'ordre, jusqu'à ce qu'un argument corresponde. DEFAULT correspond à la -valeur actuelle de JAVA_HOME, si elle est définie. -Si aucun argument n'est défini, on assume "DEFAULT 5 6 7 8 1.4" -}}} -!! {{{get_java_version}}} -{{{ -Afficher la version de java qui installée dans $1(=$JAVA_HOME) -En cas d'erreur, ne rien afficher. -}}} -!! {{{get_default_javahome_prefix}}} -!! {{{get_javaextensions_prefix}}} -!! {{{compute_java_prefixes}}} -!! {{{recompute_java_prefixes}}} -!! {{{get_JAVA_HOME_prefix}}} -!! {{{get_JAVAEXTENSIONS_prefix}}} diff --git a/doc/ulib_javaproperties.twp b/doc/ulib_javaproperties.twp deleted file mode 100644 index 0a82e0e..0000000 --- a/doc/ulib_javaproperties.twp +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/javaproperties - -!! {{{read_property}}} -{{{ -Lire la propriété $2 dans le fichier $1, et placer la valeur dans la -variable $3. Si la propriété n'existe pas, prendre la valeur par défaut -$4. Si $3=="", elle est construite à partir de $2 en remplaçant les '.' -par '_' -Retourner 1 si une erreur s'est produite (par exemple si le fichier -n'existe pas ou n'est pas accessible en lecture) -}}} -!! {{{write_property}}} -{{{ -Ecrire la propriété $2 dans le fichier $1 avec la valeur $3. -Retourner 1 si une erreur s'est produite (par exemple si le fichier -n'existe pas ou n'est pas accessible en écriture) -}}} -!! {{{write_properties}}} -{{{ -Ecrire les propriétés $2..* dans le fichier $1. Les propriétés sont de la -forme "name=value" -}}} -!! {{{norm_properties}}} -{{{ -Normaliser un fichier de propriété: Les commentaires sont supprimés, les -valeurs sont triées par ordre alphabétique, les caractères accentués sont -remplacés par des caractères unicode \\uxxxx, les séquences unicodes sont -transformées en minuscule. -}}} diff --git a/doc/ulib_json.twp b/doc/ulib_json.twp deleted file mode 100644 index e2b6f2e..0000000 --- a/doc/ulib_json.twp +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/json - -!! {{{json_filter}}} -{{{ -traiter un flux json pour que chaque valeur soit sur une ligne séparée, -facilitant le traitement par un script bash -}}} -!! {{{awkjson}}} diff --git a/doc/ulib_ldap.twp b/doc/ulib_ldap.twp deleted file mode 100644 index 27953f6..0000000 --- a/doc/ulib_ldap.twp +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ldap - -!! {{{get_default_ldapconfdir_prefix}}} -{{{ -Calculer et afficher la valeur par défaut de LDAPCONFDIR, ou une chaine -vide si l'on n'a pas pu le détecter automatiquement. -}}} -!! {{{get_default_ldapowner_prefix}}} -{{{ -Calculer et afficher la valeur par défaut de LDAPOWNER, ou une chaine -vide si l'on n'a pas pu le détecter automatiquement. -}}} -!! {{{compute_ldap_prefixes}}} -!! {{{recompute_ldap_prefixes}}} -!! {{{get_LDAPCONFDIR_prefix}}} -!! {{{get_LDAPOWNER_prefix}}} -!! {{{split_ldapuri}}} -{{{ -spliter le ldapuri $1 en $2(=proto), $3(=host) et $4(=port) -}}} -!! {{{get_suffixes}}} -{{{ -obtenir les suffixes de connexion du serveur avec l'uri $1, un par ligne -retourner 1 si la valeur n'a pas pu être obtenue -}}} -!! {{{get_anysuffix}}} -{{{ -obtenir le *premier* suffixe du serveur avec l'uri $1 -retourner 1 si la valeur n'a pas pu être obtenue -}}} -!! {{{get_dcsuffix}}} -{{{ -obtenir le *premier* suffixe du serveur avec l'uri $1 qui se termine par -dc=TLD où TLD est une valeur quelconque. A priori, c'est un suffixe d'une -base de donnée non administrative. -retourner 1 si la valeur n'a pas pu être obtenue -}}} -!! {{{get_suffix}}} -{{{ -obtenir le *premier* suffixe du serveur avec l'uri $1 qui se termine si -possible par dc=TLD où TLD est une valeur quelconque. Dans le cas normal, -le suffixe affiché est celui d'une base non administrative. -retourner 1 si la valeur n'a pas pu être obtenue -}}} -!! {{{reldn}}} -!! {{{absdn}}} -{{{ -obtenir le dn absolu correspondant au dn $1, le dn de base étant -$2(=$SUFFIX) -}}} -!! {{{subof}}} -{{{ -tester si le dn absolu $1 est $2 ou un enfant de $2 -}}} -!! {{{rabsdn}}} -{{{ -comme absdn, mais tient compte de la valeur de $3(=$SEARCHBASE) -Si le dn commence par "~/", le dn est relatif à $2(=$SUFFIX) -Si le dn commence par "/", le dn est absolu -Sinon, le dn est relatif à $3 -}}} -!! {{{pdn}}} -{{{ -corriger pour *affichage* un dn *absolu*. pour la racine "", afficher -'/'. pour $2(=$SUFFIX), afficher '~'. sinon, afficher le dn relativement à -$2 -}}} -!! {{{filter_slapdconf}}} -{{{ -Traiter un fichier de configuration slapd.conf en fusionnant les lignes -qui sont découpées. Ceci permet de faire des traitements sur le contenu. -Ce filtre s'utilisera normalement avec filter_conf, e.g.: -result.conf -}}} diff --git a/doc/ulib_ldif.twp b/doc/ulib_ldif.twp deleted file mode 100644 index a0db719..0000000 --- a/doc/ulib_ldif.twp +++ /dev/null @@ -1,68 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ldif - -!! {{{def_match_attr}}} -!! {{{def_match_value}}} -!! {{{uncut_lines}}} -{{{ -reformer les lignes qui sont coupées -}}} -!! {{{cut_lines}}} -{{{ -couper les lignes trop longues -}}} -!! {{{ensure_complete_objects}}} -{{{ -S'assurer que le ldif ne contient que des objets complets (éliminant ainsi -les groupes ayant seulement dn:) -}}} -!! {{{delete_marked_objects}}} -{{{ -Supprimer les objets marqués avec --DELETE--: -}}} -!! {{{dump_ldif}}} -!! {{{tl_addattr}}} -!! {{{tl_modifyattr}}} -!! {{{tl_deleteattr}}} -!! {{{tl_deleteentry}}} -!! {{{tl_touchentry}}} -!! {{{tl_keepattr}}} -!! {{{tl_keepval}}} -!! {{{tl_excludeattr}}} -!! {{{tl_excludeval}}} -!! {{{tl_keepvalentry}}} -!! {{{tl_excludevalentry}}} -!! {{{tl_replval}}} -!! {{{tl_addval}}} -!! {{{tl_defval}}} -!! {{{print_values}}} -!! {{{tl_ensureval}}} -!! {{{print_ensure_values}}} -!! {{{tl_decode}}} -!! {{{tl_encode}}} -!! {{{tl_format}}} -!! {{{dump_headers}}} -!! {{{tl_formatcsv}}} -!! {{{tl_parsecsv}}} -!! {{{tl_parsecsvmod}}} -!! {{{get_transform_cmd}}} -{{{ -Créer une liste de commandes bash à évaluer en fonction des arguments: une -suite de commandes séparées par // -Les variables suivantes peuvent être définies en entrée: -_T_inputfile: -Si cette variable est non vide, lire à partir du fichier $_T_inputfile -au lieu de stdin -_T_uncut_before: -faut-il fusionner automatiquement les lignes *avant* de lancer les -commandes. -_T_cut_after: -faut-il découper automatiquement les lignes *après* avoir lancé les -commandes. -}}} -!! {{{transform}}} diff --git a/doc/ulib_legacy.twp b/doc/ulib_legacy.twp deleted file mode 100644 index d190e06..0000000 --- a/doc/ulib_legacy.twp +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/legacy - -!! {{{file_get_vars}}} -{{{ -lire les variables dans un fichier -}}} -!! {{{file_set_vars}}} -{{{ -écrire les variables dans un fichier. Le fichier *doit exister* -}}} -!! {{{write_all_remaining_vars}}} -!! {{{file_get_properties}}} -{{{ -lire les propriétés d'un fichier de propriété java ou xml -}}} -!! {{{file_set_properties}}} -{{{ -écrire les propriétés d'un fichier de propriété java ou xml -}}} -!! {{{file_get_java_properties}}} -{{{ -lire les propriétés d'un fichier de propriétés java. note: les noms de -propriété java peuvent contenir le caractère "." mais pas les noms de -variable bash. La conversion est faite automatiquement. Par exemple:: -file_get_properties build.properties path.to.package "default value" -charge la valeur de la propriété dans la variable path_to_package -}}} -!! {{{file_set_java_properties}}} -{{{ -écrire des propriétés dans un fichier de propriétés java. -}}} -!! {{{write_all_remaining_vars}}} -!! {{{file_get_xml_properties}}} -{{{ -lire les propriétés d'un fichier de propriétés xml. Limitation: les -propriétés ne doivent pas être continuées sur plusieurs lignes. Les -propriétés doivent être écrites sous la forme:: -propvalue -}}} -!! {{{file_set_xml_properties}}} -{{{ -écrire des propriétés dans un fichier de propriétés java. -}}} -!! {{{write_all_remaining_vars}}} diff --git a/doc/ulib_macosx.twp b/doc/ulib_macosx.twp deleted file mode 100644 index a110d1d..0000000 --- a/doc/ulib_macosx.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 02/06/2012 09:54 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/macosx - -!! {{{local_shellfix}}} -{{{ -Modifier le compte local $1 pour qu'il utilise bash au lieu de sh -}}} -!! {{{local_usercheck}}} -{{{ -Vérifier si le user local $1 existe -}}} -!! {{{local_useradd}}} -{{{ -Créer le user local $1 -USAGE: local_useradd username [gecos [passwd]] -OPTIONS --s Créer l'utilisateur avec les droits d'administrateur --m Créer le home directory -}}} diff --git a/doc/ulib_mkcrypt.twp b/doc/ulib_mkcrypt.twp deleted file mode 100644 index 66d9a19..0000000 --- a/doc/ulib_mkcrypt.twp +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/mkcrypt - -!! {{{mkcrypt}}} diff --git a/doc/ulib_modeline.twp b/doc/ulib_modeline.twp deleted file mode 100644 index c250911..0000000 --- a/doc/ulib_modeline.twp +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/modeline - -!! {{{printml}}} -!! {{{addml}}} diff --git a/doc/ulib_network-manager-service.twp b/doc/ulib_network-manager-service.twp deleted file mode 100644 index fdb9199..0000000 --- a/doc/ulib_network-manager-service.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/network-manager-service - -!! {{{SERVICE_OVERRIDE_network_manager_stopx}}} -{{{ -désactiver network-manager avant de l'arrêter, ce qui permet de s'assurer -que chaque chaque connexion est arrêtée proprement -}}} -!! {{{SERVICE_OVERRIDE_network_manager_startx}}} -{{{ -cette fonction est le pendant de stopx: penser à relancer network-manager -après avoir démarré le service -}}} diff --git a/doc/ulib_password.twp b/doc/ulib_password.twp deleted file mode 100644 index 676ca5b..0000000 --- a/doc/ulib_password.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/password - -!! {{{random_index}}} -{{{ -Afficher un index au hasard dans le tableau $1 -}}} -!! {{{random_value}}} -{{{ -Afficher une valeur au hasard dans le tableau $1 -}}} -!! {{{random_char}}} -{{{ -Afficher un caractère au hasard dans la chaine $1 -}}} -!! {{{genpass}}} -{{{ -Générer un mot de passe au hasard avec les paramètres GENPASS_* -}}} diff --git a/doc/ulib_pkg.twp b/doc/ulib_pkg.twp deleted file mode 100644 index af586c9..0000000 --- a/doc/ulib_pkg.twp +++ /dev/null @@ -1,20 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/pkg - -!! {{{pkg_check}}} -{{{ -Vérifier que les packages sont installés sur le système -Retourner 123 si le système n'est pas supporté, et donc qu'aucune commande -d'installation de package n'est disponible. -}}} -!! {{{pkg_install}}} -{{{ -Installer les packages sans confirmation -Retourner 123 si le système n'est pas supporté, et donc qu'aucune commande -d'installation de package n'est disponible. -}}} diff --git a/doc/ulib_prefixes.twp b/doc/ulib_prefixes.twp deleted file mode 100644 index 5ce1ae0..0000000 --- a/doc/ulib_prefixes.twp +++ /dev/null @@ -1,14 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/prefixes - -!! {{{get_USER_prefix}}} -!! {{{get_HOME_prefix}}} -!! {{{has_prefix}}} -!! {{{expand_prefix}}} -!! {{{list_prefixes}}} -!! {{{dump_prefixes}}} diff --git a/doc/ulib_pretty.twp b/doc/ulib_pretty.twp deleted file mode 100644 index d10a426..0000000 --- a/doc/ulib_pretty.twp +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/pretty - -!! {{{get_color}}} -!! {{{set_verbosity}}} -!! {{{set_interaction}}} -!! {{{show_error}}} -!! {{{show_warn}}} -!! {{{show_info}}} -!! {{{show_verbose}}} -!! {{{show_debug}}} -!! {{{check_verbosity}}} -!! {{{get_verbosity_option}}} -!! {{{check_interaction}}} -!! {{{is_interaction}}} -!! {{{get_interaction_option}}} diff --git a/doc/ulib_ptools.twp b/doc/ulib_ptools.twp deleted file mode 100644 index 147394e..0000000 --- a/doc/ulib_ptools.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ptools - -!! {{{is_any_branch}}} -!! {{{is_master_branch}}} -!! {{{is_develop_branch}}} -!! {{{is_release_branch}}} -!! {{{is_hotfix_branch}}} -!! {{{is_feature_branch}}} -!! {{{list_release_branches}}} -!! {{{list_hotfix_branches}}} -!! {{{list_feature_branches}}} -!! {{{pver}}} diff --git a/doc/ulib_redhat.twp b/doc/ulib_redhat.twp deleted file mode 100644 index 600ac67..0000000 --- a/doc/ulib_redhat.twp +++ /dev/null @@ -1,44 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/redhat - -!! {{{pkg_check}}} -{{{ -Vérifier que les packages sont installés sur le système -}}} -!! {{{pkg_update}}} -{{{ -Mettre à jour la liste des packages silencieusement sans confirmation -}}} -!! {{{pkg_upgrade}}} -{{{ -Mettre à jour la liste des packages silencieusement sans confirmation -}}} -!! {{{pkg_install}}} -{{{ -Installer les packages silencieusement et sans confirmation -}}} -!! {{{pkg_installm}}} -{{{ -Installer les packages silencieusement et sans confirmation -Retourner 0 si au moins un des packages a été installé. Sinon, les -packages n'ont pas été instllés, soit parce qu'ils sont déjà installé, -soit parce qu'il y a eu une erreur. -}}} -!! {{{service_disable}}} -{{{ -Désactiver le service $1 pour qu'il ne se lance pas automatiquement au -démarrage -}}} -!! {{{service_enable}}} -{{{ -Activer le service $1 pour qu'il se lance automatiquement au démarrage -}}} -!! {{{create_bridge}}} -{{{ -Créer un nouveau pont nommé $1 avec les paramètres $2 -}}} diff --git a/doc/ulib_runs.twp b/doc/ulib_runs.twp deleted file mode 100644 index a3fd072..0000000 --- a/doc/ulib_runs.twp +++ /dev/null @@ -1,242 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/runs - -!! {{{runs_initdir}}} -{{{ -Initialiser le répertoire d'hôte. $1 est un nom d'hôte pleinement -qualifié, et les fichiers sont créés dans le premier répertoire de -RUNSHOSTSDIRS qui convient: si un fichier .udir existe avec un tableau -runs_domains qui contient le domaine de l'hôte spécifié, alors c'est ce -répertoire qui est sélectionné. Sinon, on prend le premier répertoire de -RUNSHOSTSDIRS. -$2 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le -script minimum (no) -$3 est le contenu à placer dans le fichier sysinfos.conf, s'il n'a pas -déjà été provisionné. -Il faut lancer __runs_setpath avant d'utiliser cette fonction et -RUNSHOSTDIRS ne doit pas être vide -}}} -!! {{{runs_create_rscript}}} -{{{ -Créer un modèle de script. Si $2 est spécifié, c'est un nom d'hôte -pleinement qualifié. Le répertoire d'hôte correspondant *doit* exister. -$3 spécifie si le fichier doit être créé avec de l'aide (yes) ou avec le -script minimum (no) -Si $2!="", il faut lancer __runs_setpath avant d'utiliser cette fonction -et RUNSHOSTDIRS ne doit pas être vide -Le chemin du nouveau script est ajouté au tableau new_rscripts -}}} -!! {{{runs_unsupported_system}}} -{{{ -Afficher un message d'erreur indiquant que le système actuel n'est pas -supporté, et quitter le script -}}} -!! {{{runs_require_sysinfos}}} -{{{ -Vérifier le système actuel avec check_sysinfos(), et afficher un message -d'erreur avec runs_unsupported_system() s'il ne correspond pas à la -requête -}}} -!! {{{runs_find_host}}} -!! {{{runs_add_domain}}} -{{{ -Si $1 est nom d'hôte pleinement qualifié, retourner cette valeur -Sinon, lui rajouter le domaine RUNSDOMAIN -}}} -!! {{{runs_find_hostfile}}} -{{{ -Trouver et afficher le fichier d'hôte $1 dans les répertoires du tableau -$3(=RUNSHOSTSDIRS), pour l'hôte $2(=$RUNSHOST). Retourner 0 en cas de -succès, 1 en cas d'échec. -Si host=$2 est une valeur non vide, la recherche est effectuée dans -{$RUNSHOSTSDIRS}/$host et {$RUNSHOSTSDIRS}/$domain/$hostname. Sinon, -retourner 1, car il faut spécifier un nom d'hôte. -}}} -!! {{{runs_find_datafile}}} -{{{ -Trouver et afficher le fichier de données $1 dans le répertoire $3 s'il -est non vide puis dans les répertoires des tableaux $4(=RUNSSCRIPTSDIRS), -$5(=RUNSMODULESDIRS) et $6(=RUNSHOSTSDIRS), pour l'hôte -$2(=$RUNSHOST). Retourner 0 en cas de succès, 1 en cas d'échec. -- D'abord, si $1 *n'est pas* de la forme "./path" ou "../path", chercher -dans $3. -- Puis si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et -{$RUNSHOSTSDIRS}/$domain/$hostname. -- Puis chercher dans {$RUNSSCRIPTSDIRS} puis {$RUNSMODULESDIRS}. -- Puis, si $1 est de la forme "./path" ou "../path", chercher dans $3. -- Sinon, retourner 1 -}}} -!! {{{runs_initvars}}} -{{{ -Initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSDIRPATH, -RUNSSCRIPTPATH, RUNSSCRIPTDIR et RUNSSCRIPTNAME pour le script $1. -Les valeurs sont initialisées comme suit: -RUNSSCRIPT="$(abspath "$1")" -RUNSDIR="$2" (le répertoire de $RUNS*PATH dans lequel a été trouvé le -script) -Si $3!="", RUNSDIRPATH="$3" et RUNSSCRIPTPATH="$4" -Sinon, RUNSDIRPATH="$RUNSSCRIPTDIR" et RUNSSCRIPTPATH="$RUNSSCRIPTNAME" -}}} -!! {{{runs_find_scriptfile}}} -{{{ -Trouver sans l'afficher le script $1 dans les répertoires des tableaux -$3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS) et $5(=RUNSHOSTSDIRS), en -considérant que le script sera lancé sur l'hôte $2(=$RUNSHOST), et -initialiser les variables RUNSDIR, RUNSSCRIPT, RUNSSCRIPTDIR, -RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0 en cas de -succès, 1 en cas d'échec. -$6 vaut ".rs" par défaut est c'est une extension à rajouter au nom -spécifié si le fichier sans l'extension n'existe pas -RUNSDIR est le répertoire dans lequel a été trouvé le script (parmi les -valeurs fournies dans les tableaux RUNSSCRIPTSDIRS, RUNSMODULESDIRS, -RUNSHOSTSDIRS), RUNSDIRPATH est le répertoire à partir duquel est exprimé -le chemin du script (i.e RUNSDIRPATH + RUNSSCRIPTPATH == RUNSSCRIPT), -RUNSSCRIPT contient le chemin absolu vers le script, RUNSSCRIPTPATH -contient le chemin du script dans RUNSDIRPATH, RUNSSCRIPTDIR le répertoire -du script, et RUNSSCRIPTNAME le nom du script. -D'abord, si l'hôte est spécifié, chercher dans {$RUNSHOSTSDIRS}/$host et -{$RUNSHOSTSDIRS}/$domain/$hostname. Puis chercher dans {$RUNSSCRIPTSDIRS} -}}} -!! {{{runs_find_scriptfile_reverse}}} -{{{ -Soit le fichier de script $1, exprimée de façon absolue, trouver le -fichier parmi les tableaux $3(=RUNSSCRIPTSDIRS), $4(=RUNSMODULESDIRS) -et $5(=RUNSHOSTSDIRS), en considérant que le script sera lancé sur l'hôte -$2(=$RUNSHOST), puis initialiser les variables RUNSDIR, RUNSSCRIPT, -RUNSSCRIPTDIR, RUNSSCRIPTNAME, RUNSDIRPATH et RUNSSCRIPTPATH. Retourner 0 -en cas de succès, 1 en cas d'échec. -}}} -!! {{{runs_rscript}}} -{{{ -Lancer le fichier $1 comme un script avec les arguments $2..$*. Retourner -la valeur de retour du script. -}}} -!! {{{runs_recipe}}} -{{{ -Lancer les scripts de la recette contenue dans le fichier $1. Arrêter au -premier script qui est en erreur -}}} -!! {{{runs_rscriptpath}}} -{{{ -Lancer le script $1 avec les arguments $2..$*. Le script est cherché dans -les répertoires de RUNSSCRIPTSPATH. Retourner 123 si le script n'est pas -trouvé, sinon retourner la valeur de retour du script. -}}} -!! {{{runs_recipepath}}} -{{{ -Lancer la recette $1. Le fichier de recette est cherché dans les -répertoires de RUNSSCRIPTSPATH. Retourner 123 si le fichier de recette n'a -pas été trouvé, sinon retourner la valeur de retour de runs_recipe() -}}} -!! {{{runs_init}}} -!! {{{runs_initdomains}}} -{{{ -Si ce n'est pas déjà le cas, initialiser RUNSDOMAINS en fonction de -/etc/resolv.conf -}}} -!! {{{runs_inithost}}} -!! {{{runs_initsysinfos}}} -!! {{{runs_initworkdir}}} -!! {{{runs_after_export}}} -{{{ -après l'export, initialiser varsfile avec les valeurs qu'il faut garder -entre le déploiement local et le déploiement distant. -}}} -!! {{{runs_check_runsscript}}} -!! {{{runs_var}}} -{{{ -Initialiser les variables selon les directives données en ligne de -commande. -Les arguments peuvent être une suite de définitions de la forme -'scalar=value', 'scalar!=name', 'array+=value', 'array-=value' ou -'array@=name'. -Sinon, le *dernier* argument peut-être de l'une des formes suivantes: -'array value0 [value1...]' pour initialiser un tableau, -'array+ value0 [value1...]' pour ajouter des valeurs à un tableau, -'array- value0 [value1...]' pour enlever des valeurs à un tableau. -Les formes 'scalar!=value' et 'array@=value' sont des indirections et -permettent d'initialiser la variable avec la valeur d'une autre -variable. L'avantage est que la résolution de la valeur est faite -uniquement lors de l'appel de cette fonction, ce qui est utile avec des -fonction comme 'after -r' -}}} -!! {{{runs_conf}}} -{{{ -Activer les flags $* -}}} -!! {{{runs_indref}}} -{{{ -fonction de convenance pour créer des références $3..* avec le fichier de -configuration $1 et les variables $2 -}}} -!! {{{runs_refcerts}}} -{{{ -fonction de convenance pour créer une référence à un répertoire contenant -des certificats mentionnés dans le fichier de configuration $1. Si les -références $2..* ne sont pas mentionnées, la variable certsdir dans le -fichier de configuration est utilisée. -}}} -!! {{{runs_refapacheconfig}}} -{{{ -fonction de convenance pour créer les références à un répertoire de -configuration pour apache. -USAGE: refapacheconfig autoconfdir=path/to/autoconfdir [certsdir=[path/to/certsdir]] -- autoconfdir= est requis et permet de définir à la fois la variable qui -contiendra la référence ainsi que le répertoire à référencer. -- certsdir= est optionel. Si cet argument est spécifié sous la forme -certsdir=path/to/certsdir, il permet de définir à la fois la variable qui -contiendra la référence ainsi que le répertoire à référencer. Si -l'argument est spécifié sous la forme certsdir=, il permet de définir la -variable qui contiendra la référence. C'est cette variable qui sera lue -dans les fichiers de configuration. Si l'argument n'est pas spécifié, on -considère que l'argument 'certsdir=' a été utilisé. -}}} -!! {{{runs_set_lang}}} -{{{ -Charger la valeur de LANG depuis l'environnement. La variable LANG est -initialisée -}}} -!! {{{runs_set_proxy}}} -{{{ -Charger la valeur du proxy depuis l'environnement. Les variables -http_proxy, ftp_proxy et no_proxy sont initialisées -}}} -!! {{{runs_check_confs}}} -{{{ -Vérifier l'état courant par rapport aux flags -}}} -!! {{{runs_after}}} -{{{ -Vérifier que ce script est lancé après le scriptpath $1, par rapport à -RUNSSTORY -}}} -!! {{{runs_clvars}}} -{{{ -Traiter les spécifications de variables données en ligne de commande ou -dans un fichier de recettes -}}} -!! {{{runs_indvars}}} -{{{ -Résoudre les valeurs effectives des variables qui sont des indirections -}}} -!! {{{runs_clvars_cmd}}} -{{{ -écrire la ligne de recette correspondant au script $1 et aux variables -$2..$* -}}} -!! {{{runs_loadconfs}}} -!! {{{runs_clearvars}}} -!! {{{runs_action_desc}}} -!! {{{runs_action_dump}}} -!! {{{runs_action_run}}} -!! {{{runs_action_export}}} -!! {{{shouldrun}}} -!! {{{checkdone}}} -!! {{{requiredone}}} -!! {{{setdone}}} -!! {{{resetdone}}} diff --git a/doc/ulib_runsmod.defaults.twp b/doc/ulib_runsmod.defaults.twp deleted file mode 100644 index 09ea60a..0000000 --- a/doc/ulib_runsmod.defaults.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/runsmod.defaults - diff --git a/doc/ulib_runsmod.twp b/doc/ulib_runsmod.twp deleted file mode 100644 index 20a4f62..0000000 --- a/doc/ulib_runsmod.twp +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/runsmod - -!! {{{runsmod_checkenv}}} -{{{ -vérifier l'environement. créer les répertoires nécessaires. -}}} -!! {{{runsmod_should_update_repolists}}} -{{{ -tester s'il faut mettre à jour au moins un des fichiers contenant les -listes des dépôts -}}} -!! {{{runsmod_update_repolists}}} -{{{ -mettre à jour si nécessaire les fichiers contenant les listes des dépôts. -Si $1 n'est pas vide, forcer la mise à jour de tous les fichiers -}}} -!! {{{runsmod_setup_vars}}} -{{{ -récupérer configuration statique pour la mettre à jour -}}} -!! {{{runsmod_clone_or_pull}}} -{{{ -Chercher les modules $3..@, pour l'hôte $1 qui est le mode d'hôte: none, -all, self ou one pour un hôte spécifique $2. Ajouter les chemins dans le -tableau REPO_DIRS. Mettre à jour les tableaux SCRIPTS_DIRS, MODULES_DIRS -et HOSTS_DIRS -}}} -!! {{{runsmod_teardown_vars}}} diff --git a/doc/ulib_semver.md b/doc/ulib_semver.md deleted file mode 100644 index 268e795..0000000 --- a/doc/ulib_semver.md +++ /dev/null @@ -1,33 +0,0 @@ -# ulib/semver - -## `semver_parse` -## `semver_incmajor` -## `semver_incminor` -## `semver_incpatchlevel` -## `semver_setversion` -## `semver_setprelease` -## `semver_compare_prelease` -## `semver_setmetadata` -## `semver_addmetadata` -## `semver_compare_metadata` -~~~ -même algo que pour prelease -~~~ -## `semver_copy` -## `semver_build` -## `semver_setvar` -## `psemver_parse` -## `psemver_incmajor` -## `psemver_incminor` -## `psemver_incpatchlevel` -## `psemver_setversion` -## `psemver_setprelease` -## `psemver_compare_prelease` -## `psemver_setmetadata` -## `psemver_addmetadata` -## `psemver_compare_metadata` -## `psemver_copy` -## `psemver_build` -## `psemver_setvar` - --*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/doc/ulib_semver.twp b/doc/ulib_semver.twp deleted file mode 100644 index 2289d1e..0000000 --- a/doc/ulib_semver.twp +++ /dev/null @@ -1,37 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/semver - -!! {{{semver_parse}}} -!! {{{semver_incmajor}}} -!! {{{semver_incminor}}} -!! {{{semver_incpatchlevel}}} -!! {{{semver_setversion}}} -!! {{{semver_setprelease}}} -!! {{{semver_compare_prelease}}} -!! {{{semver_setmetadata}}} -!! {{{semver_addmetadata}}} -!! {{{semver_compare_metadata}}} -{{{ -même algo que pour prelease -}}} -!! {{{semver_copy}}} -!! {{{semver_build}}} -!! {{{semver_setvar}}} -!! {{{psemver_parse}}} -!! {{{psemver_incmajor}}} -!! {{{psemver_incminor}}} -!! {{{psemver_incpatchlevel}}} -!! {{{psemver_setversion}}} -!! {{{psemver_setprelease}}} -!! {{{psemver_compare_prelease}}} -!! {{{psemver_setmetadata}}} -!! {{{psemver_addmetadata}}} -!! {{{psemver_compare_metadata}}} -!! {{{psemver_copy}}} -!! {{{psemver_build}}} -!! {{{psemver_setvar}}} diff --git a/doc/ulib_service.twp b/doc/ulib_service.twp deleted file mode 100644 index aac011e..0000000 --- a/doc/ulib_service.twp +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/service - -!! {{{service}}} -!! {{{service_start}}} -{{{ -démarrer le service $1 de façon inconditionnelle -}}} -!! {{{service_startm}}} -{{{ -démarrer le service $1 s'il n'est pas déjà démarré -}}} -!! {{{service_stop}}} -{{{ -arrêter le service $1 de façon inconditionnelle -}}} -!! {{{service_stopm}}} -{{{ -arrêter le service $1 s'il n'est pas déjà arrêté -}}} -!! {{{service_reload}}} -{{{ -recharger le service $1 -}}} -!! {{{service_status}}} -{{{ -tester/afficher le status du service $1 -}}} diff --git a/doc/ulib_sysinfos.twp b/doc/ulib_sysinfos.twp deleted file mode 100644 index 00fa623..0000000 --- a/doc/ulib_sysinfos.twp +++ /dev/null @@ -1,66 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/sysinfos - -!! {{{read_data}}} -!! {{{dump_data}}} -!! {{{compute_local_sysinfos}}} -!! {{{compute_remote_sysinfos}}} -!! {{{ensure_sysinfos}}} -{{{ -Essayer de déterminer les valeurs des variables $1(=SYSNAME), $2(=SYSDIST) -et $3(=SYSVER) en fonction des valeurs des autres. Cette fonction est à -utiliser quand on récupère cette information de la part de l'utilisateur, -et qu'il faut compléter -}}} -!! {{{get_sysinfos_desc}}} -{{{ -Afficher une chaine de la forme SYSNAME/SYSDIST/SYSVER qui décrit le -système actuel -}}} -!! {{{check_sysinfos}}} -{{{ -Tester si le système courant ($MYSYSNAME, $MYSYSDIST, $MYSYSVER, $MYBITS) -correspond à au moins un des arguments. -Il est possible de spécifier des variables différentes pour tester le -système courant avec l'option --vars qui doit être spécifiée en premier: -check_sysinfos --vars sysname sysdist sysver bits -d debian -Les options -s, -d, -v, -b permettent respectivement de vérifier le -système, la distribution, la version et le nombre de bits. Il est possible -de spécifier plusieurs tests à effectuer, e.g.: -check_sysinfos -d debian ubuntu -b 64 -pour tester si l'on est sur une distribution debian ou ubuntu *et* sur un -système 64 bits -Note: avec l'option --vars, il peut arriver que sysname, sysdist ou sysver -ne soient pas des tableaux mais des variables scalaires, surtout si elles -sont fournies par l'utilisateur. Il est conseillé dans ce cas de tester -toutes les possibilités quand on vérifie une valeur, e.g.: -check_sysinfos --vars sysname sysdist sysver bits -s linux64 linux32 linux -pour tester si on est sur un système linux -Avec l'option -v, il est possible de suffixer la valeur avec + ou - selon -que l'on veut toutes les versions situées après ou avant la version -spécifiée. Attention, à cause d'une limitation de l'implémentation, il -faut alors impérativement filtrer aussi sur la distribution, e.g: -check_sysinfo -d debian -v lenny+ -pour tester si on est en lenny ou en squeeze. -De même, l'option -d accepte aussi de suffixer la valeur avec + ou -, mais -cela n'a actuellement de sens qu'avec les version de MacOS X. Il faut -aussi impérativement filtrer sur le système, e.g: -check_sysinfos -s macosx -d 10.5+ -}}} -!! {{{on_debian}}} -{{{ -Tester si on est sur debian. charger le module debian si c'est le cas. -Si une commande $1..@ est spécifiée, la lancer, mais il n'est alors plus -possible de lancer des tests plus spécifiques avec __on_debian() -}}} -!! {{{on_debian:}}} -!! {{{on_stretch}}} -!! {{{on_jessie}}} -!! {{{on_wheezy}}} -!! {{{on_squeeze}}} -!! {{{on_default}}} diff --git a/doc/ulib_template.twp b/doc/ulib_template.twp deleted file mode 100644 index af28d99..0000000 --- a/doc/ulib_template.twp +++ /dev/null @@ -1,101 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/template - -!! {{{template_list}}} -{{{ -Soit $N le séparateur --, lister les fichiers des répertoires sources -$2..$(N-1) qui seraient fusionnés avec template_merge() ou supprimés avec -template_unmerge() du répertoire destination $1. Si des chemins sont spécifiés -avec les arguments $(N+1)..@, ne traiter que les fichiers qui correspondent à -ces spécifications. Exemple: -template_list destdir srcdirs... -- specs... -}}} -!! {{{template_merge}}} -{{{ -Soit $N le séparateur --, copier dans le répertoire destination $1 les -fichiers des répertoires sources $2..$(N-1) correspondant aux spécifications -$(N+1)..@, si ces fichiers n'ont pas été modifiés dans le répertoire de -destination. -Les fichiers sources ayant l'extension .template sont ignorés par défaut, sauf -s'ils sonts demandés explicitement. Exemple: -template_merge destdir srcdirs... -- specs... -}}} -!! {{{template_unmerge}}} -{{{ -Soit $N le séparateur --, supprimer du répertoire destination $1 les fichiers -provenant des répertoires sources $2..$(N-1) et qui n'ont pas été modifiés. Si -des chemins sont spécifiés avec les arguments $(N+1)..@, ne traiter que les -fichiers qui correspondent à ces spécifications. Exemple: -template_unmerge destdir srcdirs... -- specs... -}}} -!! {{{template_cleandest}}} -{{{ -Supprimer dans le répertoire de destination $1 tous les répertoires vides. -Cette fonction est habituellement utilisée après template_unmerge() -Ignorer les chemins qui contiennent .git/ et .svn/ -}}} -!! {{{template_diff}}} -{{{ -Afficher les différences entre les fichiers du répertoire de destination $1 et -les fichiers des répertoires sources $2..@ -}}} -!! {{{template_srcdir}}} -{{{ -Obtenir le chemin vers le répertoire source de templates $1, situé dans -ULIBDIR/templates -}}} -!! {{{templatectl_config}}} -{{{ -Obtenir le chemin vers le fichier de configuration pour le répertoire $1 -Si $2==nohideconfig, utiliser le nom CONFIG.conf, sinon utiliser par défaut -.CONFIG sauf si le fichier CONFIG.conf existe -}}} -!! {{{templatectl_loadvars}}} -{{{ -Charger les valeurs des variables depuis le fichier $1 -Les variables suivantes doivent être définies: -- Le tableau TEMPLATECTL_DEFAULTS permet de donner une valeur par défaut aux -variables mentionnées dans TEMPLATE_STATIC_VARS. C'est une liste de valeurs -de la forme 'name=value' -- Le tableau TEMPLATECTL_VARS contient des variables supplémentaires -spécifiées par l'utilisateur. C'est une liste de valeurs de la forme -'name=value' -- TEMPLATE_STATIC_VARS doit contenir une liste de noms de variables qui -peuvent être remplacés dans les fichiers de template. -- TEMPLATE_DYNAMIC_VARS contient une liste de noms de variables valides, mais -qui ne doivent pas être remplacés, en effet, ils sont utilisés pour le -déploiement des fichiers. -- TEMPLATE_NOWRITE_VARS contient une liste de noms de variables qui ne -devraient pas être écrits dans le fichier des variables, sauf si elles -reçoivent une valeur explicite de la part de l'utilisateur. Ce tableau est -mis à jour lors de l'analyse du tableau TEMPLATECTL_VARS -}}} -!! {{{templatectl_writevars}}} -{{{ -Ecrire les variables dans le fichier $1 -}}} -!! {{{templatectl_list_vars}}} -{{{ -Afficher les valeurs des variables -}}} -!! {{{templatectl}}} -{{{ -Fonction de haut niveau qui facilite l'utilisation des fonctions template_* -définir la fonction __display_templatectl_help() pour l'affichage de l'aide -- Le tableau TEMPLATECTL_SRCDIRS doit contenir la liste des répertoires -sources pour les templates. Alternativement, il est possible de définir la -variable TEMPLATECTL_SRCDIR s'il n'y a qu'un seul répertoire source pour le -template -- TEMPLATECTL_CONFIG est le nom de base du fichier à partir duquel sont -chargées les variables et dans lequel sont écrites les variables avec -l'option --write-vars -Si le nom de base est CONFIG, le fichier s'appelera .CONFIG si l'option ---hide-config est utilisée (par défaut) ou CONFIG.conf si l'option ---no-hide-config est utilisée -Les variables de template_loadvars() sont aussi prises en compte -}}} diff --git a/doc/ulib_tiddlywiki.twp b/doc/ulib_tiddlywiki.twp deleted file mode 100644 index db55550..0000000 --- a/doc/ulib_tiddlywiki.twp +++ /dev/null @@ -1,115 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/tiddlywiki - -!! {{{twget_version}}} -{{{ -lire le numéro de version dans le fichier $1 -}}} -!! {{{twdump_header}}} -{{{ -lire et afficher le contenu avant-storeArea du tiddlywiki $1 -}}} -!! {{{twdump_footer}}} -{{{ -lire et afficher le contenu après-storeArea du tiddlywiki $1 -}}} -!! {{{twdump_storeArea}}} -{{{ -lire et afficher le storeArea dans le tiddlywiki $1 -}}} -!! {{{twreplace_storeArea}}} -{{{ -dans le tiddlywiki $1, remplacer le storeArea par le fichier $2 (par défaut, lu sur stdin) -}}} -!! {{{twupgrade}}} -{{{ -mettre à jour le tiddlywiki $1 sur la base du tiddlywiki plus récent $2 -}}} -!! {{{twdate_curtwp}}} -{{{ -obtenir la date courante dans le format "dd/mm/YYYY HH:MM" exprimée dans -l'heure locale -$1 est éventuellement la date exprimée en nombre de secondes depuis -l'epoch, exprimée dans l'heure locale -}}} -!! {{{twdate_tid2twp}}} -{{{ -Transformer $1, une date de la forme "YYYYmmddHHMM" exprimée dans le -timezone UTC en une chaine "dd/mm/YYYY HH:MM" exprimée dans l'heure locale -Si $1 n'est pas dans le bon format, ne rien afficher -}}} -!! {{{twdate_curtid}}} -{{{ -obtenir la date courante dans le format "YYYYmmddHHMM" exprimée dans le -timezone UTC -$1 est éventuellement la date exprimée en nombre de secondes depuis -l'epoch, exprimée dans l'heure locale -}}} -!! {{{twdate_twp2tid}}} -{{{ -Transformer $1, une date de la forme "dd/mm/YYYY HH:MM" exprimée en heure -locale en une chaine "YYYYmmddHHMM" exprimée dans le timezone UTC -Si $1 n'est pas dans le bon format, ne rien afficher -}}} -!! {{{twdump_tiddlers}}} -{{{ -dumper les tiddlers du fichier $1 généré avec twdump_storeArea() sous -forme d'une liste d'appel de fonction '__tiddler_data title creator -modifier created modified tags changecount content' -Les arguments de la fonction sont les valeurs brutes du tiddler, qui ont -simplement été corrigées avec unquote_html() -}}} -!! {{{dump_tiddler}}} -!! {{{twdump_twpage}}} -{{{ -Dumper le contenu de la twpage $1 sous forme d'un appel à une function -'__twpage_data title creator modifier created modified tags changecount -content' -Les arguments de la fonction sont les valeurs brutes de la twpage, sauf -que le champ modified contient toujours la date de dernière modification -du fichier. -}}} -!! {{{twwrite_tiddler}}} -{{{ -Ecrire sur STDOUT le tiddler correspondant aux paramètres sont spécifiés -sur la ligne de commande. Les arguments sont les valeurs brutes prises de -la twpage, telles qu'elles sont générées par twdump_twpage() -}}} -!! {{{twcheck_twpage_modified}}} -{{{ -Vérifier si la twpage $1 peut être écrasée par un tiddler dont la date de -modification est $2, de format "YYYYmmddHHMM" exprimée dans le timezone -UTC -C'est le cas si le fichier $1 n'existe pas, ou a une date de modification -antérieure à $2 -}}} -!! {{{twcheck_twpage_newtwpage}}} -{{{ -Vérifier si la twpage $1 peut être écrasée par la twpage $2 -C'est le cas si le fichier $1 n'existe pas, ou a une date de modification -antérieure à $2 -}}} -!! {{{twwrite_twpage}}} -{{{ -Ecrire dans le répertoire courant le fichier correspondant au tiddler dont -les paramètres sont spécifiés sur la ligne de commande. Les arguments sont -les valeurs brutes prises du tiddler, telles qu'elles sont générées par -twdump_tiddlers() -Retourner 0 si le fichier a été écrasé, 1 s'il n'a pas été écrasé parce -qu'il n'a pas été modifié, 2 s'il n'a pas été écrasé parce qu'il est plus -récent. -Si TW_VERBOSE=1, afficher un message informatif lors de l'export -}}} -!! {{{export_to_twpages}}} -{{{ -Exporter tous les tiddlers du tiddlywiki $1 dans le répertoire $2 -}}} -!! {{{import_from_twpages}}} -{{{ -Remplacer les tiddlers du tiddlywiki $1 par les twpages du répertoire $2 -}}} diff --git a/doc/ulib_udir.twp b/doc/ulib_udir.twp deleted file mode 100644 index b722924..0000000 --- a/doc/ulib_udir.twp +++ /dev/null @@ -1,61 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/udir - -!! {{{udir_check}}} -{{{ -Vérifier si le fichier $1 existe -Si $1 est un répertoire, prendre $1/.udir -}}} -!! {{{udir_create_maybe}}} -{{{ -Si le fichier $1 n'existe pas, le créer comme un template .udir -Si $1 est un répertoire, prendre $1/.udir -}}} -!! {{{udir_dump}}} -{{{ -Dumper toutes les variables définies pour le fichier $1 -Si $1 est un répertoire, prendre $1/.udir -}}} -!! {{{udir_eval}}} -{{{ -Evaluer la commande "$2..$*" dans le contexte des variables définies pour -le répertoire $1. La commande est évaluée dans un sous-shell pour ne pas -polluer l'espace de noms courant. -}}} -!! {{{udir_dump_all}}} -{{{ -Dumper toutes les variables définies pour le répertoire $1 et *tous ses -parents* jusqu'à la racine -}}} -!! {{{udir_eval_all}}} -{{{ -Evaluer la commande "$2..$*" dans le contexte des variables définies pour -le répertoire $1 et *tous ses parents* jusqu'à la racine -}}} -!! {{{udir_parse}}} -{{{ -Dans le fichier $1, lire les noms des variables -Si $1 est un répertoire, prendre $1/.udir -Les noms des variables sont placés dans le tableau $2(=UDIR_VARS), et les noms -des tableaux sont placés dans le tableau $3(=UDIR_ARRAYS) -note: les regex qui sont entre "" au lieu de // le sont à cause d'un bug -de awk sous macosx -}}} -!! {{{udir_update}}} -{{{ -Dans le fichier $1, mettre à jour les variables $2..* -Si $1 est un répertoire, prendre $1/.udir -Chaque argument de cette fonction est de la forme name[=value] -Si value n'est pas précisée, la variable obtient une valeur nulle -(i.e. var=) -Si la variable ne figure pas dans le fichier, elle est rajoutée à la fin -du fichier. -Cette fonction nécessite gawk. -}}} -!! {{{write_unseen}}} -!! {{{udir_edit}}} diff --git a/doc/ulib_uenv.twp b/doc/ulib_uenv.twp deleted file mode 100644 index 6254f29..0000000 --- a/doc/ulib_uenv.twp +++ /dev/null @@ -1,8 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/uenv - diff --git a/doc/ulib_uenv_update.twp b/doc/ulib_uenv_update.twp deleted file mode 100644 index 954c69f..0000000 --- a/doc/ulib_uenv_update.twp +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/uenv_update - -!! {{{uenv_update_dir}}} -{{{ -Mettre à jour l'ordre de chargement pour le répertoire $1 qui contient des -fichiers de profil pour le shell. L'ordre dans lequel le fichiers de -profil doivent être chargé est écrit dans le fichier $1/.source_in_order -Si $2 est spécifié, il s'agit d'un fichier temporaire utilisé pour les -calculs de l'ordre des chargements. -$3(=$1) est le répertoire de destination. Si $1 est un répertoire de -préparation temporaire, on peut spécifier grâce à $3 quel est le -répertoire final après préparation. -S'ils sont spécifiés, les arguments $4..* sont des répertoires contenant -des fichiers de profils supplémentaires qu'il faut considérer aussi. Dans -ce cas, $3 est ignoré. -}}} -!! {{{uenv_set_destdirs}}} -!! {{{uenv_sourced_in}}} -{{{ -vérifier que l'un des fichiers $2..$* est sourcé dans $1 -}}} -!! {{{uenv_configure_profiles}}} -!! {{{uenv_install_profiles}}} diff --git a/doc/ulib_uinc.twp b/doc/ulib_uinc.twp deleted file mode 100644 index 79106f5..0000000 --- a/doc/ulib_uinc.twp +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/uinc - -!! {{{uinc}}} diff --git a/doc/ulib_uinst.twp b/doc/ulib_uinst.twp deleted file mode 100644 index baa25be..0000000 --- a/doc/ulib_uinst.twp +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/uinst - -!! {{{uinst}}} -{{{ -lancer uinst en déclarant les variables locales, de façon à ne pas polluer -l'environnement de l'appelant. -}}} -!! {{{uinst_nolocal}}} -{{{ -Interface en mode ligne de commande pour uinst. Appeler cette fonction -avec les paramètres de la ligne de commande, e.g.: -uinst_nolocal "$@" -}}} diff --git a/doc/ulib_ulib.twp b/doc/ulib_ulib.twp deleted file mode 100644 index 33986f8..0000000 --- a/doc/ulib_ulib.twp +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ulib - -!! {{{eerror}}} -!! {{{die}}} -!! {{{uprovided}}} -{{{ -Tester si le module $1 a déjà été chargé par urequire -}}} -!! {{{uprovide}}} -{{{ -Spécifier que le module $1 a été sourcée, ou prétendre que c'est le cas. -Retourner 1 si le module était déjà pourvu, 0 si c'est la première fois -qu'on le pourvoit -}}} -!! {{{urequire}}} -{{{ -Sourcer un module recherché dans ULIBDIRS -Le module DEFAULTS est traité de façon particulière: si le fichier associé -n'est pas trouvé, charger base, pretty, sysinfos et compat à la place -Si un module n'est pas trouvé, quitter le script avec die() -}}} -!! {{{ulibadd}}} -{{{ -Ajouter $1 au chemin de recherche de urequire -}}} -!! {{{ulibsync}}} -{{{ -Synchroniser les modules de ulib dans le répertoire $1 -}}} -!! {{{ulibver}}} -{{{ -Vérifier que la version actuelle de ulib est au moins à la version $1 -(inclue) et éventuellement au plus à la version $2 (exclue) -}}} -!! {{{ulibver_require}}} diff --git a/doc/ulib_ulibsh.twp b/doc/ulib_ulibsh.twp deleted file mode 100644 index 5ac59be..0000000 --- a/doc/ulib_ulibsh.twp +++ /dev/null @@ -1,17 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/ulibsh - -!! {{{eerror}}} -!! {{{die}}} -!! {{{uprovided}}} -!! {{{uprovide}}} -!! {{{urequire}}} -!! {{{ulibadd}}} -!! {{{ulibsync}}} -!! {{{ulibver}}} -!! {{{ulibver_require}}} diff --git a/doc/ulib_vcs.twp b/doc/ulib_vcs.twp deleted file mode 100644 index fba8f5e..0000000 --- a/doc/ulib_vcs.twp +++ /dev/null @@ -1,161 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/vcs - -!! {{{vcs_getvcs_help}}} -!! {{{vcs_getvcs}}} -!! {{{vcs_getroot_help}}} -!! {{{vcs_getroot}}} -!! {{{vcs_getrepos_help}}} -!! {{{vcs_getrepos}}} -!! {{{vcs_geturl_help}}} -!! {{{vcs_geturl}}} -!! {{{vcs_vcs_help}}} -!! {{{vcs_vcs}}} -!! {{{vcs_add_help}}} -!! {{{vcs_add}}} -{{{ -le répertoire de référence est le répertoire du premier fichier ajouté -}}} -!! {{{vcs_remove_help}}} -!! {{{vcs_remove}}} -{{{ -le répertoire de référence est le répertoire du premier fichier supprimé -}}} -!! {{{vcs_copy_help}}} -!! {{{vcs_copy}}} -{{{ -le répertoire de référence est le répertoire de destination -}}} -!! {{{vcs_move_help}}} -!! {{{vcs_move}}} -{{{ -le répertoire de référence est le répertoire de destination -}}} -!! {{{vcs_mkdir_help}}} -!! {{{vcs_mkdir}}} -{{{ -le répertoire de référence est le répertoire du premier répertoire créé -}}} -!! {{{vcs_commit_help}}} -!! {{{vcs_commit}}} -!! {{{vcs_status_help}}} -!! {{{vcs_status}}} -!! {{{vcs_update_help}}} -!! {{{vcs_update}}} -!! {{{vcs_push_help}}} -!! {{{vcs_push}}} -!! {{{vcs_diff_help}}} -!! {{{vcs_diff}}} -!! {{{vcs_tag_help}}} -!! {{{vcs_tag}}} -!! {{{git_getrepos}}} -!! {{{git_geturl}}} -!! {{{git_have_annex}}} -!! {{{git_add}}} -!! {{{git_remove}}} -!! {{{git_copy}}} -!! {{{git_move}}} -!! {{{git_mkdir}}} -!! {{{git_commit}}} -!! {{{git_status}}} -!! {{{git_update}}} -!! {{{git_push}}} -!! {{{git_diff}}} -!! {{{git_tag}}} -!! {{{git_check_gitvcs}}} -!! {{{git_ensure_gitvcs}}} -!! {{{git_list_branches}}} -!! {{{git_list_rbranches}}} -!! {{{git_list_pbranches}}} -{{{ -lister les branches locales et celles qui existent dans l'origine -$1(=origin) et qui pourraient devenir une branche locale avec la commande -git checkout -b -}}} -!! {{{git_have_branch}}} -!! {{{git_have_rbranch}}} -!! {{{git_get_branch}}} -!! {{{git_get_branch_remote}}} -!! {{{git_get_branch_merge}}} -!! {{{git_get_branch_rbranch}}} -!! {{{git_is_branch}}} -!! {{{git_have_remote}}} -!! {{{git_track_branch}}} -!! {{{git_ensure_branch}}} -{{{ -retourner 0 si la branche a été créée, 1 si elle existait déjà, 2 en cas d'erreur -}}} -!! {{{git_check_cleancheckout}}} -{{{ -vérifier qu'il n'y a pas de modification locales dans le dépôt -correspondant au répertoire courant. -}}} -!! {{{git_ensure_cleancheckout}}} -!! {{{git_is_ancestor}}} -{{{ -vérifier que la branche $1 est un ancêtre direct de la branche $2, qui -vaut par défaut refs/remotes/${3:-origin}/$1 -note: cette fonction retourne vrai si $1 et $2 identifient le même commit -}}} -!! {{{git_should_ff}}} -{{{ -vérifier si la branche $1 devrait être fast-forwardée à partir de la -branche d'origine $2, qui vaut par défaut refs/remotes/${3:-origin}/$1 -note: cette fonction est similaire à git_is_ancestor(), mais retourne -false si $1 et $2 identifient le même commit -}}} -!! {{{git_should_push}}} -{{{ -vérifier si la branche $1 devrait être poussée vers la branche de même nom -dans l'origine $2(=origin), parce que l'origin peut-être fast-forwardée à -partir de cette branche. -}}} -!! {{{git_fast_forward}}} -{{{ -vérifier que la branche courante est bien $1, puis tester s'il faut la -fast-forwarder à partir de la branche d'origine $2, puis le faire si c'est -nécessaire. la branche d'origine $2 vaut par défaut refs/remotes/origin/$1 -}}} -!! {{{git_is_merged}}} -{{{ -vérifier que les branches $1 et $2 ont un ancêtre commun, et que la -branche $1 a été complètement fusionnée dans la branche destination $2 -}}} -!! {{{git_annex_initial}}} -{{{ -sur le dépôt $1 fraichement cloné, vérifier s'il faut faire git annex -init. Si oui, l'initialiser avec le nom d'hôte, et récupérer tous les -fichiers annexés -retourner 1 si une erreur s'est produite -}}} -!! {{{svn_getrepos}}} -!! {{{svn_geturl}}} -!! {{{svn_add}}} -!! {{{svn_remove}}} -!! {{{svn_copy}}} -!! {{{svn_move}}} -!! {{{svn_mkdir}}} -!! {{{svn_commit}}} -!! {{{svn_status}}} -!! {{{svn_update}}} -!! {{{svn_push}}} -!! {{{svn_diff}}} -!! {{{svn_tag}}} -!! {{{cvs_getrepos}}} -!! {{{cvs_geturl}}} -!! {{{cvs_add}}} -!! {{{cvs_remove}}} -!! {{{cvs_copy}}} -!! {{{cvs_move}}} -!! {{{cvs_mkdir}}} -!! {{{cvs_commit}}} -!! {{{cvs_status}}} -!! {{{cvs_update}}} -!! {{{cvs_push}}} -!! {{{cvs_diff}}} -!! {{{cvs_tag}}} diff --git a/doc/ulib_virsh.twp b/doc/ulib_virsh.twp deleted file mode 100644 index e852823..0000000 --- a/doc/ulib_virsh.twp +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 30/03/2012 04:43 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/virsh - -!! {{{virsh_filter}}} -{{{ -filtrer une sortie liste de virsh. En pratique, ne prendre que les lignes -non vides à partir de la ligne "----*" -}}} -!! {{{virsh_list}}} -!! {{{virsh_pool_list}}} -!! {{{guess_vm_type}}} -{{{ -Afficher hn, kvm, vmware, virtualbox ou openvz suivant que l'on est -*probablement* respectivement sur une machine physique, une machine -virtuelle kvm, vmware, virtualbox, openvz -XXX pour le moment, seuls openvz, kvm et hn sont supportés -}}} diff --git a/doc/ulib_webobjects.twp b/doc/ulib_webobjects.twp deleted file mode 100644 index f8aa045..0000000 --- a/doc/ulib_webobjects.twp +++ /dev/null @@ -1,180 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/webobjects - -!! {{{compute_webobjects_prefixes}}} -!! {{{recompute_webobjects_prefixes}}} -!! {{{get_NEXT_ROOT_prefix}}} -!! {{{get_WOROOT_prefix}}} -!! {{{get_LOCALROOT_prefix}}} -!! {{{get_SYSTEMFRAMEWORKS_prefix}}} -!! {{{get_WOEXTENSIONS_prefix}}} -!! {{{get_WOFRAMEWORKS_prefix}}} -!! {{{get_WOAPPLICATIONS_prefix}}} -!! {{{get_WOCONFIGURATION_prefix}}} -!! {{{get_WOAUTOSTART_prefix}}} -!! {{{get_WOLOGS_prefix}}} -!! {{{get_WOVERSION_prefix}}} -!! {{{is_wobundle}}} -{{{ -Tester si $1 a un nom de bundle valide, c'est à dire avec l'extension .woa -ou .framework -}}} -!! {{{is_woappdir}}} -{{{ -Tester si $1 est un répertoire d'application webobjects. Le test est -effectué sur le contenu du bundle, pas sur le nom (utiliser is_wobundle() -pour cela) -}}} -!! {{{is_wofwkdir}}} -{{{ -Tester si $1 est un répertoire de framework webobjects. Le test est -effectué sur le contenu du bundle, pas sur le nom (utiliser is_wobundle() -pour cela) -}}} -!! {{{get_app_winclspth}}} -{{{ -calculer la valeur de Contents/Windows/CLSSPATH.txt pour l'application $1 -}}} -!! {{{get_infofile}}} -{{{ -Obtenir le chemin vers le fichier Info.plist dans le répertoire de -resource du bundle $1 -}}} -!! {{{read_infofile}}} -{{{ -Lire la version et le numéro de release dans le fichier $1 (chemin vers -Info.plist) et les placer dans les variables $2(=version) et $3(=release) -Retourner 1 si un erreur s'est produite, par exemple si le fichier $1 -n'existe pas ou n'est pas accessible en lecture -}}} -!! {{{write_infofile}}} -{{{ -Ecrire $2 (la version) et $3 (le numéro de release) dans le fichier $1 -(chemin vers Info.plist) -Retourner 1 si un erreur s'est produite, par exemple si le fichier $1 -n'existe pas -}}} -!! {{{get_jawotoolsfile}}} -{{{ -Obtenir le chemin vers le fichier jawotools.properties dans le bundle $1 -}}} -!! {{{read_jawotoolsfile}}} -{{{ -lire le fichier de propriété $1 et placer les valeurs dans les variables -$2(=version), $3(=releaseDate), $4(=description) -}}} -!! {{{save_jawotoolsfile}}} -{{{ -écrire le fichier de propriété $1 avec les valeurs version ($2), -releaseDate ($3) et description ($4) -}}} -!! {{{get_versionfile}}} -{{{ -Obtenir le chemin vers le fichier VERSION.txt dans le répertoire de -resource du bundle $1 -}}} -!! {{{get_configfile}}} -{{{ -obtenir le chemin vers le fichier de configuration du répertoire de -resource du bundle -$1=bundle ou resdir (appdir/Contents/Resources ou fwkdir/Resources) -}}} -!! {{{searchreplace_classpath}}} -{{{ -Dans les fichiers classpath de l'application $1, remplacer $2 par $3. Si -$3 est vide, la ligne est supprimée -}}} -!! {{{dump_jars}}} -{{{ -Afficher les jars des frameworks utilisés par l'application $1 -}}} -!! {{{dump_frameworks}}} -{{{ -Afficher les frameworks utilisés par l'application $1 -}}} -!! {{{remove_framework}}} -{{{ -supprimer le framework $2 (nom de base) des fichiers de classpath du -bundle d'application $1 -}}} -!! {{{add_framework}}} -{{{ -s'il n'y existe pas déjà, ajouter le framework $2 (nom de base ou chemin -absolu) aux fichiers de classpath du bundle d'application $1 -}}} -!! {{{fix_jars_case}}} -{{{ -Vérifier que la casse des jars de tous les frameworks utilisés par -l'application $1 est conforme au système de fichier -}}} -!! {{{verifix_bundle}}} -{{{ -vérifier et corriger le bundle $1. Pour une application, on vérifie que le -script est exécutable. Pour un framework, on vérifie que le framework est -conforme au modèle des framework générés par WebObjects. -}}} -!! {{{compute_fapps}}} -{{{ -Placer dans le tableau $1(=fappnames) la liste des noms de bundle -d'applications qui dépendent du framework $2 -Cette opération est faite à partir des informations sur le système de -fichier. Elle ne peut donc concerner qu'une installation locale. -}}} -!! {{{woraurl}}} -{{{ -Faire une requête avec la méthode $1 sur l'url $2 avec le payload $3 (par -exemple pour la méthode POST). la réponse est disponible dans le fichier -$WORAURL_DATA, $4(=http_code) contient le code de réponse. -Retourner 0 en cas de succès, ou une valeur différente de zéro si un -erreur se produit (typiquement, 3 pour une erreur du serveur, 1 pour une -réponse applicative, comme par exemple si l'application n'existe pas) -Les codes de réponse 2xx et 417 sont des succès -Les autres codes (à priori 4xx ou 5xx) sont des erreurs -note: le code 417 est utilisé par le moniteur pour répondre "Non", par -opposition à 200 utilisé pour répondre "OUI" -}}} -!! {{{wogeturl}}} -!! {{{splitins}}} -{{{ -Analyser le nom $1, qui peut être de forme '', 'Name.woa', -'Name.framework', 'App' ou 'Instance-N' (où N est un nombre), et -initialiser les variables $2(=type) et $3(=name) -Si $1=="", type=all et name="" -Si $1==Name.woa, type=woa et name=Name.woa -Si $1==Name.framework, type=fwk et name=Name.framework -Si $1==App, type=app et name=App -si $1==App-N, type=ins et name=App-N -}}} -!! {{{create_wodirs_maybe}}} -!! {{{check_autostart}}} -{{{ -vérifier la présence du fichier $WOAUTOSTART. Si ce n'est pas le cas, le -créer avec le contenu du tableau $1 -}}} -!! {{{get_autostart_order}}} -{{{ -Initialiser le tableau $1 avec la liste donnée dans le fichier -$WOAUTOSTART -}}} -!! {{{apply_autostart_order}}} -{{{ -Réordonner les valeurs $3..* selon la liste donnée dans le tableau $2, -puis placer le résultat dans le tableau $1. $2 doit être construit avec -get_autostart_order(). Si $2 n'est pas spécifié, la liste est construite -localement. -Si le tableau contient des lignes de délai @N, replacer les délais après -les applications appropriées -}}} -!! {{{wotaskd_stop}}} -!! {{{wotaskd_start}}} -!! {{{javamonitor_stop}}} -!! {{{womonitor_stop}}} -!! {{{javamonitor_start}}} -!! {{{womonitor_start}}} -!! {{{woservices_stop}}} -!! {{{woservices_start}}} diff --git a/doc/ulib_woinst.twp b/doc/ulib_woinst.twp deleted file mode 100644 index 4d5fa0e..0000000 --- a/doc/ulib_woinst.twp +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/woinst - -!! {{{date2version}}} -!! {{{woconf}}} -!! {{{wotag}}} -!! {{{woinst}}} diff --git a/doc/ulib_wondermonitor.twp b/doc/ulib_wondermonitor.twp deleted file mode 100644 index 54aac8b..0000000 --- a/doc/ulib_wondermonitor.twp +++ /dev/null @@ -1,219 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/wondermonitor - -!! {{{wom__statistics}}} -{{{ -Afficher les statistiques pour le serveur $1, avec éventuellement le mot -de passe $2 -}}} -!! {{{wom__info}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et afficher des informations sur l'application $1 (par défaut, all) -}}} -!! {{{wom__info_filter}}} -{{{ -filtrer le résultat de wom__info en ne gardant que les tags -name, state, activeSessions, autoRecover, deaths, host, port -}}} -!! {{{wom_info}}} -{{{ -Contacter le moniteur sur l'hôte $3, avec éventuellement le mot de passe -$4, et initialiser le tableau $1 avec une liste de valeurs quotés de la -forme: -"'name' 'state' 'activeSessions' 'autoRecover' 'deaths' 'host' 'port'" -concernant l'application $2 (par défaut, toutes les applications). Notez -qu'il y a une ligne par instance d'application -Ces valeurs peuvent être utilisées comme arguments d'une fonction. par -exemple: -wom_info appinfos "" host pw -for args in "${appinfos[@]}"; do -eval "userfunc $args" -done -}}} -!! {{{wom_getValidAndRunning}}} -{{{ -Placer la liste des applications valides dans le tableau $1(=valid_apps) -et la liste des applications qui tournent dans le tableau -$2=(running_apps), en contactant le moniteur sur l'hôte $3, avec -éventuellement le mot de passe $4. -}}} -!! {{{show_appinfo}}} -{{{ -Afficher des informations sur une application. Les arguments doivent être -le résultat de la fonction wom_info() -}}} -!! {{{wom_running}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et tester si l'application $1 (par défaut, all) tourne actuellement -}}} -!! {{{wom_start}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et démarrer l'application $1 (par défaut, all) -}}} -!! {{{wom_stopped}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et tester si l'application $1 (par défaut, all) est actuellement arrêtée -}}} -!! {{{wom_stop}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et arrêter l'application $1 (par défaut, all) -}}} -!! {{{wom_forceQuit}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et forcer l'arrêt de l'application $1 (par défaut, all) -}}} -!! {{{wom_turnScheduledOn}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et activer le flag scheduled sur l'application $1 (par défaut, all) -}}} -!! {{{wom_turnScheduledOff}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et désactiver le flag scheduled sur l'application $1 (par défaut, all) -}}} -!! {{{wom_turnRefuseNewSessionOn}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et activer le flag refuseNewSession sur l'application $1 (par défaut, -all) -}}} -!! {{{wom_turnRefuseNewSessionOff}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et désactiver le flag refuseNewSession sur l'application $1 (par -défaut, all) -}}} -!! {{{wom_turnAutoRecoverOn}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et activer le flag autoRecover sur l'application $1 (par défaut, all) -}}} -!! {{{wom_turnAutoRecoverOff}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et désactiver le flag autoRecover sur l'application $1 (par défaut, -all) -}}} -!! {{{wom_bounce}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et redémarrer l'application $1 (par défaut, all) en mode bounce -}}} -!! {{{wom_clearDeaths}}} -{{{ -Contacter le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3, et effacer le compte des morts suspectes pour l'application $1 (par -défaut, all) -}}} -!! {{{wom__getApplications}}} -{{{ -Obtenir des information sur la définition de l'application $1 (ou de -toutes les applications si $1=="") en contactant le moniteur sur l'hôte $2 -avec éventuellement le mot de passe $3. Le résultat est un flux xml, -chaque application étant défini dans un tag . Si un erreur -se produit, l'erreur est dans un tag -}}} -!! {{{wom__getApplications_filter}}} -{{{ -filtrer le résultat de wom__getApplications en ne gardant que les tags -name, unixPath, macPath, winPath -}}} -!! {{{wom_getApplications}}} -{{{ -Obtenir la liste des applications définies en contactant le moniteur sur -l'hôte $3 avec éventuellement le mot de passe $4, et initialiser le -tableau $1 avec une liste de valeurs quotées de la forme: -"'name' 'unixPath' 'macPath' 'winPath'" -concernant l'application $2 (par défaut, toutes les applications) -Ces valeurs peuvent être utilisées comme arguments d'une fonction. par -exemple: -wom_getApplications appinfos "" host pw -for args in "${appinfos[@]}"; do -eval "userfunc $args" -done -}}} -!! {{{wom_addApplication}}} -{{{ -Ajouter une application nommée $1 en contactant le moniteur sur l'hôte $2, -avec éventuellement le mot de passe $3. -Soit le nom Name, par défaut l'exécutable se trouve dans -WOAPPLICATIONS/Name.woa/Name et les logs dans /var/log/WebObjects, et le -flag autoRecover est activé -XXX supporter la possibilité de modifier les valeurs par défaut -}}} -!! {{{wom_addInstance}}} -{{{ -Ajouter une instance sur localhost pour l'application nommée $1 en -contactant le moniteur sur l'hôte $2, avec éventuellement le mot de passe -$3. -XXX supporter la possibilité de modifier les valeurs par défaut -}}} -!! {{{check_compute_apps_localhost}}} -{{{ -si les arguments de compute_apps contiennent des bundles de framework, il -faut avoir accès au système de fichier local. vérifier si l'un des -arguments $2..* est un framework. si c'est le cas, vérifier que l'hôte $1 -est localhost. -retourner 0 si c'est ok, 1 s'il y a des frameworks et que host n'est pas -localhost -}}} -!! {{{compute_apps}}} -{{{ -Remplir le tableau $1(=apps) avec la liste des applications correspondant -aux arguments $3...* -Un bundle de framework (Name.framework) est remplacé par la liste des -bundles d'applications qui dépendent de ce framework. Cette information -est obtenue en consultant le système de fichier local. -Un bundle d'application est remplacé par la liste des applications qui -sont définies pour ce bundle. Cette information est obtenue en consultant -le tableau généré par wom_getApplications(), dont le nom est $2 -Les arguments de la forme @N sont ignorés, ils correspondent à des délais -à respecter lors du démarrage de l'application -}}} -!! {{{get_error_msg}}} -!! {{{start_apps}}} -{{{ -Démarrer les applications $3..$* en contactant le moniteur sur l'hôte $1 -avec le mot de passe éventuel $2 -Les variables globales enable_autorecover et force_enable_autorecover -permettent respectivement d'activer l'autoRecover après le démarrage de -l'application et de forcer l'activation de l'autoRecover même si -l'instance tournait déjà. -Un argument de la forme @N provoque une attente de N secondes. Ceci permet -de placer un temps d'attente entre le démarrage de certaines applications. -}}} -!! {{{stop_apps}}} -{{{ -Arrêter les applications $3..$* en contactant le moniteur sur l'hôte $1 -avec le mot de passe éventuel $2 -Les variables globales disable_autorecover et force_disable_autorecover -permettent respectivement de désactiver l'autoRecover après l'arrêt de -l'application et de forcer la désactivation de l'autoRecover même si -l'instance ne tournait pas. -L'option {-a ARRAY} permet de remplir ARRAY avec la liste des applications -qui ont été effectivement arrêtées. Cette option si elle est spécifiée -doit être en premier -Pour compatibilité avec start_apps, les arguments de la forme @N sont -ignorés. Il n'y a pas de temps d'attente entre les applications lors de -l'arrêt. -}}} -!! {{{bounce_apps}}} -{{{ -Redémarrer les applications $3..$* en mode bounce en contactant le -moniteur sur l'hôte $1 avec le mot de passe éventuel $2 -Pour compatibilité avec start_apps, les arguments de la forme @N sont -ignorés. Il n'y a pas de temps d'attente entre les applications lors du -redémarrage. -}}} diff --git a/doc/ulib_wosign.twp b/doc/ulib_wosign.twp deleted file mode 100644 index 692f067..0000000 --- a/doc/ulib_wosign.twp +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/wosign - -!! {{{wosign_setup_maybe}}} -!! {{{wosign_jar}}} -!! {{{wosignable}}} -!! {{{wosign}}} -{{{ -Signer un bundle, les jars d'un répertoire, ou un jar -L'option -f force la resignature des jars d'un répertoire ou d'un -bundle. Elle force aussi la signature d'un jar, même s'il semble qu'il -soit la version signée d'un autre jar -on présuppose que wosignable a retourné true -}}} diff --git a/doc/ulib_wotaskd.twp b/doc/ulib_wotaskd.twp deleted file mode 100644 index be9dcc8..0000000 --- a/doc/ulib_wotaskd.twp +++ /dev/null @@ -1,12 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:15 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulib/wotaskd - -!! {{{wot_config}}} -{{{ -Afficher la configuration de wotaskd -}}} diff --git a/doc/ulibshell.twp b/doc/ulibshell.twp deleted file mode 100644 index 6b5e3ce..0000000 --- a/doc/ulibshell.twp +++ /dev/null @@ -1,23 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulibshell - -{{{ -ulibshell: Lancer un shell après avoir chargé des modules de ulib - -USAGE - ulibshell [options] [args...] - -OPTIONS - -r module - Spécifier un module à charger avec urequire. Plusieurs modules peuvent - être spécifiés en les séparant par ':' - -Un shell est lancé dans lequel les modules spécifiés sont chargés. Par défaut, -seul le module DEFAULTS est chargé. Les arguments sont passés inchangés au -shell. -}}} diff --git a/doc/ulibsync.twp b/doc/ulibsync.twp deleted file mode 100644 index b8b8fa6..0000000 --- a/doc/ulibsync.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulibsync - -{{{ -ulibsync: Copier les librairies ulib et/ou pyulib - -USAGE - ulibsync [options] destdir - -OPTIONS - -u Copier ulib - -p Copier pyulib -}}} diff --git a/doc/ulink.twp b/doc/ulink.twp deleted file mode 100644 index 89734a8..0000000 --- a/doc/ulink.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ulink - -{{{ -ulink: déplacer, supprimer, copier un fichier ou un lien - -Quand on déplace ou qu'on copie un lien, la destination du lien est mise à jour - -USAGE - ulink mv files... dest - ulink cp files... dest - ulink rm files... - -OPTIONS - -d UPDATEDIR - Chercher dans UPDATEDIR tous les liens qui pointent vers le fichier - concerné, et mettre à jour ces liens après avoir déplacé le fichier, ou - supprimer ces liens si le fichier est supprimé. -}}} diff --git a/doc/umatch.twp b/doc/umatch.twp deleted file mode 100644 index 01fef0f..0000000 --- a/doc/umatch.twp +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: umatch - -{{{ -umatch: Afficher le résultat d'une recherche par regexp et compter -éventuellement leurs occurences - -USAGE - umatch [options] [regexp] - -Chaque ligne *entière* de stdin est mise en correspondance avec l'expression -regulière qui doit avoir la syntaxe de awk. S'il y a correspondance, afficher -soit toute l'expression matchée, soit chaque groupe s'il y a des expressions -parenthésées, chacun des groupes étant séparé par le caractère sep. - -Si l'expression régulière n'est pas spécifiée, elle vaut par défaut '.*' ce qui -fait que toutes les lignes correspondent. Ceci peut être utile avec les options --s et -c. - -Certaines expressions régulières sont prédéfinies. regexp peut avoir l'une des -valeurs prédéfinies suivantes: - - ip --> [0-9]+\.[0-9]+\.[0-9]+\.[0-9]+ - -OPTIONS - -F sep - Spécifier le caractère séparateur en sortie s'il faut afficher des - champs multiples. Par défaut, utiliser le caractère ':' - Si un séparateur vide est spécifié, le séparateur standard de awk est - utilisé. - -s Trier le résultat - -C FIELDNUM - -c, --count - Compter les occurences successives de la valeur du champ FIELDNUM, et - afficher une ligne de la forme 'countlast_line' pour chaque groupe, - last_line étant la dernière occurence du groupe. L'option -c correspond - à '-C 0', c'est à dire compter les occurences successives de toute les - lignes. - Le séparateur utilisé pour calculer le numéro de champ est sep, spécifié - avec l'option -F. - -a, --all-lines - Avec les options -c/-C, afficher toutes les lignes des occurences - successives au lieu de seulement la ligne de la dernière occurence. Ceci - est utile surtout avec l'option -C, pour voir les différences avec les - différentes lignes. - -m, --multiple - Avec les options -c/-C, n'afficher que les lignes dont le compte est - supérieur à 1. Avec l'option -a, les groupes contigus sont séparés par - une ligne '--' -}}} diff --git a/doc/umirror.twp b/doc/umirror.twp deleted file mode 100644 index effcaab..0000000 --- a/doc/umirror.twp +++ /dev/null @@ -1,18 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: umirror - -{{{ -umirror: faire un miroir d'un site web - -USAGE - umirror [options] url [wget_options] - -OPTIONS - -l - Convertir les liens pour consultation locale -}}} diff --git a/doc/upassword.twp b/doc/upassword.twp deleted file mode 100644 index f202734..0000000 --- a/doc/upassword.twp +++ /dev/null @@ -1,70 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: upassword - -{{{ -USAGE: - upassword -p [-f aeskeyfile] [clear [salts...]] - upassword -p [-f aeskeyfile] -j JKEY codetu [salts...] - upassword -p -f aeskeyfile -k crypted [salts...] - upassword -f aeskeyfile -G [password [salt]] - upassword -f aeskeyfile -s - upassword -f aeskeyfile -e clear - upassword -f aeskeyfile -d crypted - upassword --batch - -OPTIONS - -p, --hash-password - Crypter un mot de passe (option par défaut). Si le mot de passe en clair - et/ou le salt ne sont pas spécifiés, ils sont choisis au hasard. - -j, --clear-is-codetu JKEY - Indiquer que l'argument clear est un numéro d'étudiant, à partir duquel - il faut générer le mot de passe. Cette option n'est valide qu'avec -p - -k, --clear-is-crypted - Indiquer que l'argument clear doit d'abord être décrypté avec la clé AES - spécifiée avant utilisation. Cette option n'est valide qu'avec -p - -G, --aes-genkey - Générer une clé AES pour utilisation avec les options -s, -e, -d - -s, --aes-showkey - Afficher encodée en base64 la clé AES contenue dans le fichier spécifié - -e, --aes-encrypt - Crypter un mot de passe avec la clé AES spécifiée - -d, --aes-decrypt - Décrypter un mot de passe avec la clé AES spécifiée - -f, --aes-keyfile - Spécifier le fichier contenant la clé AES. Cette option est obligatoire - avec les options -G, -s, -e et -d - --shell - Afficher les valeurs pour évaluation par le shell - -MODE BATCH -Utiliser l'option --batch active le mode batch. Dans ce mode, chaque ligne est -un ensemble d'arguments, comme si on avait lancé le script à plusieurs reprises. -L'analyseur est limité: le découpage des arguments est fait sur les espaces. -Les lignes commençant par # sont ignorées. -Si une ligne commence par --batch-after, alors cette ligne est affichée après -chaque résultat. Ceci permet de générer un script qui peut être évalué. - -Voici un exemple: - upassword --batch <"$newcontent" for ulibname in "${ulibnames[@]}"; do - echo "* [ulib/$ulibname](ulib_$ulibname)" >>"$newcontent" + echo "* [ulib/$ulibname]()" >>"$newcontent" done write_mdpage "$oldcontent" ulib "$newcontent" ulib.md # faire les pages for ulibname in "${ulibnames[@]}"; do ulib="../lib/ulib/$ulibname" - mdpage="ulib_$ulibname.md" + mdpage="ulib/$ulibname.md" title="ulib/$ulibname" dump_content "$mdpage" "$oldcontent" awkrun 'BEGIN { diff --git a/doc/update-nutools.twp b/doc/update-nutools.twp deleted file mode 100644 index df35959..0000000 --- a/doc/update-nutools.twp +++ /dev/null @@ -1,11 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: update-nutools - -{{{ -update-nutools: mettre à jour nutools -}}} diff --git a/doc/update-twp b/doc/update-twp deleted file mode 100755 index 0fc5012..0000000 --- a/doc/update-twp +++ /dev/null @@ -1,156 +0,0 @@ -#!/bin/bash -# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -source "$(dirname "$0")/../lib/ulib/ulib" || exit 1 -urequire DEFAULTS - -function display_help() { - uecho "$scriptname: Mettre à jour la documentation pour les outils de nutools - -USAGE - $scriptname [options] - -OPTIONS - -t, --tools - Ne générer la documentation que pour les outils - -l, --ulib - Ne générer la documentation que pour ulib - -s, --sync - Synchroniser avec nutools.html après la génération de la documentation -Par défaut, la documentation est (re)générée pour les outils et pour ulib" -} - -function dump_content() { - local twpage="$1" content="$2" - if [ -f "$twpage" ]; then - awk ' -BEGIN { dump = 0 } -!dump && ($0 == "" || $0 ~ /^[^#]/) { dump = 1 } -dump { print }' <"$twpage" >"$content" - else - >"$content" - fi -} - -function write_twpage() { - local oldcontent="$1" title="$2" newcontent="$3" twpage="$4" - if testdiff "$oldcontent" "$newcontent"; then - local created="$(date +"%d/%m/%Y %H:%M")" - echo "# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: $USER -##@created: $created -##@modifier: $USER -##@changecount: 1 -##@tags: -##@title: $title" >"$twpage" - cat "$newcontent" >>"$twpage" - fi -} - -auto=1 -tools= -ulib= -sync= -force= -parse_opts "${PRETTYOPTS[@]}" \ - --help '$exit_with display_help' \ - -t,--tools '$auto=; tools=1' \ - -l,--ulib '$auto=; ulib=1' \ - -s,--sync '$auto=; sync=1' \ - --force force=1 \ - @ args -- "$@" && set -- "${args[@]}" || die "$args" - -[ -n "$auto" ] && { - tools=1 - ulib=1 - sync=1 -} -ac_set_tmpfile oldcontent -ac_set_tmpfile newcontent -cd "$scriptdir" - -if [ -n "$tools" ]; then - if [ -n "$*" ]; then - cmds=("$@") - else - array_lsfiles cmds .. - fi - - for cmd in "${cmds[@]}"; do - cmdname="$(basename "$cmd")" - twpage="$cmdname.twp" - [ -x "$cmd" ] || continue - estep "$cmdname" - - dump_content "$twpage" "$oldcontent" - echo " -{{{ -$("$cmd" --help) -}}}" >"$newcontent" - - write_twpage "$oldcontent" "$cmdname" "$newcontent" "$twpage" - done -fi - -if [ -n "$ulib" ]; then - array_from_lines ulibnames "$(list_files ../lib/ulib)" - # faire l'entête - dump_content ulib.twp "$oldcontent" - echo " -!Liste des librairies de ulib" >"$newcontent" - for ulibname in "${ulibnames[@]}"; do - echo "* [[ulib/$ulibname]]" >>"$newcontent" - done - write_twpage "$oldcontent" ulib "$newcontent" ulib.twp - # faire les pages - for ulibname in "${ulibnames[@]}"; do - ulib="../lib/ulib/$ulibname" - twpage="ulib_$ulibname.twp" - title="ulib/$ulibname" - dump_content "$twpage" "$oldcontent" - awkrun 'BEGIN { - in_func = 0 - dump_doc = 0 - dumped_doc = 0 - print -} -!in_func && $0 ~ /^function / { - if (match($0, /function +([^ ]+)\(\)/, vs)) { - funcname = vs[1] - if (funcname !~ /^_/) { - in_func = 1 - dump_doc = 1 - dumped_doc = 0 - print "!! {{{" funcname "}}}" - if ($0 ~ /}$/) { - in_func = 0 - dump_doc = 0 - dumped_doc = 0 - } - next - } - } -} -in_func && dump_doc && $0 !~ /^ *#/ { - dump_doc = 0 -} -in_func && dump_doc && $0 ~ /^ *#/ { - if (!dumped_doc) print "{{{" - gsub(/^ *#+ */, "") - print - dumped_doc = 1 -} -in_func && $0 ~ /}$/ { - if (dumped_doc) print "}}}" - in_func = 0 - dump_doc = 0 - dumped_doc = 0 -} -END { if (in_func) print "}}}" } -' <"$ulib" >"$newcontent" - write_twpage "$oldcontent" "$title" "$newcontent" "$twpage" - done -fi - -if [ -n "$sync" ]; then - ../twsync ${force:+--force} -fi diff --git a/doc/uprefix.twp b/doc/uprefix.twp deleted file mode 100644 index bbef41f..0000000 --- a/doc/uprefix.twp +++ /dev/null @@ -1,22 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uprefix - -{{{ -uprefix: Afficher les préfixes valides pour uinst - -USAGE - uprefix -l|--dump|prefix... - -OPTIONS - -l - Afficher la liste des préfixes valides - --dump - Afficher la liste des préfixes valides et leurs valeurs - prefix - Afficher la valeur du préfixe spécifié -}}} diff --git a/doc/uproject.twp b/doc/uproject.twp deleted file mode 100644 index 3a5abfe..0000000 --- a/doc/uproject.twp +++ /dev/null @@ -1,125 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uproject - -{{{ -uproject: Outil pour gérer des projets - -USAGE - uproject cmd [args] - -COMMANDS - getvcs [dir] - Afficher le type de VCS pour dir. - getroot [dir] - Si dir est un répertoire versionné, retourner le répertoire racine du - projet versionné. - getrepos [dir] - Si dir est un répertoire versionné, retourner l'url du repository du - projet versionné. - geturl [dir] - Si dir est un répertoire versionné, retourner son url dans le - repository. - fold [dir] - unfold [dir] - Utiliser uinc pour défaire (resp. refaire) toutes les inclusions des - fichiers de dir. Cela nécessite qu'un fichier .udir soit configuré à la - racine du projet avec uinc=true - vcs [args] - Appeler le gestionnaire de gestion approprié avec les arguments donnés. - add files... - Ajouter les fichiers files dans le gestionnaire de version. - remove files... - Supprimer les fichiers versionnés files. - copy from to - Copier le fichier versionné from vers le fichier to. - move from to - Renommer le fichier versionné from vers le fichier to. - mkdir dir - Créer un nouveau répertoire versionné. - commit message [files...] - Enregistrer les modifications (par défaut sur tous les fichiers - modifiés) avec le commentaire message. - status - Afficher l'état des fichiers versionnés et non versionnés. - update [-x] - Mettre à jour la copie locale avec la copie sur le serveur. - -x Ne pas mettre à jour les références externes (si appliquable) - -n, --no-autoff - Ne pas faire de fast-forward automatique pour toutes les branches - traquées. Par défaut, s'il n'y a pas de modifications locales, - essayer de fast-fowarder toutes les branches locales traquées. - diff [options] - Afficher les différences. - -l Afficher les différences non commitées (par défaut) - -c Afficher les différences en passe d'être commitées (si appliquable) - -r REV - Afficher les différences depuis la révision REV. - -R Afficher les modifications effectuées depuis la dernière release. - - clone git@host:path/to/repo [destdir] - Cloner un dépôt distant. Initialiser git annex si le dépôt contient des - fichiers annexés. Récupérer aussi ces fichiers avec 'git annex get' - - crone git@host:path/to/repo [destdir] - Créer un dépôt distant sur gitolite, puis le cloner - - develop - release - hotfix - Démarrer le travail sur une branche respectivement de développement, de - release, ou de correction de bugs. Lancer chaque commande avec --help - pour les détails. Nécessite git. - - archive - Créer une archive du projet courant. Nécessite git. - - annex [args] - Lancer git annex avec les arguments spécifiés. - xadd - xunlock - xdrop - xwhereis - xwebapp - Chacune de ces commandes est un raccourci vers la commande - correspondante de git annex, sans le préfixe 'x' - xsync - Sur un dépot où git-annex est activé, lancer 'git annex sync' si on est - en mode indirect ou 'git annex sync --content' si on est en mode direct. - Sur un dépôt où git-annex n'est pas activé, faire l'équivalent des - commandes 'git add -A && git commit && git pull && git push' - xcopy - xmove - xget - Comme ci-dessus, mais si la commande s'exécute sans erreur, lancer - aussi 'git annex sync' - xinitial - Sur un dépôt fraichement cloné, initialiser le dépôt avec 'annex init' - s'il contient des fichiers annexés. Récupérer aussi ces fichiers avec - 'annex get' - xconfig-export [dir] - Installer des hooks pour qu'un dépôt puisse être utilisé pour servir des - fichiers, par exemple avec un serveur web. Plus précisément, un hook - post-receive est créé avec la commande 'git annex merge', et un hook - post-update est créé avec la commande 'git update-server-info' - - printml [-t TYPE] - Afficher le modeline pour un fichier du type spécifié - addml [-t TYPE] file - Ajouter un modele pour le fichier spécifié, s'il n'en a pas déjà un. - Si nécessaire, forcer le type du fichier au lieu de l'autodétecter - new [options] file [template options] - Créer un nouveau fichier à partir d'un modèle. - Avant le nom du fichier, les options suivantes sont valides: - -t TEMPLATE - Spécifier le modèle de fichier à utiliser. Par défaut, le modèle - à utiliser est déduit de l'extension ou du nom du fichier. - -e Editer le fichier après l'avoir créé. - Après le nom du fichier, toutes les options sont spécifiques au modèle - utilisé pour créer le nouveau fichier. Utiliser l'option --help pour - avoir une description des options disponibles. -}}} diff --git a/doc/uscrontab.twp b/doc/uscrontab.twp deleted file mode 100644 index 1e166e0..0000000 --- a/doc/uscrontab.twp +++ /dev/null @@ -1,292 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uscrontab - -{{{ -uscrontab: lancer une suite de commande en respectant une planification de type cron - -USAGE - uscrontab [options] [/path/to/uscrontab] [var=value...] - uscrontab -e [/path/to/uscrontab] - uscrontab -l - -La première forme du script doit normalement être lancé toutes les minutes par -une tâche cron. Utiliser l'option --install pour ajouter automatique la ligne -dans la crontab de l'utilisateur. - -Avec la première forme du script, le fichier spécifié est traité. Si aucun -fichier n'est spécifié, fusionner s'il existe le fichier - /var/local/uscrontab/users/jclain -avec chacun des fichiers du répertoire - /var/local/uscrontab/users.d/jclain -dans un fichier temporaire, puis exécuter le fichier résultat avec le nom -virtuel - /var/local/uscrontab/jclain -note: le nom virtuel est utilisé pour le verrouillage avec --lock - -A chaque lancement de ce script, le fichier /path/to/uscrontab spécifié est -examiné pour déterminer quels commandes doivent être exécutées. Ce fichier est -composé de lignes dans un format particulier, qui sont analysées et traitées -dans l'ordre. - -Quelles que soient les lignes qui sont sélectionnées pour l'exécution, elles -sont garanties de s'exécuter dans l'ordre du fichier, l'une après l'autre. - -Les définitions var=value mentionnées sur la ligne de commande sont des -définitions de variables à effectuer avant de lancer les commandes. - -Les lignes commençant par # sont des commentaires et sont ignorées - -== Définitions de variables et exécution de commandes == - - Les lignes de la forme suivante sont des définitions de variable: - - [export] var="valeur de la variable" - - Ces lignes sont des définitions de variable bash qui sont exécutées telles - quelles. Il n'est donc pas autorisé de mettre des espaces autour de =. Par - exemple, les lignes suivantes sont des erreurs de syntaxe: - - var = bad - var=pas de quotes autour de la valeur - - alors que celles-ci sont correctes: - - var=ok - var="valeur avec des espaces" - var='on peut utiliser des quotes aussi' - - Il est possible de manipuler les variables de type PATH avec une syntaxe - particulière de l'opérateur d'assignation. Les opérateurs += et %= utilisent - uaddpath(), #= utilise uinspath() et -= utilise udelpath(). Par exemple, les - lignes suivantes ajoutent respectivement /usr/local/nutools puis enlèvent - /opt/rogue au PATH: - - PATH+=/usr/local/nutools - PATH-=/opt/rogue - - Bien sûr, il ne faut pas oublier de quoter les espaces: - - PATH+="/path/to/dir with spaces" - - La syntaxe ?= permet de définir la valeur d'une variable si elle n'est pas - déjà définie: - - var?=default - - Les lignes de la forme suivante permettent d'exécuter une commande qui est - exécutée systématiquement et ignore la planification: - - $one-line-command - - Une variante permet de spécifier des commandes sur plusieurs lignes. - ATTENTION! ${ et $} doivent être tous seuls sur la ligne. - - ${ - several - commands - ... - $} - - Ces commandes sont exécutées systématiquement et ignorent la planification. - On peut s'en servir notamment pour lire un fichier de configuration qui - définit des variables ou des fonctions: - - $source path/to/file - - Le code d'erreur de ces commandes est ignoré, contrairement à ce qui se - passe pour les commandes qui font l'objet d'une planification. - -== Planification de commandes == - - Les autres lignes doivent être au format d'une ligne de crontab: - - minutes hours days months dows command-line - - command-line peut être n'importe quelle ligne de commande bash, pourvu - qu'elle soit sur une seule ligne. - - Certaines extensions par rapport à la syntaxe de crontab sont autorisées. Il - est en particulier possible de spécifier plusieurs planifications pour une - seule commande. Par exemple, les lignes suivantes permettent d'exécuter - 'command' toutes les heures ET à 1h05: - - 0 * * * * - 5 1 * * * command - - Il est aussi possible d'utiliser la même planification pour plusieurs - commandes sans devoir répéter la définition de la planification. Les lignes - suivantes planifient command1 et command2 toutes les heures: - - 0 * * * * command1 - command2 - - Pour être prise en compte, la ligne command2 doit commencer par au moins un - espace ou une tabulation. Pour la lisibilité, la syntaxe suivante est - supportée aussi: - - 0 * * * * - command1 - command2 - - Les deux formats peuvent être utilisés ensemble. Par exemple les lignes - suivantes exécutent command1 et command2 toutes les heures ET à 1h05: - - 0 * * * * - 5 1 * * * command1 - command2 - - Par défaut, le script s'arrête à la première commande planifiée qui retourne - avec un code d'erreur. Il est possible d'ignorer le code d'erreur d'une - commande avec nostop, e.g: - - 0 * * * * nostop command - - Cf aussi l'option --continuous pour modifier le comportement par défaut - -== Fonctions disponibles == - - La fonction check_pidfile() est disponible, et permet de vérifier qu'une - opération n'est pas déjà en cours. Si cette fonction est utilisée, il ne - faut pas modifier la valeur de -k. Par exemple: - - 0 1 * * * - check_pidfile /path/to/pid [args] - long-running-script - - check_pidfile() doit être utilisée toute seule sur la ligne et s'utilise - avec les argument suivants: - - check_pidfile PIDFILE [DESC] [BARRIER] - - - PIDFILE est le fichier de PID qui est vérifié - - DESC est la description du traitement qui est effectué. La valeur par - défaut est "Une synchronisation". Si le fichier de PID est présent, le - message suivant est affiché: - DESC est en cours. - Si vous pensez que c'est une erreur, veuillez vérifier le process de pid PID - puis supprimez le cas échéant le fichier PIDFILE - - BARRIER est un fichier qui est créé avec le contenu 'PID' s'il n'existe - pas encore, et si la vérification du fichier de PID est faite avec succès. - La présence de ce fichier peut-être vérifiée par un processus externe pour - empêcher par exemple de mettre à jour les scripts pendant qu'il sont en - train de tourner. - Son contenu peut être examiné pour connaître le PID du processus qui l'a - créé initialement. Le fichier est automatiquement supprimé à la fin de ce - script. - Attention: ce fichier n'est pas un verrou, il peut être supprimé à tout - moment. Notamment, si deux scripts sont configurés pour créer le même - fichier barrière, le premier script supprimera le fichier barrière avant - la fin de l'exécution du second script. - - La fonction remove_pidfile() permet de supprimer un fichier de pid pour - spécifier qu'une opération est terminée. Considérons l'exemple suivant: - - 0 1 * * * - check_pidfile /path/to/pid - script1 - script2 - remove_pidfile /path/to/pid - script3 - - Dans cet exemple, il ne faut pas qu'une autre occurence de script1 tourne - pendant que script2 tourne. Par contre, plusieurs occurences de script3 - peuvent tourner en parallèle. - - La fonction elogto() permet de spécifier un fichier vers lequel toutes les - sorties sont redirigées. - -OPTIONS - -A, --install - Installer une planification toutes les minutes du script dans la crontab - de l'utilisateur. Si l'argument /path/to/uscrontab n'est pas spécifié, - c'est une planification générique qui exécute les fichiers par défaut - qui est installée. - -R, --uninstall - Désinstaller la planification toutes les minutes du script de la crontab - de l'utilisateur. Si l'argument /path/to/uscrontab est spécifié, cette - instance est désinstallée. Sinon, ne désinstaller que la planification - générique. - -d, --disable-only - Avec l'option -R, désactiver la planification au lieu de la supprimer. - -e, --edit - Lancer un editeur pour modifier l'uscrontab spécifiée. Si aucun fichier - n'est spécifié, éditer /var/local/uscrontab/users/jclain - -a, --add - Installer un script uscrontab dans le répertoire approprié. L'argument - doit être de la forme [name:]/path/to/uscrontab - Si name n'est pas spécifié, le nom de base du fichier spécifié est - utilisé. Si name est vide ou vaut $USER (soit jclain en l'occurence), - copier le fichier spécifié vers le chemin /var/local/uscrontab/users/jclain - Sinon, copier le fichier spécifié vers /var/local/uscrontab/users.d/jclain/name - -r, --remove - Supprimer le script uscrontab spécifié. L'argument doit être le nom du - script à supprimer. Si l'argument n'est pas spécifié ou vaut $USER - (soit jclain en l'occurence), supprimer le fichier /var/local/uscrontab/users/jclain - s'il existe - -l, --list - Si l'argument /path/to/crontab est spécifié, afficher le contenu de ce - fichier. Sinon, lister les contenus des fichiers crontab qui sont - exécutés avec la planification actuelle. Si une planification générique - est installée, ou si aucune planification n'est en cours, afficher le - contenu du fichier - /var/local/uscrontab/users/jclain - et chacun des fichiers du répertoire - /var/local/uscrontab/users.d/jclain - -n, --fake - Afficher au lieu de les exécuter les commandes qui doivent être lancées - -P, --pause-for NBMINS - Désactiver les planifications pendant NBMINS minutes. Utiliser -1 pour - désactiver les planifications sans limite de durée. Pendant la période - de pause, toutes les invocations de uscrontab n'ont aucun effet, sauf si - on utilise l'option --force - -Y, --unpause - Réactiver les planifications après une mise en pause - -p, --pause - Désactiver les planifications pendant 1 journée. Equivalent à -P 1440 - -f, --force - Forcer l'exécution de la planification, même si elle a été mise en pause - avec l'option --pause - -OPTIONS AVANCEES - --lock LOCKFILE - Inscrire dans le fichier spécifié des informations permettant d'éviter - les invocations simultanées de ce script. Si selon ce fichier, le script - tourne depuis plus de 8 heures, un message d'erreur - est consigné et un message d'avertissement est affiché au plus une fois. - Utiliser --lock '' pour désactiver cette fonctionnalité - Par défaut, si ce script est lancé en root, le fichier utilisé pour le - verrouillage est de la forme /var/run/uscrontab/abspath/to/crontab - Si le script est lancé avec un compte utilisateur, aucun verrouillage - n'est effectué. - --lockdelay LOCKDELAY[=8] - Changer le nombre d'heures pendant lesquelles on autorise le script a - verrouiller l'exécution avant d'afficher un avertissement. - -c, --continuous - Par défaut, ce script s'arrête à la première commande planifiée qui - retourne avec un code d'erreur. Notez que les codes d'erreur des - commandes sans planification sont toujours ignorés. Avec cette option, - ce script ne s'arrête jamais, bien qu'il retourne toujours un code - d'erreur si une erreur s'est produite. Il est possible d'ignorer le - code d'erreur pour une commande en particulier avec le préfixe nostop - -k, --stopec EXITCODE[=101] - Spécifier un code d'erreur spécial qui arrête ce script sans erreur, ou - '' pour désactiver cette fonctionnalité. Ceci permet en début de script - de faire des tests par exemple sur l'environnement avant de lancer les - scripts planifiés. Si l'environnement ne convient pas, il suffit au - script de contrôle de retourner le code d'erreur spécifique pour arrêter - le traitement. - --show-ctnow - Afficher l'heure de référence au format crontab 'min hou day mon dow' - Cette valeur peut être utilisée avec l'option --force-ctnow dans des - tests pour reproduire une condition spécifique. - --force-ctnow 'min hou day mon dow' - Pour le développement ou des tests, forcer la valeur de l'heure de - référence. Il faut respecter le format, sinon les résultats ne sont pas - garantis. Le mieux est de reprendre le résultat de l'option --show-ctnow - en le modifiant un peu si nécessaire. -}}} diff --git a/doc/ussh.twp b/doc/ussh.twp deleted file mode 100644 index 1644e2c..0000000 --- a/doc/ussh.twp +++ /dev/null @@ -1,80 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: ussh - -{{{ -ussh: se connecter par ssh à un ou plusieurs hôtes - -USAGE - ussh [options] hosts - ussh [options] @hostsfile - ussh -r hosts - ussh --parse hosts - -OPTIONS - hosts - @hostsfile - Spécifier un ou plusieurs hôtes distants sur lequels faire la connexion. - Pour spécifier plusieurs hôtes, il faut les séparer par un espace ou le - caractère ':', e.g. 'host1 host2' ou 'host1:host2'. Si la spécification - contient les caractères { et }, l'expansion est effectuée, e.g - 'root@{host1,host2}.univ.run' - La forme @hostsfile permet de lire la liste des hôtes depuis le fichier - hostsfile, à raison d'un hôte par ligne. - -Toutes les options de ssh sont reconnues. Les options longues suivantes sont -reconnues comme alias de certaines options courtes de ssh: - --quiet - alias de -q, activer le mode non verbeux - --tty - alias de -t, forcer l'allocation d'un TTY - --login USER - alias de -l, spécifier le user avec lequel se connecter - --port PORT - alias de -p, spécifier le port sur lequel se connecter - -Les options suivantes sont exclusives à ce script: - -d, --domain DOMAIN - Spécifier un domaine par défaut pour les hôtes qui sont spécifiés sans - domaine. - -z, --ssh SSH - Spécifier l'exécutable à utiliser pour lancer ssh. - -r, --remove - Lancer 'ssh-keygen -R' pour chacun des hôtes spécifiés avant de s'y - connecter. Par exemple: - ussh -r host.tld - est équivalent à: - ssh-keygen -R host.tld - ssh-keygen -R host - ssh-keygen -R 10.10.1.5 - ssh host.tld - si l'adresse ip de host.tld est 10.10.1.5 - Quand cette option est spécifié, l'option -j est reconnue et permet de - NE PAS se reconnecter à l'hôte juste après avoir nettoyé les clés. Avec - l'option -j, TOUS les arguments sont des noms d'hôte puisqu'aucune - connexion n'est effectuée. - --exec - --no-exec - Avec --exec, si un seul hôte est spécifié, lancer le processus ssh avec - exec, pour éviter d'encombrer la mémoire. C'est l'option par défaut. - Avec --no-exec, ne jamais utiliser exec pour lancer ssh. - --parse - Afficher la définition des variables ssh, options, hosts et args qui - permettent d'effectuer la connexion à partir d'un autre script. Exemple: - eval "$(ussh --parse args...)" - for host in "${hosts[@]}"; do - ${exec:+exec} "$ssh" "${options[@]}" "$host" "${args[@]}" - done - --cc - Assumer que nutools est installé sur l'hôte distant, et y lancer uwatch - avec l'option --cc, pour permettre de garder la connexion active dans le - cadre d'une redirection de port. - -Si la variable UTOOLS_USSH_RSYNC_SUPPORT contient une valeur non vide, l'analyse -des arguments s'arrête à la première valeur qui n'est pas une option, afin de -permettre l'utilisation de ce script avec l'option -e de rsync. -}}} diff --git a/doc/usysinfos.twp b/doc/usysinfos.twp deleted file mode 100644 index 2502411..0000000 --- a/doc/usysinfos.twp +++ /dev/null @@ -1,19 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: usysinfos - -{{{ -usysinfos: Afficher les informations sur le système - -USAGE - usysinfos [query] - -Si la requête est spécifiée, tester si le système courant correspond à la -requête. Voir la doc de check_sysinfos() pour le format de la requête. - -Sinon, afficher les informations sur le système courant. -}}} diff --git a/doc/utempl.twp b/doc/utempl.twp deleted file mode 100644 index 4854620..0000000 --- a/doc/utempl.twp +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: utempl - -{{{ -utempl: Créer un nouveau fichier à partir d'un modèle - -USAGE - utempl [options] file [template options] - -OPTIONS -Avant le nom du nouveau fichier, les options suivantes peuvent être utilisées: - -t TEMPLATE - Spécifier le modèle de fichier à utiliser. Par défaut, le modèle - à utiliser est déduit de l'extension ou du nom du fichier. - -e, --edit - Editer le fichier après l'avoir créé - -g, --no-edit - Ne pas éditer le fichier après l'avoir créé - -Après le nom du fichier, toutes les options sont spécifiques au modèle -utilisé pour créer le nouveau fichier. Utiliser l'option --help pour -avoir une description des options disponibles. -}}} diff --git a/doc/utrigger.twp b/doc/utrigger.twp deleted file mode 100644 index 2050029..0000000 --- a/doc/utrigger.twp +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: utrigger - -{{{ -utrigger: lancer une commande en différé - -USAGE - utrigger [options] -- command [args] - -La commande est lancée après un certain temps, sauf si ce script est rappelé -(auquel cas le compte est réinitialisé), ou si l'opération est annulée. -Attention! La commande est lancée en tâche de fond, et son entrée standard est -connectée à un fichier qui peut être provisionné avec l'option -a - -note: ce script ne fonctionne que sous Linux puisqu'il utilise la commande flock - -OPTIONS - -n, --name NAME - Spécifier un nom identifiant la tâche. Par défaut, le nom est généré à - partir des détails de la tâche à lancer. Ce nom est utilisé pour - identifier les invocations successives. - -f, --cmdfile CMDFILE - Spécifier un fichier contenant les commandes à lancer. Le fichier est - sourcé dans un sous-shell. Utiliser - pour lire les commandes depuis - l'entrée standard. - --rundelay RUNDELAY[=5] - Nombre de secondes au bout desquelles la commande est lancée. Si ce - script est relancé avant la fin de ce décompte, le compte est remis à - zéro. - Utiliser --rundelay '' pour désactiver cette fonctionnalité, auquel cas - la commande est lancée immédiatement. - -s, --sudo - Forcer l'exécution de la commande avec l'utilisateur root si ce n'est - pas déjà le cas - -a, --datafile DATAFILE - Accumuler des données à fournir à la commande. Les informations du - fichier DATAFILE (utiliser - pour l'entrée standard) sont ajoutées à un - fichier temporaires, et sont fournies en une seule fois à la commande - sur son entrée standard. - -A, --data DATA - Variante de --datafile où les données sont fournies sur la ligne de - commande au lieu d'un fichier externe. Si les deux options -a et -A sont - spécifiées, les données sont accumulées dans l'ordre --datafile puis - --data - -k, --cancel - Annuler le lancement planifié d'une commande. Si la commande est déjà en - train de tourner, cette option n'a aucun effet. -}}} diff --git a/doc/uwatch.twp b/doc/uwatch.twp deleted file mode 100644 index dafc8c4..0000000 --- a/doc/uwatch.twp +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: uwatch - -{{{ -uwatch: afficher l'heure - -USAGE - uwatch [options] - -OPTIONS - -t, --time - Afficher l'heure (par défaut) - -c, --count - Afficher le temps écoulé depuis le lancement de ce script - -u, --units - Avec l'option --count, afficher l'unité: sec., min. ou heures - -o, --offset NBSEC - Avec l'option --count, spécifier un nombre de secondes à partir duquel - compter - -s, --step NBSECS[=1] - Spécifier la période de rafraichissement de l'affichage - -a, --prefix PREFIX - Spécifier une chaine à afficher avant l'heure - -z, --suffix SUFFIX - Spécifier une chaine à afficher après l'heure - --cc - Equivalent à -c -s 5 -a 'Connecté sur $MYHOST depuis ' -z '...' - Permet de garantir une activité sur une connexion SSH utilisée - uniquement pour faire une redirection de port -}}} diff --git a/doc/vzusage.twp b/doc/vzusage.twp deleted file mode 100644 index f39b7cd..0000000 --- a/doc/vzusage.twp +++ /dev/null @@ -1,25 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: vzusage - -{{{ -vzusage: afficher des informations sur une machine virtuelle OpenVZ - -USAGE - vzusage [options] [params...]d - -OPTIONS - -b Afficher les informations de /proc/user_beancounters - -f N'afficher que les valeurs pour lesquelles failcnt > 0. - Implique -b - -z coef - Afficher les instructions à utiliser pour augmenter de coef% les - valeurs pour lesquelles failcnt > 0. Implique -f - -c config|veid - Afficher les informations du fichier de configuration plutôt que les - beancounters -}}} diff --git a/doc/woArchive.twp b/doc/woArchive.twp deleted file mode 100644 index 0285875..0000000 --- a/doc/woArchive.twp +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: woArchive - -{{{ -woArchive: créer une archive de la distribution WebObjects en cours -USAGE - woArchive [-n name] [-f files] - -OPTIONS - -n NAME - Nom de base de l'archive. Par défaut il s'agit de WebObjects- - - -f FILES - Nom de la liste des fichiers de l'archives. Par défaut il s'agit de - $NAME.files -}}} diff --git a/doc/woSwitch.twp b/doc/woSwitch.twp deleted file mode 100644 index 75c42c1..0000000 --- a/doc/woSwitch.twp +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: woSwitch - -{{{ -woSwitch: Changer la version de WebObjects pour le système en cours -USAGE - woSwitch [-f from.files] to-archive.tar.gz - -OPTIONS - -f from.files - Spécifier la liste des fichiers pour la version de WebObjects - installée. Par défaut, il s'agit de WebObjects-.files - La liste doit correspondre à la version en cours. - - -F Forcer l'installation, même si la version en cours ne correspond pas à ce - qui est inscrit dans from.files - - to-archive.tar.gz - Nom de l'archive qui contient la version à installer. - -Ce script ne fonctionne que sur MacOS X -Le contenu des répertoires suivants est sauvegardé avant le changement: - /Library/WebObject/Applications - /Library/WebObject/Configuration - /Library/WebObject/Extensions -Ensuite, les répertoires Applications et Configuration sont restaurés. Il faudra -restaurer Extensions manuellement. -}}} diff --git a/doc/woctl.twp b/doc/woctl.twp deleted file mode 100644 index a775af2..0000000 --- a/doc/woctl.twp +++ /dev/null @@ -1,60 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: woctl - -{{{ -woctl: Contrôler des applications WebObjects - -USAGE - woctl [options] action args - wostart args... - wostop args... - wobounce args... - worestart args... - -OPTIONS - -h HOST - Spécifier l'hôte qui fait tourner le moniteur sous la forme host[:port] - -p PASSWORD - Spécifier le mot de passe pour le moniteur - -ACTIONS - Dans les arguments des actions ci-dessous, une application peut être - spécifiée sous la forme App ou App.woa. Spécifier une application revient à - spécifier toutes les instances configurées pour cette application. - Si on spécifie un framework sous la forme Fwk.framework, cela revient à - spécifier toutes les applications qui dépendent de ce framework. - Sinon, une instance individuelle est de la forme App-N, où N est un entier - positif. - - status - afficher l'état des instances qui tournent actuellement - version - afficher la version de WebObjects installée - wotaskd - javamonitor - woservices - piloter wotaskd/javamonitor - _create - créer une instance par défaut dans javamonitor - configure - configurer un bundle - tag - ajouter une information de version à un bundle - run - lancer une application localement en mode debug - download - télécharger une application ou un framework - start apps... - démarrer une ou plusieurs applications - stop apps... - arrêter une ou plusieurs applications - restart apps... - relancer une ou plusieurs applications - bounce apps... - relancer une ou plusieurs applications en mode bounce -}}} diff --git a/doc/woinst.twp b/doc/woinst.twp deleted file mode 100644 index 3795a83..0000000 --- a/doc/woinst.twp +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 15/03/2012 22:20 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: woinst - -{{{ -woinst: Déployer un bundle (application ou framework) de WebObjects - -USAGE - woinst [options] ... - -OPTIONS - PREFIX=value - Spécifier une valeur pour un préfixe, plutôt que de laisser uprefix - l'autodétecter. Utiliser uprefix -l pour une liste de préfixes valides. - -b Redémarrer les instances en mode bounce. - Par défaut, les instances sont arrêtées avant le déploiement, et - redémarrées après - -W Ne déployer que les resources web. Implique -n - -n Ne pas tagger les bundles déployés avec un numéro de version. Par - défaut, l'utilisateur est invité à compléter des informations telles - que n° de version et date de release si ces informations ne sont pas - disponible. - -x CMD - Exécuter la commande CMD après avoir effectué le déploiement -}}} diff --git a/doc/wosign.twp b/doc/wosign.twp deleted file mode 100644 index bfd6e7e..0000000 --- a/doc/wosign.twp +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -##@creator: jclain -##@created: 27/04/2016 03:19 -##@modifier: jclain -##@changecount: 1 -##@tags: -##@title: wosign - -{{{ -wosign: signer les jars d'un bundle - -USAGE - wosign - -OPTIONS - -f Forcer la (re)signature des jars - -d Enlever la signature des jars originaux - -s Signer les jar du bundle [PAR DEFAUT] - --init - Initialiser les fichiers de configuration pour la signature des bundles. - --sudo - Si le répertoire de destination des fichiers de configuration n'est - accessible en écriture, relancer le script en root. -}}} From 390233a4a230650f4044597e7f521a0660e8e9a0 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 28 Nov 2016 09:25:52 +0400 Subject: [PATCH 21/39] =?UTF-8?q?modification=20pour=20afficher=20l'aide?= =?UTF-8?q?=20m=C3=AAme=20si=20certains=20outils=20requis=20sont=20absents?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mocifs | 10 +++++----- modav | 10 +++++----- mossh | 5 +++-- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/mocifs b/mocifs index 3821f9e..7c36126 100755 --- a/mocifs +++ b/mocifs @@ -26,11 +26,6 @@ OPTIONS Spécifier les credentials à utiliser pour la connexion" } -if ! progexists mount.cifs; then - [ -x /sbin/mount.cifs ] || die "Ce script nécessite cifs-utils. Sur debian, vous pouvez l'installer avec la commande - sudo apt-get install cifs-utils" -fi - action=auto options= username= @@ -46,6 +41,11 @@ parse_opts "${PRETTYOPTS[@]}" \ -c:,--credentials: credentials= \ @ args -- "$@" && set -- "${args[@]}" || die "$args" +if ! progexists mount.cifs; then + [ -x /sbin/mount.cifs ] || die "Ce script nécessite cifs-utils. Sur debian, vous pouvez l'installer avec la commande + sudo apt-get install cifs-utils" +fi + if is_root; then sudo= else diff --git a/modav b/modav index 9d4fe8f..8860d92 100755 --- a/modav +++ b/modav @@ -40,11 +40,6 @@ OPTIONS mountpoint username password" } -if ! progexists mount.davfs; then - [ -x /usr/sbin/mount.davfs ] || die "Ce script nécessite davfs2. Sur debian, vous pouvez l'installer avec la commande - sudo apt-get install davfs2" -fi - action=auto options= username= @@ -60,6 +55,11 @@ parse_opts "${PRETTYOPTS[@]}" \ -c:,--credentials: credentials= \ @ args -- "$@" && set -- "${args[@]}" || die "$args" +if ! progexists mount.davfs; then + [ -x /usr/sbin/mount.davfs ] || die "Ce script nécessite davfs2. Sur debian, vous pouvez l'installer avec la commande + sudo apt-get install davfs2" +fi + if is_root; then sudo= else diff --git a/mossh b/mossh index 900cab0..d414566 100755 --- a/mossh +++ b/mossh @@ -29,8 +29,6 @@ OPTIONS prioritaire par rapport à cette option." } -progexists sshfs || die "Ce script nécessite sshfs" - action=auto ssh=ssh options= @@ -46,6 +44,9 @@ parse_opts "${PRETTYOPTS[@]}" \ -l:,-u:,--user: user= \ @ args -- "$@" && set -- "${args[@]}" || die "$args" +progexists sshfs || die "Ce script nécessite sshfs. Sur debian, vous pouvez l'installer avec la commande + sudo apt-get install sshfs" + if [ -n "$shared" ]; then if is_root; then options="${options:+"$options,"}allow_other" From 24812c1795b0dfa99eb9c853e3dfc4a0c3c89ea7 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 28 Nov 2016 11:21:22 +0400 Subject: [PATCH 22/39] =?UTF-8?q?initialiser=20PYTHONIOENCODING=20pour=20f?= =?UTF-8?q?aciliter=20l'ex=C3=A9cution=20de=20scripts=20python?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/profile.d/nutools.shared | 9 +++++++++ lib/profile.d/nutools.userconf | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/profile.d/nutools.shared b/lib/profile.d/nutools.shared index 33ed097..d9e670d 100644 --- a/lib/profile.d/nutools.shared +++ b/lib/profile.d/nutools.shared @@ -105,6 +105,15 @@ __uaddpath "@@dest@@/legacy" UINCPATH [ -z "$UTOOLS_LANG" ] && UTOOLS_LANG="$LANG" export LANG UTOOLS_LANG +if [ -z "$PYTHONIOENCODING" ]; then + __shopt="$(shopt -p nocasematch)"; shopt -s nocasematch + case "$LANG" in + *.utf8|*.utf-8) export PYTHONIOENCODING=utf-8;; + *@euro) export PYTHONIOENCODING=latin1;; + esac + eval "$__shopt"; unset __shopt +fi + # Le fichier nutoolsrc doit être chargé systématiquement [ -f /etc/nutoolsrc ] && . /etc/nutoolsrc [ -f ~/.nutoolsrc ] && . ~/.nutoolsrc diff --git a/lib/profile.d/nutools.userconf b/lib/profile.d/nutools.userconf index af0fa61..676a470 100644 --- a/lib/profile.d/nutools.userconf +++ b/lib/profile.d/nutools.userconf @@ -1,7 +1,7 @@ # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 ##@before nutools -# Valeur de LANG à forcer +# Valeur de LANG à initialiser, si elle n'est pas déjà définie #export UTOOLS_LANG=fr_FR.UTF-8 # Valeur de LESSCHARSET s'il faut la forcer. Normalement, less détecte @@ -15,6 +15,10 @@ #export UTOOLS_INPUT_ENCODING=utf-8 #export UTOOLS_EDITOR_ENCODING=utf-8 +# Encoding pour Python. Cette valeur est automatiquement initialisée si LANG est +# de la forme *.UTF-8 ou *@euro +#export PYTHONIOENCODING=utf-8 + # Ne pas utiliser la librairie readline pour la saisie des informations #export UTOOLS_NO_READLINE=1 From 24a8839feca7ea660376b7854d4bcc1b9c64b93b Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Mon, 28 Nov 2016 17:43:34 +0400 Subject: [PATCH 23/39] =?UTF-8?q?update-nutools:=20possibilit=C3=A9=20de?= =?UTF-8?q?=20cloner=20la=20branche=20develop=20avec=20l'option=20--develo?= =?UTF-8?q?p?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/tools/update-nutools.md | 9 +++++++++ update-nutools | 25 ++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/doc/tools/update-nutools.md b/doc/tools/update-nutools.md index 517410d..4ba94b0 100644 --- a/doc/tools/update-nutools.md +++ b/doc/tools/update-nutools.md @@ -2,6 +2,15 @@ ~~~ update-nutools: mettre à jour nutools + +USAGE: + update-nutools [--develop] + +OPTIONS + -d, --develop + Mettre à jour avec la branche develop pour avoir les dernières fonctions + qui ne sont pas encore stabilisées. Par défaut, la mise à jour est faite + avec la branche master. ~~~ -*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary \ No newline at end of file diff --git a/update-nutools b/update-nutools index f939e82..1df08a2 100755 --- a/update-nutools +++ b/update-nutools @@ -9,7 +9,16 @@ NAME=nutools ################################################################################ if [ $# -eq 1 -a "$1" == --help ]; then - echo "update-nutools: mettre à jour nutools" + echo "update-nutools: mettre à jour nutools + +USAGE: + update-nutools [--develop] + +OPTIONS + -d, --develop + Mettre à jour avec la branche develop pour avoir les dernières fonctions + qui ne sont pas encore stabilisées. Par défaut, la mise à jour est faite + avec la branche master." exit 0 fi @@ -41,14 +50,20 @@ NOTE: si une erreur se produit, utiliser l'adresse privée, e.g. NUTOOLS_REPO=$PRIV_REPO $0" fi +case "$1" in +-d|--d|--dev|--develop) develop=develop;; +*) develop=;; +esac + scriptdir="$(dirname "$0")" -if [ $# -ne 2 -o "$1" != --do-update ]; then +if [ $# -ne 3 -o "$1" != --do-update ]; then cp "$0" "$TMPSCRIPT" chmod 755 "$TMPSCRIPT" - exec bash "$TMPSCRIPT" --do-update "$scriptdir" + exec bash "$TMPSCRIPT" --do-update "$scriptdir" "$develop" fi scriptdir="$2" +develop="$3" clonerepo= if [ -z "$NUTOOLS_REPO" -a -f "$scriptdir/.nutools-devel" -a -d "$scriptdir/.git" ]; then echo "NOTE: tentative de mise à jour du dépôt local" @@ -62,13 +77,13 @@ if [ -n "$clonerepo" ]; then echo "NOTE: clonage du dépôt distant $REPO" cd /tmp rm -rf "$NAME" - git clone --depth 1 "$REPO" || exit 1 + git clone --depth 1 ${develop:+--branch "$develop"} "$REPO" || exit 1 cd "$NAME" fi if ! diff -q "$SCRIPTNAME" "$0"; then echo "NOTE: Le script $SCRIPTNAME a été mis à jour. Il va être relancé." - exec bash "./$SCRIPTNAME" + exec bash "./$SCRIPTNAME" ${develop:+--develop} fi bash ./uinst -y || exit 1 From 6a4e6a71461e6721e3cdab39b1619fa3bb4d3619 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 4 Dec 2016 17:53:53 +0400 Subject: [PATCH 24/39] =?UTF-8?q?foreach:=20ajouter=20une=20syntaxe=20alte?= =?UTF-8?q?rnative=20pour=20lister=20directement=20les=20=C3=A9l=C3=A9ment?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- doc/tools/foreach.md | 31 +++++++---- foreach | 125 ++++++++++++++++++++++++++++++------------- 2 files changed, 111 insertions(+), 45 deletions(-) diff --git a/doc/tools/foreach.md b/doc/tools/foreach.md index 0e1e8ab..3048e78 100644 --- a/doc/tools/foreach.md +++ b/doc/tools/foreach.md @@ -5,11 +5,16 @@ foreach: lancer une commande pour un ensemble d'arguments USAGE foreach [options] [VAR=]expr cmd... + foreach [options] [VAR=]items... -- cmd... La commande est lancée avec la variable 'VAR', qui vaut par défaut 'item', définie à la valeur courante de l'énumération. De plus, la variable 'index' reçoit une valeur incrémentale commençant à 0. +La deuxième syntaxe appelée syntaxe alternative permet de spécifier un ensemble +d'éléments directement, mais nécessite l'utilisation du séparateur '--' pour +identifier où s'arrête la liste des éléments. + En plus de VAR, les variables file, dir, name, basename, ext et dotext sont définies. Elle valent respectivement le chemin absolu du fichier, le répertoire absolu du fichier, le nom du fichier, le nom de base sans extension, l'extension @@ -17,22 +22,32 @@ sans le point et l'extension avec le point, e.g pour item=dir/file.ext on a file=/path/to/dir/file.ext, dir=/path/to/dir, name=file.ext, basename=file, ext=ext, dotext=.ext -Exemple: +Les 3 exemples suivants sont équivalents: foreach '*.c' cp %item dest/dir -Equivalent à: foreach item='*.c' cp %item dest/dir + foreach *.c -- cp %item dest/dir OPTIONS -b, --basedir BASEDIR Chercher les expressions -d, -f, -a à partir de BASEDIR au lieu du répertoire courant. -d, --dir - Faire la correspondance de l'expression sur les répertoires uniquement -f, --file - Faire la correspondance de l'expression sur les fichier uniquement -a, --all - Faire la correspondance de l'expression sur les répertoires et les - fichiers + -s, --string + Ces options permettent de spécifier le type d'expression et la façon de + les traiter. Avec -d, faire la correspondance de l'expression sur les + répertoires uniquement. Avec -f, faire la correspondance sur les fichier + uniquement. Avec -a, faire la correspondance sur les répertoires et les + fichiers. Avec -s, évaluer l'expression avec la fonction 'eval' du + shell. + Si la syntaxe alternative est utilisée, le premier élément est évalué + conformément à ces options *si et seulement s'il* est précédé d'une + chaine 'VAR='. Les autres éléments sont pris tels-quels. e.g: + cette commande affiche uniquement la chaine '*.c': + foreach '*.c' -- + alors que celle-ci liste les fichiers qui ont l'extension '.c': + foreach 'item=*.c' -- -p, --parent Pour chaque fichier/répertoire, se placer dans le répertoire parent avant de lancer la commande. item est aussi modifié pour ne plus @@ -47,14 +62,12 @@ OPTIONS la cible, soit '.' pour le répertoire courant, soit le nom du fichier dans le répertoire courant. Si cette option est mentionnée seule, elle implique --dir - -s, --string - Evaluer l'expression avec la fonction 'eval' du shell. -x, --expand -n, --no-expand Reconnaitre et traiter (resp. ne pas reconnaitre) la syntaxe %var dans les arguments. C'est le cas par défaut, ce qui permet de simplifier l'écriture d'une ligne de commande. Pour écrire le caractère '%', il - suffit de le double e.g %%item + suffit de le doubler e.g %%item Si l'expansion est désactivée, il faut protéger le caractère $ pour qu'il soit traité, e.g, avec les examples ci-dessus: foreach -n '*.c' 'cp "$item" dest/dir' diff --git a/foreach b/foreach index 34e3b1f..af4e14c 100755 --- a/foreach +++ b/foreach @@ -8,11 +8,16 @@ function display_help() { USAGE $scriptname [options] [VAR=]expr cmd... + $scriptname [options] [VAR=]items... -- cmd... La commande est lancée avec la variable 'VAR', qui vaut par défaut 'item', définie à la valeur courante de l'énumération. De plus, la variable 'index' reçoit une valeur incrémentale commençant à 0. +La deuxième syntaxe appelée syntaxe alternative permet de spécifier un ensemble +d'éléments directement, mais nécessite l'utilisation du séparateur '--' pour +identifier où s'arrête la liste des éléments. + En plus de VAR, les variables file, dir, name, basename, ext et dotext sont définies. Elle valent respectivement le chemin absolu du fichier, le répertoire absolu du fichier, le nom du fichier, le nom de base sans extension, l'extension @@ -20,22 +25,32 @@ sans le point et l'extension avec le point, e.g pour item=dir/file.ext on a file=/path/to/dir/file.ext, dir=/path/to/dir, name=file.ext, basename=file, ext=ext, dotext=.ext -Exemple: +Les 3 exemples suivants sont équivalents: $scriptname '*.c' cp %item dest/dir -Equivalent à: $scriptname item='*.c' cp %item dest/dir + $scriptname *.c -- cp %item dest/dir OPTIONS -b, --basedir BASEDIR Chercher les expressions -d, -f, -a à partir de BASEDIR au lieu du répertoire courant. -d, --dir - Faire la correspondance de l'expression sur les répertoires uniquement -f, --file - Faire la correspondance de l'expression sur les fichier uniquement -a, --all - Faire la correspondance de l'expression sur les répertoires et les - fichiers + -s, --string + Ces options permettent de spécifier le type d'expression et la façon de + les traiter. Avec -d, faire la correspondance de l'expression sur les + répertoires uniquement. Avec -f, faire la correspondance sur les fichier + uniquement. Avec -a, faire la correspondance sur les répertoires et les + fichiers. Avec -s, évaluer l'expression avec la fonction 'eval' du + shell. + Si la syntaxe alternative est utilisée, le premier élément est évalué + conformément à ces options *si et seulement s'il* est précédé d'une + chaine 'VAR='. Les autres éléments sont pris tels-quels. e.g: + cette commande affiche uniquement la chaine '*.c': + $scriptname '*.c' -- + alors que celle-ci liste les fichiers qui ont l'extension '.c': + $scriptname 'item=*.c' -- -p, --parent Pour chaque fichier/répertoire, se placer dans le répertoire parent avant de lancer la commande. item est aussi modifié pour ne plus @@ -50,14 +65,12 @@ OPTIONS la cible, soit '.' pour le répertoire courant, soit le nom du fichier dans le répertoire courant. Si cette option est mentionnée seule, elle implique --dir - -s, --string - Evaluer l'expression avec la fonction 'eval' du shell. -x, --expand -n, --no-expand Reconnaitre et traiter (resp. ne pas reconnaitre) la syntaxe %var dans les arguments. C'est le cas par défaut, ce qui permet de simplifier l'écriture d'une ligne de commande. Pour écrire le caractère '%', il - suffit de le double e.g %%item + suffit de le doubler e.g %%item Si l'expansion est désactivée, il faut protéger le caractère \$ pour qu'il soit traité, e.g, avec les examples ci-dessus: $scriptname -n '*.c' 'cp \"\$item\" dest/dir' @@ -97,10 +110,58 @@ if [ "$match" == auto ]; then fi fi +alt_syntax= +for sep in "$@"; do + if [ "$sep" == -- ]; then + alt_syntax=1 + break + fi +done +suppl_items=() varexpr="$1"; shift +if [ -n "$alt_syntax" ]; then + while [ $# -gt 0 ]; do + [ "$1" == -- ] && break + array_add suppl_items "$1" + shift + done + [ "$1" == -- ] && shift +fi splitfsep2 "$varexpr" = varname expr -[ -n "$varname" ] || varname=item -[ -n "$expr" ] || expr="*" +if [ -n "$alt_syntax" ]; then + if [ -n "$varname" ]; then + [ -n "$expr" ] || expr="*" + elif [[ "$varexpr" == *=* ]]; then + [ -n "$varname" ] || varname=item + [ -n "$expr" ] || expr="*" + else + items=("$varexpr") + varname=item + expr= + fi +else + [ -n "$varname" ] || varname=item + [ -n "$expr" ] || expr="*" +fi +if [ -n "$expr" ]; then + if [ -n "$basedir" ]; then + case "$match" in + all) array_lsall items "$basedir" "$expr";; + dir) array_lsdirs items "$basedir" "$expr";; + file) array_lsfiles items "$basedir" "$expr";; + string) eval "items=($expr)";; + esac + else + basedir=. + case "$match" in + all) array_from_lines items "$(list_all "$basedir" "$expr")";; + dir) array_from_lines items "$(list_dirs "$basedir" "$expr")";; + file) array_from_lines items "$(list_files "$basedir" "$expr")";; + string) eval "items=($expr)";; + esac + fi +fi +array_extend items suppl_items cmd=("$@") if [ ${#cmd[*]} -eq 0 ]; then @@ -111,33 +172,20 @@ if [ ${#cmd[*]} -eq 0 ]; then fi fi -if [ -n "$basedir" ]; then - case "$match" in - all) array_lsall items "$basedir" "$expr";; - dir) array_lsdirs items "$basedir" "$expr";; - file) array_lsfiles items "$basedir" "$expr";; - string) eval "items=($expr)";; - esac -else - basedir=. - case "$match" in - all) array_from_lines items "$(list_all "$basedir" "$expr")";; - dir) array_from_lines items "$(list_dirs "$basedir" "$expr")";; - file) array_from_lines items "$(list_files "$basedir" "$expr")";; - string) eval "items=($expr)";; - esac -fi - [ -n "$title" ] && einfo "${#items[@]} correspondance(s) trouvée(s)" let index=0 for item in "${items[@]}"; do [ -n "$title" ] && etitle "$item" + i="$item" setx file=abspath "$item" setx dir=dirname -- "$file" setx name=basename -- "$file" splitname "$name" basename ext [[ "$name" == *.* ]] && dotext=".$ext" || dotext= - export file dir name basename ext dotext + # ne pas changer les noms des variables sans modifier le script awk + # ci-dessous + __VARS=(i file dir name basename ext dotext) + export "${__VARS[@]}" ( if [ -n "$changedir" ]; then if [ -f "$item" ]; then @@ -154,13 +202,17 @@ for item in "${items[@]}"; do cd "$item" item=. fi + i="$item" + + __VALUES=() + for __VALUE in "${__VARS[@]}"; do + array_add __VALUES "$__VALUE=${!__VALUE}" + done + if [ -n "$expand" ]; then - array_from_lines cmd "$(awkrun -f \ -file="$file" dir="$dir" name="$name" basename="$basename" ext="$ext" dotext="$dotext" \ -xitem="$index" item="$item" varname="$varname" cmd[@] \ -' + array_from_lines cmd "$(awkrun -f "${__VALUES[@]}" xitem="$index" item="$item" varname="$varname" cmd[@] ' function replace_vars(src, re, vs, dest) { - re = "(%(file|dir|name|basename|ext|dotext|index|" varname "))" + re = "(%(" varname "|i|file|dir|name|basename|ext|dotext|index))" while (src != "") { if (match(src, "%%") != 0) { dest = dest substr(src, 1, RSTART) @@ -175,14 +227,15 @@ function replace_vars(src, re, vs, dest) { break } var = vs[2] - if (var == "file") dest = dest file + if (var == varname) dest = dest item + else if (var == "i") dest = dest item + else if (var == "file") dest = dest file else if (var == "dir") dest = dest dir else if (var == "name") dest = dest name else if (var == "basename") dest = dest basename else if (var == "ext") dest = dest ext else if (var == "dotext") dest = dest dotext else if (var == "index") dest = dest xitem - else if (var == varname) dest = dest item } if (src != "") dest = dest src return dest From 5aa60a33674b16559a0c7c294eecfa72d8d858d3 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 8 Dec 2016 16:49:35 +0400 Subject: [PATCH 25/39] =?UTF-8?q?d=C3=A9sactiver=20la=20cr=C3=A9ation=20du?= =?UTF-8?q?=20lien=20pour=20xpathtool.py,=20qui=20ne=20fonctionne=20qu'?= =?UTF-8?q?=C3=A0=20partir=20de=20lib/ulib/support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/uinst/conf | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/uinst/conf b/lib/uinst/conf index 510fc38..6d11c96 100644 --- a/lib/uinst/conf +++ b/lib/uinst/conf @@ -25,7 +25,6 @@ done ln -s lib/ulib/support/cgiupload.py ln -s lib/ulib/support/cgiparams.py ln -s lib/ulib/support/cgilsxml.py -ln -s lib/ulib/support/xpathtool.py # liens pour les scripts shell for i in cg cgs; do From 4e1e5a44ca355bb37f638d4b9a97e4166d5e0acd Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Thu, 15 Dec 2016 08:40:08 +0400 Subject: [PATCH 26/39] =?UTF-8?q?ruinst:=20tracer=20la=20collecte=20d'info?= =?UTF-8?q?rmations=20sur=20l'h=C3=B4te=20distant?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/uinst | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/ulib/uinst b/lib/ulib/uinst index 2bfdd5a..26e1027 100644 --- a/lib/ulib/uinst +++ b/lib/ulib/uinst @@ -969,6 +969,7 @@ function __uinst_rsync() { # corriger éventuellement destdir s'il contient un préfixe if [ -n "$UINST_AUTOPREFIX" ] && has_prefix "$__destdir_path"; then if [ -n "$__destdir_remote" ]; then + estep "Calcul des informations sur l'hôte distant" eval "$SYSINFOSLOCALS" local APACHE_PREFIXES_CHECK_OR_FIRST=1 compute_remote_sysinfos "${__destdir_user:+$__destdir_user@}$__destdir_host" "$destdir_ssh" From c95d1cd39a675ee5ac6c6fa0186d281dae1e0824 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 21 Dec 2016 05:08:45 +0400 Subject: [PATCH 27/39] =?UTF-8?q?ajouter=20un=20service=20pour=20tuer=20le?= =?UTF-8?q?s=20sessions=20ssh=20lors=20de=20l'arr=C3=AAt=20de=20la=20machi?= =?UTF-8?q?ne?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/init.d/install-kill-ssh-user-sessions | 8 ++++++++ lib/init.d/install-kvm-stop-all | 2 +- lib/init.d/install-openvz-fix-etchosts | 2 +- lib/init.d/kill-ssh-user-sessions.service | 11 +++++++++++ 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100755 lib/init.d/install-kill-ssh-user-sessions create mode 100644 lib/init.d/kill-ssh-user-sessions.service diff --git a/lib/init.d/install-kill-ssh-user-sessions b/lib/init.d/install-kill-ssh-user-sessions new file mode 100755 index 0000000..47f17f3 --- /dev/null +++ b/lib/init.d/install-kill-ssh-user-sessions @@ -0,0 +1,8 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +source "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 +urequire DEFAULTS + +run_as_root "$@" +cp "$scriptdir/kill-ssh-user-sessions.service" /etc/systemd/system +systemctl enable kill-ssh-user-sessions.service diff --git a/lib/init.d/install-kvm-stop-all b/lib/init.d/install-kvm-stop-all index 31ef516..48ccde7 100755 --- a/lib/init.d/install-kvm-stop-all +++ b/lib/init.d/install-kvm-stop-all @@ -1,6 +1,6 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -. "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 +source "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 urequire DEFAULTS check_sysinfos -s linux -d debian || exit 0 diff --git a/lib/init.d/install-openvz-fix-etchosts b/lib/init.d/install-openvz-fix-etchosts index f89fdbb..589ff8a 100755 --- a/lib/init.d/install-openvz-fix-etchosts +++ b/lib/init.d/install-openvz-fix-etchosts @@ -1,6 +1,6 @@ #!/bin/bash # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -. "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 +source "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 urequire DEFAULTS check_sysinfos -s linux -d debian || exit 0 diff --git a/lib/init.d/kill-ssh-user-sessions.service b/lib/init.d/kill-ssh-user-sessions.service new file mode 100644 index 0000000..d3def27 --- /dev/null +++ b/lib/init.d/kill-ssh-user-sessions.service @@ -0,0 +1,11 @@ +[Unit] +Description=Shutdown all ssh sessions before network +DefaultDependencies=no +Before=network.target shutdown.target + +[Service] +Type=oneshot +ExecStart=/bin/sh -c "/usr/bin/killall sshd;:" + +[Install] +WantedBy=poweroff.target halt.target reboot.target From 4b7d4f4c044a55f4d63a75eb836288c42e26f1e4 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 21 Dec 2016 05:23:20 +0400 Subject: [PATCH 28/39] ajouter une option pour installer le service kill-ssh-user-sessions --- .udir | 2 ++ lib/init.d/install-kill-ssh-user-sessions | 2 ++ lib/uinst/rootconf | 8 ++++++-- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.udir b/.udir index c816bf3..a24fb4c 100644 --- a/.udir +++ b/.udir @@ -16,6 +16,8 @@ rm_utools= kvm_service= # Faut-il installer le service openvz-fix-etchosts? openvz_service= +# Faut-il installer le service kill-ssh-user-sessions? +ksus_service= configure_variables=(dest uninst_utools rm_utools kvm_service openvz_service) configure_dest_for=(bashrc profile lib/uinst/conf lib/uinst/rootconf lib/profile.d/nutools.shared lib/bashrc.d/bash_completion.shared lib/init.d/kvm-stop-all legacy/sysinc/utools legacy/sysinc/system_caps legacy/sysinc/private/init) diff --git a/lib/init.d/install-kill-ssh-user-sessions b/lib/init.d/install-kill-ssh-user-sessions index 47f17f3..f41df24 100755 --- a/lib/init.d/install-kill-ssh-user-sessions +++ b/lib/init.d/install-kill-ssh-user-sessions @@ -3,6 +3,8 @@ source "$(dirname "$0")/../../lib/ulib/ulib" || exit 1 urequire DEFAULTS +[ -d /etc/systemd ] || exit 0 run_as_root "$@" + cp "$scriptdir/kill-ssh-user-sessions.service" /etc/systemd/system systemctl enable kill-ssh-user-sessions.service diff --git a/lib/uinst/rootconf b/lib/uinst/rootconf index 76fd5a1..f58a8aa 100644 --- a/lib/uinst/rootconf +++ b/lib/uinst/rootconf @@ -66,13 +66,17 @@ if [ -n "$rm_utools" ]; then fi [ -n "$kvm_service" ] && -etitle "Installation de /etc/init.d/kvm-stop-all" \ +etitle "Installation du service kvm-stop-all" \ "$scriptdir/../init.d/install-kvm-stop-all" [ -n "$openvz_service" ] && -etitle "Installation de /etc/init.d/openvz-fix-etchosts" \ +etitle "Installation du service openvz-fix-etchosts" \ "$scriptdir/../init.d/install-openvz-fix-etchosts" +[ -n "$ksus_service" ] && +etitle "Installation du service kill-ssh-user-sessions" \ + "$scriptdir/../init.d/install-kill-ssh-user-sessions" + etitle "Installation des répertoires pour uscrontab" if [ -d /var/uscrontab -a ! -d /var/local/uscrontab ]; then eimportant "Migration du répertoire /var/uscrontab vers /var/local/uscrontab" From 62a59e51f4df2540f94fb00bd8578ed113d2632a Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Wed, 21 Dec 2016 06:00:13 +0400 Subject: [PATCH 29/39] =?UTF-8?q?kill-ssh-user-sessions.service:=20laisser?= =?UTF-8?q?=20un=20peu=20de=20temps=20pour=20que=20le=20client=20soit=20no?= =?UTF-8?q?tifi=C3=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/init.d/kill-ssh-user-sessions.service | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/init.d/kill-ssh-user-sessions.service b/lib/init.d/kill-ssh-user-sessions.service index d3def27..d310625 100644 --- a/lib/init.d/kill-ssh-user-sessions.service +++ b/lib/init.d/kill-ssh-user-sessions.service @@ -5,7 +5,7 @@ Before=network.target shutdown.target [Service] Type=oneshot -ExecStart=/bin/sh -c "/usr/bin/killall sshd;:" +ExecStart=/bin/sh -c "/usr/bin/killall sshd; sleep 2; :" [Install] WantedBy=poweroff.target halt.target reboot.target From c552d2de564d277d0ec67894978064dcfc5292da Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Sun, 25 Dec 2016 12:44:31 +0400 Subject: [PATCH 30/39] corriger le module python ulib.ext.xpath --- lib/pyulib/src/ulib/ext/xpath/__init__.py | 164 +-------------------- lib/pyulib/src/ulib/ext/xpath/expr.py | 3 +- lib/pyulib/src/ulib/ext/xpath/main.py | 169 ++++++++++++++++++++++ lib/pyulib/src/ulib/ext/xpath/parser.py | 4 +- 4 files changed, 174 insertions(+), 166 deletions(-) create mode 100644 lib/pyulib/src/ulib/ext/xpath/main.py diff --git a/lib/pyulib/src/ulib/ext/xpath/__init__.py b/lib/pyulib/src/ulib/ext/xpath/__init__.py index 0741f64..71c2778 100644 --- a/lib/pyulib/src/ulib/ext/xpath/__init__.py +++ b/lib/pyulib/src/ulib/ext/xpath/__init__.py @@ -1,162 +1,2 @@ -from xpath.exceptions import * -import xpath.exceptions -import xpath.expr -import xpath.parser -import xpath.yappsrt - -__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath'] -__all__.extend((x for x in dir(xpath.exceptions) if not x.startswith('_'))) - -def api(f): - """Decorator for functions and methods that are part of the external - module API and that can throw XPathError exceptions. - - The call stack for these exceptions can be very large, and not very - interesting to the user. This decorator rethrows XPathErrors to - trim the stack. - - """ - def api_function(*args, **kwargs): - try: - return f(*args, **kwargs) - except XPathError, e: - raise e - api_function.__name__ = f.__name__ - api_function.__doc__ = f.__doc__ - return api_function - -class XPathContext(object): - def __init__(self, document=None, **kwargs): - self.default_namespace = None - self.namespaces = {} - self.variables = {} - - if document is not None: - if document.nodeType != document.DOCUMENT_NODE: - document = document.ownerDocument - if document.documentElement is not None: - attrs = document.documentElement.attributes - for attr in (attrs.item(i) for i in xrange(attrs.length)): - if attr.name == 'xmlns': - self.default_namespace = attr.value - elif attr.name.startswith('xmlns:'): - self.namespaces[attr.name[6:]] = attr.value - - self.update(**kwargs) - - def clone(self): - dup = XPathContext() - dup.default_namespace = self.default_namespace - dup.namespaces.update(self.namespaces) - dup.variables.update(self.variables) - return dup - - def update(self, default_namespace=None, namespaces=None, - variables=None, **kwargs): - if default_namespace is not None: - self.default_namespace = default_namespace - if namespaces is not None: - self.namespaces = namespaces - if variables is not None: - self.variables = variables - self.variables.update(kwargs) - - @api - def find(self, expr, node, **kwargs): - return xpath.find(expr, node, context=self, **kwargs) - - @api - def findnode(self, expr, node, **kwargs): - return xpath.findnode(expr, node, context=self, **kwargs) - - @api - def findvalue(self, expr, node, **kwargs): - return xpath.findvalue(expr, node, context=self, **kwargs) - - @api - def findvalues(self, expr, node, **kwargs): - return xpath.findvalues(expr, node, context=self, **kwargs) - -class XPath(): - _max_cache = 100 - _cache = {} - - def __init__(self, expr): - """Init docs. - """ - try: - parser = xpath.parser.XPath(xpath.parser.XPathScanner(str(expr))) - self.expr = parser.XPath() - except xpath.yappsrt.SyntaxError, e: - raise XPathParseError(str(expr), e.pos, e.msg) - - @classmethod - def get(cls, s): - if isinstance(s, cls): - return s - try: - return cls._cache[s] - except KeyError: - if len(cls._cache) > cls._max_cache: - cls._cache.clear() - expr = cls(s) - cls._cache[s] = expr - return expr - - @api - def find(self, node, context=None, **kwargs): - if context is None: - context = XPathContext(node, **kwargs) - elif kwargs: - context = context.clone() - context.update(**kwargs) - return self.expr.evaluate(node, 1, 1, context) - - @api - def findnode(self, node, context=None, **kwargs): - result = self.find(node, context, **kwargs) - if not xpath.expr.nodesetp(result): - raise XPathTypeError("expression is not a node-set") - if len(result) == 0: - return None - return result[0] - - @api - def findvalue(self, node, context=None, **kwargs): - result = self.find(node, context, **kwargs) - if xpath.expr.nodesetp(result): - if len(result) == 0: - return None - result = xpath.expr.string(result) - return result - - @api - def findvalues(self, node, context=None, **kwargs): - result = self.find(node, context, **kwargs) - if not xpath.expr.nodesetp(result): - raise XPathTypeError("expression is not a node-set") - return [xpath.expr.string_value(x) for x in result] - - def __repr__(self): - return '%s.%s(%s)' % (self.__class__.__module__, - self.__class__.__name__, - repr(str(self.expr))) - - def __str__(self): - return str(self.expr) - -@api -def find(expr, node, **kwargs): - return XPath.get(expr).find(node, **kwargs) - -@api -def findnode(expr, node, **kwargs): - return XPath.get(expr).findnode(node, **kwargs) - -@api -def findvalue(expr, node, **kwargs): - return XPath.get(expr).findvalue(node, **kwargs) - -@api -def findvalues(expr, node, **kwargs): - return XPath.get(expr).findvalues(node, **kwargs) +from main import __all__ +from main import * diff --git a/lib/pyulib/src/ulib/ext/xpath/expr.py b/lib/pyulib/src/ulib/ext/xpath/expr.py index 4199813..a6a1611 100644 --- a/lib/pyulib/src/ulib/ext/xpath/expr.py +++ b/lib/pyulib/src/ulib/ext/xpath/expr.py @@ -6,8 +6,7 @@ import re import xml.dom import weakref -from xpath.exceptions import * -import xpath +from exceptions import * # diff --git a/lib/pyulib/src/ulib/ext/xpath/main.py b/lib/pyulib/src/ulib/ext/xpath/main.py new file mode 100644 index 0000000..bd8d839 --- /dev/null +++ b/lib/pyulib/src/ulib/ext/xpath/main.py @@ -0,0 +1,169 @@ +from exceptions import * +import exceptions +import expr +import parser +import yappsrt + +class MODULE: pass +XPATH = MODULE() +XPATH.exceptions = exceptions +XPATH.expr = expr +XPATH.parser = parser +XPATH.yappsrt = yappsrt + +__all__ = ['find', 'findnode', 'findvalue', 'XPathContext', 'XPath'] +__all__.extend((x for x in dir(XPATH.exceptions) if not x.startswith('_'))) + +def api(f): + """Decorator for functions and methods that are part of the external + module API and that can throw XPathError exceptions. + + The call stack for these exceptions can be very large, and not very + interesting to the user. This decorator rethrows XPathErrors to + trim the stack. + + """ + def api_function(*args, **kwargs): + try: + return f(*args, **kwargs) + except XPathError, e: + raise e + api_function.__name__ = f.__name__ + api_function.__doc__ = f.__doc__ + return api_function + +class XPathContext(object): + def __init__(self, document=None, **kwargs): + self.default_namespace = None + self.namespaces = {} + self.variables = {} + + if document is not None: + if document.nodeType != document.DOCUMENT_NODE: + document = document.ownerDocument + if document.documentElement is not None: + attrs = document.documentElement.attributes + for attr in (attrs.item(i) for i in xrange(attrs.length)): + if attr.name == 'xmlns': + self.default_namespace = attr.value + elif attr.name.startswith('xmlns:'): + self.namespaces[attr.name[6:]] = attr.value + + self.update(**kwargs) + + def clone(self): + dup = XPathContext() + dup.default_namespace = self.default_namespace + dup.namespaces.update(self.namespaces) + dup.variables.update(self.variables) + return dup + + def update(self, default_namespace=None, namespaces=None, + variables=None, **kwargs): + if default_namespace is not None: + self.default_namespace = default_namespace + if namespaces is not None: + self.namespaces = namespaces + if variables is not None: + self.variables = variables + self.variables.update(kwargs) + + @api + def find(self, expr, node, **kwargs): + return XPATH.find(expr, node, context=self, **kwargs) + + @api + def findnode(self, expr, node, **kwargs): + return XPATH.findnode(expr, node, context=self, **kwargs) + + @api + def findvalue(self, expr, node, **kwargs): + return XPATH.findvalue(expr, node, context=self, **kwargs) + + @api + def findvalues(self, expr, node, **kwargs): + return XPATH.findvalues(expr, node, context=self, **kwargs) + +class XPath(): + _max_cache = 100 + _cache = {} + + def __init__(self, expr): + """Init docs. + """ + try: + parser = XPATH.parser.XPath(XPATH.parser.XPathScanner(str(expr))) + self.expr = parser.XPath() + except XPATH.yappsrt.SyntaxError, e: + raise XPathParseError(str(expr), e.pos, e.msg) + + @classmethod + def get(cls, s): + if isinstance(s, cls): + return s + try: + return cls._cache[s] + except KeyError: + if len(cls._cache) > cls._max_cache: + cls._cache.clear() + expr = cls(s) + cls._cache[s] = expr + return expr + + @api + def find(self, node, context=None, **kwargs): + if context is None: + context = XPathContext(node, **kwargs) + elif kwargs: + context = context.clone() + context.update(**kwargs) + return self.expr.evaluate(node, 1, 1, context) + + @api + def findnode(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if not XPATH.expr.nodesetp(result): + raise XPathTypeError("expression is not a node-set") + if len(result) == 0: + return None + return result[0] + + @api + def findvalue(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if XPATH.expr.nodesetp(result): + if len(result) == 0: + return None + result = XPATH.expr.string(result) + return result + + @api + def findvalues(self, node, context=None, **kwargs): + result = self.find(node, context, **kwargs) + if not XPATH.expr.nodesetp(result): + raise XPathTypeError("expression is not a node-set") + return [XPATH.expr.string_value(x) for x in result] + + def __repr__(self): + return '%s.%s(%s)' % (self.__class__.__module__, + self.__class__.__name__, + repr(str(self.expr))) + + def __str__(self): + return str(self.expr) + +@api +def find(expr, node, **kwargs): + return XPath.get(expr).find(node, **kwargs) + +@api +def findnode(expr, node, **kwargs): + return XPath.get(expr).findnode(node, **kwargs) + +@api +def findvalue(expr, node, **kwargs): + return XPath.get(expr).findvalue(node, **kwargs) + +@api +def findvalues(expr, node, **kwargs): + return XPath.get(expr).findvalues(node, **kwargs) diff --git a/lib/pyulib/src/ulib/ext/xpath/parser.py b/lib/pyulib/src/ulib/ext/xpath/parser.py index 504de78..2fd7b4f 100644 --- a/lib/pyulib/src/ulib/ext/xpath/parser.py +++ b/lib/pyulib/src/ulib/ext/xpath/parser.py @@ -1,5 +1,5 @@ -import xpath.expr as X -from xpath.yappsrt import * +import expr as X +from yappsrt import * from string import * From e3cd3cec3f9e6d305d8d610b49a2b42bd1606702 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:10:59 +0400 Subject: [PATCH 31/39] diverses modification de apacheconfig et apache.tools MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - rétablir deux répertoires de templates différents: celui pour debian wheezy- est distinct de celui pour jessie+ - support d'une configuration complète ou partielle - support de la mise à jour de la configuration réseau: configuration complète (interfaces standards et bridge) ou partielle (ajout d'adresse ip) - support de templates pour la création de nouveaux site - améliorer le support des certificats: utiliser ceux qui sont déjà installés le cas échéant. - support de fichiers *rewrite*.rules directement dans le répertoire principal. Les fichiers de RewriteRules/ sont obsolètes. - quickstart pour apacheconfig, afin de simplifier son utilisation dans des scripts - fonction legacy_mkRewriteRules() pour pouvoir traiter les fichiers *rewrite*.rules dans des scripts. --- apacheconfig | 163 ++++- lib/ulib/apache.tools | 659 ++++++++++++++---- .../apacheconfig.d8/README-apacheconfig.txt | 76 ++ .../certsconf/default-certs.conf | 15 + .../templates/apacheconfig.d8/cgi-bin/.udir | 24 + .../apacheconfig.d8/cgi-bin/test.cgi | 6 + .../confs.conf} | 0 .../apacheconfig.d8/confs/ssl-config.conf | 15 + .../templates/apacheconfig.d8/modules.conf | 4 + .../modules/ssl.conf} | 4 +- .../templates/apacheconfig.d8/network.conf | 24 + .../ports.conf} | 8 - .../templates/apacheconfig.d8/rewrite.rules | 2 + lib/ulib/templates/apacheconfig.d8/sites.conf | 4 + .../sites/default.conf} | 0 .../sites/default.ssl.conf} | 0 .../templates/apacheconfig.d8/syspkgs.conf | 9 + .../apacheconfig.d8/templates/SITE-certs.conf | 15 + .../apacheconfig.d8/templates/SITE.conf | 31 + .../templates/SITE.ssl.conf} | 90 +-- .../apacheconfig.d8/templates/SITE/.udir | 24 + .../templates/SITE/favicon.ico | Bin 0 -> 1150 bytes .../apacheconfig.d8/templates/SITE/icon.png | Bin 0 -> 29641 bytes .../apacheconfig.d8/workers.properties | 17 + lib/ulib/templates/apacheconfig.d8/www/.udir | 24 + .../templates/apacheconfig.d8/www/favicon.ico | Bin 0 -> 1150 bytes .../templates/apacheconfig.d8/www/icon.png | Bin 0 -> 29641 bytes .../apacheconfig/RewriteRules/update.sh | 6 +- lib/ulib/templates/apacheconfig/confs.conf | 0 .../templates/apacheconfig/modules/ssl.conf | 103 +++ .../apacheconfig/modules/ssl.conf..d | 103 --- lib/ulib/templates/apacheconfig/ports.conf | 29 + lib/ulib/templates/apacheconfig/ports.conf..d | 29 - .../templates/apacheconfig/sites/default.conf | 51 ++ .../apacheconfig/sites/default.conf..d | 51 -- .../apacheconfig/sites/default.ssl.conf | 190 +++++ .../templates/apacheconfig/www/favicon.ico | Bin 0 -> 1150 bytes lib/ulib/templates/apacheconfig/www/icon.png | Bin 0 -> 29641 bytes mkRewriteRules | 207 +----- 39 files changed, 1395 insertions(+), 588 deletions(-) create mode 100644 lib/ulib/templates/apacheconfig.d8/README-apacheconfig.txt create mode 100644 lib/ulib/templates/apacheconfig.d8/certsconf/default-certs.conf create mode 100644 lib/ulib/templates/apacheconfig.d8/cgi-bin/.udir create mode 100755 lib/ulib/templates/apacheconfig.d8/cgi-bin/test.cgi rename lib/ulib/templates/{apacheconfig/confs.conf..d8 => apacheconfig.d8/confs.conf} (100%) create mode 100644 lib/ulib/templates/apacheconfig.d8/confs/ssl-config.conf create mode 100644 lib/ulib/templates/apacheconfig.d8/modules.conf rename lib/ulib/templates/{apacheconfig/modules/ssl.conf..d8 => apacheconfig.d8/modules/ssl.conf} (99%) create mode 100644 lib/ulib/templates/apacheconfig.d8/network.conf rename lib/ulib/templates/{apacheconfig/ports.conf..d8 => apacheconfig.d8/ports.conf} (65%) create mode 100644 lib/ulib/templates/apacheconfig.d8/rewrite.rules create mode 100644 lib/ulib/templates/apacheconfig.d8/sites.conf rename lib/ulib/templates/{apacheconfig/sites/default.conf..d8 => apacheconfig.d8/sites/default.conf} (100%) rename lib/ulib/templates/{apacheconfig/sites/default.ssl.conf..d8 => apacheconfig.d8/sites/default.ssl.conf} (100%) create mode 100644 lib/ulib/templates/apacheconfig.d8/syspkgs.conf create mode 100644 lib/ulib/templates/apacheconfig.d8/templates/SITE-certs.conf create mode 100644 lib/ulib/templates/apacheconfig.d8/templates/SITE.conf rename lib/ulib/templates/{apacheconfig/sites/default.ssl.conf..d => apacheconfig.d8/templates/SITE.ssl.conf} (69%) create mode 100644 lib/ulib/templates/apacheconfig.d8/templates/SITE/.udir create mode 100644 lib/ulib/templates/apacheconfig.d8/templates/SITE/favicon.ico create mode 100644 lib/ulib/templates/apacheconfig.d8/templates/SITE/icon.png create mode 100644 lib/ulib/templates/apacheconfig.d8/workers.properties create mode 100644 lib/ulib/templates/apacheconfig.d8/www/.udir create mode 100644 lib/ulib/templates/apacheconfig.d8/www/favicon.ico create mode 100644 lib/ulib/templates/apacheconfig.d8/www/icon.png delete mode 100644 lib/ulib/templates/apacheconfig/confs.conf delete mode 100644 lib/ulib/templates/apacheconfig/modules/ssl.conf..d delete mode 100644 lib/ulib/templates/apacheconfig/ports.conf..d delete mode 100644 lib/ulib/templates/apacheconfig/sites/default.conf..d create mode 100644 lib/ulib/templates/apacheconfig/www/favicon.ico create mode 100644 lib/ulib/templates/apacheconfig/www/icon.png diff --git a/apacheconfig b/apacheconfig index ec52ddf..6f824b6 100755 --- a/apacheconfig +++ b/apacheconfig @@ -16,6 +16,19 @@ OPTIONS Créer un nouveau répertoire de configuration pour un hôte -d, --destdir DESTDIR[=$TEMPLATECTL_NAME] Nom du répertoire local de configuration. + -f,--full + --partial + Indiquer respectivement que la configuration est complète ou partielle. + Avec la configuration complète, le serveur peut être complètement + configuré avec tous les fichiers présents. Avec la configuration + partielle, uniquement les informations spécifiques à un service en + particulier sont disponibles. + Cette option est utilisée avec --create. Par défaut, la configuration + est partielle. + Pour le moment, la seule différence est que --full crée un fichier de + configuration nommé .apacheconfig alors que --partial crée un fichier + nommé apacheconfig.conf qui est visible et donc découvrable et éditable + plus facilement -t, --template [OPT] Gérer les fichiers du répertoire local avec templatectl. La valeur de @@ -46,9 +59,12 @@ OPTIONS Lors du déploiement de la configuration, les valeurs des variables dynamiques sont remplacées dans les fichiers destination. Les arguments qui restent sont passés tels quels à apache_autoconf + -N, --network-config + Mettre aussi à jour la configuration réseau. -r, --certsdir CERTSDIR Spécifier le cas échéant le répertoire contenant les certificats à - déployer. Cet argument est requis si le répertoire certsconf/ existe. + déployer. Cet argument est requis si le répertoire certsconf/ existe, + sauf si les certificats sont déjà déployés. --localhosts Créer dans le fichier /etc/hosts tous les noms d'hôte ayant un suffixe @@ -63,24 +79,45 @@ OPTIONS -S, --one-site SITE Ne déployer que le fichier de site spécifié. Cette option est utilisée avec --deploy ou --localhosts et est utile pour le développement et les - tests." + tests. + + -k, --new-site HOST.TLD + Créer une définition pour un nouveau site à partir des fichiers du + répertoires templates/ + -K, --new-site-templatedir TEMPLATEDIR + Spécifier le répertoire source pour les templates de site utilisés par + l'option --new-site. Par défaut, utiliser le répertoire templates/ situé + dans le répertoire de configuration. + Si TEMPLATEDIR est un nom simple sans séparateur de chemin '/' et qu'un + répertoire templates/TEMPLATEDIR existe, alors prendre ce répertoire-là + comme source. + --new-site-force + Avec --new-site, utiliser le nom d'hôte fourni même s'il n'est pas + pleinement qualifié" } action= destdir= -nohideconfig= +nohideconfig=auto templateopt= +FULLCONF= +netconf= aac_certsdir= bits= oneconf= onemodule= onesite= +site_host= +site_templdir= +site_force= args=( --help '$exit_with display_help' -c,--create action=create -d:,--destdir: destdir= --no-hideconfig nohideconfig=1 --hideconfig nohideconfig= + -f,--full FULLCONF=1 + --partial FULLCONF= -t::,--template:: '$set@ templateopt; action=template' --help-template '$templateopt=-help; action=template' -l,--list '$templateopt=l; action=template' @@ -96,15 +133,23 @@ args=( -8,--jessie '$array_add TEMPLATECTL_VARS sysver=jessie' --bits: bits= -u,--update,--deploy action=deploy + -N,--network-config netconf=1 -r:,--certsdir: aac_certsdir= --localhosts action=localhosts -C:,--one-conf: oneconf= -M:,--one-module: onemodule= -S:,--one-site: onesite= + -k:,--new-site: '$action=new-site; set@ site_host' + -K:,--new-site-templatedir: site_templdir= + --new-site-force site_force= ) parse_args "$@"; set -- "${args[@]}" -apacheconfig_loadconf "$destdir" || die +if [ "$nohideconfig" == auto ]; then + [ -n "$FULLCONF" ] && nohideconfig= || nohideconfig=1 +fi + +apacheconfig_loadconf "$destdir" "$nohideconfig" || die apacheconfig_sysinfos "$sysname" "$sysdist" "$sysver" "$bits" ################################################################################ @@ -128,7 +173,7 @@ if [ "$action" == create ]; then ask_yesno "Le fichier $(ppath "$config") sera écrasé. Voulez-vous continuer?" O || die rm -f "$config" || die fi - templatectl -d "$destdir" --config "$config" --no-load-vars -m --write-vars + templatectl -d "$destdir" --config "$config" ${nohideconfig:+--no-hide-config} --no-load-vars -m --write-vars ################################################################################ elif [ "$action" == template ]; then @@ -142,7 +187,9 @@ elif [ "$action" == deploy -o "$action" == localhosts ]; then [ -d "$destdir" ] || die "$destdir: répertoire introuvable" args=( - -d "$destdir" --$action ${aac_certsdir:+-r "$aac_certsdir"} + -d "$destdir" --$action + ${netconf:+--network-config} + ${aac_certsdir:+-r "$aac_certsdir"} ${oneconf:+--one-conf "$oneconf"} ${onemodule:+--one-module "$onemodule"} ${onesite:+--one-site "$onesite"} @@ -160,11 +207,113 @@ elif [ "$action" == deploy -o "$action" == localhosts ]; then apacheconfig_deploy \ "$destdir" "$aac_certsdir" \ "$config" "$oneconf" "$onemodule" "$onesite" \ - "$custom_sysinfos" "$sysname" "$sysdist" "$sysver" "$bits" || die + "$custom_sysinfos" "$sysname" "$sysdist" "$sysver" "$bits" \ + "$netconf" || die eend elif [ "$action" == localhosts ]; then etitle "Mise à jour de /etc/hosts" apacheconfig_deploy_localhosts "$destdir" "$aac_certsdir" "$onesite" || die eend fi + +################################################################################ +elif [ "$action" == new-site ]; then + host="$site_host" + templdir="$site_templdir" + if [[ "$templdir" != */* ]] && [ -d "$destdir/templates/$templdir" ]; then + templdir="$destdir/templates/$templdir" + elif [ -z "$templdir" ]; then + templdir="$destdir/templates" + fi + [ -d "$templdir" ] || die "$templdir: répertoire introuvable" + force="$site_force" + + clrtempl= + ssltempl= + certstempl= + wwwtempl= + array_from_lines templs "$(list_files "$templdir" "*SITE.conf")" + [ ${#templs[*]} -gt 0 ] && clrtempl="${templs[0]}" + array_from_lines templs "$(list_files "$templdir" "*SITE.ssl.conf")" + [ ${#templs[*]} -gt 0 ] && ssltempl="${templs[0]}" + array_from_lines templs "$(list_files "$templdir" "*SITE-certs.conf")" + [ ${#templs[*]} -gt 0 ] && certstempl="${templs[0]}" + array_from_lines templs "$(list_dirs "$templdir" "*SITE")" + [ ${#templs[*]} -gt 0 ] && wwwtempl="${templs[0]}" + + found= + for i in "$clrtempl" "$ssltempl" "$certstempl" "$wwwtempl"; do + [ -n "$i" ] && { found=1; break; } + done + [ -n "$found" ] || die "Aucun template disponible" + + if [ -z "$force" ] && [[ "$host" != *.* ]]; then + die "$host n'est pas un nom d'hôte pleinement qualifié" + fi + + etitle "$host" + hostname="${host%%.*}" + clrconf="${clrtempl/SITE/$hostname}" + sslconf="${ssltempl/SITE/$hostname}" + certsconf="${certstempl/SITE/$hostname}" + wwwdir="${wwwtempl/SITE/$hostname}" + + mkdir -p "$destdir/certsconf" + mkdir -p "$destdir/sites" + + sedscript="\ +s/SITE.TLD/$host/g +s/SITE/$hostname/g" + + if [ -z "$clrtempl" ]; then + : + elif [ ! -f "$templdir/$clrtempl" ]; then + ewarn "Le fichier $(ppath "$templdir/$clrtempl") n'existe pas. La copie ne sera pas complète" + elif [ -f "$destdir/sites/$clrconf" ]; then + ewarn "Le fichier sites/$clrconf existe déjà. Il ne sera pas écrasé." + else + estep "sites/$clrconf" + sed "$sedscript" "$templdir/$clrtempl" >"$destdir/sites/$clrconf" || die + fi + + if [ -z "$ssltempl" ]; then + : + elif [ ! -f "$templdir/$ssltempl" ]; then + ewarn "Le fichier $(ppath "$templdir/$ssltempl") n'existe pas. La copie ne sera pas complète" + elif [ -f "$destdir/sites/$sslconf" ]; then + ewarn "Le fichier sites/$sslconf existe déjà. Il ne sera pas écrasé." + else + estep "sites/$sslconf" + sed "$sedscript" "$templdir/$ssltempl" >"$destdir/sites/$sslconf" || die + fi + + if [ -z "$certstempl" ]; then + : + elif [ ! -f "$templdir/$certstempl" ]; then + ewarn "Le fichier $(ppath "$templdir/$certstempl") n'existe pas. La copie ne sera pas complète" + elif [ -f "$destdir/certsconf/$certsconf" ]; then + ewarn "Le fichier certsconf/$certsconf exite déjà. Il ne sera pas écrasé." + else + estep "certsconf/$certsconf" + sed "$sedscript" "$templdir/$certstempl" >"$destdir/certsconf/$certsconf" || die + fi + + if [ -z "$wwwtempl" ]; then + : + elif [ ! -d "$templdir/$wwwtempl" ]; then + ewarn "Le répertoire $(ppath "$templdir/$wwwtempl") n'existe pas. La copie ne sera pas complète" + elif [ -d "$destdir/$wwwdir" ]; then + ewarn "Le répertoire $wwwdir existe déjà. Il ne sera pas écrasé." + else + estep "$wwwdir" + cpdirnovcs "$templdir/$wwwtempl" "$destdir/$wwwdir" || die + sed -i "$sedscript" "$destdir/$wwwdir/.udir" || die + fi + + eend + + if [ -n "$wwwtempl" ]; then + eimportant "Ne pas oublier le cas échéant de mettre à jour HTDMAPPINGS dans $(ppath "$config") e.g. + HTDMAPPINGS=($wwwdir)" + fi fi diff --git a/lib/ulib/apache.tools b/lib/ulib/apache.tools index 722b6e5..eaa8d8e 100644 --- a/lib/ulib/apache.tools +++ b/lib/ulib/apache.tools @@ -5,28 +5,101 @@ ##@require sysinfos ##@require apache uprovide apache.tools -urequire base sysinfos apache +urequire base sysinfos template apache -function __apache_resolvcert() { +function __apache_rc_destdir() { + [ -z "$3" ] && set_var "${1:-certsdir}" "$(get_APACHESSLCERTSDIR_prefix)" + [ -z "$4" ] && set_var "${2:-keysdir}" "$(get_APACHESSLKEYSDIR_prefix)" +} + +function __apache_rc_loadconf() { [ -n "$__rc_dir" ] || __rc_dir="$(dirname "$__rc_conf")" eval "$( source "$__rc_conf" - set_var_cmd __rc_cert "$cert" - set_var_cmd __rc_key "$key" - set_var_cmd __rc_ca "$ca" + echo_setv __rc_cert "$cert" + echo_setv __rc_key "$key" + echo_setv __rc_ca "$ca" )" [ -n "$__rc_cert" ] && __rc_cert="$(abspath "$__rc_cert" "$__rc_dir")" [ -n "$__rc_key" ] && __rc_key="$(abspath "$__rc_key" "$__rc_dir")" [ -n "$__rc_ca" ] && __rc_ca="$(abspath "$__rc_ca" "$__rc_dir")" } -function __apache_checkvars() { +function __apache_rc_resolveprefix() { + local __prefix __cert __key + local __certsdir="$1" __keysdir="$2" + __apache_rc_destdir __certsdir __keysdir "$__certsdir" "$__keysdir" + + if [ -z "$__rc_cert" ]; then + # si pas de certificat, alors générer un préfixe pour chercher les + # fichiers + setx __prefix=basename "$__rc_conf" + __prefix="${__prefix%certs.conf}" + elif [ ! -f "$__rc_cert" ]; then + # si le fichier source n'existe pas, vérifier s'il existe dans la + # destination + setx __cert=basename "$__rc_cert" + setx __key=basename "$__rc_key" + if [ -f "$__certsdir/$__cert" -a -f "$__keysdir/$__key" ]; then + # parfait, les fichiers existent déjà à l'endroit prévu + : + else + # construire un préfixe avec le nom du fichier + __prefix="$__cert" + if [ "${__prefix%.pem}" != "$__prefix" ]; then + __prefix="${__prefix%.pem}" + elif [ "${__prefix%.crt}" != "$__prefix" ]; then + __prefix="${__prefix%.crt}" + fi + if [ -n "${__prefix//[0-9]/}" ]; then + # enlever le suffixe numérique, uniquement si le nom ne contient + # pas que des chiffres + while [ -n "$__prefix" -a "${__prefix%[0-9]}" != "$__prefix" ]; do + __prefix="${__prefix%[0-9]}" + done + fi + fi + fi + + if [ -n "$__prefix" ]; then + local -a __certs + array_from_lines __certs "$(list_files "$__certsdir" "$__prefix*" | LANG=C sort -r)" + if [ ${#__certs[*]} -gt 0 ]; then + __cert="${__certs[0]}" + __key="${__cert%.*}.key" + __rc_cert="$__rc_dir/$__cert" + __rc_key="$__rc_dir/$__key" + fi + fi +} + +function __apache_rc_checkfiles() { + local destdir="$1"; shift + local file + for file in "$@"; do + [ -n "$file" ] || continue + [ -f "$file" ] && continue + if [ -n "$destdir" -a -f "$destdir/$(basename "$file")" ]; then + [ -z "$__apache_rc_quiet" ] && ewarn "$file: fichier introuvable +Le fichier existant $destdir/$(basename "$file") sera utilisé" + continue + fi + eerror "$file: fichier introuvable" + return 1 + done + return 0 +} + +function __apache_rc_checkvars() { + local __certsdir="$1" __keysdir="$2" + __apache_rc_destdir __certsdir __keysdir "$__certsdir" "$__keysdir" + if [ -n "$__rc_cert" -a -z "$__rc_key" ]; then local __rc_name __rc_ext splitname "$__rc_cert" __rc_name __rc_ext if [ "$__rc_ext" == "crt" -o "$__rc_ext" == "pem" ]; then __rc_key="$__rc_name.key" - enote "La clé privée n'a pas été spécifiée. La valeur $(ppath "$__rc_key") sera utilisée" + [ -z "$__apache_rc_quiet" ] && enote "La clé privée n'a pas été spécifiée. La valeur $(ppath "$__rc_key") sera utilisée" else eerror "Impossible de trouver la clé privée correspondant au certificat $(ppath "$__rc_cert")" return 1 @@ -36,30 +109,31 @@ function __apache_checkvars() { eerror "Vous devez spécifier le certificat à installer" return 1 elif [ -z "$__rc_cert" ]; then - eattention "Seul le certificat autorité a été spécifié." + [ -z "$__apache_rc_quiet" ] && eattention "Seul le certificat autorité a été spécifié." elif [ -z "$__rc_ca" ]; then - ewarn "Aucun certificat autorité n'a pas été spécifié. Cela ne peut marcher que si le certificat est autosigné" + [ -z "$__apache_rc_quiet" ] && ewarn "Aucun certificat autorité n'a pas été spécifié. Cela ne peut marcher que si le certificat est autosigné" fi - local i - for i in "$__rc_cert" "$__rc_key" "$__rc_ca"; do - [ -n "$i" ] || continue - [ -f "$i" ] || { - eerror "$i: Fichier introuvable" - return 1 - } - done + __apache_rc_checkfiles "$__certsdir" "$__rc_ca" "$__rc_cert" || return 1 + __apache_rc_checkfiles "$__keysdir" "$__rc_key" || return 1 + return 0 } function apache_resolvecert() { # Calculer l'emplacement des certificats correspondant aux arguments $1 et # $2 (qui correspondent aux options --conf et --dir de apache_addcert()), # puis initialiser les variables $3(=cert), $4(=key) et $5(=ca) + # Si ces valeurs sont déjà calculées, on peut fournir $6=certsdir et + # $7=keysdir local __rc_conf="$1" __rc_dir="$2" local __rc_cert __rc_key __rc_ca - __apache_resolvcert - __apache_checkvars || return 1 + local __certsdir="$6" __keysdir="$7" + __apache_rc_destdir __certsdir __keysdir "$__certsdir" "$__keysdir" + + __apache_rc_loadconf + __apache_rc_resolveprefix "$__certsdir" "$__keysdir" + __apache_rc_checkvars "$__certsdir" "$__keysdir" || return 1 set_var "${3:-cert}" "$__rc_cert" set_var "${4:-key}" "$__rc_key" set_var "${5:-ca}" "$__rc_ca" @@ -93,29 +167,33 @@ OPTIONS eval "$(utools_local)" local action=install - local certsconf certsdir cert key ca + local certsconf certssrcdir cert key ca local __out_cert __out_key __out_ca parse_opts "${PRETTYOPTS[@]}" \ --help '$exit_with __apache_addcert_display_help' \ -C:,--conf: certsconf= \ - -d:,--dir: certsdir= \ + -d:,--dir: certssrcdir= \ --out-cert: '$set@ __out_cert; action=dump' \ --out-key: '$set@ __out_key; action=dump' \ --out-ca: '$set@ __out_ca; action=dump' \ @ args -- "$@" && set -- "${args[@]}" || die "$args" + local certsdir keysdir + __apache_rc_destdir certsdir keysdir + local __rc_conf __rc_dir local __rc_cert __rc_key __rc_ca if [ -n "$certsconf" ]; then __rc_conf="$certsconf" - __rc_dir="$certsdir" - __apache_resolvconf - __apache_checkvars || return 1 + __rc_dir="$certssrcdir" + __apache_rc_loadconf + __apache_rc_resolveprefix "$certsdir" "$keysdir" + __apache_rc_checkvars "$certsdir" "$keysdir" || return 1 else __rc_cert="$1" __rc_key="$2" __rc_ca="$3" - __apache_checkvars || return 1 + __apache_rc_checkvars "$certsdir" "$keysdir" || return 1 fi cert="$__rc_cert" key="$__rc_key" @@ -129,9 +207,7 @@ OPTIONS ask_yesno "Voulez-vous continuer?" O || return 1 urequire install - etitle "Installation des certificats" - certsdir="$(get_APACHESSLCERTSDIR_prefix)" - keysdir="$(get_APACHESSLKEYSDIR_prefix)" + etitled "Copie des fichiers" if [ ! -d "$certsdir" ]; then mkdir -p "$certsdir" || return 1 chmod 755 "$certsdir" || return 1 @@ -140,38 +216,36 @@ OPTIONS mkdir -p "$keysdir" || return 1 chmod 710 "$keysdir" || return 1 fi - if [ -n "$cert" ]; then - copy_replace "$cert" "$certsdir" || return 1 - chmod 644 "$certsdir/$(basename "$cert")" || return 1 - copy_replace "$key" "$keysdir" || return 1 - chmod 640 "$keysdir/$(basename "$key")" || return 1 + if [ -n "$cert" -a -f "$cert" ]; then + if copy_update "$cert" "$certsdir"; then + chmod 644 "$certsdir/$(basename "$cert")" || return 1 + fi + if copy_update "$key" "$keysdir"; then + chmod 640 "$keysdir/$(basename "$key")" || return 1 + fi fi - if [ -n "$ca" ]; then - copy_replace "$ca" "$certsdir" || return 1 - chmod 644 "$certsdir/$(basename "$ca")" || return 1 + if [ -n "$ca" -a -f "$ca" ]; then + if copy_update "$ca" "$certsdir"; then + chmod 644 "$certsdir/$(basename "$ca")" || return 1 + fi fi eend return 0 } -__APACHE_AUTOCONF_SUFFIXES=(d8 d) -__APACHE_AUTOCONF_SUFFIX_d8=(-d debian -v jessie+) -__APACHE_AUTOCONF_SUFFIX_d=(-d debian) -function __apache_autoconf_check_suffix() { - array_contains __APACHE_AUTOCONF_SUFFIXES "$1" || return 1 - local sysinfos="__APACHE_AUTOCONF_SUFFIX_${1}[@]" - check_sysinfos --vars sysname sysdist sysver bits "${!sysinfos}" -} -function __apache_autoconf_filter_suffix_files() { - grep -vF .. -} function __apache_autoconf_setup() { if ! check_sysinfos --vars sysname sysdist sysver bits -s linux64 linux32 linux -d debian; then - eerror "apache_autoconf n'est supporté que sur Debian linux" + eerror "$(get_sysinfos_desc): système non supporté. debian linux est requis" return 1 fi - urequire install + urequire debian install + if [ -z "$__apache_autoconf_no_require_apache" ]; then + pkg_check apache2 || { + eerror "apache2 non installé. impossible de continuer" + return 1 + } + fi compute_apache_prefixes return 0 } @@ -193,24 +267,8 @@ function __apache_autoconf_fillcopy() { # script sed $FILLSCRIPT. Le fichier temporaire $FILLTEMP est utilisé pour # le remplacement des valeurs. $3 contient le cas échéant des commandes sed # supplémentaires - # Si des fichiers suffixes existent, ne faire la copie que si un fichier - # approprié correspondant au système courant est trouvé local src="$1" dest="$2" sedscript="$3" perms="${4:-go+rX}" - # vérifier les fichiers suffixe - local suffix have_suffix found_suffix - for suffix in "${__APACHE_AUTOCONF_SUFFIXES[@]}"; do - if [ -f "$src..$suffix" ]; then - have_suffix=1 - if __apache_autoconf_check_suffix "$suffix"; then - found_suffix=1 - src="$src..$suffix" - break - fi - fi - done - [ -n "$have_suffix" -a -z "$found_suffix" ] && return 1 - # valeurs à remplacer dans le fichier local var found_var for var in "${FILLVARS[@]}"; do @@ -225,14 +283,16 @@ $sedscript" <"$src" >"$FILLTEMP" src="$FILLTEMP" fi - copy_update "$src" "$dest" "$perms" + copy_update "$src" "$dest" "$perms" && return + estepn "$(basename -- "$dest")" + return 1 } __APACHE_AUTOCONF_HELP="\ --confdir CONFDIR Spécifier l'emplacement des fichiers de configuration apache ainsi que des - fichiers 'confs.conf', 'modules.conf' et 'sites.conf'. Par défaut, prendre - le répertoire local DESTDIR. + fichiers 'syspkgs.conf', 'confs.conf', 'modules.conf' et 'sites.conf'. Par + défaut, prendre le répertoire local DESTDIR. --confsdir CONFSDIR Spécifier l'emplacement des fichiers des configuration. Par défaut, utiliser DESTDIR/confs si ce répertoire existe. @@ -264,7 +324,7 @@ function apache_autoconf() { local autoconfdir certsdir confdir confsdir oneconf modulesdir onemodule local sitesdir onesite cgibindir wwwdir certsconfdir rrdir onecms local sysname sysdist sysver bits - local destconfsdir a2xconf + local netconf destconfsdir a2xconf local restart=1 parse_opts "${PRETTYOPTS[@]}" \ --help '$exit_with __display_apache_autoconf_help' \ @@ -288,6 +348,7 @@ function apache_autoconf() { -7,--wheezy sysver=wheezy \ -8,--jessie sysver=jessie \ --bits: bits= \ + --network-config netconf=1 \ @ args -- "$@" && set -- "${args[@]}" || die "$args" if [ -n "$sysname" -o -n "$sysdist" -o -n "$sysver" ]; then @@ -298,13 +359,13 @@ function apache_autoconf() { sysver=("${MYSYSVER[@]}") bits="$MYBITS" fi - __apache_autoconf_setup || return 1 - if __apache_autoconf_check_suffix d8; then + __apache_autoconf_no_require_apache= __apache_autoconf_setup || return 1 + if check_sysinfos --vars sysname sysdist sysver bits -d debian -v jessie+; then confdefault=000-default.conf confdefaultssl=default-ssl.conf destconfsdir="$APACHECONFDIR/conf-available" a2xconf=1 - elif __apache_autoconf_check_suffix d; then + elif check_sysinfos --vars sysname sysdist sysver bits -d debian; then confdefault=default confdefaultssl=default-ssl destconfsdir="$APACHECONFDIR/conf.d" @@ -340,6 +401,19 @@ function apache_autoconf() { local -a FILLVARS; local FILLSCRIPT FILLTEMP __apache_autoconf_fillxxx "$@" + # Installation des packages système + if [ -f "$confdir/syspkgs.conf" ]; then + local -a syspkgs + local syspkg + array_from_lines syspkgs "$(<"$confdir/syspkgs.conf" filter_conf)" + if ! pkg_check "${syspkgs[@]}"; then + etitle "Installation de paquets système" + estep "${syspkgs[@]}" + pkg_install "${syspkgs[@]}" || return 1 + eend + fi + fi + # Copie des certificats local modified rehash conf if [ -d "$certsconfdir" ]; then @@ -350,17 +424,10 @@ function apache_autoconf() { array_addu FILLVARS ca etitle "Installation des certificats" + [ -n "$certsdir" -a ! -d "$certsdir" ] && ewarn "$certsdir: répertoire invalide" array_lsfiles certsconfs "$certsconfdir" "*.conf" for certsconf in "${certsconfs[@]}"; do - if [ -z "$certsdir" ]; then - eerror "CERTSDIR est requis si --certsconfdir est spécifié" - return 1 - elif [ ! -d "$certsdir" ]; then - eerror "$certsdir: répertoire invalide" - return 1 - fi - apache_resolvecert "$certsconf" "$certsdir" cert key ca || return 1 - apache_addcert -y "$cert" "$key" "$ca" + apache_addcert -y -C "$certsconf" -d "$certsdir" "$cert" "$key" "$ca" || return 1 modified=1 done array_lsfiles certspems "$certsconfdir" "*.crt" "*.pem" @@ -378,11 +445,9 @@ function apache_autoconf() { local -a confs local conf etitle "Installation des configurations" - array_from_lines confs "$(list_files "$confsdir" "*.conf" | __apache_autoconf_filter_suffix_files)" + array_from_lines confs "$(list_files "$confsdir" "*.conf")" for conf in "${confs[@]}"; do [ -z "$oneconf" -o "$conf" == "$oneconf" ] || continue - - estep "$conf" __apache_autoconf_fillcopy \ "$confsdir/$conf" \ "$destconfsdir/$conf" && modified=1 @@ -395,11 +460,9 @@ function apache_autoconf() { local -a confs local conf etitle "Installation des configurations des modules" - array_from_lines confs "$(list_files "$modulesdir" "*.conf" | __apache_autoconf_filter_suffix_files)" + array_from_lines confs "$(list_files "$modulesdir" "*.conf")" for conf in "${confs[@]}"; do [ -z "$onemodule" -o "$conf" == "$onemodule" ] || continue - - estep "$conf" __apache_autoconf_fillcopy \ "$modulesdir/$conf" \ "$APACHECONFDIR/mods-available/$conf" && modified=1 @@ -409,12 +472,12 @@ function apache_autoconf() { # Règles de réécriture if [ -d "$rrdir" -a -z "$onecms" ]; then + # legacy... remplacé par des fichiers de règles directement dans le répertoire de configuration local -a confs local conf etitle "Installation des règles de réécriture" array_from_lines confs "$(list_files "$rrdir" "RewriteRules*.conf")" for conf in "${confs[@]}"; do - estep "$conf" __apache_autoconf_fillcopy \ "$rrdir/$conf" \ "$APACHECONFDIR/$conf" && modified=1 @@ -426,9 +489,9 @@ function apache_autoconf() { local -a enablesites disablesites if [ -d "$sitesdir" -a \( -z "$onecms" -o -n "$onesite" \) ]; then local -a confs - local conf confname destconf certsconf + local conf confname destconf certsconf sedscript copied etitle "Installation des sites" - array_from_lines confs "$(list_files "$sitesdir" "*.conf" | __apache_autoconf_filter_suffix_files)" + array_from_lines confs "$(list_files "$sitesdir" "*.conf")" for confname in "${confs[@]}"; do conf="$sitesdir/$confname" [ -z "$onesite" -o "$confname" == "$onesite" ] || continue @@ -449,27 +512,44 @@ function apache_autoconf() { *) destconf="$confname";; esac + copied= if [ -n "$certsconf" ]; then certsconf="$certsconfdir/$certsconf" if [ -f "$certsconf" ]; then - apache_resolvecert "$certsconf" "$certsdir" cert key ca || return 1 - __apache_autoconf_fillcopy \ - "$conf" \ - "$APACHEAVSITESDIR/$destconf" "\ + __apache_rc_quiet=1 apache_resolvecert "$certsconf" "$certsdir" cert key ca || return 1 + if [ -n "$cert" -a -n "$key" ]; then + sedscript="\ s#@@cert@@#$APACHESSLCERTSDIR/$(basename "$cert")#g -s#@@key@@#$APACHESSLKEYSDIR/$(basename "$key")#g -s#@@ca@@#$APACHESSLCERTSDIR/$(basename "$ca")#g -" +s#@@key@@#$APACHESSLKEYSDIR/$(basename "$key")#g" + if [ -n "$ca" ]; then + sedscript="$sedscript +s#@@ca@@#$APACHESSLCERTSDIR/$(basename "$ca")#g" + else + sedscript="$sedscript +/@@ca@@/s/^/#/g" + fi + __apache_autoconf_fillcopy \ + "$conf" \ + "$APACHEAVSITESDIR/$destconf" "$sedscript" + copied=1 + else + eerror "$(ppath "$certsconf"): définition des certificats introuvable +Le fichier de configuration $confname a été ignoré" + fi else - eerror "$(ppath "$certsconf"): fichier introuvable. Il a été ignoré" + eerror "$(ppath "$certsconf"): fichier introuvable +Le fichier de configuration $confname a été ignoré" fi else __apache_autoconf_fillcopy \ "$conf" \ "$APACHEAVSITESDIR/$destconf" + copied=1 + fi + if [ -n "$copied" ]; then + enablesites=("${enablesites[@]}" "$destconf") + modified=1 fi - enablesites=("${enablesites[@]}" "$destconf") - modified=1 done eend fi @@ -478,16 +558,28 @@ s#@@ca@@#$APACHESSLCERTSDIR/$(basename "$ca")#g if [ -d "$confdir" -a -z "$onecms" ]; then local -a confs local conf + etitle "Configuration de base" - array_add ignores confs.conf modules.conf sites.conf - array_from_lines confs "$(list_files "$confdir" | __apache_autoconf_filter_suffix_files)" + array_add ignores syspkgs.conf confs.conf modules.conf sites.conf network.conf + array_from_lines confs "$(list_files "$confdir")" for conf in "${confs[@]}"; do array_contains ignores "$conf" && continue - estep "$conf" __apache_autoconf_fillcopy \ "$confdir/$conf" \ "$APACHECONFDIR/$conf" && modified=1 done + + array_from_lines confs "$(list_files "$confdir" "*rewrite*.rules")" + if [ ${#confs[*]} -gt 0 ]; then + etitle "Règles de réécriture" + for conf in "${confs[@]}"; do + [ -f "$APACHECONFDIR/$conf" ] || continue + estep "$conf" + legacy_mkRewriteRules "$APACHECONFDIR/$conf" && modified=1 + done + eend + fi + if [ -f "$confdir/confs.conf" -a -n "$a2xconf" ]; then local -a confs local conf @@ -564,9 +656,30 @@ s#@@ca@@#$APACHESSLCERTSDIR/$(basename "$ca")#g fi # Contenu web - if [ -d "$wwwdir" -a -z "$onecms" ]; then - etitle "Installation des fichiers du serveur web" - cpdirnovcs "$wwwdir" "$HTDOCSDIR" + if [ -z "$onecms" ]; then + etitled "Installation des fichiers du serveur web" + if is_defined HTDMAPPINGS; then + local htdmapping src dest + for htdmapping in "${HTDMAPPINGS[@]}"; do + splitpair "$htdmapping" dest src + [ -n "$dest" ] || dest=html + case "$dest" in + html) [ -n "$src" ] || src=www;; + *) [ -n "$src" ] || src="$dest";; + esac + withpath "$src" || src="$confdir/$src" + withpath "$dest" || dest="$HTDOCSBASE/$dest" + estep "$src --> $dest" + cpdirnovcs "$src" "$dest" + # par défaut, le propriétaire est root. est-ce nécessaire? + #chown -R www-data: "$dest" + done + elif [ -d "$wwwdir" ]; then + estep "$wwwdir --> $HTDOCSDIR" + cpdirnovcs "$wwwdir" "$HTDOCSDIR" + # par défaut, le propriétaire est root. est-ce nécessaire? + #chown -R www-data: "$HTDOCSDIR" + fi eend fi @@ -587,6 +700,30 @@ s#@@ca@@#$APACHESSLCERTSDIR/$(basename "$ca")#g eend fi + # Mettre à jour la configuration réseau + if [ -z "$onecms" -a -n "$netconf" -a -f "$confdir/network.conf" ]; then + local -a ips brs; local host etc_networks + eval "$( + source "$confdir/network.conf" + set_array_cmd ips + set_array_cmd brs + echo_setv host "$host" + echo_setv etc_networks "$etc_networks" + )" + etitled "Vérification de la configuration du réseau" + if [ -n "$FULLCONF" ]; then + if [ ${#ips[*]} -gt 0 -o ${#brs[*]} -gt 0 -o -n "$hosts" ]; then + network_config "$host" ips brs && modified=1 + fi + [ -n "$etc_networks" ] && network_update_etc_networks "$etc_networks" + else + if [ ${#ips[*]} -gt 0 ]; then + network_config_partial ips && modified=1 + fi + fi + eend + fi + if [ -n "$modified" ]; then [ -n "$rehash" ] && elinedots "Hashage des certificats" c_rehash if [ -n "$restart" ]; then @@ -604,7 +741,7 @@ function apache_autoconf_localhosts() { --one-site: onesite= \ @ args -- "$@" && set -- "${args[@]}" || die "$args" - __apache_autoconf_setup || return 1 + __apache_autoconf_no_require_apache=1 __apache_autoconf_setup || return 1 # Configuration autoconfdir="$1"; shift @@ -711,27 +848,48 @@ function __template_updatef_dhost() { [ -n "$ips" ] || __template_set_var ips "" } -# toujours placer une variable dépendante AVANT la variable maitre +# syntaxe: var[:depvars,...][=desc] APACHECONFIG_TEMPLATE_STATIC_VARS=( - hostname aliases host - certsdir caname + host:hostname,aliases="hôte pour lequel ce template a été créé. +# les variables hostname et aliases sont automatiquement générées. +# utiliser @@dhost@@ pour déployer dynamiquement avec le nom d'hôte courant." + certsdir="répertoire par défaut contenant les certificats à déployer" + caname="nom de l'autorité par défaut" ) APACHECONFIG_TEMPLATE_DYNAMIC_VARS=( - ips_namevirtualhosts ips_listens ips - dhostname daliases dhost - admin configdir + ips:ips_namevirtualhosts,ips_listens="liste d'adresses de la forme ip[:port], séparées par un espace. +# ces adresses sont celles sur lesquelles apache doit écouter. ce paramètre n'a +# de sens que sur squeeze. en effet, la configuration par défaut sur jessie rend +# ce paramétrage inutile." + dhost:dhostname,daliases="hôte pour lequel les fichiers doivent être déployés. +# les variables dhostname et daliases sont automatiquement générées. +# cette variable n'a besoin d'être modifiée que si host=@@dhost@@ ci-dessous" + admin="mail de l'administrateur du serveur" + configdir="répertoire dans lequel le template a été généré" +) +APACHECONFIG_TEMPLATE_NOWRITE_VARS=(configdir) +APACHECONFIG_TEMPLATE_USER_VARS=( + FULLCONF="Est-on en mode configuration complète?" + HTDMAPPINGS="Mapping des répertoires destination dans /var/www vers le répertoire local, e.g. html:www" ) -APACHECONFIG_TEMPLATE_NOWRITE_VARS=(hostname aliases dhostname daliases configdir) +function __apacheconfig_initsrcdirs() { + if check_sysinfos "$@" -d debian -v jessie+; then + TEMPLATECTL_SRCDIRS=(apacheconfig.d8) + else + TEMPLATECTL_SRCDIRS=(apacheconfig) + fi +} function apacheconfig_initvars() { DEFAULT_ADMIN=supervision-gdrsi@listes.univ-reunion.fr DEFAULT_CERTSDIR=1507-renater DEFAULT_CANAME=1507-DigiCertCA.crt set_defaults apacheconfig - TEMPLATE_STATIC_VARS=("${APACHECONFIG_TEMPLATE_STATIC_VARS[@]}") - TEMPLATE_DYNAMIC_VARS=("${APACHECONFIG_TEMPLATE_DYNAMIC_VARS[@]}") TEMPLATE_NOWRITE_VARS=("${APACHECONFIG_TEMPLATE_NOWRITE_VARS[@]}") + template_build_vars TEMPLATE_STATIC_VARS TEMPLATE_NOWRITE_VARS "${APACHECONFIG_TEMPLATE_STATIC_VARS[@]}" + template_build_vars TEMPLATE_DYNAMIC_VARS TEMPLATE_NOWRITE_VARS "${APACHECONFIG_TEMPLATE_DYNAMIC_VARS[@]}" + template_build_vars TEMPLATE_USER_VARS "" "${APACHECONFIG_TEMPLATE_USER_VARS[@]}" __TEMPLATE_DEFAULTF_host=__template_defaultf_host __TEMPLATE_UPDATEF_host=__template_updatef_host __TEMPLATE_DEFAULTF_ips=__template_defaultf_ips @@ -740,7 +898,7 @@ function apacheconfig_initvars() { __TEMPLATE_UPDATEF_dhost=__template_updatef_dhost TEMPLATECTL_NAME=apacheconfig - TEMPLATECTL_SRCDIRS=(apacheconfig) + __apacheconfig_initsrcdirs TEMPLATECTL_CONFIG="$TEMPLATECTL_NAME" TEMPLATECTL_DEFAULTS=( admin="$DEFAULT_ADMIN" @@ -751,11 +909,14 @@ function apacheconfig_initvars() { } function apacheconfig_loadconf() { - local config modified - local destdir="$1" autocreate + local config modified autocreate + local destdir="$1" nohideconfig="$2" + + # valeurs par défaut + is_defined HTDMAPPINGS || HTDMAPPINGS=(html:www) __template_set_destdir destdir autocreate "$TEMPLATECTL_NAME" || return 1 - setx config=templatectl_config "$destdir" + setx config=templatectl_config "$destdir" ${nohideconfig:+nohideconfig} modified= templatectl_loadvars "$config" && modified=1 @@ -779,7 +940,8 @@ function apacheconfig_sysinfos() { __template_set_var sysname "$sysname" __template_set_var sysdist "$sysdist" __template_set_var sysver "$sysver" - #check_sysinfos --vars sysname sysdist sysver bits "${templatectl_suffix[@]} + # mettre à jour la source en fonction du système cible + __apacheconfig_initsrcdirs --vars sysname sysdist sysver bits upvars sysname "$sysname" sysdist "$sysdist" sysver "$sysver" bits "$bits" \ custom_sysinfos "$custom_sysinfos" @@ -789,6 +951,7 @@ function apacheconfig_deploy() { local destdir="$1" certsdir="$2"; shift; shift local config="$1" oneconf="$2" onemodule="$3"; onesite="$4"; shift; shift; shift; shift local custom_sysinfos="$1" sysname="$2" sysdist="$3" sysver="$4" bits="$5"; shift; shift; shift; shift; shift + local netconf="$1"; shift local -a args args=(--ignore "$(basename -- "$config")") @@ -796,6 +959,7 @@ function apacheconfig_deploy() { [ -n "$onemodule" ] && array_add args --one-module "$(basename -- "$onemodule")" [ -n "$onesite" ] && array_add args --one-site "$(basename -- "$onesite")" [ -n "$custom_sysinfos" ] && array_add args --sysname "$sysname" --sysdist "$sysdist" --sysver "$sysver" --bits "$bits" + [ -n "$netconf" ] && array_add args --network-config array_add args "$destdir" "$certsdir" for __name in "${TEMPLATE_DYNAMIC_VARS[@]}"; do array_add args "$__name=${!__name}" @@ -803,6 +967,21 @@ function apacheconfig_deploy() { apache_autoconf "${args[@]}" "$@" } +function apacheconfig_qs() { + # fonction pour simplifier l'utilisation de apacheconfig_deploy pour un + # répertoire spécifique + # $1=destdir $2=certsdir $3=netconf + local destdir="$1" certsdir="$2" netconf="$3" + local config modified destdir autocreate + apacheconfig_initvars + apacheconfig_loadconf "$1" + apacheconfig_deploy \ + "$destdir" "$2" \ + "$config" "" "" "" \ + "" "" "" "" "" \ + "$3" +} + function apacheconfig_localhosts() { local destdir="$1" certsdir="$2"; shift; shift local onesite="$1"; shift @@ -815,3 +994,241 @@ function apacheconfig_localhosts() { done apache_autoconf_localhosts "${args[@]}" "$@" } + +function __mrr_joinurl() { + # joindre chaque élément de $1..@ par /, en évitant les slashes en double + local i url + for i in "$@"; do + [ -n "$i" ] || continue + if [ -n "$url" ]; then + url="${url%/}/${i#/}" + else + url="$i" + fi + done + [ -n "$url" ] && echo "$url" +} +function __mrr_has_proxy() { + # vérifier que les options $1 contiennent 'P' + local -a options + array_split options "$1" "," + array_contains options P +} +function legacy_mkRewriteRules() { + # $1=infile, $2=thishost, $3=outfile, $4=htmlfile, $5=proxy_enabled? + local infile="$1" thishost="$2" outfile="$3" htmlfile="$4" proxy_enabled="$5" + local -a rules; local rule prefix index done current + local tmpinfile tmpoutfile + local src dest host suffix options prot proxy_acls usrc trail noslash proxy_url proxy_use + + if [ -z "$infile" -o "$infile" == - ]; then + infile=/dev/stdin + elif [ -z "$outfile" ]; then + local outdir="$(dirname -- "$infile")" + outfile="$(basename -- "$infile")" + if [[ "$outfile" == *rewrite*.rules ]]; then + outfile="${outfile/rewrite/RewriteRules}" + outfile="${outfile/.rules/.conf}" + else + outfile="$outfile-RewriteRules.conf" + fi + outfile="$outdir/$outfile" + fi + [ -n "$outfile" -a "$outfile" != - ] || outfile=/dev/stdout + + if [ -z "$thishost" -o -z "$proxy_enabled" ]; then + # le cas échéant, lire les paramètres manquant depuis le fichier + if [ "$infile" == /dev/stdin ]; then + ac_set_tmpfile tmpinfile + cat >"$tmpinfile" + infile="$tmpinfile" + fi + eval "$(awkrun -f <"$infile" ' +/^[^#]/ { exit 0 } +/^#+ *host *=/ { sub(/^#+ *host *= */, ""); sub(/ *$/, ""); print "thishost=" qval($0); next } +/^#+ *enable_proxy *=/ { sub(/^#+ *enable_proxy *= */, ""); sub(/ *$/, ""); print "proxy_enabled=" qval($0); next } +')" + fi + [ -n "$thishost" ] || thishost="$(myhost)" + normyesval proxy_enabled + + if [ -n "$htmlfile" ]; then + echo ' + + + + +'"$thishost + + +

$thishost

+
+ +' >>"$htmlfile" + fi + + [ -n "$tmpinfile" ] && ac_clean "$tmpinfile" + ac_clean "$tmpoutfile" + [ -n "$modified" ] +} diff --git a/lib/ulib/templates/apacheconfig.d8/README-apacheconfig.txt b/lib/ulib/templates/apacheconfig.d8/README-apacheconfig.txt new file mode 100644 index 0000000..bd574ef --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/README-apacheconfig.txt @@ -0,0 +1,76 @@ +# -*- coding: utf-8 mode: text -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +Ce répertoire peut contenir les fichiers et répertoires suivants, qui sont tous +optionnels: + +confs.conf + Liste des configurations qu'il faut activer. Si un fichier de configuration + existe mais n'est pas mentionnée dans ce fichier, ou si ce fichier n'existe + pas, aucune modification n'est effectuée. Ce fichier contient une liste de + ligne de configuration. + Si une configuration est de la forme -conf, elle est désactivée. Si une + configuration est de la forme +conf, elle est activée. Cette syntaxe permet + de supporter les configurations dont le nom commencerait par '-' + IMPORTANT: Ce fichier n'est supporté qu'à partir de debian jessie. + +modules.conf + Liste des modules qu'il faut activer. Si un module existe mais n'est pas + mentionné dans ce fichier, ou si ce fichier n'existe pas, aucune + modification n'est effectuée. + Si un module est de la forme -module, il est désactivé. Si un module est de + la forme +module, il est activé. Cette syntaxe permet de supporter les + modules dont le nom commencerait par '-' + +sites.conf + Liste des sites qu'il faut activer. Si ce fichier n'existe pas, tous les + sites existant sont activés. Si un site existe mais ne figure pas dans ce + fichier, il est désactivé. + +confs/ + Répertoire des configurations à installer. Les fichiers de ce répertoire + sont de la forme CONF.conf et sont installés dans le répertoire + /etc/apache2/conf-available. Il faut mentionner la configuration dans le + fichier confs.conf pour l'activer. + IMPORTANT: Ce répertoire n'est supporté qu'à partir de debian jessie. + +modules/ + Répertoire des configurations de modules à installer. Les fichiers de ce + répertoire sont de la forme MODULE.conf et sont installés dans le répertoire + /etc/apache2/mods-available. Il faut mentioner le module dans le fichier + modules.conf pour l'activer. + +sites/ + Répertoire des sites à installer. Les fichiers de ce répertoire sont de la + forme SITE.conf pour les sites écoutant en clair, et SITE.ssl.conf pour les + sites écoutant en https. + Pour chaque site SITE.ssl.conf, un fichier SITE-certs.conf doit exister dans + certsconf/. Pour chaque fichier SITE.ssl.conf, les balises @@ca@@, @@cert@@ + et @@key@@ sont remplacés par les valeurs des variables ca, cert et key + définies dans le fichier correspondant SITE-certs.conf + +cgi-bin/ + Répertoire des scripts cgi + +www/ + Répertoire des fichiers du serveur web + +certsconf/ + Répertoire qui contient la configuration pour les certificats à installer. + Les fichiers de ce répertoire sont de la forme SITE-certs.conf et chacun + d'eux correspond à un fichier SITE.ssl.conf dans sites/ + +RewriteRules/ + Répertoire qui contient la configuration de réécriture. Tous les fichiers + RewriteRules*.conf de ce répertoire sont copiés dans /etc/apache2 + +Tous les autres fichiers sont copiés tels quels dans /etc/apache2. Notamment, +apache2.conf est le fichier de configuration principal d'apache et ports.conf le +fichier de configuration des ports d'écoute. + +## Configuration TLS + +Le site https://mozilla.github.io/server-side-tls/ssl-config-generator/ contient +des informations sur la façon de configurer ssl côté serveur pour la sécurité et +les navigateurs modernes + +Voir les détails sur https://wiki.mozilla.org/Security/Server_Side_TLS diff --git a/lib/ulib/templates/apacheconfig.d8/certsconf/default-certs.conf b/lib/ulib/templates/apacheconfig.d8/certsconf/default-certs.conf new file mode 100644 index 0000000..2ca6ace --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/certsconf/default-certs.conf @@ -0,0 +1,15 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Cette variable est utilisée par la fonction refcerts() du script runs. C'est +# le nom d'un répertoire à chercher dans RUNSMODULESPATH qui contient les +# certificats à installer sur le serveur. +certsdir=@@certsdir@@ + +# Fichier contenant les certificats racines qui valident le certificat à +# installer, ainsi que les certificats qui sont rencontrés dans le dialogue avec +# d'autres serveurs web +ca=@@caname@@ + +# Certificat et clé privée à installer +cert= +key= diff --git a/lib/ulib/templates/apacheconfig.d8/cgi-bin/.udir b/lib/ulib/templates/apacheconfig.d8/cgi-bin/.udir new file mode 100644 index 0000000..eb74a9a --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/cgi-bin/.udir @@ -0,0 +1,24 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="Fichiers à déployer sur @@host@@ dans le répertoire des cgi-bins" +udir_note="Il est possible de déployer les modifications dans ce répertoire avec 'uinst -y'" +udir_types=(uinst:rsync) +uinc=release +uinc_options=() +uinc_args=() +configure_variables=(dest) +configure_dest_for=() +config_scripts=() +install_profiles=false +workdir_rsync_options=() +workdir_excludes=() +workdir_includes=() +copy_files=true +rsync_options=() +destdir=root@@@host@@:CGIBINDIR +srcdir=. +files=() +owner=root: +modes=(u=rwX,g=rX,o=rX) +root_scripts=() diff --git a/lib/ulib/templates/apacheconfig.d8/cgi-bin/test.cgi b/lib/ulib/templates/apacheconfig.d8/cgi-bin/test.cgi new file mode 100755 index 0000000..dd4bf37 --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/cgi-bin/test.cgi @@ -0,0 +1,6 @@ +#!/bin/bash +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +echo "Content-Type: text/plain" +echo "" +echo "OK" diff --git a/lib/ulib/templates/apacheconfig/confs.conf..d8 b/lib/ulib/templates/apacheconfig.d8/confs.conf similarity index 100% rename from lib/ulib/templates/apacheconfig/confs.conf..d8 rename to lib/ulib/templates/apacheconfig.d8/confs.conf diff --git a/lib/ulib/templates/apacheconfig.d8/confs/ssl-config.conf b/lib/ulib/templates/apacheconfig.d8/confs/ssl-config.conf new file mode 100644 index 0000000..d00b377 --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/confs/ssl-config.conf @@ -0,0 +1,15 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + + # cf https://wiki.mozilla.org/Security/Server_Side_TLS + + # Choisir un des profils. Clients les plus anciens pouvant se connecter: + # modern: Firefox 27, Chrome 30, Windows 7 IE 11, Edge, Opera 17, Safari 9, Android 5.0, Java 8 + # intermediate: Firefox 1, Chrome 1, IE 7, Opera 5, Safari 1, Windows XP IE8, Android 2.3, Java 7 + # old: Windows XP IE6, Java 6 + #Define SSL_CONFIG_MODERN + #Define SSL_CONFIG_INTERMEDIATE + #Define SSL_CONFIG_OLD + + # Faut-il activer HSTS? + #Define SSL_CONFIG_HSTS + diff --git a/lib/ulib/templates/apacheconfig.d8/modules.conf b/lib/ulib/templates/apacheconfig.d8/modules.conf new file mode 100644 index 0000000..84e06fb --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/modules.conf @@ -0,0 +1,4 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Liste des modules à activer. Syntaxe: +# module ou +module pour activer un module +# -module pour le désactiver diff --git a/lib/ulib/templates/apacheconfig/modules/ssl.conf..d8 b/lib/ulib/templates/apacheconfig.d8/modules/ssl.conf similarity index 99% rename from lib/ulib/templates/apacheconfig/modules/ssl.conf..d8 rename to lib/ulib/templates/apacheconfig.d8/modules/ssl.conf index d733884..c7eb837 100644 --- a/lib/ulib/templates/apacheconfig/modules/ssl.conf..d8 +++ b/lib/ulib/templates/apacheconfig.d8/modules/ssl.conf @@ -107,7 +107,7 @@ SSLPassPhraseDialog exec:/usr/share/apache2/ask-for-passphrase # Inter-Process Session Cache: - # Configure the SSL Session Cache: First the mechanism + # Configure the SSL Session Cache: First the mechanism # to use and second the expiring timeout (in seconds). # (The mechanism dbm has known memory leaks and should not be used). #SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache @@ -116,7 +116,7 @@ # Semaphore: # Configure the path to the mutual exclusion semaphore the - # SSL engine uses internally for inter-process synchronization. + # SSL engine uses internally for inter-process synchronization. # (Disabled by default, the global Mutex directive consolidates by default # this) #Mutex file:${APACHE_LOCK_DIR}/ssl_mutex ssl-cache diff --git a/lib/ulib/templates/apacheconfig.d8/network.conf b/lib/ulib/templates/apacheconfig.d8/network.conf new file mode 100644 index 0000000..31e2bae --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/network.conf @@ -0,0 +1,24 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Configuration du réseau sur le serveur. Ce fichier est traité différemment +# selon le mode de configuration. +# - En mode complet, ce fichier définit le nom d'hôte ainsi que toutes les +# interfaces, ponts et adresses. La variable host et les tableaux ips et brs +# sont pris en compte. +# - En mode partiel, seuls le tableau ips est pris en compte: il est utilisé +# pour définir des adresses ips supplémentaires à configurer sur le serveur. + +# Liste des adresses IPs à configurer. Chaque élément est de la forme +# [IFACE:]dhcp ou [[IFACE][//GATEWAY]:]IP[/SUFFIX] +ips=() + +# Liste des ponts à configurer. Chaque élément est de la forme BR:IFACES +# BR est le nom du pont, e.g. br0. IFACES est une liste d'interfaces séparées +# par une virgule. e.g. br0:eth0,eth1 +brs=() + +# Nom d'hôte pleinement qualifié. Si ce paramètre est spécifié, les fichiers +# /etc/hosts, /etc/hostname et /etc/mailname sont mis à jour. +host= + +# Contenu du fichier /etc/networks +etc_networks= diff --git a/lib/ulib/templates/apacheconfig/ports.conf..d8 b/lib/ulib/templates/apacheconfig.d8/ports.conf similarity index 65% rename from lib/ulib/templates/apacheconfig/ports.conf..d8 rename to lib/ulib/templates/apacheconfig.d8/ports.conf index d858a80..92c0c92 100644 --- a/lib/ulib/templates/apacheconfig/ports.conf..d8 +++ b/lib/ulib/templates/apacheconfig.d8/ports.conf @@ -7,16 +7,8 @@ Listen 80 Listen 443 - #NameVirtualHost IP:443 - #Listen IP:443 - #@@ips_namevirtualhosts@@ - #@@ips_listens@@ Listen 443 - #NameVirtualHost IP:443 - #Listen IP:443 - #@@ips_namevirtualhosts@@ - #@@ips_listens@@ diff --git a/lib/ulib/templates/apacheconfig.d8/rewrite.rules b/lib/ulib/templates/apacheconfig.d8/rewrite.rules new file mode 100644 index 0000000..b445618 --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/rewrite.rules @@ -0,0 +1,2 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + diff --git a/lib/ulib/templates/apacheconfig.d8/sites.conf b/lib/ulib/templates/apacheconfig.d8/sites.conf new file mode 100644 index 0000000..0a2108b --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/sites.conf @@ -0,0 +1,4 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Liste des sites à activer. Syntaxe: +# site ou +site pour activer un site +# -site pour le désactiver diff --git a/lib/ulib/templates/apacheconfig/sites/default.conf..d8 b/lib/ulib/templates/apacheconfig.d8/sites/default.conf similarity index 100% rename from lib/ulib/templates/apacheconfig/sites/default.conf..d8 rename to lib/ulib/templates/apacheconfig.d8/sites/default.conf diff --git a/lib/ulib/templates/apacheconfig/sites/default.ssl.conf..d8 b/lib/ulib/templates/apacheconfig.d8/sites/default.ssl.conf similarity index 100% rename from lib/ulib/templates/apacheconfig/sites/default.ssl.conf..d8 rename to lib/ulib/templates/apacheconfig.d8/sites/default.ssl.conf diff --git a/lib/ulib/templates/apacheconfig.d8/syspkgs.conf b/lib/ulib/templates/apacheconfig.d8/syspkgs.conf new file mode 100644 index 0000000..5efb6ae --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/syspkgs.conf @@ -0,0 +1,9 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Liste de paquets système à installer, e.g. php5 ou libapache2-mod-jk +# Chaque package doit être indiqué sur une ligne à part +#libapache2-mod-jk +#libapache2-mod-auth-cas +#php5-mysql +#php5-ldap +#php5-gmp +#php5-gd diff --git a/lib/ulib/templates/apacheconfig.d8/templates/SITE-certs.conf b/lib/ulib/templates/apacheconfig.d8/templates/SITE-certs.conf new file mode 100644 index 0000000..2ca6ace --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/templates/SITE-certs.conf @@ -0,0 +1,15 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + +# Cette variable est utilisée par la fonction refcerts() du script runs. C'est +# le nom d'un répertoire à chercher dans RUNSMODULESPATH qui contient les +# certificats à installer sur le serveur. +certsdir=@@certsdir@@ + +# Fichier contenant les certificats racines qui valident le certificat à +# installer, ainsi que les certificats qui sont rencontrés dans le dialogue avec +# d'autres serveurs web +ca=@@caname@@ + +# Certificat et clé privée à installer +cert= +key= diff --git a/lib/ulib/templates/apacheconfig.d8/templates/SITE.conf b/lib/ulib/templates/apacheconfig.d8/templates/SITE.conf new file mode 100644 index 0000000..91c14d5 --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/templates/SITE.conf @@ -0,0 +1,31 @@ +# -*- coding: utf-8 mode: conf -*- vim:syntax=apache:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + + # The ServerName directive sets the request scheme, hostname and port that + # the server uses to identify itself. This is used when creating + # redirection URLs. In the context of virtual hosts, the ServerName + # specifies what hostname must appear in the request's Host: header to + # match this virtual host. For the default virtual host (this file) this + # value is not decisive as it is used as a last resort host regardless. + # However, you must set it for any further virtual host explicitly. + ServerName SITE.TLD + ServerAlias SITE SITE.local + ServerAdmin @@admin@@ + + DocumentRoot /var/www/SITE + + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn + + ErrorLog ${APACHE_LOG_DIR}/SITE_error.log + CustomLog ${APACHE_LOG_DIR}/SITE_access.log combined + + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf + diff --git a/lib/ulib/templates/apacheconfig/sites/default.ssl.conf..d b/lib/ulib/templates/apacheconfig.d8/templates/SITE.ssl.conf similarity index 69% rename from lib/ulib/templates/apacheconfig/sites/default.ssl.conf..d rename to lib/ulib/templates/apacheconfig.d8/templates/SITE.ssl.conf index 52f4a83..1af0b69 100644 --- a/lib/ulib/templates/apacheconfig/sites/default.ssl.conf..d +++ b/lib/ulib/templates/apacheconfig.d8/templates/SITE.ssl.conf @@ -1,54 +1,27 @@ -# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - +# -*- coding: utf-8 mode: conf -*- vim:syntax=apache:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - ServerName @@host@@ - ServerAlias @@aliases@@ + ServerName SITE.TLD + ServerAlias SITE SITE.local ServerAdmin @@admin@@ - DocumentRoot /var/www - - Options FollowSymLinks - AllowOverride None - - - Options Indexes FollowSymLinks MultiViews - AllowOverride None - Order allow,deny - allow from all - + DocumentRoot /var/www/SITE - # Pour les serveurs qui ont le module mod_WebObjects: - # mod_WebObjects et ScriptAlias ne peuvent pas gérer le même préfixe. Pour - # utiliser des cgi-bin avec WebObjects, il faut soit changer le préfixe de - # ScriptAlias, soit changer le préfixe de WebObjectsAlias dans le fichier - # mod-webobjects.conf - # Sinon, il suffit de commenter les lignes suivantes: - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Order allow,deny - Allow from all - + # Available loglevels: trace8, ..., trace1, debug, info, notice, warn, + # error, crit, alert, emerg. + # It is also possible to configure the loglevel for particular + # modules, e.g. + #LogLevel info ssl:warn - ErrorLog ${APACHE_LOG_DIR}/ssl_error.log + ErrorLog ${APACHE_LOG_DIR}/SITE_error.log + CustomLog ${APACHE_LOG_DIR}/SITE_access.log combined - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined - - - Order allow,deny - Allow from all - - - - Order allow,deny - Allow from all - + # For most configuration files from conf-available/, which are + # enabled or disabled at a global level, it is possible to + # include a line for only one particular virtual host. For example the + # following line enables the CGI configuration for this host only + # after it has been globally disabled with "a2disconf". + #Include conf-available/serve-cgi-bin.conf # SSL Engine Switch: # Enable/Disable SSL for this virtual host. @@ -56,7 +29,7 @@ # A self-signed (snakeoil) certificate can be created by installing # the ssl-cert package. See - # /usr/share/doc/apache2.2-common/README.Debian.gz for more info. + # /usr/share/doc/apache2/README.Debian.gz for more info. # If both key and certificate are stored in the same file, only the # SSLCertificateFile directive is needed. SSLCertificateFile @@cert@@ @@ -99,21 +72,6 @@ #SSLVerifyClient require #SSLVerifyDepth 10 - # Access Control: - # With SSLRequire you can do per-directory access control based - # on arbitrary complex boolean expressions containing server - # variable checks and other lookup directives. The syntax is a - # mixture between C and Perl. See the mod_ssl documentation - # for more details. - # - #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ - # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ - # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ - # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ - # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ - # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ - # - # SSL Engine Options: # Set various options for the SSL engine. # o FakeBasicAuth: @@ -134,19 +92,15 @@ # because the extraction step is an expensive operation and is usually # useless for serving static content. So one usually enables the # exportation for CGI and SSI requests only. - # o StrictRequire: - # This denies access when "SSLRequireSSL" or "SSLRequire" applied even - # under a "Satisfy any" situation, i.e. when it applies access is denied - # and no other module can change it. # o OptRenegotiate: # This enables optimized SSL connection renegotiation handling when SSL # directives are used in per-directory context. #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire - SSLOptions +StdEnvVars + SSLOptions +StdEnvVars - SSLOptions +StdEnvVars + SSLOptions +StdEnvVars # SSL Protocol Adjustments: @@ -174,8 +128,8 @@ # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and # "force-response-1.0" for this. BrowserMatch "MSIE [2-6]" \ - nokeepalive ssl-unclean-shutdown \ - downgrade-1.0 force-response-1.0 + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 # MSIE 7 and newer should be able to use keepalive BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown diff --git a/lib/ulib/templates/apacheconfig.d8/templates/SITE/.udir b/lib/ulib/templates/apacheconfig.d8/templates/SITE/.udir new file mode 100644 index 0000000..a212c72 --- /dev/null +++ b/lib/ulib/templates/apacheconfig.d8/templates/SITE/.udir @@ -0,0 +1,24 @@ +# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# Utiliser 'udir --help-vars' pour une description de la signification des +# variables suivantes: +udir_desc="Fichiers à déployer dans le répertoire des documents web" +udir_note="Il est possible de déployer les modifications dans ce répertoire avec 'uinst -y'" +udir_types=(uinst:rsync) +uinc=release +uinc_options=() +uinc_args=() +configure_variables=(dest) +configure_dest_for=() +config_scripts=() +install_profiles=false +workdir_rsync_options=() +workdir_excludes=() +workdir_includes=() +copy_files=true +rsync_options=(--delete-after) +destdir=root@@@host@@:HTDOCSBASE/SITE +srcdir=. +files=() +owner=www-data: +modes=(u=rwX,g=rX,o=rX) +root_scripts=() diff --git a/lib/ulib/templates/apacheconfig.d8/templates/SITE/favicon.ico b/lib/ulib/templates/apacheconfig.d8/templates/SITE/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a1a0be8884a24d60bda51751dcbfffcb3a99b76d GIT binary patch literal 1150 zcmb7^eN5DK9LIldEw{2p|G3dwYtE*%Y32H(Qfi3DjnIu$lJeOUSZlMC4zQ4mL(Gem z2v;a+Jm`2jl62w2qKtc}<8I;I!65=7ryx>rcif?F-M#w$zV7(LR$IT{KEKbyckj>l z{dw3=QD%uZE>02Orx!V zn(SxE(n*q11FRIW7Zdzw(*mTdzUOOJ1xV0LU~A4t#b;+-Mqd)fa3D{YFs0?w6c|Em$@0(OHOW29Upy(Lc=HzOa*}8+euon}EsgrO zX)>k{K2z2hT8R#OIPJfg2zWV4!iHrUo6C|pDki@u=y(3ic(;kc zb2`E2*?;6w@e@7=IZ2PBPg}&{ci*7BwS)GHcNza9g1xPtTdoGYjTu~S*(UR-y^V2F z@wbCxwgmUHWAd`tF3#ZL#u zsLI`drTI9vmKug!zj1T04|CHoYV}!!#s?U&f6dU3g>ru0hIRDRuf<)h#daW`;Fy=z zioKNW&~jSbZ#7k8GUj32_!=K-o?!SFqvUJWKPdL;W%^F-5P6dEHe}#AxteRv8hY(N zQMBfHSwrQ{bsXHff}G{?9L>#;yBMoCrqvmG#2$H@){FV=V)D9!wyIC5F5FJ8=1F!g zjpINh=RPvB}cV;&up6|F59A&tt6K89wsPRp)GY|czsQZ2Y|KR`n Fe**|-!I}U7 literal 0 HcmV?d00001 diff --git a/lib/ulib/templates/apacheconfig.d8/templates/SITE/icon.png b/lib/ulib/templates/apacheconfig.d8/templates/SITE/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..581bad1b4f515184b0d5af4f1c66474d0d283961 GIT binary patch literal 29641 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 Fe**|-!I}U7 literal 0 HcmV?d00001 diff --git a/lib/ulib/templates/apacheconfig.d8/www/icon.png b/lib/ulib/templates/apacheconfig.d8/www/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..581bad1b4f515184b0d5af4f1c66474d0d283961 GIT binary patch literal 29641 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 + # cf https://wiki.mozilla.org/Security/Server_Side_TLS + + # modern configuration not supported. same as SSL_CONFIG_INTERMEDIATE below + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS + SSLHonorCipherOrder on + + + + # intermediate configuration, tweak to your needs + SSLProtocol all -SSLv2 -SSLv3 + SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS + SSLHonorCipherOrder on + SSLCompression off + SSLSessionTickets off + + + + # old configuration, tweak to your needs + SSLProtocol all -SSLv2 + SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP + SSLHonorCipherOrder on + SSLCompression off + SSLSessionTickets off + + + # default debian configuration + + # SSL Cipher Suite: + # List the ciphers that the client is permitted to negotiate. + # See the mod_ssl documentation for a complete list. + # enable only secure ciphers: + SSLCipherSuite HIGH:MEDIUM:!ADH + # Use this instead if you want to allow cipher upgrades via SGC facility. + # In this case you also have to use something like + # SSLRequire %{SSL_CIPHER_USEKEYSIZE} >= 128 + # see http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html.en#upgradeenc + #SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL + + # enable only secure protocols: SSLv3 and TLSv1, but not SSLv2 + SSLProtocol all -SSLv2 + + + + +# +# Pseudo Random Number Generator (PRNG): +# Configure one or more sources to seed the PRNG of the SSL library. +# The seed data should be of good random quality. +# WARNING! On some platforms /dev/random blocks if not enough entropy +# is available. This means you then cannot use the /dev/random device +# because it would lead to very long connection times (as long as +# it requires to make more entropy available). But usually those +# platforms additionally provide a /dev/urandom device which doesn't +# block. So, if available, use this one instead. Read the mod_ssl User +# Manual for more details. +# +SSLRandomSeed startup builtin +SSLRandomSeed startup file:/dev/urandom 512 +SSLRandomSeed connect builtin +SSLRandomSeed connect file:/dev/urandom 512 + +## +## SSL Global Context +## +## All SSL configuration in this context applies both to +## the main server and all SSL-enabled virtual hosts. +## + +# +# Some MIME-types for downloading Certificates and CRLs +# +AddType application/x-x509-ca-cert .crt +AddType application/x-pkcs7-crl .crl + +# Pass Phrase Dialog: +# Configure the pass phrase gathering process. +# The filtering dialog program (`builtin' is a internal +# terminal dialog) has to provide the pass phrase on stdout. +SSLPassPhraseDialog builtin + +# Inter-Process Session Cache: +# Configure the SSL Session Cache: First the mechanism +# to use and second the expiring timeout (in seconds). +# (The mechanism dbm has known memory leaks and should not be used). +#SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache +SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) +SSLSessionCacheTimeout 300 + +# Semaphore: +# Configure the path to the mutual exclusion semaphore the +# SSL engine uses internally for inter-process synchronization. +SSLMutex file:${APACHE_RUN_DIR}/ssl_mutex + +# Allow insecure renegotiation with clients which do not yet support the +# secure renegotiation protocol. Default: Off +#SSLInsecureRenegotiation on + +# Whether to forbid non-SNI clients to access name based virtual hosts. +# Default: Off +#SSLStrictSNIVHostCheck On + diff --git a/lib/ulib/templates/apacheconfig/modules/ssl.conf..d b/lib/ulib/templates/apacheconfig/modules/ssl.conf..d deleted file mode 100644 index 9994776..0000000 --- a/lib/ulib/templates/apacheconfig/modules/ssl.conf..d +++ /dev/null @@ -1,103 +0,0 @@ - - # cf https://wiki.mozilla.org/Security/Server_Side_TLS - - # modern configuration not supported. same as SSL_CONFIG_INTERMEDIATE below - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS - SSLHonorCipherOrder on - - - - # intermediate configuration, tweak to your needs - SSLProtocol all -SSLv2 -SSLv3 - SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS - SSLHonorCipherOrder on - SSLCompression off - SSLSessionTickets off - - - - # old configuration, tweak to your needs - SSLProtocol all -SSLv2 - SSLCipherSuite ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:SEED:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!RSAPSK:!aDH:!aECDH:!EDH-DSS-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA:!SRP - SSLHonorCipherOrder on - SSLCompression off - SSLSessionTickets off - - - # default debian configuration - - # SSL Cipher Suite: - # List the ciphers that the client is permitted to negotiate. - # See the mod_ssl documentation for a complete list. - # enable only secure ciphers: - SSLCipherSuite HIGH:MEDIUM:!ADH - # Use this instead if you want to allow cipher upgrades via SGC facility. - # In this case you also have to use something like - # SSLRequire %{SSL_CIPHER_USEKEYSIZE} >= 128 - # see http://httpd.apache.org/docs/2.2/ssl/ssl_howto.html.en#upgradeenc - #SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL - - # enable only secure protocols: SSLv3 and TLSv1, but not SSLv2 - SSLProtocol all -SSLv2 - - - - -# -# Pseudo Random Number Generator (PRNG): -# Configure one or more sources to seed the PRNG of the SSL library. -# The seed data should be of good random quality. -# WARNING! On some platforms /dev/random blocks if not enough entropy -# is available. This means you then cannot use the /dev/random device -# because it would lead to very long connection times (as long as -# it requires to make more entropy available). But usually those -# platforms additionally provide a /dev/urandom device which doesn't -# block. So, if available, use this one instead. Read the mod_ssl User -# Manual for more details. -# -SSLRandomSeed startup builtin -SSLRandomSeed startup file:/dev/urandom 512 -SSLRandomSeed connect builtin -SSLRandomSeed connect file:/dev/urandom 512 - -## -## SSL Global Context -## -## All SSL configuration in this context applies both to -## the main server and all SSL-enabled virtual hosts. -## - -# -# Some MIME-types for downloading Certificates and CRLs -# -AddType application/x-x509-ca-cert .crt -AddType application/x-pkcs7-crl .crl - -# Pass Phrase Dialog: -# Configure the pass phrase gathering process. -# The filtering dialog program (`builtin' is a internal -# terminal dialog) has to provide the pass phrase on stdout. -SSLPassPhraseDialog builtin - -# Inter-Process Session Cache: -# Configure the SSL Session Cache: First the mechanism -# to use and second the expiring timeout (in seconds). -# (The mechanism dbm has known memory leaks and should not be used). -#SSLSessionCache dbm:${APACHE_RUN_DIR}/ssl_scache -SSLSessionCache shmcb:${APACHE_RUN_DIR}/ssl_scache(512000) -SSLSessionCacheTimeout 300 - -# Semaphore: -# Configure the path to the mutual exclusion semaphore the -# SSL engine uses internally for inter-process synchronization. -SSLMutex file:${APACHE_RUN_DIR}/ssl_mutex - -# Allow insecure renegotiation with clients which do not yet support the -# secure renegotiation protocol. Default: Off -#SSLInsecureRenegotiation on - -# Whether to forbid non-SNI clients to access name based virtual hosts. -# Default: Off -#SSLStrictSNIVHostCheck On - diff --git a/lib/ulib/templates/apacheconfig/ports.conf b/lib/ulib/templates/apacheconfig/ports.conf index e69de29..a6bceab 100644 --- a/lib/ulib/templates/apacheconfig/ports.conf +++ b/lib/ulib/templates/apacheconfig/ports.conf @@ -0,0 +1,29 @@ +# -*- coding: utf-8 mode: conf -*- vim:syntax=apache:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 +# If you just change the port or add more ports here, you will likely also +# have to change the VirtualHost statement in +# /etc/apache2/sites-enabled/000-default +# This is also true if you have upgraded from before 2.2.9-3 (i.e. from +# Debian etch). See /usr/share/doc/apache2.2-common/NEWS.Debian.gz and +# README.Debian.gz + +NameVirtualHost *:80 +Listen *:80 + + + # If you add NameVirtualHost *:443 here, you will also have to change + # the VirtualHost statement in /etc/apache2/sites-available/default-ssl + # to + # Server Name Indication for SSL named virtual hosts is currently not + # supported by MSIE on Windows XP. + #NameVirtualHost IP:443 + #Listen IP:443 + #@@ips_namevirtualhosts@@ + #@@ips_listens@@ + + + + #NameVirtualHost IP:443 + #Listen IP:443 + #@@ips_namevirtualhosts@@ + #@@ips_listens@@ + diff --git a/lib/ulib/templates/apacheconfig/ports.conf..d b/lib/ulib/templates/apacheconfig/ports.conf..d deleted file mode 100644 index a6bceab..0000000 --- a/lib/ulib/templates/apacheconfig/ports.conf..d +++ /dev/null @@ -1,29 +0,0 @@ -# -*- coding: utf-8 mode: conf -*- vim:syntax=apache:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 -# If you just change the port or add more ports here, you will likely also -# have to change the VirtualHost statement in -# /etc/apache2/sites-enabled/000-default -# This is also true if you have upgraded from before 2.2.9-3 (i.e. from -# Debian etch). See /usr/share/doc/apache2.2-common/NEWS.Debian.gz and -# README.Debian.gz - -NameVirtualHost *:80 -Listen *:80 - - - # If you add NameVirtualHost *:443 here, you will also have to change - # the VirtualHost statement in /etc/apache2/sites-available/default-ssl - # to - # Server Name Indication for SSL named virtual hosts is currently not - # supported by MSIE on Windows XP. - #NameVirtualHost IP:443 - #Listen IP:443 - #@@ips_namevirtualhosts@@ - #@@ips_listens@@ - - - - #NameVirtualHost IP:443 - #Listen IP:443 - #@@ips_namevirtualhosts@@ - #@@ips_listens@@ - diff --git a/lib/ulib/templates/apacheconfig/sites/default.conf b/lib/ulib/templates/apacheconfig/sites/default.conf index e69de29..0fc5304 100644 --- a/lib/ulib/templates/apacheconfig/sites/default.conf +++ b/lib/ulib/templates/apacheconfig/sites/default.conf @@ -0,0 +1,51 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + + + ServerName @@host@@ + ServerAlias @@aliases@@ + ServerAdmin @@admin@@ + + DocumentRoot /var/www + + Options FollowSymLinks + AllowOverride None + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + + + # Pour les serveurs qui ont le module mod_WebObjects: + # mod_WebObjects et ScriptAlias ne peuvent pas gérer le même préfixe. Pour + # utiliser des cgi-bin avec WebObjects, il faut soit changer le préfixe de + # ScriptAlias, soit changer le préfixe de WebObjectsAlias dans le fichier + # mod-webobjects.conf + # Sinon, il suffit de commenter les lignes suivantes: + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + ErrorLog ${APACHE_LOG_DIR}/error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/access.log combined + + # Pour les serveurs qui ont le module mod_WebObjects: + + Order allow,deny + Allow from all + + + Order allow,deny + Allow from all + + diff --git a/lib/ulib/templates/apacheconfig/sites/default.conf..d b/lib/ulib/templates/apacheconfig/sites/default.conf..d deleted file mode 100644 index 0fc5304..0000000 --- a/lib/ulib/templates/apacheconfig/sites/default.conf..d +++ /dev/null @@ -1,51 +0,0 @@ -# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 - - - ServerName @@host@@ - ServerAlias @@aliases@@ - ServerAdmin @@admin@@ - - DocumentRoot /var/www - - Options FollowSymLinks - AllowOverride None - - - Options Indexes FollowSymLinks MultiViews - AllowOverride None - Order allow,deny - allow from all - - - # Pour les serveurs qui ont le module mod_WebObjects: - # mod_WebObjects et ScriptAlias ne peuvent pas gérer le même préfixe. Pour - # utiliser des cgi-bin avec WebObjects, il faut soit changer le préfixe de - # ScriptAlias, soit changer le préfixe de WebObjectsAlias dans le fichier - # mod-webobjects.conf - # Sinon, il suffit de commenter les lignes suivantes: - ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ - - AllowOverride None - Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch - Order allow,deny - Allow from all - - - ErrorLog ${APACHE_LOG_DIR}/error.log - - # Possible values include: debug, info, notice, warn, error, crit, - # alert, emerg. - LogLevel warn - - CustomLog ${APACHE_LOG_DIR}/access.log combined - - # Pour les serveurs qui ont le module mod_WebObjects: - - Order allow,deny - Allow from all - - - Order allow,deny - Allow from all - - diff --git a/lib/ulib/templates/apacheconfig/sites/default.ssl.conf b/lib/ulib/templates/apacheconfig/sites/default.ssl.conf index e69de29..52f4a83 100644 --- a/lib/ulib/templates/apacheconfig/sites/default.ssl.conf +++ b/lib/ulib/templates/apacheconfig/sites/default.ssl.conf @@ -0,0 +1,190 @@ +# -*- coding: utf-8 mode: conf -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8 + + + + ServerName @@host@@ + ServerAlias @@aliases@@ + ServerAdmin @@admin@@ + + DocumentRoot /var/www + + Options FollowSymLinks + AllowOverride None + + + Options Indexes FollowSymLinks MultiViews + AllowOverride None + Order allow,deny + allow from all + + + # Pour les serveurs qui ont le module mod_WebObjects: + # mod_WebObjects et ScriptAlias ne peuvent pas gérer le même préfixe. Pour + # utiliser des cgi-bin avec WebObjects, il faut soit changer le préfixe de + # ScriptAlias, soit changer le préfixe de WebObjectsAlias dans le fichier + # mod-webobjects.conf + # Sinon, il suffit de commenter les lignes suivantes: + ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/ + + AllowOverride None + Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch + Order allow,deny + Allow from all + + + ErrorLog ${APACHE_LOG_DIR}/ssl_error.log + + # Possible values include: debug, info, notice, warn, error, crit, + # alert, emerg. + LogLevel warn + + CustomLog ${APACHE_LOG_DIR}/ssl_access.log combined + + + Order allow,deny + Allow from all + + + + Order allow,deny + Allow from all + + + # SSL Engine Switch: + # Enable/Disable SSL for this virtual host. + SSLEngine on + + # A self-signed (snakeoil) certificate can be created by installing + # the ssl-cert package. See + # /usr/share/doc/apache2.2-common/README.Debian.gz for more info. + # If both key and certificate are stored in the same file, only the + # SSLCertificateFile directive is needed. + SSLCertificateFile @@cert@@ + SSLCertificateKeyFile @@key@@ + + # Server Certificate Chain: + # Point SSLCertificateChainFile at a file containing the + # concatenation of PEM encoded CA certificates which form the + # certificate chain for the server certificate. Alternatively + # the referenced file can be the same as SSLCertificateFile + # when the CA certificates are directly appended to the server + # certificate for convinience. + SSLCertificateChainFile @@ca@@ + + # Certificate Authority (CA): + # Set the CA certificate verification path where to find CA + # certificates for client authentication or alternatively one + # huge file containing all of them (file must be PEM encoded) + # Note: Inside SSLCACertificatePath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCACertificatePath /etc/ssl/certs/ + #SSLCACertificateFile /etc/apache2/ssl.crt/ca-bundle.crt + + # Certificate Revocation Lists (CRL): + # Set the CA revocation path where to find CA CRLs for client + # authentication or alternatively one huge file containing all + # of them (file must be PEM encoded) + # Note: Inside SSLCARevocationPath you need hash symlinks + # to point to the certificate files. Use the provided + # Makefile to update the hash symlinks after changes. + #SSLCARevocationPath /etc/apache2/ssl.crl/ + #SSLCARevocationFile /etc/apache2/ssl.crl/ca-bundle.crl + + # Client Authentication (Type): + # Client certificate verification type and depth. Types are + # none, optional, require and optional_no_ca. Depth is a + # number which specifies how deeply to verify the certificate + # issuer chain before deciding the certificate is not valid. + #SSLVerifyClient require + #SSLVerifyDepth 10 + + # Access Control: + # With SSLRequire you can do per-directory access control based + # on arbitrary complex boolean expressions containing server + # variable checks and other lookup directives. The syntax is a + # mixture between C and Perl. See the mod_ssl documentation + # for more details. + # + #SSLRequire ( %{SSL_CIPHER} !~ m/^(EXP|NULL)/ \ + # and %{SSL_CLIENT_S_DN_O} eq "Snake Oil, Ltd." \ + # and %{SSL_CLIENT_S_DN_OU} in {"Staff", "CA", "Dev"} \ + # and %{TIME_WDAY} >= 1 and %{TIME_WDAY} <= 5 \ + # and %{TIME_HOUR} >= 8 and %{TIME_HOUR} <= 20 ) \ + # or %{REMOTE_ADDR} =~ m/^192\.76\.162\.[0-9]+$/ + # + + # SSL Engine Options: + # Set various options for the SSL engine. + # o FakeBasicAuth: + # Translate the client X.509 into a Basic Authorisation. This means that + # the standard Auth/DBMAuth methods can be used for access control. The + # user name is the `one line' version of the client's X.509 certificate. + # Note that no password is obtained from the user. Every entry in the user + # file needs this password: `xxj31ZMTZzkVA'. + # o ExportCertData: + # This exports two additional environment variables: SSL_CLIENT_CERT and + # SSL_SERVER_CERT. These contain the PEM-encoded certificates of the + # server (always existing) and the client (only existing when client + # authentication is used). This can be used to import the certificates + # into CGI scripts. + # o StdEnvVars: + # This exports the standard SSL/TLS related `SSL_*' environment variables. + # Per default this exportation is switched off for performance reasons, + # because the extraction step is an expensive operation and is usually + # useless for serving static content. So one usually enables the + # exportation for CGI and SSI requests only. + # o StrictRequire: + # This denies access when "SSLRequireSSL" or "SSLRequire" applied even + # under a "Satisfy any" situation, i.e. when it applies access is denied + # and no other module can change it. + # o OptRenegotiate: + # This enables optimized SSL connection renegotiation handling when SSL + # directives are used in per-directory context. + #SSLOptions +FakeBasicAuth +ExportCertData +StrictRequire + + SSLOptions +StdEnvVars + + + SSLOptions +StdEnvVars + + + # SSL Protocol Adjustments: + # The safe and default but still SSL/TLS standard compliant shutdown + # approach is that mod_ssl sends the close notify alert but doesn't wait for + # the close notify alert from client. When you need a different shutdown + # approach you can use one of the following variables: + # o ssl-unclean-shutdown: + # This forces an unclean shutdown when the connection is closed, i.e. no + # SSL close notify alert is send or allowed to received. This violates + # the SSL/TLS standard but is needed for some brain-dead browsers. Use + # this when you receive I/O errors because of the standard approach where + # mod_ssl sends the close notify alert. + # o ssl-accurate-shutdown: + # This forces an accurate shutdown when the connection is closed, i.e. a + # SSL close notify alert is send and mod_ssl waits for the close notify + # alert of the client. This is 100% SSL/TLS standard compliant, but in + # practice often causes hanging connections with brain-dead browsers. Use + # this only for browsers where you know that their SSL implementation + # works correctly. + # Notice: Most problems of broken clients are also related to the HTTP + # keep-alive facility, so you usually additionally want to disable + # keep-alive for those clients, too. Use variable "nokeepalive" for this. + # Similarly, one has to force some clients to use HTTP/1.0 to workaround + # their broken HTTP/1.1 implementation. Use variables "downgrade-1.0" and + # "force-response-1.0" for this. + BrowserMatch "MSIE [2-6]" \ + nokeepalive ssl-unclean-shutdown \ + downgrade-1.0 force-response-1.0 + # MSIE 7 and newer should be able to use keepalive + BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown + + # cf https://wiki.mozilla.org/Security/Server_Side_TLS + + + # HSTS (15768000 seconds = 6 months) + Header always set Strict-Transport-Security "max-age=15768000" + + + + diff --git a/lib/ulib/templates/apacheconfig/www/favicon.ico b/lib/ulib/templates/apacheconfig/www/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..a1a0be8884a24d60bda51751dcbfffcb3a99b76d GIT binary patch literal 1150 zcmb7^eN5DK9LIldEw{2p|G3dwYtE*%Y32H(Qfi3DjnIu$lJeOUSZlMC4zQ4mL(Gem z2v;a+Jm`2jl62w2qKtc}<8I;I!65=7ryx>rcif?F-M#w$zV7(LR$IT{KEKbyckj>l z{dw3=QD%uZE>02Orx!V zn(SxE(n*q11FRIW7Zdzw(*mTdzUOOJ1xV0LU~A4t#b;+-Mqd)fa3D{YFs0?w6c|Em$@0(OHOW29Upy(Lc=HzOa*}8+euon}EsgrO zX)>k{K2z2hT8R#OIPJfg2zWV4!iHrUo6C|pDki@u=y(3ic(;kc zb2`E2*?;6w@e@7=IZ2PBPg}&{ci*7BwS)GHcNza9g1xPtTdoGYjTu~S*(UR-y^V2F z@wbCxwgmUHWAd`tF3#ZL#u zsLI`drTI9vmKug!zj1T04|CHoYV}!!#s?U&f6dU3g>ru0hIRDRuf<)h#daW`;Fy=z zioKNW&~jSbZ#7k8GUj32_!=K-o?!SFqvUJWKPdL;W%^F-5P6dEHe}#AxteRv8hY(N zQMBfHSwrQ{bsXHff}G{?9L>#;yBMoCrqvmG#2$H@){FV=V)D9!wyIC5F5FJ8=1F!g zjpINh=RPvB}cV;&up6|F59A&tt6K89wsPRp)GY|czsQZ2Y|KR`n Fe**|-!I}U7 literal 0 HcmV?d00001 diff --git a/lib/ulib/templates/apacheconfig/www/icon.png b/lib/ulib/templates/apacheconfig/www/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..581bad1b4f515184b0d5af4f1c66474d0d283961 GIT binary patch literal 29641 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 - - - - -'"$thishost - - -

$thishost

-
    " >"$htmlfile" +if [ -n "$infile" ]; then infiles=("$infile") +else array_lsfiles infiles . "*rewrite*.rules" fi ->"$outfile" -array_from_lines rules "$(<"$infile" filter_comment)" -prefix= -for rule in "${rules[@]}"; do - if beginswith "$rule" ^; then - # Collecter les préfixe pour la règle suivante - prefix="${prefix:+$prefix -}${rule#^}" - continue - elif beginswith "$rule" =; then - # ligne litérale - echo "${rule#=}" >>"$outfile" - continue - fi - - IFS=:; set -- $rule; unset IFS - index=1 - done= - while [ -z "$done" ]; do - current="$1"; shift - while endswith "$current" "\\"; do - current="${current%\\}:$1"; shift - done - case $index in - 1) src="$current";; - 2) dest="$current";; - 3) host="$current";; - 4) suffix="$current";; - 5) options="$current";; - 6) prot="${current:-http}";; - 7) proxy_acls="$current";; - *) done=1;; - esac - index=$(($index + 1)) - done - - # mettre en forme prefix s'il est défini - [ -n "$prefix" ] && prefix="$prefix -" - - if [ "$thishost" == "$host" ]; then - host= - fi - - usrc="$src" - - trail=1 - if endswith "$src" '$'; then - trail= - usrc="${src%$}" - fi - - noslash= - if endswith "$suffix" '$'; then - noslash=1 - suffix="${suffix%$}" - fi - if endswith "$dest" '$'; then - noslash=1 - dest="${dest%$}" - fi - - proxy_url= - proxy_use= - - if endswith "$dest" .woa; then - # lien vers une application - if [ -n "$host" ]; then - # sur un autre hôte - if [ -n "$noslash" ]; then - echo "${prefix}RewriteRule ^/$src${trail:+(.*)} $(joinurl "$prot://$host/cgi-bin/WebObjects" "$dest" "$suffix")${trail:+\$1} [L${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc" - setx proxy_url joinurl "$prot://$host/cgi-bin/WebObjects" "$dest" "$suffix" - else - echo "${prefix}RewriteRule ^/$src\$ /$src/" >>"$outfile" - echo "${prefix}RewriteRule ^/$src/(.*) $(joinurl "$prot://$host/cgi-bin/WebObjects" "$dest" "$suffix" "\$1") [L${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc/" - setx proxy_url joinurl "$prot://$host/cgi-bin/WebObjects" "$dest" "$suffix/" - fi - else - # sur le même hôte - if [ -n "$noslash" ]; then - echo "${prefix}RewriteRule ^/$src${trail:+(.*)} $(joinurl /cgi-bin/WebObjects "$dest" "$suffix")${trail:+\$1} [L,P${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc" - setx proxy_url joinurl "$prot://$thishost/cgi-bin/WebObjects" "$dest" "$suffix" - proxy_use=1 - else - echo "${prefix}RewriteRule ^/$src\$ /$src/" >>"$outfile" - echo "${prefix}RewriteRule ^/$src/(.*) $(joinurl /cgi-bin/WebObjects "$dest" "$suffix" "\$1") [L,P${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc/" - setx proxy_url joinurl "$prot://$thishost/cgi-bin/WebObjects" "$dest" "$suffix/" - proxy_use=1 - fi - fi +[ ${#infiles[*]} -gt 0 ] || die "Il faut spécifier le fichier de règles avec -f" +for infile in "${infiles[@]}"; do + if [ -f "$infile" ]; then + estep "$(ppath "$infile")" + legacy_mkRewriteRules "$infile" "$thishost" "$outfile" "$htmlfile" "$proxy_enabled" else - # lien vers une url - if [ -n "$host" ]; then - # sur un autre hôte - if [ -n "$noslash" ]; then - echo "${prefix}RewriteRule ^/$src${trail:+(.*)} $(joinurl "$prot://$host" "$dest" "$suffix")${trail:+\$1} [L${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc" - setx proxy_url joinurl "$prot://$host" "$dest" "$suffix" - else - echo "${prefix}RewriteRule ^/$src\$ /$src/" >>"$outfile" - echo "${prefix}RewriteRule ^/$src/(.*) $(joinurl "$prot://$host" "$dest" "$suffix" "\$1") [L${options:+,$options}]" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc/" - setx proxy_url joinurl "$prot://$host" "$dest" "$suffix/" - fi - else - # sur le même hôte - if [ -n "$noslash" ]; then - echo "${prefix}RewriteRule ^/$src${trail:+(.*)} $(joinurl / "$dest" "$suffix")${trail:+\$1}${options:+ [$options]}" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc" - setx proxy_url joinurl "http://$thishost" "$dest" "$suffix" - else - echo "${prefix}RewriteRule ^/$src\$ /$src/" >>"$outfile" - echo "${prefix}RewriteRule ^/$src/(.*) $(joinurl / "$dest" "$suffix" "\$1")${options:+ [$options]}" >>"$outfile" - setx url joinurl "http://$thishost" "$usrc/" - setx proxy_url joinurl "http://$thishost" "$dest" "$suffix/" - fi - fi + eerror "$(ppath "$infile"): fichier introuvable" fi - has_proxy "$options" && proxy_use=1 - if [ -n "$proxy_enabled" -a -n "$proxy_use" ]; then - if [ "$proxy_acls" == "None" ]; then - : - elif [ -z "$proxy_acls" ]; then - echo "\ - - AddDefaultCharset off - Order Deny,Allow - Allow from all -" >>"$outfile" - else - echo "\ - - AddDefaultCharset off - Order Allow,Deny - Allow from $proxy_acls -" >>"$outfile" - fi - fi - - echo "" >>"$outfile" - if [ -n "$htmlfile" ]; then - echo "
  • $url
  • " >>"$htmlfile" - fi - - # Réinitialiser les préfixes pour chaque règle - prefix= + # réinitialiser pour ne pas écraser un fichier existant + outfile= + htmlfile= done - -if [ -n "$htmlfile" ]; then - echo '
- -' >>"$htmlfile" -fi From fbc1a5cc9b89114f50486a9f82ee19c0e6fe3c5e Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:14:01 +0400 Subject: [PATCH 32/39] =?UTF-8?q?mise=20=C3=A0=20jour=20du=20module=20debi?= =?UTF-8?q?an?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - support de jessie+ - possibilité de spécifier qu'une interface n'a pas de passerelle avec la syntaxe iface//-:ipsuffixes - support de la configuration de réseau partiel - support de la mise à jour de /etc/networks --- lib/ulib/debian | 151 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 127 insertions(+), 24 deletions(-) diff --git a/lib/ulib/debian b/lib/ulib/debian index 1e9d06a..52e3605 100644 --- a/lib/ulib/debian +++ b/lib/ulib/debian @@ -176,20 +176,40 @@ function network_fix_confbrs() { function __network_fix_confips() { local -a confips ipsuffixes ifaces ips - local confip iface gateway ip suffix mainip + local confip iface gateway network ip suffix mainip local mainiface="$1" # recenser les interfaces et créer les tableaux __IFACE_ipspecs for confip in "${__nfc_confips[@]}"; do network_parse_confip "$confip" iface gateway ipsuffixes - [ -n "$iface" ] || iface="$mainiface" - array_addu ifaces "$iface" - eval "local ${iface}_gateway; local -a ${iface}_ipsuffixes" + if [ -n "$iface" ]; then + network="${iface}_network" + if ! array_contains ifaces "$iface"; then + array_add ifaces "$iface" + eval "local ${iface}_gateway $network; local -a ${iface}_ipsuffixes" + fi + if [ -z "${!network}" -a -n "${ipsuffixes[0]}" ]; then + setv "$network" "$(ipcalc_network "${ipsuffixes[0]}")" + fi + fi done # puis construire la liste des adresses IP associées à chaque interface for confip in "${__nfc_confips[@]}"; do network_parse_confip "$confip" iface gateway ipsuffixes + # si aucune interface n'est spécifiée, sélectionner celle correspondant + # à la même adresse de réseau. sinon prendre $mainiface + if [ -z "$iface" ]; then + network="$(ipcalc_network "${ipsuffixes[0]}")" + local ip_iface ip_network + for ip_iface in "${ifaces[@]}"; do + ip_network="${ip_iface}_network" + if [ "${!ip_network}" == "$network" ]; then + iface="$ip_iface" + break + fi + done + fi [ -n "$iface" ] || iface="$mainiface" # si la passerelle a déjà été spécifiée, la récupérer local tmpgw="${iface}_gateway" @@ -356,6 +376,7 @@ function __network_set_confips() { array_from_lines ifaces "$(__network_valid_ifaces)" for iface in "${ifaces[@]}"; do __network_set_gateway "$iface" + [ -n "$gateway" ] || gateway=- __network_set_confip "$iface" "$gateway" || continue array_add confips "$confip" done @@ -462,6 +483,7 @@ BEGIN { have_iface = 0 have_method = 0 # static ou dhcp have_gateway = ipsuffixes[1] == "dhcp" || gateway == "" + if (gateway == "none" || gateway == "-") gateway = "" array_new(have_ips) array_new(have_ip_ups) array_new(have_ip_downs) @@ -692,6 +714,8 @@ function network_interfaces_add_confip() { local nifile="${4:-$__DEBIAN_NETWORK_INTERFACES}" local mainip netmask method + [ "$gateway" == "none" -o "$gateway" == "-" ] && gateway= + network_parse_ipsuffix "${ipsuffixes[0]}" mainip netmask if [ "$mainip" == "dhcp" ]; then mainip= @@ -928,7 +952,9 @@ $ip$TAB$host $hostname" "$tmpfile" } function network_config() { - # (Re)configurer le réseau sur l'hôte courant. + # (Re)configurer le réseau sur l'hôte courant. Des efforts sont faits pour + # ne mettre à jour les fichiers que si c'est nécessaire. Si un des arguments + # $1..$6 n'est pas spécifié, il est ignoré. # $1 (host) est le nom d'hôte. # $2 (confips) est le nom d'un tableau contenant la configuration des # adresses ips pour les interfaces. @@ -939,9 +965,12 @@ function network_config() { # son interface. En principe, l'interface principale est le premier bridge # défini ou la première interface définie. # $5 (reset_interfaces) spécifie de ne pas chercher à mettre à jour le - # fichier /etc/network/interfaces, mais de le recréer depuis zéro. + # fichier /etc/network/interfaces, mais de le recréer depuis zéro. la valeur + # 'force' spécifie de recréer le fichier même si ce n'est pas nécessaire à + # priori. # $6 (oldhost) est le nom d'hôte actuel, avant la modification - # Si un des arguments n'est pas spécifié, il est ignoré. + # $7(=/etc/network/interfaces) est le fichier à mettre à jour + # # Le tableau confips doit contenir des définitions d'une des formes # suivantes: # [[iface][//gateway]:]address[/suffix],... @@ -952,6 +981,7 @@ function network_config() { # interface, seule la première spécification d'adresse IP tient compte de # l'argument gateway. Les autres spécifications définissent des adresses IP # supplémentaires pour l'interface. + # # Le tableau brs doit contenir des définitions de la forme suivante: # br:ifaces,... # br est le nom du bridge, e.g. br0. ifaces est une liste d'interfaces @@ -967,6 +997,7 @@ function network_config() { local -a confbrs; array_copy confbrs __nc_confbrs local mainiface="$4" reset_interfaces="$5" oldhost="$6" local modified + local nifile="${7:-$__DEBIAN_NETWORK_INTERFACES}" network_fix_confs confbrs confips "$mainiface" @@ -1001,13 +1032,16 @@ function network_config() { # vérifier si une modification est nécessaire local modify - for confbr in "${confbrs[@]}"; do - network_parse_confbr "$confbr" br ifaces - if ! network_interfaces_check_confbr "$br" iface; then - modify=1 - break - fi - done + [ "$reset_interfaces" == force ] && modify=1 + if [ -z "$modify" ]; then + for confbr in "${confbrs[@]}"; do + network_parse_confbr "$confbr" br ifaces + if ! network_interfaces_check_confbr "$br" iface; then + modify=1 + break + fi + done + fi if [ -z "$modify" ]; then for confip in "${confips[@]}"; do network_parse_confip "$confip" iface gateway ipsuffixes @@ -1022,13 +1056,17 @@ function network_config() { if [ -n "$modify" ]; then # faire une copie de travail local interfaces; ac_set_tmpfile interfaces - cat "$__DEBIAN_NETWORK_INTERFACES" >"$interfaces" + cat "$nifile" >"$interfaces" if [ -n "$reset_interfaces" ]; then + local source_itfd + check_sysinfos -d debian -v jessie+ && source_itfd=1 echo >"$interfaces" "\ # This file describes the network interfaces available on your system # and how to activate them. For more information, see interfaces(5). - +${source_itfd:+ +source /etc/network/interfaces.d/* +} # The loopback network interface auto lo iface lo inet loopback @@ -1059,21 +1097,21 @@ iface lo inet loopback # Fin de traitement if [ -n "$__DEBIAN_NETWORK_DEVEL_SHOW_MODIFS" ]; then - if testdiff "$interfaces" "$__DEBIAN_NETWORK_INTERFACES"; then - uecho "Setting $__DEBIAN_NETWORK_INTERFACES to:" + if testdiff "$interfaces" "$nifile"; then + uecho "Setting $nifile to:" cat "$interfaces" | sed 's/^/ /g' 1>&2 else - uecho "$__DEBIAN_NETWORK_INTERFACES: pas de modifications" + uecho "$nifile: pas de modifications" fi - elif testdiff "$interfaces" "$__DEBIAN_NETWORK_INTERFACES"; then - __network_backup "$__DEBIAN_NETWORK_INTERFACES" + elif testdiff "$interfaces" "$nifile"; then + __network_backup "$nifile" if show_debug; then - edebug "Setting $__DEBIAN_NETWORK_INTERFACES to:" + edebug "Setting $nifile to:" cat "$interfaces" | sed 's/^/ /g' 1>&2 else - estep "$__DEBIAN_NETWORK_INTERFACES" + estep "$nifile" fi - cat "$interfaces" >"$__DEBIAN_NETWORK_INTERFACES" + cat "$interfaces" >"$nifile" modified=1 fi ac_clean "$interfaces" @@ -1112,3 +1150,68 @@ iface lo inet loopback [ -z "$modified" ] && return 10 return 0 } + +function network_config_partial() { + # une version allégée de network_config() qui ne permet que de rajouter des + # adresses IPs en plus de celles qui sont déjà configurées. + # $1(=confips) est le nom d'un tableau contenant la configuration des + # adresses ips pour les interfaces. + # $2(=/etc/network/interfaces) est le fichier à mettre à jour + local -a __ncp_newconfips; array_copy __ncp_newconfips "${1:-confips}" + local nifile="${2:-$__DEBIAN_NETWORK_INTERFACES}" + + local -a confips confbrs + network_set_confips + network_set_confbrs + network_fix_confs + + array_extend confips __ncp_newconfips + network_config "" confips confbrs "" "" "" "$nifile" || return + + # des modifications ont eu lieu, tenter de configurer les nouvelles adresses + # d'abord calculer les adresses ips qui sont activées + network_set_confips + local -a ips + local ipsuffix ip suffix + for confip in "${confips[@]}"; do + network_parse_confip "$confip" iface gateway ipsuffixes + for ipsuffix in "${ipsuffixes[@]}"; do + network_parse_ipsuffix "$ipsuffix" ip suffix + array_addu ips "$ip" + done + done + # puis lister les commandes correspondant aux adresses ips non activées + local -a ups; local up + array_from_lines ups "$(awkrun <"$nifile" -f ips[@] ' +$1 == "up" && $2 == "ip" && $3 == "addr" && $4 == "add" { + ip = $5; sub(/(\/[0-9.]+)?$/, "", ip) # enlever suffixe + if (! in_array(ip, ips)) { + $1 = "" + print + } +}')" + if [ ${#ups[*]} -gt 0 ]; then + etitle "Configuration des nouvelles adresses" + for up in "${ups[@]}"; do + estep "$up" + eval "$up" + done + eend + fi + + return 0 +} + +function network_update_etc_networks() { + # écraser le contenu du fichier /etc/networks avec la chaine $1 + # si $1 est vide, initialiser le fichier avec un contenu par défaut. + local contents="$1" TAB=$'\t' + [ -n "$contents" ] || contents="\ +default${TAB}${TAB}0.0.0.0 +loopback${TAB}127.0.0.0 +link-local${TAB}169.254.0.0" + if [ "$(/etc/networks + fi +} From 6366256c5234fbc458466c4d01f02836bdd7c1e6 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:14:53 +0400 Subject: [PATCH 33/39] module ipcalc: ajout de ipcalc_network() --- lib/ulib/ipcalc | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/lib/ulib/ipcalc b/lib/ulib/ipcalc index 9616095..61ca049 100644 --- a/lib/ulib/ipcalc +++ b/lib/ulib/ipcalc @@ -83,6 +83,32 @@ function ipcalc_netmask() { return 0 } +function ipcalc_network() { + # Calculer l'adresse de réseau correspondant à l'adresse ip $1. Le masque + # de sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou + # /x.x.x.x ou donné dans l'argument $2. Seuls les suffixes 0, 8, 16, 24, 32 + # sont supportés. + # Retourner 1 si un erreur s'est produite, par exemple si l'adresse ou le + # suffixe sont invalides ou non supportés. + [ -n "$1" ] || return + local ip mask + + ipcalc_splitipmask "$1" ip mask + [ -n "$mask" ] || mask="$2" + [ -n "$mask" ] || mask=24 + + ip="$(ipcalc_checkip "$ip")" || return + mask="$(ipcalc_checkmask "$mask")" || return + + case "$mask" in + 0) echo "0.0.0.0";; + 8) ip="${ip%.*.*.*}"; echo "$ip.0.0.0";; + 16) ip="${ip%.*.*}"; echo "$ip.0.0";; + 24) ip="${ip%.*}"; echo "$ip.0";; + 32) echo "$ip";; + esac +} + function ipcalc_broadcast() { # Calculer l'adresse de broadcast correspondant à l'adresse ip $1. Le masque # de sous-réseau peut-être indiqué dans l'adresse ip avec le suffixe /n ou From fd9105fa44f06309336a43b182b1be0f82d085c5 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:17:46 +0400 Subject: [PATCH 34/39] =?UTF-8?q?bug=20dans=20le=20module=20base.num:=20'i?= =?UTF-8?q?s*num=20-'=20=C3=A9tait=20vrai?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/base.num | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/lib/ulib/base.num b/lib/ulib/base.num index a07c96a..9725806 100644 --- a/lib/ulib/base.num +++ b/lib/ulib/base.num @@ -6,24 +6,22 @@ uprovide base.num function isnum() { # retourner vrai si $1 est une valeur numérique entière (positive ou négative) [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { # retourner vrai si $1 est une valeur numérique entière positive [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { # retourner vrai si $1 est une valeur numérique réelle (positive ou négative) # le séparateur décimal peut être . ou , [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" From b920641fca897351ede034949889e18e6092cb61 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:18:56 +0400 Subject: [PATCH 35/39] =?UTF-8?q?l'installation=20distante=20de=20modules?= =?UTF-8?q?=20uinst=20par=20rruns=20se=20fait=20par=20d=C3=A9faut=20sans?= =?UTF-8?q?=20confirmation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/runs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/ulib/runs b/lib/ulib/runs index 4fa2f06..1f9aa55 100644 --- a/lib/ulib/runs +++ b/lib/ulib/runs @@ -888,8 +888,7 @@ function runs_initworkdir() { >"$RUNSEXPORTDIR/varsfile" >"$RUNSEXPORTDIR/localuser" >"$RUNSEXPORTDIR/localroot" - >"$RUNSEXPORTDIR/remoteuser" - >"$RUNSEXPORTDIR/remoteroot" + >"$RUNSEXPORTDIR/remote" # synchronisation ulib # comme on copie runs et uinst, il faut copier ulib et pyulib comme pour nutools: dans le répertoire lib/ @@ -917,6 +916,7 @@ exit 0 cd "$(dirname "$0")" '"$args_def"' if [ -s remote ]; then + export RUNS_REMOTE_SET_Y=1 [ -f remote-needs-root ] && args=("${args[@]}" -s) "${args[@]}" -r remote || exit 1 fi @@ -1628,6 +1628,13 @@ function runs_action_run() { __vars=("${__vars[@]}" "$(set_var_cmd "$__name" "$__value")") done + if [ -n "$RUNS_REMOTE_SET_Y" ]; then + # par défaut, sur l'hôte distant, utiliser un niveau + # d'interaction plus bas que runs + # ainsi, (rruns) ==> (uinst -y) et (rruns -i) ==> (uinst) + set_interaction -y + fi + urequire uinst udir prefixes uinc # Il faut définir UINST, chemin vers le script uinst. UINST="$RUNSSRCDIR/uinst" From ed1633a30bee00595cfbd6c07ea9f01ad0424f52 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:21:25 +0400 Subject: [PATCH 36/39] =?UTF-8?q?module=20template:=20support=20de=20varia?= =?UTF-8?q?bles=20suppl=C3=A9mentaires=20avec=20TEMPLATE=5FUSER=5FVARS=20e?= =?UTF-8?q?t=20de=20la=20description=20des=20variables=20dans=20le=20fichi?= =?UTF-8?q?er=20de=20configuration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/ulib/template | 67 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 57 insertions(+), 10 deletions(-) diff --git a/lib/ulib/template b/lib/ulib/template index 0699063..ff7f140 100644 --- a/lib/ulib/template +++ b/lib/ulib/template @@ -419,15 +419,42 @@ function template_srcdir() { fi } +function template_build_vars() { +# Initialiser les tableaux $1 et $2 avec la description des variables $3..@ +# les descriptions sont de la forme var[:depvars,...]=desc +# $1 reçoit les noms (depvars... var) parce que les variables dépendantes +# doivent toujours être placées AVANT la variable maitre +# $2 reçoit les noms (depvars...) +# pour chaque description, une variable __TEMPLATE_DESC_var est créée avec +# la valeur desc + local __t_destvs="$1"; shift + local __t_destnw="$1"; shift + local -a __t_depvars + local __t_vardesc __t_var __t_depvar __t_desc + for __t_vardesc in "$@"; do + splitvar "$__t_vardesc" __t_depvar __t_desc + splitpair "$__t_depvar" __t_var __t_depvar + array_split __t_depvars "$__t_depvar" , + for __t_depvar in "${__t_depvars[@]}"; do + array_addu "$__t_destvs" "$__t_depvar" + [ -n "$__t_destnw" ] && array_addu "$__t_destnw" "$__t_depvar" + done + array_del "$__t_destvs" "$__t_var" + array_add "$__t_destvs" "$__t_var" + eval "__TEMPLATE_DESC_$__t_var=\"\$__t_desc\"" + done +} + function templatectl_config() { -# Obtenir le chemin vers le fichier de configuration pour le répertoire $1 -# Si $2==nohideconfig, utiliser le nom CONFIG.conf, sinon utiliser par défaut -# .CONFIG sauf si le fichier CONFIG.conf existe +# Obtenir le chemin vers le fichier de configuration pour le répertoire $1 Si +# l'un des fichiers CONFIG.conf ou .CONFIG existent déjà, prendre ceux-là. +# Sinon, si $2==nohideconfig, utiliser le nom CONFIG.conf, sinon utiliser +# .CONFIG local config="$(basename -- "$TEMPLATECTL_CONFIG")" - if [ -f "$1/$config.conf" -o "$2" == nohideconfig ]; then - echo "$1/$config.conf" - else - echo "$1/.$config" + if [ -f "$1/$config.conf" ]; then echo "$1/$config.conf" + elif [ -f "$1/.$config" ]; then echo "$1/.$config" + elif [ "$2" == nohideconfig ]; then echo "$1/$config.conf" + else echo "$1/.$config" fi } @@ -449,6 +476,8 @@ function templatectl_loadvars() { # devraient pas être écrits dans le fichier des variables, sauf si elles # reçoivent une valeur explicite de la part de l'utilisateur. Ce tableau est # mis à jour lors de l'analyse du tableau TEMPLATECTL_VARS +# - TEMPLATE_USER_VARS contient une liste de noms de tableaux qui sont définis +# en plus et qui peuvent être utilisés par des scripts annexes local -a __template_vars __dynamic_vars local __t_var __t_name __t_value @@ -469,6 +498,11 @@ function templatectl_loadvars() { done array_contains TEMPLATE_DYNAMIC_VARS configdir && __template_set_var configdir "$configdir" + is_defined TEMPLATE_USER_VARS || TEMPLATE_USER_VARS=() + for __t_var in "${TEMPLATE_USER_VARS[@]}"; do + is_defined "$__t_var" || array_new "$__t_var" + done + local __t_modified=1 for __t_var in "${TEMPLATECTL_VARS[@]}"; do splitvar "$__t_var" __t_name __t_value @@ -479,20 +513,33 @@ function templatectl_loadvars() { function templatectl_writevars() { # Ecrire les variables dans le fichier $1 - local __t_var + local __t_var __t_desc echo "# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8" >"$1" + for __t_var in "${TEMPLATE_USER_VARS[@]}"; do + __t_desc="__TEMPLATE_DESC_$__t_var" + [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1" + if is_array "$__t_var"; then + set_array_cmd "$__t_var" >>"$1" + else + echo_setv "$__t_var" "${!__t_var}" >>"$1" + fi + done if [ ${#TEMPLATE_DYNAMIC_VARS[*]} -gt 0 ]; then echo "__dynamic_vars=(" >>"$1" for __t_var in "${TEMPLATE_DYNAMIC_VARS[@]}"; do array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue - echo_setv "$__t_var=${!__t_var}" >>"$1" + __t_desc="__TEMPLATE_DESC_$__t_var" + [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1" + echo_setv "$__t_var" "${!__t_var}" >>"$1" done echo ")" >>"$1" fi echo "__template_vars=(# ne pas modifier" >>"$1" for __t_var in "${TEMPLATE_STATIC_VARS[@]}"; do array_contains TEMPLATE_NOWRITE_VARS "$__t_var" && continue - echo_setv "$__t_var=${!__t_var}" >>"$1" + __t_desc="__TEMPLATE_DESC_$__t_var" + [ -n "${!__t_desc}" ] && echo "# ${!__t_desc}" >>"$1" + echo_setv "$__t_var" "${!__t_var}" >>"$1" done echo ")" >>"$1" } From 683d5df062d03f3862375d04449635e7810c2f47 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:22:42 +0400 Subject: [PATCH 37/39] =?UTF-8?q?pour=20simplifier=20le=20d=C3=A9buggage,?= =?UTF-8?q?=20set=20-x=20est=20d=C3=A9sactiv=C3=A9=20pour=20les=20fonction?= =?UTF-8?q?=20urequire(),=20parse=5Fargs()=20et=20awkdef()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .udir | 2 +- lib/ulib/base | 5 +- lib/ulib/base.args | 19 +- lib/ulib/base.string | 4 + lib/ulib/support/install-pubkeys.sh | 1111 ++- lib/ulib/ulib | 4 + lib/ulib/ulibsh | 4 + ucrontab | 9814 ++++++++++++++++----------- 8 files changed, 6843 insertions(+), 4120 deletions(-) diff --git a/.udir b/.udir index a24fb4c..6a259d2 100644 --- a/.udir +++ b/.udir @@ -19,7 +19,7 @@ openvz_service= # Faut-il installer le service kill-ssh-user-sessions? ksus_service= -configure_variables=(dest uninst_utools rm_utools kvm_service openvz_service) +configure_variables=(dest uninst_utools rm_utools kvm_service openvz_service ksus_service) configure_dest_for=(bashrc profile lib/uinst/conf lib/uinst/rootconf lib/profile.d/nutools.shared lib/bashrc.d/bash_completion.shared lib/init.d/kvm-stop-all legacy/sysinc/utools legacy/sysinc/system_caps legacy/sysinc/private/init) config_scripts=(lib/uinst/conf lib/uinst/system_caps.legacy) install_profiles=true diff --git a/lib/ulib/base b/lib/ulib/base index c71fc4d..1987ab1 100644 --- a/lib/ulib/base +++ b/lib/ulib/base @@ -2464,6 +2464,7 @@ function awkdef() { # pour un tableau values qui contiendra deux valeurs: value1 et value2 # Avec l'option -f, des fonctions supplémentaires sont définies. Elles sont # décrites dans le module awk. + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_AWKDEF_SET_X=1; } # désactiver set -x pour cette fonction if [ "${1:0:3}" == "-f" ]; then shift @@ -2541,6 +2542,8 @@ function awkdef() { rawecho "$__ad_arg" done fi + + [ -n "$__ULIB_AWKDEF_SET_X" ] && set -x; return 0 } function lawkrun() { # wrapper pour lancer awk avec un script préparé par awkdef. Les définitions et @@ -3570,7 +3573,7 @@ function ask_any() { format="${format/+X/oN}" fi local i count="${#format}" - + if [ -n "$interactive" ]; then eflush local message="${1:-Voulez-vous continuer?}" diff --git a/lib/ulib/base.args b/lib/ulib/base.args index 40f816c..44f971c 100644 --- a/lib/ulib/base.args +++ b/lib/ulib/base.args @@ -288,6 +288,8 @@ function parse_opts() { # flags_ contient la liste des flags pour les options: '' pour une option # simple, ':' pour option avec argument obligatoire, '::' pour option avec # argument facultatif + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_OPTS_SET_X=1; } # désactiver set -x pour cette fonction + local -a options_ names_ flags_ destargs_ local opts_ longopts_ __po_parse_optdescs "$@" || shift $? @@ -297,8 +299,11 @@ function parse_opts() { __po_process_options "$@" else [ -n "$destargs_" ] && setv "$destargs_" "$args_" + [ -n "$__ULIB_SET_X" ] && set -x return 1 fi + + [ -n "$__ULIB_PARSE_OPTS_SET_X" ] && set -x; return 0 } function parse_args_check() { @@ -308,9 +313,15 @@ function parse_args_check() { # Exemple d'utilisation: # args=(...) # parse_args_check "$@" || return; set -- "${args[@]}" - parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0 - eerror "$args" - return 1 + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction + if parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 0 + else + eerror "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 1 + fi } function parse_args() { # Simplifier l'utilisation de parse_opts(). En entrée, le tableau args doit être @@ -320,7 +331,9 @@ function parse_args() { # Exemple d'utilisation: # args=(...) # parse_args_check "$@"; set -- "${args[@]}" + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x; return 0 } HELP_DESC= diff --git a/lib/ulib/base.string b/lib/ulib/base.string index eb91a50..9a7fba3 100644 --- a/lib/ulib/base.string +++ b/lib/ulib/base.string @@ -6,6 +6,10 @@ uprovide base.string urequire base.core +# IMPORTANT: aucune des fonctions suivantes ne met en échappement les valeur des +# patterns. Ainsi, si un pattern contient des caractères interdits comme \ ou $, +# il faut d'abord le traiter avec _qval() + function straddp() { # ajouter le préfixe $1 à $2* local p="$1"; shift diff --git a/lib/ulib/support/install-pubkeys.sh b/lib/ulib/support/install-pubkeys.sh index efe0c41..40fc73c 100755 --- a/lib/ulib/support/install-pubkeys.sh +++ b/lib/ulib/support/install-pubkeys.sh @@ -62,6 +62,7 @@ export TMPDIR="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}" [ -z "$USER" -a -n "$LOGNAME" ] && export USER="$LOGNAME" +[ -f /etc/debian_chroot ] && UTOOLS_CHROOT=1 [ -f /etc/nutoolsrc ] && . /etc/nutoolsrc [ -f ~/.nutoolsrc ] && . ~/.nutoolsrc true @@ -357,12 +358,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -453,6 +533,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -490,21 +602,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -519,21 +629,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -628,6 +736,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -651,6 +761,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote ##@inc[base.split ## Fonctions de base: analyse et découpage de valeurs @@ -1051,12 +1169,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -1147,6 +1344,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -1374,6 +1603,8 @@ function parse_opts() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_OPTS_SET_X=1; } # désactiver set -x pour cette fonction + local -a options_ names_ flags_ destargs_ local opts_ longopts_ __po_parse_optdescs "$@" || shift $? @@ -1383,17 +1614,28 @@ function parse_opts() { __po_process_options "$@" else [ -n "$destargs_" ] && setv "$destargs_" "$args_" + [ -n "$__ULIB_SET_X" ] && set -x return 1 fi + + [ -n "$__ULIB_PARSE_OPTS_SET_X" ] && set -x; return 0 } function parse_args_check() { - parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0 - eerror "$args" - return 1 + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction + if parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 0 + else + eerror "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 1 + fi } function parse_args() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x; return 0 } HELP_DESC= @@ -1766,12 +2008,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -1862,6 +2183,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -2089,6 +2442,8 @@ function parse_opts() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_OPTS_SET_X=1; } # désactiver set -x pour cette fonction + local -a options_ names_ flags_ destargs_ local opts_ longopts_ __po_parse_optdescs "$@" || shift $? @@ -2098,17 +2453,28 @@ function parse_opts() { __po_process_options "$@" else [ -n "$destargs_" ] && setv "$destargs_" "$args_" + [ -n "$__ULIB_SET_X" ] && set -x return 1 fi + + [ -n "$__ULIB_PARSE_OPTS_SET_X" ] && set -x; return 0 } function parse_args_check() { - parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0 - eerror "$args" - return 1 + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction + if parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 0 + else + eerror "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 1 + fi } function parse_args() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x; return 0 } HELP_DESC= @@ -2602,6 +2968,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -2609,21 +3053,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -2638,21 +3080,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -2742,6 +3182,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -2765,6 +3207,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -3318,8 +3768,8 @@ function ppath() { [ -n "$cwd" ] || cwd="$(pwd)" [ "$path" = "$cwd" ] && path="." - [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path/#$cwd\//}" - path="${path/#$HOME/~}" + [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path#$cwd/}" + [ "${path#$HOME/}" != "$path" ] && path="~${path#$HOME}" rawecho "$path" } @@ -3734,15 +4184,18 @@ function __cpdir() { } __CPNOVCS_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) __CPNOVCS_RSYNC_ARGS=(-q) +__CPNOVCS_INCLUDE_VCS= # ne pas ignorer les répertoires de VCS function cpnovcs() { local src="$1" destdir="$2" [ -d "$destdir" ] || mkdir -p "$destdir" || return 1 if progexists rsync; then - local gitexclude=/.git/ - if [ "${src%/}" == "$src" ]; then - gitexclude="/$(basename -- "$src")$gitexclude" + local -a novcs + if [ -z "$__CPNOVCS_INCLUDE_VCS" ]; then + local gitexclude=/.git/ + [ "${src%/}" == "$src" ] && gitexclude="/$(basename -- "$src")$gitexclude" + novcs=(--exclude CVS/ --exclude .svn/ --exclude "$gitexclude") fi - rsync -a ${__CPNOVCS_RSYNC_SLOW:+-c} --exclude CVS/ --exclude .svn/ --exclude "$gitexclude" "${__CPNOVCS_RSYNC_ARGS[@]}" "$src" "$destdir/" + rsync -a ${__CPNOVCS_RSYNC_SLOW:+-c} "${novcs[@]}" "${__CPNOVCS_RSYNC_ARGS[@]}" "$src" "$destdir/" elif [ "${src%/}" != "$src" ]; then __cpdir "$src" "$destdir" else @@ -3751,6 +4204,10 @@ function cpnovcs() { __cpdir "$src" "$destdir/$srcname" fi } +function cpvcs() { + local __CPNOVCS_INCLUDE_VCS=1 + cpnovcs "$@" +} function cpdirnovcs() { if [ -d "$1" ]; then cpnovcs "$1/" "$2" @@ -4175,8 +4632,10 @@ function ensure_hostname() { ewarn "Cette commande n'est valide que sur l'hôte ${userhosts[0]}" fi - enote "Vous pouvez tenter de relancer le script sur ${userhosts[0]}, mais cela requière que ce script ET les données dont il a besoin soient installés dans la même version et dans le même répertoire sur l'hôte distant" - if ask_yesno "Voulez-vous tenter de relancer le script sur l'hôte distant?" N; then + eimportant "Vous pouvez tenter de relancer le script sur ${userhosts[0]}" + eimportant "Cela requière que ce script ET les données dont il a besoin soient installés dans la même version et dans le même répertoire sur l'hôte distant. +En l'occurence, ce script est accédé par le chemin $script et ce chemin doit exister aussi sur le serveur distant." + if ask_yesno "Voulez-vous tenter de relancer le script sur l'hôte distant?" X; then splitfsep "${userhosts[0]}" : userhost path splituserhost "$userhost" user host [ -n "$user" ] || user=root @@ -4222,7 +4681,7 @@ function hex(i, s) { if (length(s) < 2) s = "0" s return s } -function quote_html(s) { +function qhtml(s) { gsub(/&/, "\\&", s) gsub(/"/, "\\"", s) gsub(/>/, "\\>", s) @@ -4236,39 +4695,57 @@ function unquote_html(s) { gsub(/&/, "\\&", s) return s } -function quote_value(s) {'" +function qval(s) {'" gsub(/'/, \"'\\\\''\", s) return \"'\" s \"'\" "'} -function quoted_values( i, line) { +function sqval(s) { + return " " qval(s) +} +function qvals( i, line) { line = "" for (i = 1; i <= NF; i++) { if (i > 1) line = line " " - line = line quote_value($i) + line = line qval($i) } return line } -function quote_regexp(s) { +function sqvals() { + return " " qvals() +} +function qarr(values, prefix, i, count, line) { + line = prefix + count = array_len(values) + for (i = 1; i <= count; i++) { + if (i > 1 || line != "") line = line " " + line = line qval(values[i]) + } + return line +} +function qregexp(s) { gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) return s } -function quote_subrepl(s) { +function qsubrepl(s) { gsub(/\\/, "\\\\", s) gsub(/&/, "\\\\&", s) return s } -function quote_grep(s) { +function qgrep(s) { gsub(/[[\\.^$*]/, "\\\\&", s) return s } -function quote_egrep(s) { +function qegrep(s) { gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) return s } -function quote_sql(s) {'" +function qsql(s, suffix) {'" gsub(/'/, \"''\", s) - return \"'\" s \"'\" + return \"'\" s \"'\" (suffix != \"\"? \" \" suffix: \"\") "'} +function cqsql(s, suffix) { + return "," qsql(s, suffix) +} function unquote_mysqlcsv(s) { gsub(/\\n/, "\n", s) gsub(/\\t/, "\t", s) @@ -4276,6 +4753,24 @@ function unquote_mysqlcsv(s) { gsub(/\\\\/, "\\", s) return s } +function sval(s) { + if (s == "") return s + else return " " s +} +function cval(s, suffix) { + if (s == "") return s + else return "," s (suffix != ""? " " suffix: "") +} +function quote_html(s) { return qhtml(s) } +function quote_value(s) { return qval(s) } +function qsval(s) { return sqval(s) } +function quoted_values() { return qvals() } +function qsvals(s) { return sqvals(s) } +function quote_regexp(s) { return qregexp(s) } +function quote_subrepl(s) { return qsubrepl(s) } +function quote_grep(s) { return qgrep(s) } +function quote_egrep(s) { return qegrep(s) } +function quote_sql(s) { return qsql(s) } function __parse_date_fr(date, parts, y, m, d) { if (match(date, /([0-9][0-9]?)\/([0-9][0-9]?)\/([0-9][0-9][0-9][0-9])/, parts)) { @@ -4322,6 +4817,14 @@ function date_add(date, nbdays, serial) { return strftime("%d/%m/%Y", serial) } +function mkindices(values, indices, i, j) { + array_new(indices) + j = 1 + for (i in values) { + indices[j++] = int(i) + } + return asort(indices) +} function array_new(dest) { dest[0] = 0 # forcer awk à considérer dest comme un tableau delete dest @@ -4341,14 +4844,6 @@ function array_len(values, count, i) { } return count } -function mkindices(values, indices, i, j) { - array_new(indices) - j = 1 - for (i in values) { - indices[j++] = int(i) - } - return asort(indices) -} function array_copy(dest, src, count, indices, i) { array_new(dest) count = mkindices(src, indices) @@ -4741,6 +5236,7 @@ function b64decode(src, result, base1, base2, base3, base4) { } ' function awkdef() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_AWKDEF_SET_X=1; } # désactiver set -x pour cette fonction if [ "${1:0:3}" == "-f" ]; then shift @@ -4811,6 +5307,8 @@ function awkdef() { rawecho "$__ad_arg" done fi + + [ -n "$__ULIB_AWKDEF_SET_X" ] && set -x; return 0 } function lawkrun() { local -a __ar_defs __ar_args @@ -4943,6 +5441,43 @@ function utools_local() { done } +function stdredir() { + local __redirs __in __out __err + if [ -n "$1" -o "$1" == /dev/stdin ]; then + if [ "${1#<}" != "$1" ]; then + __in="${1#<}" + else + __in="$1" + fi + __redirs="$__redirs"' <"$__in"' + fi; shift + if [ -n "$1" -o "$1" == /dev/stdout ]; then + if [ "${1#>>}" != "$1" ]; then + __out="${1#>>}" + __redirs="$__redirs"' >>"$__out"' + elif [ "${1#>}" != "$1" ]; then + __out="${1#>}" + __redirs="$__redirs"' >"$__out"' + else + __out="$1" + __redirs="$__redirs"' >"$__out"' + fi + fi; shift + if [ -n "$1" -o "$1" == /dev/stderr ]; then + if [ "${1#>>}" != "$1" ]; then + __err="${1#>>}" + __redirs="$__redirs"' 2>>"$__err"' + elif [ "${1#>}" != "$1" ]; then + __err="${1#>}" + __redirs="$__redirs"' 2>"$__err"' + else + __err="$1" + __redirs="$__redirs"' 2>"$__err"' + fi + fi; shift + eval '"$@"'"$__redirs" +} + function isatty() { tty -s <&1 } @@ -5033,6 +5568,19 @@ function eerror_if() { fi } +function noerror() { + [ $# -gt 0 ] || set : + "$@" || return 0 +} +function noout() { + [ $# -gt 0 ] || return 0 + "$@" >/dev/null +} +function noerr() { + [ $# -gt 0 ] || return 0 + "$@" 2>/dev/null +} + TAB=$'\t' LATIN1=iso-8859-1 LATIN9=iso-8859-15 @@ -5176,9 +5724,9 @@ function __indent() { rawecho "$1" fi } -function __eerror() { tooenc "$(__edate)${__tlevel}E $(__indent "$1")"; } -function __ewarn() { tooenc "$(__edate)${__tlevel}W $(__indent "$1")"; } -function __enote() { tooenc "$(__edate)${__tlevel}N $(__indent "$1")"; } +function __eerror() { tooenc "$(__edate)${__tlevel}ERROR $(__indent "$1")"; } +function __ewarn() { tooenc "$(__edate)${__tlevel}WARNING $(__indent "$1")"; } +function __enote() { tooenc "$(__edate)${__tlevel}NOTE $(__indent "$1")"; } function __ebanner() { local maxi="${COLUMNS:-80}" local -a lines @@ -5200,23 +5748,23 @@ function __ebanner() { done tooenc "$psfix" } -function __eimportant() { tooenc "$(__edate)${__tlevel}! $(__indent "$1")"; } -function __eattention() { tooenc "$(__edate)${__tlevel}* $(__indent "$1")"; } -function __einfo() { tooenc "$(__edate)${__tlevel}I $(__indent "$1")"; } +function __eimportant() { tooenc "$(__edate)${__tlevel}IMPORTANT $(__indent "$1")"; } +function __eattention() { tooenc "$(__edate)${__tlevel}ATTENTION $(__indent "$1")"; } +function __einfo() { tooenc "$(__edate)${__tlevel}INFO $(__indent "$1")"; } function __eecho() { tooenc "$(__edate)${__tlevel}$(__indent "$1")"; } function __eecho_() { tooenc_ "$(__edate)${__tlevel}$(__indent "$1")"; } -function __edebug() { tooenc "$(__edate)${__tlevel}D $(__indent "$1")"; } +function __edebug() { tooenc "$(__edate)${__tlevel}DEBUG $(__indent "$1")"; } function __estep() { tooenc "$(__edate)${__tlevel}. $(__indent "$1")"; } -function __estepe() { __estep "$@"; } -function __estepw() { __estep "$@"; } -function __estepn() { __estep "$@"; } -function __estepi() { __estep "$@"; } +function __estepe() { tooenc "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw() { tooenc "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn() { tooenc "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi() { tooenc "$(__edate)${__tlevel}.I $(__indent "$1")"; } function __estep_() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1")"; } -function __estepe_() { __estep_ "$@"; } -function __estepw_() { __estep_ "$@"; } -function __estepn_() { __estep_ "$@"; } -function __estepi_() { __estep_ "$@"; } -function __etitle() { tooenc "$(__edate)${__tlevel}T $(__indent "$1")"; } +function __estepe_() { tooenc_ "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw_() { tooenc_ "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn_() { tooenc_ "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi_() { tooenc_ "$(__edate)${__tlevel}.I $(__indent "$1")"; } +function __etitle() { tooenc "$(__edate)${__tlevel}=== $(__indent "$1")"; } function __ebegin() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1"): "; } function __edoto() { echo_ "."; } function __edotw() { echo_ "w"; } @@ -5538,17 +6086,74 @@ function ask_yesno() { local r is_yes "$default" && prompt="[On]" if [ -n "$message" ]; then - tooenc_ "$message" 1>&2 + __eecho_ "$message" 1>&2 else - tooenc_ "Voulez-vous continuer?" "$UTF8" 1>&2 + OENC="$UTF8" __eecho_ "Voulez-vous continuer?" 1>&2 fi - tooenc_ " $prompt " "$UTF8" 1>&2 + OENC="$UTF8" tooenc_ " $prompt " 1>&2 uread r is_yes "${r:-$default}" else is_yes "$default" fi } +function ask_any() { + local interactive=1 + if [[ "$1" == -* ]]; then + if [ "$1" != -- ]; then + check_interaction "$1" || interactive= + fi + shift + else + check_interaction -c || interactive= + fi + local format="${2:-+Oq}" + format="${format/+O/On}" + format="${format/+N/oN}" + if [ -n "$interactive" ]; then + format="${format/+C/oN}" + format="${format/+X/On}" + else + format="${format/+C/On}" + format="${format/+X/oN}" + fi + local i count="${#format}" + + if [ -n "$interactive" ]; then + eflush + local message="${1:-Voulez-vous continuer?}" + local prompt="[$format]" + local r f lf defi + while true; do + __eecho_ "$message $prompt " 1>&2 + uread r + r="$(strlower "${r:0:1}")" + i=0; defi= + while [ $i -lt $count ]; do + f="${format:$i:1}" + lf="$(strlower "$f")" + [ "$r" == "$lf" ] && return $i + if [ -z "$defi" ]; then + [[ "$f" =~ [A-Z] ]] && defi="$i" + fi + if [ "$lf" == o ]; then + case "$r" in o|y|1|v|t) return $i;; esac + elif [ "$lf" == n ]; then + case "$r" in n|f|0) return $i;; esac + fi + i=$(($i + 1)) + done + [ -z "$r" ] && return ${defi:-0} + done + else + i=0 + while [ $i -lt $count ]; do + [[ "${format:$i:1}" =~ [A-Z] ]] && return $i + i=$(($i + 1)) + done + return 0 + fi +} function read_value() { local -a __rv_opts __rv_readline=1 __rv_showdef=1 __rv_nl= __rv_opts=() @@ -5584,12 +6189,12 @@ function __rv_read() { local __rv_r while true; do if [ -n "$__rv_msg" ]; then - tooenc_ "$__rv_msg" 1>&2 + __eecho_ "$__rv_msg" 1>&2 else - tooenc_ "Entrez la valeur" "$UTF8" 1>&2 + OENC="$UTF8" __eecho_ "Entrez la valeur" 1>&2 fi if [ -n "$__rv_readline" ]; then - tooenc_ ": " "$UTF8" 1>&2 + OENC="$UTF8" tooenc_ ": " 1>&2 uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r else if [ -n "$__rv_d" ]; then @@ -5599,7 +6204,7 @@ function __rv_read() { tooenc_ " [****]" 1>&2 fi fi - tooenc_ ": " "$UTF8" 1>&2 + OENC="$UTF8" tooenc_ ": " 1>&2 uread "${__rv_opts[@]}" __rv_r [ -n "$__rv_nl" ] && echo fi @@ -5630,24 +6235,24 @@ function simple_menu() { local __sm_c=0 __sm_i __sm_choice while true; do if [ "$__sm_c" == "0" ]; then - [ -n "$__sm_title" ] && tooenc "=== $__sm_title ===" 1>&2 + [ -n "$__sm_title" ] && __eecho "=== $__sm_title ===" 1>&2 __sm_i=1 for __sm_option in "${__sm_options[@]}"; do if [ "$__sm_option" == "$__sm_default" ]; then - tooenc "$__sm_i*- $__sm_option" 1>&2 + __eecho "$__sm_i*- $__sm_option" 1>&2 else - tooenc "$__sm_i - $__sm_option" 1>&2 + __eecho "$__sm_i - $__sm_option" 1>&2 fi let __sm_i=$__sm_i+1 done fi if [ -n "$__sm_yourchoice" ]; then - tooenc_ "$__sm_yourchoice" 1>&2 + __eecho_ "$__sm_yourchoice" 1>&2 else - tooenc_ "Entrez le numéro de l'option choisie" "$UTF8" 1>&2 + OENC="$UTF8" __eecho_ "Entrez le numéro de l'option choisie" 1>&2 fi - tooenc_ ": " "$UTF8" 1>&2 + OENC="$UTF8" tooenc_ ": " 1>&2 uread __sm_choice if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then @@ -5667,7 +6272,7 @@ function simple_menu() { let __sm_c=$__sm_c+1 if [ "$__sm_c" -eq 5 ]; then - tooenc "" "$UTF8" 1>&2 + OENC="$UTF8" tooenc "" 1>&2 __sm_c=0 fi done @@ -5993,6 +6598,18 @@ function debug_tee() { } +function get_user_defaults_file() { + if [ -r "$HOME/etc/default.${HOSTNAME%%.*}/$1" ]; then + echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" + elif [ -r "$HOME/etc/default/$1" ]; then + echo "$HOME/etc/default/$1" + elif [ -n "$UTOOLS_LOCAL_PROFILES" ]; then + echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" + else + echo "$HOME/etc/default/$1" + fi +} + function get_defaults_files() { local __gd_dest="${1:-defaults}"; shift local -a __gd_fs @@ -6091,96 +6708,158 @@ function __setup_ALL_SYSvars() { __setup_ALL_SYSvars unset -f __setup_ALL_SYSvars +function __compute_local_sysinfos_data() { + SYSINFOS_DATA=( + "$UNAME_SYSTEM" + "$UNAME_MACHINE" + "$([ -f /etc/debian_version ] && cat /etc/debian_version)" + "$([ -f /etc/gentoo-release ] && cat /etc/gentoo-release)" + "$([ -f /etc/redhat-release ] && cat /etc/redhat-release)" + "$([ -f /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist)" + "$([ -f /System/Library/Frameworks/CoreServices.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Resources/version.plist)" + ) +} +function __dump_remote_sysinfos_data() { + "${2:-ssh}" "$1" "\ +uname -s +echo .----------------. +uname -m +echo .----------------. +[ -f /etc/debian_version ] && cat /etc/debian_version +echo .----------------. +[ -f /etc/gentoo-release ] && cat /etc/gentoo-release +echo .----------------. +[ -f /etc/redhat-release ] && cat /etc/redhat-release +echo .----------------. +[ -f /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist +echo .----------------. +[ -f /System/Library/Frameworks/CoreServices.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Resources/version.plist +echo .----------------." +} +function __build_sysinfos_data() { + awk ' +BEGIN { + data = "" + have_data = 0 + print "SYSINFOS_DATA=(" +} +function read_data() { + if (have_data) data = data "\n" + else have_data = 1 + data = data $0 +} +function dump_data() { + gsub(/'\''/, "'\'\\\\\'\''", data) + print "'\''" data "'\''" + data = "" + have_data = 0 +} +$0 == ".----------------." { dump_data(); next } +{ read_data() } +END { + dump_data() + print ")" +} +' +} +function __compute_sysinfos() { + local system="${SYSINFOS_DATA[0]}" + local machine="${SYSINFOS_DATA[1]}" + local debian_version="${SYSINFOS_DATA[2]}" + local gentoo_release="${SYSINFOS_DATA[3]}" + local redhat_release="${SYSINFOS_DATA[4]}" + local macosx_plist1="${SYSINFOS_DATA[5]}" + local macosx_plist2="${SYSINFOS_DATA[6]}" + + if [ "$system" == "Linux" ]; then + case "$machine" in + x86_64) MYSYSNAME=(linux64 linux); MYBITS=64;; + i386|i586|i686) MYSYSNAME=(linux32 linux); MYBITS=32;; + ppc) MYSYSNAME=(linuxppc32 linuxppc linux); MYBITS=32;; + ppc64) MYSYSNAME=(linuxppc64 linuxppc linux); MYBITS=64;; + arm*) MYSYSNAME=(linuxarm linux);; + *) MYSYSNAME=(linux);; + esac + if [ -n "$debian_version" ]; then + case "$debian_version" in + 9*|stretch*) MYSYSDIST=(debian debianlike); MYSYSVER=(stretch);; + 8*|jessie*) MYSYSDIST=(debian debianlike); MYSYSVER=(jessie);; + 7*|wheezy*) MYSYSDIST=(debian debianlike); MYSYSVER=(wheezy);; + 6*|squeeze*) MYSYSDIST=(debian debianlike); MYSYSVER=(squeeze);; + 5*) MYSYSDIST=(debian debianlike); MYSYSVER=(lenny);; + 4*) MYSYSDIST=(debian debianlike); MYSYSVER=(etch);; + *) MYSYSDIST=(debianlike);; + esac + elif [ -n "$gentoo_release" ]; then + MYSYSDIST=(gentoo) + elif [ -n "$redhat_release" ]; then + case "$redhat_release" in + Fedora*) MYSYSDIST=(fedora redhatlike);; + Red*Hat*Enterprise*Linux*) MYSYSDIST=(rhel redhatlike);; + CentOS*) MYSYSDIST=(centos redhatlike);; + *) MYSYSDIST=(redhatlike);; + esac + case "$redhat_release" in + Fedora*14*) MYSYSVER=(fedora14);; + Fedora*13*) MYSYSVER=(fedora13);; + Fedora*12*) MYSYSVER=(fedora12);; + Fedora*11*) MYSYSVER=(fedora11);; + Red*Hat*Enterprise*Linux*release\ 7*) MYSYSVER=(rhel7 redhat7);; + Red*Hat*Enterprise*Linux*release\ 6*) MYSYSVER=(rhel6 redhat6);; + Red*Hat*Enterprise*Linux*release\ 5*) MYSYSVER=(rhel5 redhat5);; + Red*Hat*Enterprise*Linux*release\ 4*) MYSYSVER=(rhel4 redhat4);; + CentOS*release\ 7*) MYSYSVER=(centos7 redhat7);; + CentOS*release\ 6*) MYSYSVER=(centos6 redhat6);; + CentOS*release\ 5*) MYSYSVER=(centos5 redhat5);; + CentOS*release\ 4*) MYSYSVER=(centos4 redhat4);; + esac + fi + elif [ "$system" == "Darwin" ]; then + function get_macosx_version() { + local plist + for plist in "$@"; do + [ -n "$plist" ] || continue + echo "$plist" | grep -A 1 CFBundleShortVersionString | grep string | sed 's/.*//g +s/<\/string>.*$//g' + break + done + } + MYSYSNAME=(macosx darwin) + case "$(get_macosx_version)" in + 10.7*) MYSYSDIST=(lion);; + 10.6*) MYSYSDIST=(snowleopard);; + 10.5*) MYSYSDIST=(leopard);; + 10.4*) MYSYSDIST=(tiger);; + 10.3*) MYSYSDIST=(panther);; + esac + fi +} + +function compute_local_sysinfos() { + local SYSINFOS_DATA + __compute_local_sysinfos_data + __compute_sysinfos + if [ -n "$UTOOLS_CHROOT" ]; then + [ -n "$UTOOLS_SYSNAME" ] && eval "MYSYSNAME=($UTOOLS_SYSNAME)" + [ -n "$UTOOLS_BITS" ] && eval "MYBITS=$UTOOLS_BITS" + [ -n "$UTOOLS_SYSDIST" ] && eval "MYSYSDIST=($UTOOLS_SYSDIST)" + [ -n "$UTOOLS_SYSVER" ] && eval "MYSYSVER=($UTOOLS_SYSVER)" + fi +} +function compute_remote_sysinfos() { + local SYSINFOS_DATA + eval "$(__dump_remote_sysinfos_data "$@" | __build_sysinfos_data)" + __compute_sysinfos +} + +SYSINFOSLOCALS="\ +local -a MYSYSNAME MYSYSDIST MYSYSVER +local MYBITS" MYSYSNAME=() MYBITS= MYSYSDIST=() MYSYSVER=() -if [ "$UNAME_SYSTEM" == "Linux" ]; then - case "$UNAME_MACHINE" in - x86_64) - MYSYSNAME=(linux64 linux) - MYBITS=64 - ;; - i386|i586|i686) - MYSYSNAME=(linux32 linux) - MYBITS=32 - ;; - ppc) - MYSYSNAME=(linuxppc32 linuxppc linux) - MYBITS=32 - ;; - ppc64) - MYSYSNAME=(linuxppc64 linuxppc linux) - MYBITS=64 - ;; - arm*) - MYSYSNAME=(linuxarm linux) - ;; - *) - MYSYSNAME=(linux) - ;; - esac - - if [ -f /etc/debian_version ]; then - case "$(//g -s/<\/string>.*$//g' - break - fi - done - } - MYSYSNAME=(macosx darwin) - case "$(get_macosx_version)" in - 10.7*) MYSYSDIST=(lion);; - 10.6*) MYSYSDIST=(snowleopard);; - 10.5*) MYSYSDIST=(leopard);; - 10.4*) MYSYSDIST=(tiger);; - 10.3*) MYSYSDIST=(panther);; - esac -fi -if [ -n "$UTOOLS_CHROOT" ]; then - [ -n "$UTOOLS_SYSNAME" ] && eval "MYSYSNAME=($UTOOLS_SYSNAME)" - [ -n "$UTOOLS_BITS" ] && eval "MYBITS=$UTOOLS_BITS" - [ -n "$UTOOLS_SYSDIST" ] && eval "MYSYSDIST=($UTOOLS_SYSDIST)" - [ -n "$UTOOLS_SYSVER" ] && eval "MYSYSVER=($UTOOLS_SYSVER)" -fi +compute_local_sysinfos function __get_sysdist_alias() { if ! array_contains ALL_SYSDISTS "$1"; then @@ -6273,11 +6952,28 @@ function ensure_sysinfos() { __fix_sysinfos_downward } +function get_sysinfos_desc() { + local sysname_="${1:-MYSYSNAME}"; sysname_="${!sysname_}" + local sysdist_="${2:-MYSYSDIST}"; sysdist_="${!sysdist_}" + local sysver_="${3:-MYSYSVER}"; sysver_="${!sysver_}" + echo "$sysname_${sysdist_:+/$sysdist_}${sysver_:+/$sysver_}" +} + function check_sysinfos() { - local sysnamevar_="MYSYSNAME" - local sysdistvar_="MYSYSDIST" - local sysvervar_="MYSYSVER" - local bitsvar_="MYBITS" + local sysnamevar_ sysdistvar_ sysvervar_ bitsvar_ + if [ "$1" == --vars ]; then + shift + if [[ "$1" != -* ]]; then sysnamevar_="${1:-MYSYSNAME}"; shift; fi + if [[ "$1" != -* ]]; then sysdistvar_="${1:-MYSYSDIST}"; shift; fi + if [[ "$1" != -* ]]; then sysvervar_="${1:-MYSYSVER}"; shift; fi + if [[ "$1" != -* ]]; then bitsvar_="${1:-MYBITS}"; shift; fi + else + sysnamevar_="MYSYSNAME" + sysdistvar_="MYSYSDIST" + sysvervar_="MYSYSVER" + bitsvar_="MYBITS" + fi + local check_=sysname r_=0 while [ -n "$1" ]; do if [[ "$1" == -* ]]; then @@ -6321,6 +7017,7 @@ function check_sysinfos() { if array_contains "$sysdistvar_" "$sysdist_"; then r_=0 check_=skip + [ "$sysdist_" == "$value_" ] && break elif [ "$sysdist_" == "$value_" ]; then break fi @@ -6360,6 +7057,7 @@ function check_sysinfos() { if array_contains "$sysvervar_" "$sysver_"; then r_=0 check_=skip + [ "$sysver_" == "$value_" ] && break elif [ "$sysver_" == "$value_" ]; then break fi @@ -6388,6 +7086,59 @@ function check_sysinfos() { done return $r_ } + +function on_debian() { + NUTOOLS_ON_DEBIAN= + if check_sysinfos -d debian; then + urequire debian + if [ $# -gt 0 ]; then + NUTOOLS_ON_DEBIAN=debian + "$@" + else + NUTOOLS_ON_DEBIAN=1 + fi + return 0 + else + return 1 + fi +} +function on_debian:() { on_debian "$@"; } +function __on_debian() { + [ -z "$NUTOOLS_ON_DEBIAN" -o "$NUTOOLS_ON_DEBIAN" != 1 ] && return 1 + local sysver="$1"; shift + if [ $# -gt 0 ]; then + if check_sysinfos -d debian -v "$sysver"; then + NUTOOLS_ON_DEBIAN="$sysver" + "$@" + return 0 + else + return 1 + fi + else + if check_sysinfos -d debian -v "$sysver+"; then + NUTOOLS_ON_DEBIAN="$sysver" + return 0 + fi + fi +} + +function on_stretch() { __on_debian stretch "$@"; } +function on_jessie() { __on_debian jessie "$@"; } +function on_wheezy() { __on_debian wheezy "$@"; } +function on_squeeze() { __on_debian squeeze "$@"; } +function on_default() { + if [ "$NUTOOLS_ON_DEBIAN" == 1 ]; then + if [ $# -gt 0 ]; then + "$@" + return 0 + else + return 0 + fi + elif [ -n "$NUTOOLS_ON_DEBIAN" ]; then + return 1 + fi + return 1 +} ##@inc]../sysinfos ##@inc[../compat # Code de support pour les architectures autre que Linux diff --git a/lib/ulib/ulib b/lib/ulib/ulib index 02b44ac..38a8aa6 100644 --- a/lib/ulib/ulib +++ b/lib/ulib/ulib @@ -76,6 +76,8 @@ function urequire() { # Le module DEFAULTS est traité de façon particulière: si le fichier associé # n'est pas trouvé, charger base, pretty, sysinfos et compat à la place # Si un module n'est pas trouvé, quitter le script avec die() + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_UREQUIRE_SET_X=1; }; if [ -n "$__ULIB_UREQUIRE_SET_X" ]; then [ -n "$__ULIB_UREQUIRE_SET_X_RL1" ] || local __ULIB_UREQUIRE_SET_X_RL1; local __ULIB_UREQUIRE_SET_X_RL2=$RANDOM; [ -n "$__ULIB_UREQUIRE_SET_X_RL1" ] || __ULIB_UREQUIRE_SET_X_RL1=$__ULIB_UREQUIRE_SET_X_RL2; fi # désactiver set -x de manière réentrante + local __u_module __u_ulibdir __u_found [ -n "$*" ] || set DEFAULTS @@ -106,6 +108,8 @@ function urequire() { fi [ -n "$__u_found" ] || die "Unable to find $__u_module in ${ULIBDIR[*]}" done + + [ -n "$__ULIB_UREQUIRE_SET_X" -a "$__ULIB_UREQUIRE_SET_X_RL1" == "$__ULIB_UREQUIRE_SET_X_RL2" ] && set -x return 0 } diff --git a/lib/ulib/ulibsh b/lib/ulib/ulibsh index c7a6f55..68d6ec6 100644 --- a/lib/ulib/ulibsh +++ b/lib/ulib/ulibsh @@ -62,6 +62,8 @@ function uprovide() { __ULIB_FORCE_RELOAD= function urequire() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_UREQUIRE_SET_X=1; }; if [ -n "$__ULIB_UREQUIRE_SET_X" ]; then [ -n "$__ULIB_UREQUIRE_SET_X_RL1" ] || local __ULIB_UREQUIRE_SET_X_RL1; local __ULIB_UREQUIRE_SET_X_RL2=$RANDOM; [ -n "$__ULIB_UREQUIRE_SET_X_RL1" ] || __ULIB_UREQUIRE_SET_X_RL1=$__ULIB_UREQUIRE_SET_X_RL2; fi # désactiver set -x de manière réentrante + local __u_module __u_ulibdir __u_found [ -n "$*" ] || set DEFAULTS @@ -91,6 +93,8 @@ function urequire() { fi [ -n "$__u_found" ] || die "Unable to find $__u_module in ${ULIBDIR[*]}" done + + [ -n "$__ULIB_UREQUIRE_SET_X" -a "$__ULIB_UREQUIRE_SET_X_RL1" == "$__ULIB_UREQUIRE_SET_X_RL2" ] && set -x return 0 } diff --git a/ucrontab b/ucrontab index 4e09a41..b111c2e 100755 --- a/ucrontab +++ b/ucrontab @@ -150,6 +150,7 @@ export TMPDIR="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}" [ -z "$USER" -a -n "$LOGNAME" ] && export USER="$LOGNAME" +[ -f /etc/debian_chroot ] && UTOOLS_CHROOT=1 [ -f /etc/nutoolsrc ] && . /etc/nutoolsrc [ -f ~/.nutoolsrc ] && . ~/.nutoolsrc true @@ -179,16 +180,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -250,6 +241,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -408,12 +446,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -504,6 +621,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -541,21 +690,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -570,21 +717,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -679,6 +824,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -702,6 +849,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote ##@inc[base.split ## Fonctions de base: analyse et découpage de valeurs @@ -836,16 +991,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -907,6 +1052,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -1065,12 +1257,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -1161,6 +1432,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -1388,6 +1691,8 @@ function parse_opts() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_OPTS_SET_X=1; } # désactiver set -x pour cette fonction + local -a options_ names_ flags_ destargs_ local opts_ longopts_ __po_parse_optdescs "$@" || shift $? @@ -1397,17 +1702,28 @@ function parse_opts() { __po_process_options "$@" else [ -n "$destargs_" ] && setv "$destargs_" "$args_" + [ -n "$__ULIB_SET_X" ] && set -x return 1 fi + + [ -n "$__ULIB_PARSE_OPTS_SET_X" ] && set -x; return 0 } function parse_args_check() { - parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0 - eerror "$args" - return 1 + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction + if parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 0 + else + eerror "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 1 + fi } function parse_args() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x; return 0 } HELP_DESC= @@ -1514,16 +1830,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -1585,6 +1891,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -1743,12 +2096,91 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.string ## Fonctions de base: gestion des valeurs scalaires uprovide base.string urequire base.core + function straddp() { local p="$1"; shift echo "$p$*" @@ -1839,6 +2271,38 @@ function strrepl() { eval "$cmd" } +function strops() { + local -a __s_tmp + local __s_value="$1"; shift + while [ $# -gt 0 ]; do + case "$1" in + :-*|:=*|:\?*|:+*) eval '__s_value="${'"${__s_value}$1"'}"';; + d|deref) __s_value="${!__s_value}";; + dc|dcount|ds|dsize) + __s_value="${__s_value}[@]" + __s_tmp=("${!__s_value}") + __s_value="${#__s_tmp[@]}" + ;; + \#*|%*|/*|:*|^*|,*) eval '__s_value="${__s_value'"$1"'}"';; + l|length) __s_value="${#__s_value}";; + =|==|!=|\<|\>|-eq|-ne|-lt|-le|-gt|-ge) + __s_tmp=(\[ "$__s_value" "$@" ]); "${__s_tmp[@]}"; return $?;; + -n|-z) __s_tmp=(\[ "$1" "$__s_value" ]); "${__s_tmp[@]}"; return $?;; + +#*) eval '__s_value="'"${1#+#}"'$__s_value"';; + -#*) eval '__s_value="${__s_value'"${1#-}"'}"';; + +%*) eval '__s_value="$__s_value"'"${1#+%}";; + +*) eval '__s_value="$__s_value"'"${1#+}";; + -%*) eval '__s_value="${__s_value'"${1#-}"'}"';; + -*) eval '__s_value="${__s_value%'"${1#-}"'}"';; + mid|strmid) eval '__s_value="$(strmid "$2" "$__s_value")"'; shift;; + repl|strrepl) eval '__s_value="$(strrepl "$2" "$3" "$__s_value")"'; shift; shift;; + *) echo 1>&2 "strops: unknown operator: $1";; + esac + shift + done + echo "$__s_value" +} + function first_char() { local str="$*" echo "${str:0:1}" @@ -2066,6 +2530,8 @@ function parse_opts() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_OPTS_SET_X=1; } # désactiver set -x pour cette fonction + local -a options_ names_ flags_ destargs_ local opts_ longopts_ __po_parse_optdescs "$@" || shift $? @@ -2075,17 +2541,28 @@ function parse_opts() { __po_process_options "$@" else [ -n "$destargs_" ] && setv "$destargs_" "$args_" + [ -n "$__ULIB_SET_X" ] && set -x return 1 fi + + [ -n "$__ULIB_PARSE_OPTS_SET_X" ] && set -x; return 0 } function parse_args_check() { - parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" && return 0 - eerror "$args" - return 1 + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction + if parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@"; then + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 0 + else + eerror "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x + return 1 + fi } function parse_args() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_PARSE_ARGS_SET_X=1; } # désactiver set -x pour cette fonction parse_opts "${PRETTYOPTS[@]}" "${args[@]}" @ args -- "$@" || die "$args" + [ -n "$__ULIB_PARSE_ARGS_SET_X" ] && set -x; return 0 } HELP_DESC= @@ -2177,7 +2654,7 @@ function base_umove() { eerror_if [ $# -eq 1 ] "Vous devez spécifier la destination" || return local -a srcs - local dest + local src dest srcs=("$@") setx dest=last_value srcs @@ -2210,18 +2687,80 @@ function base_umove() { eerror "$dest: doit être un répertoire" return 1 } + local r=0 for src in "${srcs[@]}"; do if [ -n "$updatedir" ]; then if [ -L "$src" ]; then - move_link "$src" "$dest" + move_link "$src" "$dest" || r=$? else array_find_links update_links "$src" "$updatedir" - move_file "$src" "$dest" "${update_links[@]}" + move_file "$src" "$dest" "${update_links[@]}" || r=$? fi else - move_link "$src" "$dest" + move_link "$src" "$dest" || r=$? fi done + return $r +} + +function base_udelete() { + local -a args + local updatedir + args=(-d:,--updatedir: .) + parse_args_check "$@" || return; set -- "${args[@]}" + + eerror_unless [ -z "$updatedir" -o -d "$updatedir" ] "$updatedir: doit être un répertoire" || return + eerror_if [ $# -eq 0 ] "Vous devez spécifier les fichiers à supprimer" || return + + local file r=0 + for file in "$@"; do + if [ -n "$updatedir" ]; then + if [ -L "$file" ]; then + rm "$file" || r=$? + else + array_find_links update_links "$file" "$updatedir" + rm "$file" "${update_links[@]}" || r=$? + fi + else + rm "$file" || r=$? + fi + done + return $r +} + +function base_ucopy() { + eerror_if [ $# -eq 0 ] "Vous devez spécifier les fichiers à copier" || return + eerror_if [ $# -eq 1 ] "Vous devez spécifier la destination" || return + + local -a srcs + local src dest + + srcs=("$@") + setx dest=last_value srcs + array_del_last srcs + + if [ $# -eq 2 ]; then + if [ -d "$dest" ]; then + : # ce cas sera traité ci-dessous + elif [ -e "$dest" ]; then + eerror "$dest: refus d'écraser la destination" + return 1 + else + src="${srcs[0]}" + copy_link "$src" "$dest" + return $? + fi + fi + + [ -d "$dest" ] || { + eerror "$dest: doit être un répertoire" + return 1 + } + local r=0 + for src in "${srcs[@]}"; do + copy_link "$src" "$dest" || r=$? + done + return $r } ##@inc]base.tools ##@inc[base.compat @@ -2251,16 +2790,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -2322,6 +2851,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -2480,6 +3056,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -2487,21 +3141,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -2516,21 +3168,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -2620,6 +3270,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -2643,6 +3295,4055 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} +##@inc]base.quote +uprovide base.compat +urequire base.core base.num base.bool base.quote + + +function setx2() { setxx "$@"; } +function rawecho() { recho "$@"; } +function rawecho_() { recho_ "$@"; } +function quote_arg() { _qval "$@"; } +function quoted_arg() { qvalm "$@"; } +function quoted_args() { qvals "$@"; } +function set_var() { setv "$@"; } +function set_var_cmd() { echo_setv "$@"; } +function set_var_literal() { eval "$1=$2"; } + +function quote_awk() { _qawk "$@"; } +function quoted_awk() { qawk "$@"; } +function quote_seds() { qseds "$@"; } +function quote_form() { _qform "$@"; } +function quoted_form() { qform "$@"; } + + +if [ -n "$BASH_VERSINFO" -a "${BASH_VERSINFO[0]}" -lt 4 ]; then + function strlower() { tr A-Z a-z <<<"$*"; } + function strlower1() { + local str="$*" + local h="${str:0:1}" r="${str:1}" + echo "$(tr A-Z a-z <<<"$h")$r" + } + function strlowers() { + local -a vs; local v + for v in "$@"; do + vs=("${vs[@]}" "$(strlower1 "$v")") + done + echo "${vs[*]}" + } + function strupper() { tr a-z A-Z <<<"$*"; } + function strupper1() { + local str="$*" + local h="${str:0:1}" r="${str:1}" + echo "$(tr a-z A-Z <<<"$h")$r" + } + function struppers() { + local -a vs; local v + for v in "$@"; do + vs=("${vs[@]}" "$(strupper1 "$v")") + done + echo "${vs[*]}" + } + + function is_yes() { + case "$1" in + o|oui|y|yes|v|vrai|t|true|on) return 0;; + O|OUI|Y|YES|V|VRAI|T|TRUE|ON) return 0;; + esac + isnum "$1" && [ "$1" -ne 0 ] && return 0 + case "$(strlower "$1")" in + o|oui|y|yes|v|vrai|t|true|on) return 0;; + esac + return 1 + } + function is_no() { + case "$1" in + n|non|no|f|faux|false|off) return 0;; + N|NON|NO|F|FAUX|FALSE|OFF) return 0;; + esac + isnum "$1" && [ "$1" -eq 0 ] && return 0 + case "$(strlower "$1")" in + n|non|no|f|faux|false|off) return 0;; + esac + return 1 + } +fi +##@inc]base.compat +uprovide base +urequire base.init base.core base.string base.num base.bool base.array base.quote base.split base.args base.tools base.compat + +UNAME_SYSTEM=`uname -s` +[ "${UNAME_SYSTEM#CYGWIN}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Cygwin +[ "${UNAME_SYSTEM#MINGW32}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Mingw +UNAME_MACHINE=`uname -m` +if [ -n "$UTOOLS_CHROOT" ]; then + [ -n "$UTOOLS_UNAME_SYSTEM" ] && eval "UNAME_SYSTEM=$UTOOLS_UNAME_SYSTEM" + [ -n "$UTOOLS_UNAME_MACHINE" ] && eval "UNAME_MACHINE=$UTOOLS_UNAME_MACHINE" +fi + +function setyesval() { + is_yes "$2" && set_var "$1" 1 || set_var "$1" "" +} +function normyesval() { + is_yes "${2:-"${!1}"}" && set_var "$1" 1 || set_var "$1" "" +} +function normyesvals() { + local __nyv_yesvar + for __nyv_yesvar in "$@"; do + is_yes "${!__nyv_yesvar}" && set_var "$__nyv_yesvar" 1 || set_var "$__nyv_yesvar" "" + done +} +function quote_in() { + sed 's/\\/\\\\/g +s/"/\\"/g +s/\$/\\$/g +s/`/\\`/g' +} +function quote_sin() { + sed "s/'/'\\\\''/g" +} +function quote_sarg() { + quote_sin <<<"$1" +} +function quoted_sarg() { + echo "'$(quote_sarg "$1")'" +} +function quoted_sargs() { + local a s + for a in "$@"; do + s="${s:+$s }$(quoted_sarg "$a")" + done + rawecho "$s" +} + +function set_array_cmd() { + [ $# -eq 1 ] && set -- "$1" "$1" + local __sac_s __sac_v __sac_f + __sac_s="$1=("; shift + if [ "$1" == "@" ]; then + shift + else + eval "set -- \"\${$1[@]}\"" + fi + __sac_f=1 + for __sac_v in "$@"; do + [ -n "$__sac_f" ] && __sac_f= || __sac_s="$__sac_s " + __sac_s="$__sac_s$(quoted_arg "$__sac_v")" + done + __sac_s="$__sac_s)" + echo "$__sac_s" +} +function set_array() { + eval "$(set_array_cmd "$@")" +} +function array_count() { + eval "echo \${#$1[*]}" +} +function array_isempty() { + [ $(array_count "$1") -eq 0 ] +} +function array_new() { + eval "$1=()" +} +function array_add() { + local __aa_a="$1"; shift + eval "$__aa_a=(\"\${$__aa_a[@]}\" \"\$@\")" +} +function array_ins() { + local __aa_a="$1"; shift + eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")" +} +function array_del() { + local __ad_v + local -a __ad_vs + eval 'for __ad_v in "${'"$1"'[@]}"; do + if [ "$__ad_v" != '"$(quoted_arg "$2")"' ]; then + array_add __ad_vs "$__ad_v" + fi +done' + array_copy "$1" __ad_vs +} +function array_addu() { + local __as_v + eval 'for __as_v in "${'"$1"'[@]}"; do + if [ "$__as_v" == '"$(quoted_arg "$2")"' ]; then + return 1 + fi +done' + array_add "$1" "$2" + return 0 +} +function array_set() { + array_addu "$@" +} +function array_insu() { + local __as_v + eval 'for __as_v in "${'"$1"'[@]}"; do + if [ "$__as_v" == '"$(quoted_arg "$2")"' ]; then + return 1 + fi +done' + array_ins "$1" "$2" + return 0 +} +function array_fillrange() { + local -a __af_vs + local __af_i="${2:-1}" __af_to="${3:-10}" __af_step="${4:-1}" + while [ "$__af_i" -le "$__af_to" ]; do + __af_vs=("${__af_vs[@]}" "$__af_i") + __af_i=$(($__af_i + $__af_step)) + done + array_copy "$1" __af_vs +} +function array_eq() { + local -a __ae_a1 __ae_a2 + array_copy __ae_a1 "$1" + array_copy __ae_a2 "$2" + [ ${#__ae_a1[*]} -eq ${#__ae_a2[*]} ] || return 1 + local __ae_v __ae_i=0 + for __ae_v in "${__ae_a1[@]}"; do + [ "$__ae_v" == "${__ae_a2[$__ae_i]}" ] || return 1 + __ae_i=$(($__ae_i + 1)) + done + return 0 +} +function array_contains() { + local __ac_v + eval 'for __ac_v in "${'"$1"'[@]}"; do + if [ "$__ac_v" == '"$(quoted_arg "$2")"' ]; then + return 0 + fi +done' + return 1 +} +function array_icontains() { + local __ac_v + eval 'for __ac_v in "${'"$1"'[@]}"; do + if [ "$(strlower "$__ac_v")" == '"$(strlower "$(quoted_arg "$2")")"' ]; then + return 0 + fi +done' + return 1 +} +function array_find() { + local __af_i __af_v + __af_i=0 + eval 'for __af_v in "${'"$1"'[@]}"; do + if [ "$__af_v" == '"$(quoted_arg "$2")"' ]; then + if [ -n "$3" ]; then + echo "${'"$3"'[$__af_i]}" + else + echo "$__af_i" + fi + return 0 + fi + __af_i=$(($__af_i + 1)) +done' + return 1 +} +function array_reverse() { + local -a __ar_vs + local __ar_v + array_copy __ar_vs "$1" + array_new "$1" + for __ar_v in "${__ar_vs[@]}"; do + array_ins "$1" "$__ar_v" + done +} + +function array_replace() { + local __ar_sn="$1"; shift + local __ar_f="$1"; shift + local -a __ar_s __ar_d + local __ar_v + array_copy __ar_s "$__ar_sn" + for __ar_v in "${__ar_s[@]}"; do + if [ "$__ar_v" == "$__ar_f" ]; then + __ar_d=("${__ar_d[@]}" "$@") + else + __ar_d=("${__ar_d[@]}" "$__ar_v") + fi + done + array_copy "$__ar_sn" __ar_d +} +function array_each() { + local __ae_an="$1"; shift + local __ae_f="$1"; shift + local -a __ae_a + local __ae_v + array_copy __ae_a "$__ae_an" + for __ae_v in "${__ae_a[@]}"; do + "$__ae_f" "$__ae_v" "$@" + done +} +function array_map() { + local __am_an="$1"; shift + local __am_f="$1"; shift + local -a __am_a __am_vs + local __am_v + array_copy __am_a "$__am_an" + for __am_v in "${__am_a[@]}"; do + __am_vs=("${__am_vs[@]}" "$("$__am_f" "$__am_v" "$@")") + done + array_copy "$__am_an" __am_vs +} +function first_value() { + eval "rawecho \"\${$1[@]:0:1}\"" +} +function last_value() { + eval "rawecho \"\${$1[@]:\$((-1)):1}\"" +} +function array_copy() { + eval "$1=(\"\${$2[@]}\")" +} +function array_copy_firsts() { + eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")" +} +function array_del_last() { + array_copy_firsts "$1" +} +function array_copy_lasts() { + eval "$1=(\"\${${2:-$1}[@]:1}\")" +} +function array_del_first() { + array_copy_lasts "$1" +} +function array_extend() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")" +} +function array_extendu() { + local __ae_v __ae_s=1 + eval 'for __ae_v in "${'"$2"'[@]}"; do + array_addu "$1" "$__ae_v" && __ae_s=0 +done' + return "$__ae_s" +} +function array_extend_firsts() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")" +} +function array_extend_lasts() { + eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")" +} +function array_xsplit() { + eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} +function array_split() { + eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} +function array_from_path() { + array_split "$1" "$2" ":" +} +function array_from_xlines() { + eval "$1=($(recho_ "$2" | _nl2lf | awk ' +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} +function array_from_lines() { + eval "$1=($(recho_ "$2" | _nl2lf | awk ' +/^$/ { next } +{ + gsub(/'\''/, "'\'\\\\\'\''") + print "'\''" $0 "'\''" +}'))" #" +} +function array_join() { + local __aj_an __aj_l __aj_j __aj_s="${2:-,}" __aj_pf __aj_sf + if [ "$1" == "@" ]; then + __aj_an="\$@" + shift; shift + else + __aj_an="\${$1[@]}" + __aj_pf="$4" + __aj_sf="$5" + fi + eval 'for __aj_l in "'"$__aj_an"'"; do + __aj_j="${__aj_j:+$__aj_j'"$__aj_s"'}$__aj_pf$__aj_l$__aj_sf" +done' + if [ -n "$__aj_j" ]; then + rawecho "$__aj_j" + elif [ "$__aj_an" != "\$@" -a -n "$3" ]; then + rawecho "$3" + fi +} +function array_mapjoin() { + local __amj_src="$1" __amj_func="$2" __amj_sep="$3" + shift; shift; shift + if [ "$__amj_src" == "@" ]; then + local -a __amj_tmpsrc + __amj_tmpsrc=("$@") + __amj_src=__amj_tmpsrc + set -- + fi + local -a __amj_tmp + array_copy __amj_tmp "$__amj_src" + array_map __amj_tmp "$__amj_func" + array_join __amj_tmp "$__amj_sep" "$@" +} +function array_to_lines() { + array_join "$1" " +" "$2" "$3" "$4" +} +function array_to_path() { + array_join "$1" ":" "$2" "$3" "$4" +} +function array_fix_paths() { + local __afp_an="$1" __afp_s="${2:-:}" + local -a __afp_vs + local __afp_v + array_copy __afp_vs "$__afp_an" + array_new "$__afp_an" + for __afp_v in "${__afp_vs[@]}"; do + array_split __afp_v "$__afp_v" "$__afp_s" + array_extend "$__afp_an" __afp_v + done +} + + +function get_date_rfc822() { + LC_TIME=C date +"%a, %d %b %Y %H:%M:%S %Z" +} +function get_date_fr() { + LC_TIME=C date +"%d/%m/%Y" +} +function get_time_fr() { + LC_TIME=C date +"%Hh%M" +} +function parse_date() { + local value="$1" type="${2:-date}" + local now="$(awk 'BEGIN { print mktime(strftime("%Y %m %d 00 00 00 +0400")) }')" + case "$value" in + +*) + value="$(($now + ${value#+} * 86400))" + ;; + *) + value="$(<<<"$value" awk -F/ '{ + nd = strftime("%d"); nm = strftime("%m"); ny = strftime("%Y") + d = $1 + 0; if (d < 1) d = nd; + m = $2 + 0; if (m < 1) m = nm; + if ($3 == "") y = ny; + else { y = $3 + 0; if (y < 100) y = y + 2000; } + print mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)); + }')" + esac + case "$type" in + d|date) awk '{ print strftime("%d/%m/%Y", $0 + 0) }' <<<"$value";; + l|ldap) awk '{ print strftime("%Y%m%d%H%M%S+0400", $0 + 0) }' <<<"$value";; + m|mysql) awk '{ print strftime("%Y-%m-%d", $0 + 0) }' <<<"$value";; + *) + rawecho "$value" + ;; + esac +} + + +function udelpath() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'=' +} +function uaddpath() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"' +} +function uinspathm() { + local _qdir="${1//\//\\/}" + eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"' +} +function uinspath() { + udelpath "$@" + uinspathm "$@" +} + +function withpath() { + [ "${1#./}" != "$1" -o "${1#../}" != "$1" -o "${1#/}" != "$1" ] +} +function withext() { + local basename="$(basename -- "$1")" + [ "${basename%.*}" != "$basename" ] +} +function normpath() { + local -a parts + local part ap + array_split parts "$1" / + if [ "${1#/}" != "$1" ]; then + ap=/ + elif [ -n "$2" ]; then + ap="$2" + else + ap="$(pwd)" + fi + for part in "${parts[@]}"; do + if [ "$part" == "." ]; then + continue + elif [ "$part" == ".." ]; then + ap="${ap%/*}" + [ -n "$ap" ] || ap=/ + else + [ "$ap" != "/" ] && ap="$ap/" + ap="$ap$part" + fi + done + rawecho "$ap" +} +function abspath() { + local ap="$1" + if [ "${ap#/}" != "$ap" ]; then + __normpath "$ap" && return + else + local cwd + if [ -n "$2" ]; then + cwd="$(abspath "$2")" + else + cwd="$(pwd)" + fi + ap="$cwd/$ap" + __normpath "$ap" && return + fi + normpath "$ap" +} +function __normpath() { + if [ -d "$1" ]; then + if [ -x "$1" ]; then + (cd "$1"; pwd) + return 0 + fi + elif [ -f "$1" ]; then + local dn="$(dirname -- "$1")" bn="$(basename -- "$1")" + if [ -x "$dn" ]; then + (cd "$dn"; echo "$(pwd)/$bn") + return 0 + fi + fi + return 1 +} +function parentdirs() { + array_new "$1" + local __pd_d="$(abspath "$2")" + if [[ "$3" == r* ]]; then + while [ "$__pd_d" != "/" ]; do + array_ins "$1" "$__pd_d" + __pd_d="$(dirname "$__pd_d")" + done + else + while [ "$__pd_d" != "/" ]; do + array_add "$1" "$__pd_d" + __pd_d="$(dirname "$__pd_d")" + done + fi +} +function ppath() { + local path="$1" cwd="$2" + + path="$(abspath "$path")" # essayer de normaliser le chemin + [ -n "$cwd" ] || cwd="$(pwd)" + + [ "$path" = "$cwd" ] && path="." + [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path#$cwd/}" + [ "${path#$HOME/}" != "$path" ] && path="~${path#$HOME}" + + rawecho "$path" +} +function relpath() { + local p="$(abspath "$1" "$3")" cwd="$2" + if [ -z "$cwd" ]; then + cwd="$(pwd)" + else + cwd="$(abspath "$cwd" "$3")" + fi + if [ "$p" == "$cwd" ]; then + echo "" + elif [ "${p#$cwd/}" != "$p" ]; then + rawecho "${p#$cwd/}" + else + local rp + while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do + rp="${rp:+$rp/}.." + cwd="${cwd%/*}" + done + rp="$rp/${p#$cwd/}" + echo "${rp%//}" + fi +} +function relpathx() { + local p="$(relpath "$@")" + if [ -z "$p" ]; then + echo . + elif [ "${p#../}" != "$p" -o "${p#./}" != "$p" ]; then + echo "$p" + else + echo "./$p" + fi +} +function withinpath() { + local b="$1" p="$2" strict="${3:-N}" + b="$(abspath "$b")" + p="$(abspath "$p")" + if is_yes "$strict"; then + [ "${p#$b/}" != "$p" ] + else + [ "$p" == "$b" -o "${p#$b/}" != "$p" ] + fi +} +function safe_abspath() { + local p="$1" ba="$2" br="$3" + if [ -n "$ba" ]; then + ba="$(abspath "$ba")" + else + ba="$(pwd)" + fi + [ -n "$br" ] || br="$ba" + br="$(abspath "$br" "$ba")" + p="$(abspath "$p" "$ba")" + if [ "$p" == "$br" -o "${p#$br/}" != "$p" ]; then + echo "$p" + else + return 1 + fi +} +function safe_relpath() { + local p + if p="$(safe_abspath "$1" "$2" "$3")"; then + relpath "$p" "$2" "$(pwd)" + else + return 1 + fi +} +function splitwcs() { + local __sw_p="$1" + local __sw_dd="${2:-basedir}" __sw_df="${3:-filespec}" __sw_part __sw_d __sw_f + local -a __sw_parts + array_split __sw_parts "$__sw_p" "/" + for __sw_part in "${__sw_parts[@]}"; do + if [[ "$__sw_part" == *\** ]] || [[ "$__sw_part" == *\?* ]] || [ -n "$__sw_f" ]; then + __sw_f="${__sw_f:+$__sw_f/}$__sw_part" + else + __sw_d="${__sw_d:+$__sw_d/}$__sw_part" + fi + done + [ "${__sw_p#/}" != "$__sw_p" ] && __sw_d="/$__sw_d" + set_var "$__sw_dd" "$__sw_d" + set_var "$__sw_df" "$__sw_f" +} +function deref() { + local OENC="$UTF8" + + local max_deref=50 + local file="$1" + local basedir link + while [ -L "$file" ]; do + basedir="$(dirname "$file")" + link="$(readlink "$file")" + if first_char_is "$link" "/"; then + file="$link" + else + file="$basedir/$link" + fi + + max_deref=$(($max_deref - 1)) + [ $max_deref -eq 0 ] && die "Plus de 50 indirection. Le lien $file est-il récursif?" + done + abspath "$file" +} +function readlinka() { + if [ -L "$1" ]; then + local linkdir="$(dirname -- "$1")" + abspath "$(readlink "$1")" "$linkdir" + else + abspath "$1" + fi +} +function readlinkm() { + readlink -m "$1" +} +function path_if_test() { + local op="$1"; shift + local file="$1"; shift + local rel="$1" reldir=; shift + if beginswith "$rel" relative; then + reldir="${rel#relative}" + if beginswith "$reldir" :; then + reldir="${reldir#:}" + if [ -n "$reldir" ]; then + reldir="${reldir}/" + fi + else + reldir= + fi + else + rel= + fi + + while [ -n "$1" ]; do + local basedir="$1" + if [ $op "$basedir/$file" ]; then + if [ -n "$rel" ]; then + rawecho "$reldir$file" + else + rawecho "$basedir/$file" + fi + break + fi + shift + done +} +function update_link() { + [ -L "$2" ] || return 1 + local dest link="$2" + local linkdir="$(dirname "$link")" + local ldest="$(readlink "$link")" + if [ "${ldest#/}" != "$ldest" ]; then + dest="$(abspath "$1")" + else + dest="$(relpath "$1" "$linkdir")" + fi + if [ "$dest" == "$ldest" ]; then + : # pas besoin de mettre à jour + elif [ -d "$link" ]; then + rm -f "$link" && ln -s "$dest" "$link" + else + ln -sf "$dest" "$link" + fi +} +function update_links() { + [ -n "$1" ] || return 1 + local dest="$1"; shift + local r=0 link + for link in "$@"; do + update_link "$dest" "$link" || r=$? + done + return $r +} +function move_link() { + [ -n "$1" -a -n "$2" ] || return 1 + local link="$1" dest="$2" + [ -d "$dest" ] && dest="$dest/$(basename -- "$link")" + dest="$(abspath "$dest")" + if [ -L "$link" ]; then + link="$(abspath "$link")" + [ "$dest" == "$link" ] && return 0 + ldest="$(readlinka "$link")" + mv "$link" "$dest" || return 1 + update_link "$ldest" "$dest" + else + [ "$dest" == "$link" ] && return 0 + mv "$link" "$dest" + fi +} +function copy_link() { + [ -n "$1" -a -n "$2" ] || return 1 + local link="$1" dest="$2" + [ -d "$dest" ] && dest="$dest/$(basename -- "$link")" + dest="$(abspath "$dest")" + if [ -L "$link" ]; then + link="$(abspath "$link")" + [ "$dest" == "$link" ] && return 0 + ldest="$(readlinka "$link")" + cp -P "$link" "$dest" || return 1 + update_link "$ldest" "$dest" + else + [ "$dest" == "$link" ] && return 0 + cp "$link" "$dest" + fi +} +function array_find_links() { + local -a __afl_links __afl_result + local __afl_dir="${3:-.}" + local __afl_dest __afl_destname __afl_link __afl_linkdir __afl_ldest + __afl_dest="$(abspath "$2")" + __afl_destname="${__afl_dest##*/}" + array_from_lines __afl_links "$(find "$__afl_dir" -type l)" + for __afl_link in "${__afl_links[@]}"; do + __afl_ldest="$(readlink "$__afl_link")" + if [ "$__afl_ldest" != "$__afl_destname" ]; then + [[ "$__afl_ldest" == */"$__afl_destname" ]] || continue + fi + __afl_link="$(abspath "$__afl_link" "$__afl_dir")" + __afl_linkdir="$(dirname -- "$__afl_link")" + __afl_ldest="$(abspath "$__afl_ldest" "$__afl_linkdir")" + if [ "$__afl_ldest" == "$__afl_dest" ]; then + array_add __afl_result "$__afl_link" + fi + done + array_copy "$1" __afl_result +} +function list_links() { + local -a links + array_find_links links "$@" + array_to_lines links +} +function move_file() { + [ -n "$1" -a -n "$2" ] || return 1 + local src="$1" dest="$2" link + shift; shift + [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" + move_link "$src" "$dest" || return 1 + update_links "$dest" "$@" +} + +function get_nblines() { + [ -f "$1" ] && sed -ne '$=' "$1" || echo 0 +} +function mktempf() { + mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}" +} +function mktempd() { + mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}" +} +function mkdirof() { + mkdir -p "$(dirname -- "$1")" +} +function cp_a() { + /bin/cp -a "$@" +} +function cp_R() { + /bin/cp -pR "$@" +} +function quietgrep() { + grep -q "$@" 2>/dev/null +} +function quietdiff() { + diff -q "$@" >&/dev/null +} +function testsame() { + quietdiff "$@" +} +function testdiff() { + ! quietdiff "$@" +} +function testupdated() { + if [ -f "$2" ]; then + testdiff "$1" "$2" + else + return 0 + fi +} +function testnewer() { + test ! -e "$2" -o "$1" -nt "$2" +} +function ps_all() { + ps -axww +} +function progexists() { + test -n "$1" -a -x "$(which "$1" 2>/dev/null)" +} +function has_python() { + progexists python +} +function has_gawk() { + progexists gawk +} +function is_root() { + test `id -u` -eq 0 +} +function source_ifexists() { + if [ -f "$1" ]; then source "$1" || die; fi +} +function little_sleep { + LC_NUMERIC=C sleep 0.1 +} +function random_sleep { + sleep $(($RANDOM % ${1:-1800})) +} +function is_running() { + kill -0 "$1" >&/dev/null +} +function sedi() { + sed -i "$@" +} +function csort() { + LANG=C sort "$@" +} +function lsort() { sort "$@"; } +function cgrep() { + LANG=C grep "$@" +} +function lgrep() { grep "$@"; } +function csed() { + LANG=C sed "$@" +} +function lsed() { sed "$@"; } +function cawk() { + LANG=C awk "$@" +} +function lawk() { awk "$@"; } +function cdiff() { + LANG=C diff "$@" +} +function ldiff() { diff "$@"; } + + +function fix_mode() { + local file="$1" + [ -f "$file" ] || touch "$file" || return 1 + if [ ! -w "$file" ]; then + local mode="$(stat -c %a "$file")" + chmod ${mode:0:${#mode}-3}6${mode:${#mode}-2:2} "$file" + echo "$mode" + fi +} +function unfix_mode() { + [ -n "$2" ] && chmod "$2" "$1" +} +function get_mode() { + [ -f "$1" ] || touch "$1" || return 1 + stat -c %a "$1" +} +function rm_maybe() { + local parse_opts=1 arg rm + for arg in "$@"; do + if [ -n "$parse_opts" ]; then + if [ "$arg" == "--" ]; then + parse_opts= + elif [[ "$arg" == "-*" ]]; then + continue + elif [ -n "$arg" ]; then + rm=1 + break + fi + elif [ -n "$arg" ]; then + rm=1 + break + fi + done + [ -n "$rm" ] && /bin/rm "$@" +} +__CPDIR_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) +__CPDIR_RSYNC_ARGS=(-q) +function cpdir() { + + if progexists rsync; then + [ -d "$2" ] || mkdir -p "$2" || return 1 + if [ -d "$1" ]; then + rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1/" "$2/" + else + rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1" "$2/" + fi + else + __cpdir "$@" + fi +} +function __cpdir() { + local src="$1" dest="$2" method="${3:-cp_a}" + + if [ -d "$src" ]; then + [ -d "$dest" ] || mkdir -p "$dest" || return 1 + + local prevdir="$(pwd)" + + dest="$(abspath "$dest")" + cd "$src" + if [ -n "$(/bin/ls -a1)" ]; then + [ -n "$(/bin/ls -1)" ] && "$method" * "$dest" + local i + for i in .*; do + [ "$i" == "." -o "$i" == ".." ] && continue + "$method" "$i" "$dest" + done + fi + cd "$prevdir" + else + if [ -f "$dest" ]; then + "$method" "$src" "$dest" + elif [ -d "$dest" ]; then + "$method" "$src" "$dest" + else + mkdir -p "$dest" + "$method" "$src" "$dest" + fi + fi +} +__CPNOVCS_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) +__CPNOVCS_RSYNC_ARGS=(-q) +__CPNOVCS_INCLUDE_VCS= # ne pas ignorer les répertoires de VCS +function cpnovcs() { + local src="$1" destdir="$2" + [ -d "$destdir" ] || mkdir -p "$destdir" || return 1 + if progexists rsync; then + local -a novcs + if [ -z "$__CPNOVCS_INCLUDE_VCS" ]; then + local gitexclude=/.git/ + [ "${src%/}" == "$src" ] && gitexclude="/$(basename -- "$src")$gitexclude" + novcs=(--exclude CVS/ --exclude .svn/ --exclude "$gitexclude") + fi + rsync -a ${__CPNOVCS_RSYNC_SLOW:+-c} "${novcs[@]}" "${__CPNOVCS_RSYNC_ARGS[@]}" "$src" "$destdir/" + elif [ "${src%/}" != "$src" ]; then + __cpdir "$src" "$destdir" + else + local srcname="$(basename -- "$src")" + mkdir -p "$destdir/$srcname" + __cpdir "$src" "$destdir/$srcname" + fi +} +function cpvcs() { + local __CPNOVCS_INCLUDE_VCS=1 + cpnovcs "$@" +} +function cpdirnovcs() { + if [ -d "$1" ]; then + cpnovcs "$1/" "$2" + else + cpnovcs "$1" "$2" + fi +} +function doinplace() { + if [ -n "$1" -a "$1" != "-" ]; then + local __dip_file="$1"; shift + autoclean "$__dip_file.tmp.$$" + "$@" <"$__dip_file" >"$__dip_file.tmp.$$" + local s=$? + [ "$s" == 0 ] && /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" + /bin/rm -f "$__dip_file.tmp.$$" + return $s + else + shift + "$@" + fi +} +function doinplacef() { + if [ -n "$1" -a "$1" != "-" ]; then + local __dip_file="$1"; shift + autoclean "$__dip_file.tmp.$$" + "$@" <"$__dip_file" >"$__dip_file.tmp.$$" + local s=$? + /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" + /bin/rm -f "$__dip_file.tmp.$$" + return $s + else + shift + "$@" + fi +} +function stripnl() { + tr -d '\r\n' +} +function _nl2lf() { + awk 'BEGIN {RS="\r|\r\n|\n"} {print}' +} +function nl2lf() { + doinplace "$1" _nl2lf +} +function _nl2crlf() { + awk 'BEGIN {RS="\r|\r\n|\n"} {print $0 "\r"}' +} +function nl2crlf() { + doinplace "$1" _nl2crlf +} +function _nl2cr() { + awk 'BEGIN {RS="\r|\r\n|\n"; ORS=""} {print $0 "\r"}' +} +function nl2cr() { + doinplace "$1" _nl2cr +} +function _latin1compat() { + LANG=fr_FR.UTF-8 sed $' +s/[\xE2\x80\x90\xE2\x80\x91\xE2\x80\x92\xE2\x80\x93\xE2\x80\x94\xE2\x80\x95]/-/g +s/[‘’]/\x27/g +s/[«»“”]/"/g +s/[\xC2\xA0\xE2\x80\x87\xE2\x80\xAF\xE2\x81\xA0]/ /g +s/[œ]/oe/g +s/[Œ]/OE/g +s/[æ]/ae/g +s/[Æ]/AE/g +' +} +function _noaccents() { + LANG=fr_FR.UTF-8 sed ' +s/[à]/a/g +s/[éèêë]/e/g +s/[ïî]/i/g +s/[ôö]/o/g +s/[üû]/u/g +s/[ç]/c/g +s/[À]/A/g +s/[ÉÈÊË]/E/g +s/[ÏÎ]/I/g +s/[ÔÖ]/O/g +s/[ÜÛ]/U/g +s/[Ç]/C/g +' +} +function list_all() { + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "$(__la_cmd "$@")" | while read f; do + [ "$f" == "." -o "$f" == ".." ] && continue + rawecho "$f" + done + cd "$curdir" +} +function __la_cmd() { + [ $# -gt 0 ] || set '*' + local arg + local cmd="/bin/ls -1d" + for arg in "$@"; do + arg="$(qwc "$arg")" + cmd="$cmd $arg" + done + cmd="$cmd 2>/dev/null" + echo "$cmd" +} +function list_files() { + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "$(__la_cmd "$@")" | while read f; do + [ -f "$f" ] && rawecho "$f" + done + cd "$curdir" +} +function list_dirs() { + local f + local curdir="$(pwd)" + local b="${1:-.}"; shift + + cd "$b" 2>/dev/null || return + eval "$(__la_cmd "$@")" | while read f; do + [ "$f" == "." -o "$f" == ".." ] && continue + [ -d "$f" ] && rawecho "$f" + done + cd "$curdir" +} +function __array_ls() { + local __al_l="list_${1:-all}"; shift + local __al_an="$1"; shift + local __al_d="${1:-.}"; shift + local -a __al_fs + array_from_lines __al_fs "$("$__al_l" "$__al_d" "$@")" + local __al_f + array_new "$__al_an" + for __al_f in "${__al_fs[@]}"; do + array_add "$__al_an" "$__al_d/$__al_f" + done +} +function array_lsall() { + __array_ls all "$@" +} +function array_lsdirs() { + __array_ls dirs "$@" +} +function array_lsfiles() { + __array_ls files "$@" +} +function filter_empty() { + sed '/^$/d' +} +function filter_vcspath() { + sed ' +/^.git$/d +/^.git\//d +/\/.git$/d +/\/.git\//d +/^.svn$/d +/^.svn\//d +/\/.svn$/d +/\/.svn\//d +' +} +function merge_contlines() { + awk 'substr($0, length($0)) == "\\" { + while (getline nextline) { + $0 = substr($0, 1, length($0) - 1) nextline + if (substr($0, length($0)) != "\\") break + } + print + next +} +{print}' +} +function filter_comment() { + local -a merge + [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) + awk ' + /^[ \t]*#/ { next } + /^[ \t]*$/ { next } + { print }' | "${merge[@]}" +} +function filter_conf() { + local -a merge + [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) + grep -v '^#' | grep -v '^$' | "${merge[@]}" +} +function is_archive() { + local name="${1%.zip}" + name="${name%.tgz}" + name="${name%.tbz2}" + name="${name%.tar.gz}" + name="${name%.tar.bz2}" + name="${name%.tar}" + name="${name%.jar}" + name="${name%.war}" + name="${name%.ear}" + [ "$name" != "$1" ] +} +function extract_archive() { + local arch="$1" destdir="${2:-.}" + shift; shift + if endswith "$arch" .zip; then + unzip -q -d "$destdir" "$arch" "$@" || return + elif endswith "$arch" .tgz || endswith "$arch" .tar.gz; then + tar xzf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .tbz2 || endswith "$arch" .tar.bz2; then + tar xjf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .tar; then + tar xf "$arch" -C "$destdir" "$@" || return + elif endswith "$arch" .jar || endswith "$arch" .war || endswith "$arch" .ear; then + ( + arch="$(abspath "$arch")" + cd "$destdir" + jar xf "$arch" "$@" + ) || return + else + return 1 + fi +} +function get_archive_basename() { + local basename="$(basename -- "$1")" + basename="${basename%.zip}" + basename="${basename%.tgz}" + basename="${basename%.tbz2}" + basename="${basename%.gz}" + basename="${basename%.bz2}" + basename="${basename%.tar}" + basename="${basename%.jar}" + basename="${basename%.war}" + basename="${basename%.ear}" + echo "$basename" +} +function get_archive_appname() { + local appname="$(basename -- "$1")" + appname="${appname%.zip}" + appname="${appname%.tgz}" + appname="${appname%.tbz2}" + appname="${appname%.gz}" + appname="${appname%.bz2}" + appname="${appname%.tar}" + appname="${appname%.jar}" + appname="${appname%.war}" + appname="${appname%.ear}" + echo "$appname" | awk '{ + if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { + print substr($0, 1, RSTART - 1) + } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { + print substr($0, 1, RSTART - 1) + } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { + print substr($0, 1, RSTART - 1) + } else { + print $0 + } +}' +} +function get_archive_versionsuffix() { + local basename="$(get_archive_basename "$1")" + echo "$basename" | awk '{ + if (match($0, /([-_.][0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { + print vs["1"] + } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { + print vs["1"] + } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { + print vs["1"] + } +}' +} +function get_archive_version() { + local basename="$(get_archive_basename "$1")" + echo "$basename" | awk '{ + if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { + print vs["1"] + } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { + print vs["1"] + } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { + print vs["1"] + } +}' +} +function __dump_usernames() { + = 500 && $6 ~ /^\/home\// { print $1 }' +} +function dump_usernames() { + array_from_lines "${1:-usernames}" "$(__dump_usernames)" +} +function __resolv_ips() { + LANG=C host "$1" 2>/dev/null | awk '/address / { gsub(/^.*address /, ""); print }' +} +function resolv_ips() { + array_from_lines "${1:-ips}" "$(__resolv_ips "$2")" +} +function __resolv_hosts() { + LANG=C host "$1" 2>/dev/null | awk '/domain name pointer / { gsub(/^.*domain name pointer /, ""); gsub(/\.$/, ""); print }' +} +function resolv_hosts() { + array_from_lines "${1:-hosts}" "$(__resolv_hosts "$2")" +} +function runscript_as() { + local OENC="$UTF8" + local user="${1:-root}"; shift + local exec_maybe= + if [ "$1" = "exec" ]; then + exec_maybe=exec + shift + fi + + local cmd + cmd="\ +__estack=$(quoted_arg "$__estack") +__tlevel=$(quoted_args "$__tlevel") +export __estack __tlevel +exec ${BASH:-/bin/sh} $(quoted_args "$@")" + + if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then + eecho "Entrez le mot de passe de root" + $exec_maybe su "$user" -c "$cmd" + else + if [ "$user" == "root" ]; then + $exec_maybe sudo -p "Entrez le mot de passe de %u: " "${BASH:-/bin/sh}" -c "$cmd" + else + $exec_maybe sudo -p "Entrez le mot de passe de %u: " su "$user" -c "$cmd" + fi + fi +} +function runscript_as_root() { + if is_root; then + local exec_maybe= + if [ "$1" = "exec" ]; then + exec_maybe=exec + shift + fi + $exec_maybe "${BASH:-/bin/sh}" "$@" + else + runscript_as root "$@" + fi +} +function run_as() { + local user="${1:-root}"; shift + local exec_maybe=exec + if [ "$1" = "--noexec" ]; then + exec_maybe= + shift + fi + + runscript_as "$user" $exec_maybe "$0" "$@" +} +function run_as_root() { + is_root || run_as root "$@" +} +function check_user() { + local user + for user in "$@"; do + [ "$USER" == "$user" ] && return 0 + done + return 1 +} +function ensure_user() { + local -a users + while [ $# -gt 0 -a "$1" != "--" ]; do + array_add users "$1" + shift + done + [ "$1" == "--" ] && shift + + if ! check_user "${users[@]}"; then + if [ ${#users[*]} -gt 1 ]; then + ewarn "Cette commande doit être lancée avec l'un des users ${users[*]}" + else + ewarn "Cette commande doit être lancée avec le user ${users[0]}" + fi + if ask_yesno "Voulez-vous tenter de relancer la commande avec le bon user?" O; then + estep "Lancement du script avec le user ${users[0]}" + run_as "${users[0]}" "$@" + return 1 + elif is_root; then + return 11 + else + return 10 + fi + fi + return 0 +} +function check_hostname() { + local userhost user host path + for userhost in "$@"; do + splitfsep "$userhost" : userhost path + splituserhost "$userhost" user host + host="${host%%.*}" + [ "$host" == localhost -o "$host" == "$MYHOSTNAME" ] && return 0 + done + return 1 +} +function check_userhostname() { + local userhost path user host + for userhost in "$@"; do + if check_hostname "$userhost"; then + [[ "$userhost" == *@* ]] || return 0 + splitfsep "$userhost" : userhost path + splituserhost "$userhost" user host + check_user "$user" && return 0 + fi + done + return 1 +} +UTOOLS_ENSURE_HOSTNAME_SSH_OPTS=() +function ensure_hostname() { + local -a userhosts + while [ $# -gt 0 -a "$1" != "--" ]; do + array_add userhosts "$1" + shift + done + [ "$1" == "--" ] && shift + + local userhost user host path + if ! check_hostname "${userhosts[@]}"; then + if [ ${#userhosts[*]} -gt 1 ]; then + ewarn "Cette commande n'est valide que sur l'un des hôtes ${userhosts[*]}" + else + ewarn "Cette commande n'est valide que sur l'hôte ${userhosts[0]}" + fi + + eimportant "Vous pouvez tenter de relancer le script sur ${userhosts[0]}" + eimportant "Cela requière que ce script ET les données dont il a besoin soient installés dans la même version et dans le même répertoire sur l'hôte distant. +En l'occurence, ce script est accédé par le chemin $script et ce chemin doit exister aussi sur le serveur distant." + if ask_yesno "Voulez-vous tenter de relancer le script sur l'hôte distant?" X; then + splitfsep "${userhosts[0]}" : userhost path + splituserhost "$userhost" user host + [ -n "$user" ] || user=root + + estep "Lancement de la commande sur l'hôte distant $user@$host" + local cmd + [ -n "$path" ] && cmd="$(quoted_args cd "$path"); " + cmd="$cmd$(quoted_args "$script" "$@")" + ssh -qt "${UTOOLS_ENSURE_HOSTNAME_SSH_OPTS[@]}" "$user@$host" "$cmd" + [ $? -eq 255 ] && return 12 + return 1 + else + return 11 + fi + fi + local userhost user host + for userhost in "${userhosts[@]}"; do + [[ "$userhost" == *@* ]] || continue + if check_hostname "$userhost"; then + splitfsep "$userhost" : userhost path + splituserhost "$userhost" user host + [ -n "$path" ] && cd "$path" + ensure_user "$user" -- "$@" + return $? + fi + done + return 0 +} + +__AWKDEF_FUNCTIONS=' +function num(s) { + if (s ~ /^[0-9]+$/) return int(s) + else return s +} +function ord(s, i) { + s = substr(s, 1, 1) + i = index(" !\"#$%&'\''()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~", s) + if (i != 0) i += 32 - 1 + return i +} +function hex(i, s) { + s = sprintf("%x", i) + if (length(s) < 2) s = "0" s + return s +} +function qhtml(s) { + gsub(/&/, "\\&", s) + gsub(/"/, "\\"", s) + gsub(/>/, "\\>", s) + gsub(/", s) + gsub(/"/, "\"", s) + gsub(/&/, "\\&", s) + return s +} +function qval(s) {'" + gsub(/'/, \"'\\\\''\", s) + return \"'\" s \"'\" +"'} +function sqval(s) { + return " " qval(s) +} +function qvals( i, line) { + line = "" + for (i = 1; i <= NF; i++) { + if (i > 1) line = line " " + line = line qval($i) + } + return line +} +function sqvals() { + return " " qvals() +} +function qarr(values, prefix, i, count, line) { + line = prefix + count = array_len(values) + for (i = 1; i <= count; i++) { + if (i > 1 || line != "") line = line " " + line = line qval(values[i]) + } + return line +} +function qregexp(s) { + gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) + return s +} +function qsubrepl(s) { + gsub(/\\/, "\\\\", s) + gsub(/&/, "\\\\&", s) + return s +} +function qgrep(s) { + gsub(/[[\\.^$*]/, "\\\\&", s) + return s +} +function qegrep(s) { + gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) + return s +} +function qsql(s, suffix) {'" + gsub(/'/, \"''\", s) + return \"'\" s \"'\" (suffix != \"\"? \" \" suffix: \"\") +"'} +function cqsql(s, suffix) { + return "," qsql(s, suffix) +} +function unquote_mysqlcsv(s) { + gsub(/\\n/, "\n", s) + gsub(/\\t/, "\t", s) + gsub(/\\0/, "\0", s) + gsub(/\\\\/, "\\", s) + return s +} +function sval(s) { + if (s == "") return s + else return " " s +} +function cval(s, suffix) { + if (s == "") return s + else return "," s (suffix != ""? " " suffix: "") +} +function quote_html(s) { return qhtml(s) } +function quote_value(s) { return qval(s) } +function qsval(s) { return sqval(s) } +function quoted_values() { return qvals() } +function qsvals(s) { return sqvals(s) } +function quote_regexp(s) { return qregexp(s) } +function quote_subrepl(s) { return qsubrepl(s) } +function quote_grep(s) { return qgrep(s) } +function quote_egrep(s) { return qegrep(s) } +function quote_sql(s) { return qsql(s) } + +function __parse_date_fr(date, parts, y, m, d) { + if (match(date, /([0-9][0-9]?)\/([0-9][0-9]?)\/([0-9][0-9][0-9][0-9])/, parts)) { + y = int(parts[3]) + m = int(parts[2]) + d = int(parts[1]) + return mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)) + } + return -1 +} +function __parse_date_mysql(date, parts, y, m, d) { + if (match(date, /([0-9][0-9][0-9][0-9])-([0-9][0-9])-([0-9][0-9])/, parts)) { + y = int(parts[1]) + m = int(parts[2]) + d = int(parts[3]) + return mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)) + } + return -1 +} +function __parse_date_any(date, serial) { + serial = __parse_date_fr(date) + if (serial == -1) serial = __parse_date_mysql(date) + return serial +} +function date_serial(date) { + return __parse_date_any(date) +} +function date_parse(date, serial) { + serial = __parse_date_any(date) + if (serial == -1) return date + return strftime("%d/%m/%Y", serial) +} +function date_monday(date, serial, dow) { + serial = __parse_date_any(date) + if (serial == -1) return date + dow = strftime("%u", serial) + serial -= (dow - 1) * 86400 + return strftime("%d/%m/%Y", serial) +} +function date_add(date, nbdays, serial) { + serial = __parse_date_any(date) + if (serial == -1) return date + serial += nbdays * 86400 + return strftime("%d/%m/%Y", serial) +} + +function mkindices(values, indices, i, j) { + array_new(indices) + j = 1 + for (i in values) { + indices[j++] = int(i) + } + return asort(indices) +} +function array_new(dest) { + dest[0] = 0 # forcer awk à considérer dest comme un tableau + delete dest +} +function array_newsize(dest, size, i) { + dest[0] = 0 # forcer awk à considérer dest comme un tableau + delete dest + size = int(size) + for (i = 1; i <= size; i++) { + dest[i] = "" + } +} +function array_len(values, count, i) { + count = 0 + for (i in values) { + count++ + } + return count +} +function array_copy(dest, src, count, indices, i) { + array_new(dest) + count = mkindices(src, indices) + for (i = 1; i <= count; i++) { + dest[indices[i]] = src[indices[i]] + } +} +function array_getlastindex(src, count, indices) { + count = mkindices(src, indices) + if (count == 0) return 0 + return indices[count] +} +function array_add(dest, value, lastindex) { + lastindex = array_getlastindex(dest) + dest[lastindex + 1] = value +} +function array_deli(dest, i, l) { + i = int(i) + if (i == 0) return + l = array_len(dest) + while (i < l) { + dest[i] = dest[i + 1] + i++ + } + delete dest[l] +} +function array_del(dest, value, ignoreCase, i) { + do { + i = key_index(value, dest, ignoreCase) + if (i != 0) array_deli(dest, i) + } while (i != 0) +} +function array_extend(dest, src, count, lastindex, indices, i) { + lastindex = array_getlastindex(dest) + count = mkindices(src, indices) + for (i = 1; i <= count; i++) { + dest[lastindex + i] = src[indices[i]] + } +} +function array_fill(dest, i) { + array_new(dest) + for (i = 1; i <= NF; i++) { + dest[i] = $i + } +} +function array_getline(src, count, indices, i, j) { + $0 = "" + count = mkindices(src, indices) + for (i = 1; i <= count; i++) { + j = indices[i] + $j = src[j] + } +} +function array_appendline(src, count, indices, i, nf, j) { + count = mkindices(src, indices) + nf = NF + for (i = 1; i <= count; i++) { + j = nf + indices[i] + $j = src[indices[i]] + } +} +function in_array(value, values, ignoreCase, i) { + if (ignoreCase) { + value = tolower(value) + for (i in values) { + if (tolower(values[i]) == value) return 1 + } + } else { + for (i in values) { + if (values[i] == value) return 1 + } + } + return 0 +} +function key_index(value, values, ignoreCase, i) { + if (ignoreCase) { + value = tolower(value) + for (i in values) { + if (tolower(values[i]) == value) return int(i) + } + } else { + for (i in values) { + if (values[i] == value) return int(i) + } + } + return 0 +} +function array2s(values, prefix, sep, suffix, noindices, first, i, s) { + if (!prefix) prefix = "[" + if (!sep) sep = ", " + if (!suffix) suffix = "]" + s = prefix + first = 1 + for (i in values) { + if (first) first = 0 + else s = s sep + if (!noindices) s = s "[" i "]=" + s = s values[i] + } + s = s suffix + return s +} +function array2so(values, prefix, sep, suffix, noindices, count, indices, i, s) { + if (!prefix) prefix = "[" + if (!sep) sep = ", " + if (!suffix) suffix = "]" + s = prefix + count = mkindices(values, indices) + for (i = 1; i <= count; i++) { + if (i > 1) s = s sep + if (!noindices) s = s "[" indices[i] "]=" + s = s values[indices[i]] + } + s = s suffix + return s +} +function array_join(values, sep, prefix, suffix, count, indices, i, s) { + s = prefix + count = mkindices(values, indices) + for (i = 1; i <= count; i++) { + if (i > 1) s = s sep + s = s values[indices[i]] + } + s = s suffix + return s +} +function printto(s, output) { + if (output == "") { + print s + } else if (output ~ /^>>/) { + sub(/^>>/, "", output) + print s >>output + } else if (output ~ /^>/) { + sub(/^>/, "", output) + print s >output + } else { + print s >output + } +} +function find_line(input, field, value, orig, line) { + orig = $0 + line = "" + while ((getline 0) { + if ($field == value) { + line = $0 + break + } + } + close(input) + $0 = orig + return line +} +function merge_line(input, field, key, line) { + line = find_line(input, field, $key) + if (line != "") $0 = $0 FS line +} +function __csv_parse_quoted(line, destl, colsep, qchar, echar, pos, tmpl, nextc, resl) { + line = substr(line, 2) + resl = "" + while (1) { + pos = index(line, qchar) + if (pos == 0) { + resl = resl line + destl[0] = "" + destl[1] = 0 + return resl + } + if (echar != "" && pos > 1) { + prevc = substr(line, pos - 1, 1) + quotec = substr(line, pos, 1) + nextc = substr(line, pos + 1, 1) + if (prevc == echar) { + tmpl = substr(line, 1, pos - 2) + resl = resl tmpl quotec + line = substr(line, pos + 1) + continue + } + tmpl = substr(line, 1, pos - 1) + if (nextc == colsep || nextc == "") { + resl = resl tmpl + destl[0] = substr(line, pos + 2) + destl[1] = nextc == colsep + return resl + } else { + resl = resl tmpl quotec + line = substr(line, pos + 1) + } + } else { + tmpl = substr(line, 1, pos - 1) + quotec = substr(line, pos, 1) + nextc = substr(line, pos + 1, 1) + if (nextc == colsep || nextc == "") { + resl = resl tmpl + destl[0] = substr(line, pos + 2) + destl[1] = nextc == colsep + return resl + } else if (nextc == qchar) { + resl = resl tmpl quotec + line = substr(line, pos + 2) + } else { + resl = resl tmpl quotec + line = substr(line, pos + 1) + } + } + } +} +function __csv_parse_unquoted(line, destl, colsep, qchar, echar, pos) { + pos = index(line, colsep) + if (pos == 0) { + destl[0] = "" + destl[1] = 0 + return line + } else { + destl[0] = substr(line, pos + 1) + destl[1] = 1 + return substr(line, 1, pos - 1) + } +} +function __array_parsecsv(fields, line, nbfields, colsep, qchar, echar, shouldparse, destl, i) { + array_new(fields) + array_new(destl) + i = 1 + shouldparse = 0 + while (shouldparse || line != "") { + if (index(line, qchar) == 1) { + value = __csv_parse_quoted(line, destl, colsep, qchar, echar) + line = destl[0] + shouldparse = destl[1] + } else { + value = __csv_parse_unquoted(line, destl, colsep, qchar, echar) + line = destl[0] + shouldparse = destl[1] + } + fields[i] = value + i = i + 1 + } + if (nbfields) { + nbfields = int(nbfields) + i = array_len(fields) + while (i < nbfields) { + i++ + fields[i] = "" + } + } + return array_len(fields) +} +BEGIN { + DEFAULT_COLSEP = "," + DEFAULT_QCHAR = "\"" + DEFAULT_ECHAR = "" +} +function array_parsecsv2(fields, line, nbfields, colsep, qchar, echar) { + return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) +} +function array_parsecsv(fields, line, nbfields, colsep, qchar, echar) { + if (colsep == "") colsep = DEFAULT_COLSEP + if (qchar == "") qchar = DEFAULT_QCHAR + if (echar == "") echar = DEFAULT_ECHAR + return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) +} +function parsecsv(line, fields) { + array_parsecsv(fields, line) + array_getline(fields) + return NF +} +function getlinecsv(file, fields) { + if (file) { + getline 1) line = line colsep + if (qchar != "" && index(value, qchar) != 0) { + if (echar != "") gsub(qchar, quote_subrepl(echar) "&", value); + else gsub(qchar, "&&", value); + } + if (qchar != "" && (index(value, mvsep) != 0 || index(value, colsep) != 0 || index(value, qchar) != 0 || __csv_should_quote(value))) { + line = line qchar value qchar + } else { + line = line value + } + } + return line +} +function array_formatcsv(fields) { + return array_formatcsv2(fields, ",", ";", "\"", "") +} +function array_printcsv(fields, output) { + printto(array_formatcsv(fields), output) +} +function get_formatcsv( fields) { + array_fill(fields) + return array_formatcsv(fields) +} +function formatcsv() { + $0 = get_formatcsv() +} +function printcsv(output, fields) { + array_fill(fields) + array_printcsv(fields, output) +} +function array_findcsv(fields, input, field, value, nbfields, orig, found, i) { + array_new(orig) + array_fill(orig) + array_new(fields) + found = 0 + while ((getline 0) { + array_parsecsv(fields, $0, nbfields) + if (fields[field] == value) { + found = 1 + break + } + } + close(input) + array_getline(orig) + if (!found) { + delete fields + if (nbfields) { + nbfields = int(nbfields) + i = array_len(fields) + while (i < nbfields) { + i++ + fields[i] = "" + } + } + } + return found +} + +function __and(var, x, l_res, l_i) { + l_res=0; + for (l_i=0; l_i < 8; l_i++){ + if (var%2 == 1 && x%2 == 1) l_res=l_res/2 + 128; + else l_res/=2; + var=int(var/2); + x=int(x/2); + } + return l_res; +} +function __lshift(var, x) { + while(x > 0){ + var*=2; + x--; + } + return var; +} +function __rshift(var, x) { + while(x > 0){ + var=int(var/2); + x--; + } + return var; +} +BEGIN { + __BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" +} +function b64decode(src, result, base1, base2, base3, base4) { + result = "" + while (length(src) > 0) { + base1 = substr(src, 1, 1) + base2 = substr(src, 2, 1) + base3 = substr(src, 3, 1); if (base3 == "") base3 = "=" + base4 = substr(src, 4, 1); if (base4 == "") base4 = "=" + byte1 = index(__BASE64, base1) - 1 + if (byte1 < 0) byte1 = 0 + byte2 = index(__BASE64, base2) - 1 + if (byte2 < 0) byte2 = 0 + byte3 = index(__BASE64, base3) - 1 + if (byte3 < 0) byte3 = 0 + byte4 = index(__BASE64, base4) - 1 + if (byte4 < 0) byte4 = 0 + result = result sprintf( "%c", __lshift(__and(byte1, 63), 2) + __rshift(__and(byte2, 48), 4) ) + if (base3 != "=") result = result sprintf( "%c", __lshift(__and(byte2, 15), 4) + __rshift(__and(byte3, 60), 2) ) + if (base4 != "=") result = result sprintf( "%c", __lshift(__and(byte3, 3), 6) + byte4 ) + src = substr(src, 5) + } + return result +} +' +function awkdef() { + [ -z "$__ULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local __ULIB_AWKDEF_SET_X=1; } # désactiver set -x pour cette fonction + + if [ "${1:0:3}" == "-f" ]; then + shift + echo "$__AWKDEF_FUNCTIONS" + fi + if [ $# -gt 0 ]; then + local __ad_arg __ad_vpos __ad_name __ad_value + echo "BEGIN {" + while [ -n "${1:0:1}" ]; do + __ad_arg="${1:0:256}" + local __ad_array= + if [ "${__ad_arg%\[@\]}" != "$__ad_arg" ]; then + __ad_array=1 + __ad_name="${__ad_arg%\[@\]}" + [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break + __ad_value="$__ad_name" + elif [[ "$__ad_arg" == *\[@\]=* ]]; then + __ad_array=1 + __ad_name="${__ad_arg%%\[@\]=*}" + [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break + __ad_vpos=$((${#__ad_name} + 4)) + __ad_value="${1:$__ad_vpos}" + [ ${#__ad_value} -ne 0 ] || __ad_value="$__ad_name" + elif [[ "$__ad_arg" == *=* ]]; then + local __ad_int= __ad_str= + __ad_name="${__ad_arg%%=*}" + __ad_vpos=$((${#__ad_name} + 1)) + if [ "${__ad_name%:int}" != "$__ad_name" ]; then + __ad_int=1 + __ad_name="${__ad_name%:int}" + elif [ "${__ad_name%:str}" != "$__ad_name" ]; then + __ad_str=1 + __ad_name="${__ad_name%:str}" + fi + [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break + __ad_value="${1:$__ad_vpos}" + if [ -n "$__ad_int" ]; then + echo "$__ad_name = int($(quoted_awk "$__ad_value") + 0)" + elif [ -n "$__ad_str" ]; then + echo "$__ad_name = $(quoted_awk "$__ad_value")" + elif [ ${#__ad_value} -lt 256 ] && isnum "$__ad_value"; then + echo "$__ad_name = $__ad_value" + else + echo "$__ad_name = $(quoted_awk "$__ad_value")" + fi + else + break + fi + if [ -n "$__ad_array" ]; then + if [ "${__ad_value:0:2}" == $'<\n' ]; then + local -a __ad_values + array_from_lines __ad_values "${__ad_value:2}" + __ad_value=__ad_values + fi + __ad_value="${__ad_value}[@]" + local __ad_i=1 + echo "$__ad_name[0] = 0; delete $__ad_name" + for __ad_arg in "${!__ad_value}"; do + echo "$__ad_name[$__ad_i]=$(quoted_awk "$__ad_arg")" + __ad_i=$(($__ad_i + 1)) + done + eval "echo \"\${__ad_name}_count = \${#$__ad_value}\"" + fi + shift + done + echo "}" + for __ad_arg in "$@"; do + rawecho "$__ad_arg" + done + fi + + [ -n "$__ULIB_AWKDEF_SET_X" ] && set -x; return 0 +} +function lawkrun() { + local -a __ar_defs __ar_args + while [ $# -gt 0 -a "$1" != "--" ]; do + __ar_defs=("${__ar_defs[@]}" "$1") + shift + done + shift + while [ $# -gt 0 ]; do + __ar_args=("${__ar_args[@]}" "$1") + shift + done + local __ar_script="$(awkdef "${__ar_defs[@]}")" + [ -n "$UTOOLS_AWKRUN_DEBUG" ] && estep "Script awkrun: $__ar_script" + awk "$__ar_script" "${__ar_args[@]}" +} +function cawkrun() { LANG=C lawkrun "$@"; } +function awkrun() { LANG=C lawkrun "$@"; } + +function __lf_get_age() { + local y=$(date "+%Y") + local dy=$(date "+%j"); while [ "${dy#0}" != "$dy" ]; do dy="${dy#0}"; done + [ -n "$dy" ] || dy=0 + local h=$(date "+%H"); while [ "${h#0}" != "$h" ]; do h="${h#0}"; done + [ -n "$h" ] || h=0 + echo $((($y * 365 + $dy) * 24 + $h)) +} +function lf_trylock() { + local eoo lockfile max_hours=4 + while [ -n "$1" ]; do + case "$1" in + -h) shift; max_hours="$1";; + --) shift; eoo=1;; + *) eoo=1;; + esac + [ -n "$eoo" ] && break + shift + done + + lockfile="$1" + [ -n "$lockfile" ] || die "il faut spécifier un fichier pour le verrou" + + local now="$(__lf_get_age)" + if (set -C; echo "$now" >"$lockfile") 2>/dev/null; then + return 0 + fi + local prev diff + if prev="$(<"$lockfile")"; then + diff="$(($now - $prev))" + if [ "$diff" -gt "$max_hours" ]; then + echo stale + else + echo locked + fi + elif [ -f "$lockfile" ]; then + echo retry + fi + return 1 +} +function pidfile_set() { + local eoo pidfile pid=$$ replace= + while [ -n "$1" ]; do + case "$1" in + -p) + shift + pid="$1" + ;; + -r) + replace=1 + ;; + --) + shift + eoo=1 + ;; + *) + eoo=1 + ;; + esac + [ -n "$eoo" ] && break + shift + done + + pidfile="$1" + [ -n "$pidfile" ] || return 10 + + if [ -f "$pidfile" ]; then + local curpid="$(<"$pidfile")" + if is_running "$curpid"; then + return 1 + elif [ -n "$replace" ]; then + /bin/rm -f "$pidfile" || return 10 + else + return 2 + fi + fi + + echo_ "$pid" >"$pidfile" || return 10 + autoclean "$pidfile" + return 0 +} +function pidfile_check() { + local pidfile="$1" + [ -n "$pidfile" ] || return 10 + + if [ -f "$pidfile" ]; then + [ -r "$pidfile" ] || return 10 + local pid="$(<"$pidfile")" + is_running "$pid" && return 0 + fi + return 1 +} +function page_maybe() { + if isatty; then + less -XF "$@" + else + cat + fi +} + + +function utools_local() { + local arg + [ $# -gt 0 ] || set -- opts verbosity interaction + for arg in "$@"; do + case "$arg" in + parse_opts|opts|o|args) echo "local -a args";; + verbosity|v) echo "local __verbosity='$__verbosity'";; + interaction|i) echo "local __interaction='$__interaction'";; + esac + done +} + +function stdredir() { + local __redirs __in __out __err + if [ -n "$1" -o "$1" == /dev/stdin ]; then + if [ "${1#<}" != "$1" ]; then + __in="${1#<}" + else + __in="$1" + fi + __redirs="$__redirs"' <"$__in"' + fi; shift + if [ -n "$1" -o "$1" == /dev/stdout ]; then + if [ "${1#>>}" != "$1" ]; then + __out="${1#>>}" + __redirs="$__redirs"' >>"$__out"' + elif [ "${1#>}" != "$1" ]; then + __out="${1#>}" + __redirs="$__redirs"' >"$__out"' + else + __out="$1" + __redirs="$__redirs"' >"$__out"' + fi + fi; shift + if [ -n "$1" -o "$1" == /dev/stderr ]; then + if [ "${1#>>}" != "$1" ]; then + __err="${1#>>}" + __redirs="$__redirs"' 2>>"$__err"' + elif [ "${1#>}" != "$1" ]; then + __err="${1#>}" + __redirs="$__redirs"' 2>"$__err"' + else + __err="$1" + __redirs="$__redirs"' 2>"$__err"' + fi + fi; shift + eval '"$@"'"$__redirs" +} + +function isatty() { + tty -s <&1 +} +function in_isatty() { + tty -s +} +function out_isatty() { + tty -s <&1 +} +function err_isatty() { + tty -s <&2 +} +function die() { [ $# -gt 0 ] && eerror "$@"; exit 1; } +function exit_with { if [ $# -gt 0 ]; then "$@"; fi; exit $?; } +function die_with { [ $# -gt 0 ] && eerror "$1"; shift; [ $# -gt 0 ] && "$@"; exit 1; } +function die_unless() { + local count=$# + if [ $count -eq 0 ]; then + exit 1 + elif [ $count -eq 1 ]; then + "$@" || exit $? + else + local m r + m="${@:$count}" + count=$(($count - 1)) + set -- "${@:1:$count}" + if "$@"; then + : + else + r=$? + eerror "$m" + exit $r + fi + fi +} +function eerror_unless() { + local count=$# + if [ $count -eq 0 ]; then + return 1 + elif [ $count -eq 1 ]; then + "$@" || return $? + else + local m r + m="${@:$count}" + count=$(($count - 1)) + set -- "${@:1:$count}" + if "$@"; then + : + else + r=$? + eerror "$m" + return $r + fi + fi +} +function die_if() { + local count=$# + if [ $count -eq 0 ]; then + : + elif [ $count -eq 1 ]; then + "$@" && exit 1 + else + local m r + m="${@:$count}" + count=$(($count - 1)) + set -- "${@:1:$count}" + if "$@"; then + eerror "$m" + exit 1 + fi + fi +} +function eerror_if() { + local count=$# + if [ $count -eq 0 ]; then + : + elif [ $count -eq 1 ]; then + "$@" && return 1 + else + local m r + m="${@:$count}" + count=$(($count - 1)) + set -- "${@:1:$count}" + if "$@"; then + eerror "$m" + return 1 + fi + fi +} + +function noerror() { + [ $# -gt 0 ] || set : + "$@" || return 0 +} +function noout() { + [ $# -gt 0 ] || return 0 + "$@" >/dev/null +} +function noerr() { + [ $# -gt 0 ] || return 0 + "$@" 2>/dev/null +} + +TAB=$'\t' +LATIN1=iso-8859-1 +LATIN9=iso-8859-15 +UTF8=utf-8 +OENC="$UTF8" + +if ! progexists iconv; then + function iconv() { cat; } +fi + +function __lang_encoding() { + local lang="$(<<<"$LANG" awk '{ print tolower($0) }')" + case "$lang" in + *@euro) echo "iso-8859-15";; + *.utf-8|*.utf8) echo "utf-8";; + *) echo "iso-8859-1";; + esac +} +function __norm_encoding() { + awk '{ + enc = tolower($0) + gsub(/^latin$/, "latin1", enc) + gsub(/^latin1$/, "iso-8859-1", enc) + gsub(/^latin9$/, "iso-8859-15", enc) + gsub(/[-_]/, "", enc) + if (enc == "iso8859" || enc == "iso88591" || enc == "8859" || enc == "88591") print "iso-8859-1" + else if (enc == "iso885915" || enc == "885915") print "iso-8859-15" + else if (enc == "utf" || enc == "utf8") print "utf-8" + else print $0 + }' <<<"$1" +} +function __init_encoding() { + local DEFAULT_ENCODING="$(__lang_encoding)" + [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 + [ -n "$UTOOLS_OUTPUT_ENCODING" ] || UTOOLS_OUTPUT_ENCODING="$DEFAULT_ENCODING" + UTOOLS_OUTPUT_ENCODING="$(__norm_encoding "$UTOOLS_OUTPUT_ENCODING")" + [ -n "$UTOOLS_INPUT_ENCODING" ] || UTOOLS_INPUT_ENCODING="$UTOOLS_OUTPUT_ENCODING" + UTOOLS_INPUT_ENCODING="$(__norm_encoding "$UTOOLS_INPUT_ENCODING")" + [ -n "$UTOOLS_EDITOR_ENCODING" ] || UTOOLS_EDITOR_ENCODING="$UTOOLS_INPUT_ENCODING" + UTOOLS_EDITOR_ENCODING="$(__norm_encoding "$UTOOLS_EDITOR_ENCODING")" + + IENC="$UTOOLS_INPUT_ENCODING" + OENC="$UTOOLS_OUTPUT_ENCODING" +} + +if [ -n "$UTOOLS_LANG" -a -z "$LANG" ]; then + export UTOOLS_LANG + export LANG="$UTOOLS_LANG" +fi +__init_encoding + +function tooenc() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + rawecho "$src" + else + iconv -f "$from" -t "$to" <<<"$src" + fi +} +function uecho() { + tooenc "$*" +} +function tooenc_() { + local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + rawecho_ "$src" + else + rawecho_ "$src" | iconv -f "$from" -t "$to" + fi +} +function uecho_() { + tooenc_ "$*" +} +function toienc() { + local __tie_var="$1" __tie_to="${2:-$IENC}" __tie_from="${3:-$UTOOLS_INPUT_ENCODING}" + if [ "$__tie_from" != "$__tie_to" ]; then + set_var "$__tie_var" "$(iconv -f "$__tie_from" -t "$__tie_to" <<<"${!__tie_var}")" + fi +} +function uread() { + [ $# -gt 0 ] || set -- REPLY + local __r_var + read "$@" + for __r_var in "$@"; do + [ -z "$__r_var" -o "${__r_var:0:1}" == "-" ] && continue # ignorer les options + toienc "$__r_var" + done +} + +function stooenc() { + local from="${1:-$OENC}" to="${2:-$UTOOLS_OUTPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} +function stoienc() { + local to="${1:-$IENC}" from="${2:-$UTOOLS_INPUT_ENCODING}" + if [ "$from" == "$to" ]; then + cat + else + iconv -f "$from" -t "$to" + fi +} + +export UTOOLS_EDATE +function __edate() { [ -n "$UTOOLS_EDATE" ] && date +"[%d/%m/%Y-%H:%M:%S] "; } + +export UTOOLS_ELOG_OVERWRITE +function __set_no_colors() { :; } +function elogto() { + UTOOLS_EDATE=1 + if [ -n "$1" -a -n "$2" ]; then + LANG=fr_FR.UTF8 + UTOOLS_OUTPUT_ENCODING="$UTF8" + __set_no_colors 1 + if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then + exec >"$1" 2>"$2" + else + exec >>"$1" 2>>"$2" + fi + elif [ -n "$1" ]; then + LANG=fr_FR.UTF8 + UTOOLS_OUTPUT_ENCODING="$UTF8" + __set_no_colors 1 + if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then + exec >"$1" 2>&1 + else + exec >>"$1" 2>&1 + fi + fi +} + +export __estack __tlevel +function __indent() { + if [ "${1/ +/}" != "$1" ]; then + sed "2,\$s/^/${__tlevel}/g" <<<"$1" + else + rawecho "$1" + fi +} +function __eerror() { tooenc "$(__edate)${__tlevel}ERROR $(__indent "$1")"; } +function __ewarn() { tooenc "$(__edate)${__tlevel}WARNING $(__indent "$1")"; } +function __enote() { tooenc "$(__edate)${__tlevel}NOTE $(__indent "$1")"; } +function __ebanner() { + local maxi="${COLUMNS:-80}" + local -a lines + local psfix line + + psfix="$(__edate)${__tlevel}" + while [ ${#psfix} -lt $maxi ]; do psfix="$psfix="; done + + tooenc "$psfix" + maxi=$(($maxi - 1)) + array_from_xlines lines "$1" + for line in "" "${lines[@]}" ""; do + line="$(__edate)${__tlevel}= $line" + if [ ${#line} -le $maxi ]; then + while [ ${#line} -lt $maxi ]; do line="$line "; done + line="$line=" + fi + tooenc "$line" + done + tooenc "$psfix" +} +function __eimportant() { tooenc "$(__edate)${__tlevel}IMPORTANT $(__indent "$1")"; } +function __eattention() { tooenc "$(__edate)${__tlevel}ATTENTION $(__indent "$1")"; } +function __einfo() { tooenc "$(__edate)${__tlevel}INFO $(__indent "$1")"; } +function __eecho() { tooenc "$(__edate)${__tlevel}$(__indent "$1")"; } +function __eecho_() { tooenc_ "$(__edate)${__tlevel}$(__indent "$1")"; } +function __edebug() { tooenc "$(__edate)${__tlevel}DEBUG $(__indent "$1")"; } +function __estep() { tooenc "$(__edate)${__tlevel}. $(__indent "$1")"; } +function __estepe() { tooenc "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw() { tooenc "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn() { tooenc "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi() { tooenc "$(__edate)${__tlevel}.I $(__indent "$1")"; } +function __estep_() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1")"; } +function __estepe_() { tooenc_ "$(__edate)${__tlevel}.E $(__indent "$1")"; } +function __estepw_() { tooenc_ "$(__edate)${__tlevel}.W $(__indent "$1")"; } +function __estepn_() { tooenc_ "$(__edate)${__tlevel}.N $(__indent "$1")"; } +function __estepi_() { tooenc_ "$(__edate)${__tlevel}.I $(__indent "$1")"; } +function __etitle() { tooenc "$(__edate)${__tlevel}=== $(__indent "$1")"; } +function __ebegin() { tooenc_ "$(__edate)${__tlevel}. $(__indent "$1"): "; } +function __edoto() { echo_ "."; } +function __edotw() { echo_ "w"; } +function __edotx() { echo_ "x"; } +function __edotp() { echo_ "+"; } +function __edotd() { tooenc "($1)"; } +function __eendo() { echo "[ok]"; } +function __eendx() { echo "[error]"; } +PRETTYOPTS=() +function set_verbosity() { :;} +function set_interaction() { :;} +function show_error() { + return 0 +} +function show_warn() { + return 0 +} +function show_info() { + return 0 +} +function show_verbose() { + return 0 +} +function show_debug() { + [ -n "$DEBUG" ] +} +function check_verbosity() { + return 0 +} +function get_verbosity_option() { :;} +function check_interaction() { + return 0 +} +function is_interaction() { + return 1 +} +function get_interaction_option() { :;} +__epending= +function eflush() { + if [ -n "$__epending" ]; then rawecho "$__epending" 1>&2; __epending=; fi +} +function eclearp() { + __epending= +} +function eerror() { + show_error || return; eflush; __eerror "$*" 1>&2 +} +function ewarn() { + show_warn || return; eflush; __ewarn "$*" 1>&2 +} +function enote() { + show_info || return; eflush; __enote "$*" 1>&2 +} +function ebanner() { + show_error || return; eflush; __ebanner "$*" 1>&2; sleep 5 +} +function eimportant() { + show_error || return; eflush; __eimportant "$*" 1>&2 +} +function eattention() { + show_warn || return; eflush; __eattention "$*" 1>&2 +} +function einfo() { + show_info || return; eflush; __einfo "$*" 1>&2 +} +function eecho() { + show_info || return; eflush; __eecho "$*" 1>&2 +} +function eecho_() { + show_info || return; eflush; __eecho_ "$*" 1>&2 +} +function edebug() { + show_debug || return; eflush; __edebug "$*" 1>&2 +} +function trace() { + local r cmd="$(quoted_args "$@")" + show_info && { eflush; __eecho "\$ $cmd" 1>&2; } + "$@"; r=$? + if [ $r -ne 0 ]; then + if show_info; then + eflush; __eecho "^ [EC #$r]" 1>&2 + elif show_error; then + eflush; __eecho "^ $cmd [EC #$r]" 1>&2; + fi + fi + return $r +} +function trace_error() { + local r + "$@"; r=$? + if [ $r -ne 0 ]; then + local cmd="$(quoted_args "$@")" + show_error && { eflush; __eecho "^ $cmd [EC #$r]" 1>&2; } + fi + return $r +} + +function etitle() { + local __t_deferred= + __t_etitle "$@" +} +function etitled() { + local __t_deferred=1 + __t_etitle "$@" +} +function __t_etitle() { + local __t_eend=default + local __t_clearp= + while [ -n "$1" ]; do + if [ "$1" == "--" ]; then + shift + break + elif [ "$1" == "-s" ]; then + __t_eend= + shift + elif [ "$1" == "--eend" ]; then + __t_eend=1 + shift + elif [ "$1" == "-p" ]; then + __t_clearp=1 + shift + else + break + fi + done + local __t_title="$1"; shift + local __t_s=0 + [ -n "$__estack" ] && __tlevel="${__tlevel} " + __estack="$__estack:t" + if show_info; then + if [ -n "$__t_deferred" ]; then + __epending="${__epending:+$__epending +}$(__etitle "$__t_title")" + else + eflush + __etitle "$__t_title" 1>&2 + fi + fi + if [ $# -gt 0 ]; then + "$@" + __t_s=$? + [ "$__t_eend" == "default" ] && __t_eend=1 + fi + [ "$__t_eend" == "default" ] && __t_eend= + if [ -n "$__t_eend" ]; then + eend $__t_s + [ -n "$__t_clearp" ] && eclearp + fi + return $__t_s +} +function estep() { + show_info || return; eflush; __estep "$*" 1>&2 +} +function estepe() { + show_info || return; eflush; __estepe "$*" 1>&2 +} +function estepw() { + show_info || return; eflush; __estepw "$*" 1>&2 +} +function estepn() { + show_info || return; eflush; __estepn "$*" 1>&2 +} +function estepi() { + show_info || return; eflush; __estepi "$*" 1>&2 +} +function estep_() { + show_info || return; eflush; __estep_ "$*" 1>&2 +} +function estepe_() { + show_info || return; eflush; __estepe_ "$*" 1>&2 +} +function estepw_() { + show_info || return; eflush; __estepw_ "$*" 1>&2 +} +function estepn_() { + show_info || return; eflush; __estepn_ "$*" 1>&2 +} +function estepi_() { + show_info || return; eflush; __estepi_ "$*" 1>&2 +} +function ebegin() { + local __b_eend=default + while [ -n "$1" ]; do + if [ "$1" == "--" ]; then + shift + break + elif [ "$1" == "-s" ]; then + __b_eend= + shift + elif [ "$1" == "--eend" ]; then + __b_eend=1 + shift + else + break + fi + done + local __b_msg="$1"; shift + local __b_s=0 + __estack="$__estack:b" + if show_info; then + eflush + __ebegin "$__b_msg" 1>&2 + fi + if [ $# -gt 0 ]; then + "$@" + __b_s=$? + [ "$__b_eend" == "default" ] && __b_eend=1 + fi + [ "$__b_eend" == "default" ] && __b_eend= + [ -n "$__b_eend" ] && eend $__b_s + return $__b_s +} +function edot() { + local s=$? + show_info || return + eflush + [ -n "$1" ] && s="$1" + shift + if [ "$s" == "0" ]; then + __edoto 1>&2 + else + __edotx 1>&2 + fi + show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 + return $s +} +function edotw() { + local s=$? + show_info || return + eflush + [ -n "$1" ] && s="$1" + shift + __edotw 1>&2 + show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 + return $s +} +function ewait() { + [ -n "$1" ] || return 1 + if show_info; then + local count=2 + eflush + little_sleep # certains processus retournent tout de suite + while is_running "$1"; do + sleep 1 + if [ $count -gt 0 ]; then + count=$(($count - 1)) + else + __edotp 1>&2 + fi + done + __edoto 1>&2 + else + wait "$1" + fi +} +function eend() { + local s=$? + if [ "$1" == "-c" ]; then + __estack= + __tlevel= + elif [ "${__estack%:b}" != "$__estack" ]; then + __estack="${__estack%:b}" + show_info || return + eflush + [ -n "$1" ] && s="$1" + if [ "$s" == "0" ]; then + __eendo 1>&2 + else + __eendx 1>&2 + fi + elif [ "${__estack%:t}" != "$__estack" ]; then + __estack="${__estack%:t}" + __tlevel="${__tlevel% }" + fi +} +function __elinedots() { + ebegin "$1" + local line + if show_debug; then + while read line; do + __edoto 1>&2 + __edotd "$line" 1>&2 + done + else + while read line; do + __edoto 1>&2 + done + fi + eend +} +function elinedots() { + local msg="$1"; shift + if [ $# -gt 0 ]; then + "$@" | __elinedots "$msg" + else + __elinedots "$msg" + fi +} +function ask_yesno() { + local interactive=1 + if [[ "$1" == -* ]]; then + if [ "$1" != -- ]; then + check_interaction "$1" || interactive= + fi + shift + else + check_interaction -c || interactive= + fi + local default="${2:-N}" + if [ "$default" == "C" ]; then + [ -n "$interactive" ] && default=N || default=O + elif [ "$default" == "X" ]; then + [ -n "$interactive" ] && default=O || default=N + fi + if [ -n "$interactive" ]; then + eflush + local message="$1" + local prompt="[oN]" + local r + is_yes "$default" && prompt="[On]" + if [ -n "$message" ]; then + __eecho_ "$message" 1>&2 + else + OENC="$UTF8" __eecho_ "Voulez-vous continuer?" 1>&2 + fi + OENC="$UTF8" tooenc_ " $prompt " 1>&2 + uread r + is_yes "${r:-$default}" + else + is_yes "$default" + fi +} +function ask_any() { + local interactive=1 + if [[ "$1" == -* ]]; then + if [ "$1" != -- ]; then + check_interaction "$1" || interactive= + fi + shift + else + check_interaction -c || interactive= + fi + local format="${2:-+Oq}" + format="${format/+O/On}" + format="${format/+N/oN}" + if [ -n "$interactive" ]; then + format="${format/+C/oN}" + format="${format/+X/On}" + else + format="${format/+C/On}" + format="${format/+X/oN}" + fi + local i count="${#format}" + + if [ -n "$interactive" ]; then + eflush + local message="${1:-Voulez-vous continuer?}" + local prompt="[$format]" + local r f lf defi + while true; do + __eecho_ "$message $prompt " 1>&2 + uread r + r="$(strlower "${r:0:1}")" + i=0; defi= + while [ $i -lt $count ]; do + f="${format:$i:1}" + lf="$(strlower "$f")" + [ "$r" == "$lf" ] && return $i + if [ -z "$defi" ]; then + [[ "$f" =~ [A-Z] ]] && defi="$i" + fi + if [ "$lf" == o ]; then + case "$r" in o|y|1|v|t) return $i;; esac + elif [ "$lf" == n ]; then + case "$r" in n|f|0) return $i;; esac + fi + i=$(($i + 1)) + done + [ -z "$r" ] && return ${defi:-0} + done + else + i=0 + while [ $i -lt $count ]; do + [[ "${format:$i:1}" =~ [A-Z] ]] && return $i + i=$(($i + 1)) + done + return 0 + fi +} +function read_value() { + local -a __rv_opts __rv_readline=1 __rv_showdef=1 __rv_nl= + __rv_opts=() + [ -n "$UTOOLS_NO_READLINE" ] && __rv_readline= + __rv_read "$@" +} +function read_password() { + local -a __rv_opts __rv_readline= __rv_showdef= __rv_nl=1 + __rv_opts=(-s) + __rv_read "$@" +} +function __rv_read() { + local __rv_int=1 + if [[ "$1" == -* ]]; then + if [ "$1" != -- ]; then + check_interaction "$1" || __rv_int= + fi + shift + else + check_interaction -c || __rv_int= + fi + local __rv_msg="$1" __rv_v="${2:-value}" __rv_d="$3" __rv_re="${4:-O}" + if [ -z "$__rv_int" ]; then + if is_yes "$__rv_re" && [ -z "$__rv_d" ]; then + OENC="$UTF8" eerror "La valeur par défaut de $__rv_v doit être non vide" + return 1 + fi + set_var "$__rv_v" "$__rv_d" + return 0 + fi + + eflush + local __rv_r + while true; do + if [ -n "$__rv_msg" ]; then + __eecho_ "$__rv_msg" 1>&2 + else + OENC="$UTF8" __eecho_ "Entrez la valeur" 1>&2 + fi + if [ -n "$__rv_readline" ]; then + OENC="$UTF8" tooenc_ ": " 1>&2 + uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r + else + if [ -n "$__rv_d" ]; then + if [ -n "$__rv_showdef" ]; then + tooenc_ " [$__rv_d]" 1>&2 + else + tooenc_ " [****]" 1>&2 + fi + fi + OENC="$UTF8" tooenc_ ": " 1>&2 + uread "${__rv_opts[@]}" __rv_r + [ -n "$__rv_nl" ] && echo + fi + __rv_r="${__rv_r:-$__rv_d}" + if [ -n "$__rv_r" ] || ! is_yes "$__rv_re"; then + set_var "$__rv_v" "$__rv_r" + return 0 + fi + done +} +function simple_menu() { + local __sm_title= __sm_yourchoice= __sm_default= + local -a __sm_args + parse_opts -t: __sm_title= -m: __sm_yourchoice= -d: __sm_default= @ __sm_args -- "$@" && + set -- "${__sm_args[@]}" || ewarn "$__sm_args" + + local __sm_option_var="${1:-option}" __sm_options_var="${2:-options}" + local __sm_option __sm_options + __sm_options="$__sm_options_var[*]" + if [ -z "${!__sm_options}" ]; then + OENC="$UTF8" eerror "Le tableau $__sm_options_var doit être non vide" + return 1 + fi + [ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}" + + eflush + array_copy __sm_options "$__sm_options_var" + local __sm_c=0 __sm_i __sm_choice + while true; do + if [ "$__sm_c" == "0" ]; then + [ -n "$__sm_title" ] && __eecho "=== $__sm_title ===" 1>&2 + __sm_i=1 + for __sm_option in "${__sm_options[@]}"; do + if [ "$__sm_option" == "$__sm_default" ]; then + __eecho "$__sm_i*- $__sm_option" 1>&2 + else + __eecho "$__sm_i - $__sm_option" 1>&2 + fi + let __sm_i=$__sm_i+1 + done + fi + + if [ -n "$__sm_yourchoice" ]; then + __eecho_ "$__sm_yourchoice" 1>&2 + else + OENC="$UTF8" __eecho_ "Entrez le numéro de l'option choisie" 1>&2 + fi + OENC="$UTF8" tooenc_ ": " 1>&2 + uread __sm_choice + + if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then + __sm_option="$__sm_default" + break + fi + if [ -n "$__sm_choice" -a -z "${__sm_choice//[0-9]/}" ]; then + if [ "$__sm_choice" -gt 0 -a "$__sm_choice" -le "${#__sm_options[*]}" ]; then + __sm_option="${__sm_options[$(($__sm_choice - 1))]}" + break + else + OENC="$UTF8" eerror "Numéro d'option incorrect" + fi + else + OENC="$UTF8" eerror "Vous devez saisir le numéro de l'option choisie" + fi + + let __sm_c=$__sm_c+1 + if [ "$__sm_c" -eq 5 ]; then + OENC="$UTF8" tooenc "" 1>&2 + __sm_c=0 + fi + done + set_var "$__sm_option_var" "$__sm_option" +} + +function actions_menu() { + local -a __am_action_descs __am_options __am_void_actions + local __am_tmp __am_select_action __am_select_option __am_title __am_optyc __am_actyc + local __am_default_action=auto __am_quit_action=auto + local __am_default_option= + local -a __am_args + parse_opts \ + -t: __am_title= \ + -m: __am_optyc= \ + -M: __am_actyc= \ + -e: __am_void_actions \ + -d: __am_default_action= \ + -q: __am_quit_action= \ + -o: __am_default_option= \ + @ __am_args -- "$@" && set -- "${__am_args[@]}" || { eerror "$__am_args"; return 1; } + + __am_tmp="${1:-action}"; __am_select_action="${!__am_tmp}" + __am_tmp="${2:-option}"; __am_select_option="${!__am_tmp}" + [ -n "$__am_default_option" ] && __am_select_option="$__am_default_option" + array_copy __am_action_descs "${3:-actions}" + array_copy __am_options "${4:-options}" + + eerror_unless [ ${#__am_action_descs[*]} -gt 0 ] "Vous devez spécifier le tableau des actions" || return + __actions_menu || return 1 + setv "${1:-action}" "$__am_select_action" + setv "${2:-option}" "$__am_select_option" +} +function __actions_menu() { + local title="$__am_title" + local optyc="$__am_optyc" actyc="$__am_actyc" + local default_action="$__am_default_action" + local quit_action="$__am_quit_action" + local select_action="$__am_select_action" + local select_option="$__am_select_option" + local -a action_descs options void_actions + array_copy action_descs __am_action_descs + array_copy options __am_options + array_copy void_actions __am_void_actions + + local no_options + array_isempty options && no_options=1 + + local -a actions + local tmp action name + for tmp in "${action_descs[@]}"; do + splitfsep2 "$tmp" : action name + [ -n "$action" ] || action="${name:0:1}" + action="$(strlower "$action")" + array_addu actions "$action" + done + + if [ "$default_action" == auto ]; then + default_action="$select_action" + if [ -n "$default_action" ]; then + array_contains actions "$default_action" || default_action= + fi + [ -n "$default_action" ] || default_action="${actions[0]}" + fi + default_action="${default_action:0:1}" + default_action="$(strlower "$default_action")" + + if [ "$quit_action" == auto ]; then + if [ ${#actions[*]} -gt 1 ]; then + quit_action="${actions[@]:$((-1)):1}" + array_addu void_actions "$quit_action" + fi + fi + quit_action="${quit_action:0:1}" + quit_action="$(strlower "$quit_action")" + + local action_title + for tmp in "${action_descs[@]}"; do + splitfsep2 "$tmp" : action name + [ -n "$action" ] || action="${name:0:1}" + [ -n "$name" ] || name="$action" + action="$(strlower "$action")" + if [ -n "$no_options" ]; then + if ! array_contains void_actions "$action"; then + array_del actions "$action" + continue + fi + fi + [ "$action" == "$default_action" ] && name="$name*" + action_title="${action_title:+$action_title/}$name" + done + if [ -n "$default_action" ]; then + array_contains actions "$default_action" || default_action= + fi + if [ -n "$quit_action" ]; then + array_contains actions "$quit_action" || quit_action= + fi + + if [ -n "$no_options" ]; then + if array_isempty void_actions; then + eerror "Aucune option n'est définie. Il faut définir le tableau des actions vides" + return 1 + fi + __void_actions_menu + else + __options_actions_menu + fi +} +function __void_actions_menu() { + eflush + local c=0 choice + while true; do + if [ $c -eq 0 ]; then + [ -n "$title" ] && __etitle "$title" 1>&2 + __eecho_ "=== Actions disponibles: " 1>&2 + tooenc "$action_title" 1>&2 + fi + if [ -n "$actyc" ]; then + __eecho_ "$actyc" 1>&2 + elif [ -n "$optyc" ]; then + __eecho_ "$optyc" 1>&2 + else + __eecho_ "Entrez l'action à effectuer" 1>&2 + fi + tooenc_ ": " 1>&2 + uread choice + if [ -z "$choice" -a -n "$default_action" ]; then + select_action="$default_action" + break + fi + + choice="${choice:0:1}" + choice="$(strlower "$choice")" + if array_contains actions "$choice"; then + select_action="$choice" + break + elif [ -n "$choice" ]; then + eerror "$choice: action incorrecte" + else + eerror "vous devez saisir l'action à effectuer" + fi + let c=$c+1 + if [ $c -eq 5 ]; then + tooenc "" 1>&2 + c=0 + fi + done + __am_select_action="$select_action" + __am_select_option= +} +function __options_actions_menu() { + eflush + local c=0 option choice action option + while true; do + if [ $c -eq 0 ]; then + [ -n "$title" ] && __etitle "$title" 1>&2 + i=1 + for option in "${options[@]}"; do + if [ "$option" == "$select_option" ]; then + tooenc "$i*- $option" 1>&2 + else + tooenc "$i - $option" 1>&2 + fi + let i=$i+1 + done + __estepn_ "Actions disponibles: " 1>&2 + tooenc "$action_title" 1>&2 + fi + if [ -n "$optyc" ]; then + __eecho_ "$optyc" 1>&2 + else + __eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2 + fi + tooenc_ ": " 1>&2 + uread choice + + if [ -z "$choice" -a -n "$default_action" ]; then + action="$default_action" + if array_contains void_actions "$action"; then + select_action="$action" + select_option= + break + elif [ -n "$select_option" ]; then + select_action="$action" + break + fi + fi + action="${choice:0:1}" + action="$(strlower "$action")" + if array_contains actions "$action"; then + if array_contains void_actions "$action"; then + select_action="$action" + select_option= + break + else + option="${choice:1}" + option="${option// /}" + if [ -z "$option" -a -n "$select_option" ]; then + select_action="$action" + break + elif [ -z "$option" ]; then + eerror "vous devez saisir le numéro de l'option" + elif isnum "$option"; then + if [ $option -gt 0 -a $option -le ${#options[*]} ]; then + select_action="$action" + select_option="${options[$(($option - 1))]}" + break + fi + else + eerror "$option: numéro d'option incorrecte" + fi + fi + elif isnum "$choice"; then + action="$default_action" + if [ -n "$action" ]; then + if array_contains void_actions "$action"; then + select_action="$action" + select_option= + break + else + option="${choice// /}" + if [ -z "$option" ]; then + eerror "vous devez saisir le numéro de l'option" + elif isnum "$option"; then + if [ $option -gt 0 -a $option -le ${#options[*]} ]; then + select_action="$action" + select_option="${options[$(($option - 1))]}" + break + fi + else + eerror "$option: numéro d'option incorrecte" + fi + fi + else + eerror "Vous devez spécifier l'action à effectuer" + fi + elif [ -n "$choice" ]; then + eerror "$choice: action et/ou option incorrecte" + else + eerror "vous devez saisir l'action à effectuer" + fi + let c=$c+1 + if [ $c -eq 5 ]; then + tooenc "" 1>&2 + c=0 + fi + done + __am_select_action="$select_action" + __am_select_option="$select_option" +} + + +function __ac_forgetall() { __ac_files=(); } +__ac_forgetall +function __ac_trap() { + local file + for file in "${__ac_files[@]}"; do + [ -e "$file" ] && rm -rf "$file" 2>/dev/null + done + __ac_forgetall +} +trap __ac_trap 1 3 15 EXIT +function autoclean() { + local file + for file in "$@"; do + [ -n "$file" ] && array_add __ac_files "$file" + done +} +function ac_cleanall() { + __ac_trap +} +function ac_clean() { + local file + for file in "$@"; do + if array_contains __ac_files "$file"; then + [ -e "$file" ] && rm -rf "$file" 2>/dev/null + array_del __ac_files "$file" + fi + done +} +function ac_set_tmpfile() { + local __acst_d + if show_debug; then + if [ -n "$5" ]; then + is_yes "${!5}" && __acst_d=1 + else + __acst_d=1 + fi + fi + if [ -n "$__acst_d" -a -n "$3" ]; then + set_var "$1" "$3" + [ -f "$3" -a "$4" == keep ] || >"$3" + else + local __acst_t="$(mktempf "$2")" + autoclean "$__acst_t" + set_var "$1" "$__acst_t" + fi +} +function ac_set_tmpdir() { + local __acst_d + if show_debug; then + if [ -n "$4" ]; then + is_yes "${!4}" && __acst_d=1 + else + __acst_d=1 + fi + fi + if [ -n "$__acst_d" -a -n "$3" ]; then + set_var "$1" "$3" + mkdir -p "$3" + else + local __acst_t="$(mktempd "$2")" + autoclean "$__acst_t" + set_var "$1" "$__acst_t" + fi +} +function debug_tee() { + if show_debug; then + tee "$@" + else + cat + fi +} + + +function get_user_defaults_file() { + if [ -r "$HOME/etc/default.${HOSTNAME%%.*}/$1" ]; then + echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" + elif [ -r "$HOME/etc/default/$1" ]; then + echo "$HOME/etc/default/$1" + elif [ -n "$UTOOLS_LOCAL_PROFILES" ]; then + echo "$HOME/etc/default.${HOSTNAME%%.*}/$1" + else + echo "$HOME/etc/default/$1" + fi +} + +function get_defaults_files() { + local __gd_dest="${1:-defaults}"; shift + local -a __gd_fs + local __gd_f __gd_found + for __gd_f in "$@"; do + __gd_found= + if [ -r "/etc/default/$__gd_f" ]; then + __gd_fs=("${__gd_fs[@]}" "/etc/default/$__gd_f") + __gd_found=1 + fi + if [ -r "$HOME/etc/default.${HOSTNAME%%.*}/$__gd_f" ]; then + __gd_fs=("${__gd_fs[@]}" "$HOME/etc/default.${HOSTNAME%%.*}/$__gd_f") + __gd_found=1 + elif [ -r "$HOME/etc/default/$__gd_f" ]; then + __gd_fs=("${__gd_fs[@]}" "$HOME/etc/default/$__gd_f") + __gd_found=1 + fi + if [ -z "$__gd_found" -a -r "$scriptdir/lib/default/$__gd_f" ]; then + __gd_fs=("${__gd_fs[@]}" "$scriptdir/lib/default/$__gd_f") + fi + done + array_copy "$__gd_dest" __gd_fs +} + +function set_defaults() { + local -a __sd_fs + local __sd_f + get_defaults_files __sd_fs "$@" + for __sd_f in "${__sd_fs[@]}"; do + source "$__sd_f" + done +} + + +: "${MYHOST:=$HOSTNAME}" +: "${MYHOSTNAME:=${MYHOST%%.*}}" +export MYHOST MYHOSTNAME + +function myhost() { + hostname -f 2>/dev/null || echo "$MYHOST" +} +function myhostname() { + hostname -s 2>/dev/null || echo "$MYHOSTNAME" +} +##@inc]base +uprovide base.tools +urequire base + +function base_umove() { + local -a args + local updatedir + args=(-d:,--updatedir: .) + parse_args_check "$@" || return; set -- "${args[@]}" + + eerror_unless [ -z "$updatedir" -o -d "$updatedir" ] "$updatedir: doit être un répertoire" || return + eerror_if [ $# -eq 0 ] "Vous devez spécifier les fichiers à déplacer" || return + eerror_if [ $# -eq 1 ] "Vous devez spécifier la destination" || return + + local -a srcs + local dest + + srcs=("$@") + setx dest=last_value srcs + array_del_last srcs + + if [ $# -eq 2 ]; then + if [ -d "$dest" ]; then + : # ce cas sera traité ci-dessous + elif [ -e "$dest" ]; then + eerror "$dest: refus d'écraser la destination" + return 1 + else + src="${srcs[0]}" + if [ -n "$updatedir" ]; then + if [ -L "$src" ]; then + ldest="$(readlinka "$src")" + array_find_links update_links "$ldest" "$updatedir" + else + array_find_links update_links "$src" "$updatedir" + fi + move_file "$src" "$dest" "${update_links[@]}" + else + move_link "$src" "$dest" + fi + return $? + fi + fi + + [ -d "$dest" ] || { + eerror "$dest: doit être un répertoire" + return 1 + } + for src in "${srcs[@]}"; do + if [ -n "$updatedir" ]; then + if [ -L "$src" ]; then + move_link "$src" "$dest" + else + array_find_links update_links "$src" "$updatedir" + move_file "$src" "$dest" "${update_links[@]}" + fi + else + move_link "$src" "$dest" + fi + done +} +##@inc]base.tools +##@inc[base.compat +## Fonctions de base: support des fonctions obsolètes et des versions de bash < 4.x +##@inc[base.core +## Fonctions de base: fondement +uprovide base.core + +function echo_() { + echo -n "$*" +} +function recho() { + if [[ "${1:0:2}" == -[eEn] ]]; then + echo -n - + local first="${1:1}"; shift + echo "$first$@" + else + echo "$@" + fi +} +function recho_() { + if [[ "${1:0:2}" == -[eEn] ]]; then + echo -n - + local first="${1:1}"; shift + echo -n "$first$@" + else + echo -n "$@" + fi +} +function _qval() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + recho_ "$s" +} +function should_quote() { + local l="${#1}" + [ $l -eq 0 -o $l -gt 80 ] && return 0 + local s="${*//[a-zA-Z0-9]/}" + s="${s//,/}" + s="${s//./}" + s="${s//+/}" + s="${s//\//}" + s="${s//-/}" + s="${s//_/}" + s="${s//=/}" + [ -n "$s" ] +} +function qval() { + echo -n \" + _qval "$@" + echo \" +} +function qvalm() { + if should_quote "$*"; then + echo -n \" + _qval "$@" + echo \" + else + recho "$*" + fi +} +function qvalr() { + if [ -z "$*" ]; then + : + elif should_quote "$*"; then + echo -n \" + _qval "$@" + echo \" + else + recho "$*" + fi +} +function qvals() { + local arg first=1 + for arg in "$@"; do + [ -z "$first" ] && echo -n " " + if should_quote "$arg"; then + echo -n \" + _qval "$arg" + echo -n \" + else + recho_ "$arg" + fi + first= + done + [ -z "$first" ] && echo +} +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} +function qlines() { + sed "s/'/'\\\\''/g; s/.*/'&'/g" +} +function setv() { + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + eval "$__s_var=\"\$*\"" +} +function _setv() { + local __s_var="$1"; shift + eval "$__s_var=\"\$*\"" +} +function echo_setv() { + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + echo "$__s_var=$(qvalr "$*")" +} +function setx() { + if [ "$1" == -a ]; then + shift + local __s_array="$1"; shift + if [[ "$__s_array" == *=* ]]; then + set -- "${__s_array#*=}" "$@" + __s_array="${__s_array%%=*}" + fi + eval "$__s_array=($("$@" | qlines))" + else + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + eval "$__s_var="'"$("$@")"' + fi +} +function _setvx() { + local __s_var="$1"; shift + eval "$__s_var="'"$("$@")"' +} +function _setax() { + local __s_array="$1"; shift + eval "$__s_array=($("$@" | qlines))" +} +function evalx() { + local __e_val __e_arg __e_r=0 + local -a __e_cmd + + local __e_first=1 + while [ $# -gt 0 ]; do + __e_cmd=() + while [ $# -gt 0 ]; do + __e_arg="$1"; shift + [ "$__e_arg" == // ] && break + if [ "${__e_arg%//}" != "$__e_arg" ]; then + local __e_tmp="${__e_arg%//}" + if [ -z "${__e_tmp//\\/}" ]; then + __e_arg="${__e_arg#\\}" + __e_cmd=("${__e_cmd[@]}" "$__e_arg") + continue + fi + fi + __e_cmd=("${__e_cmd[@]}" "$__e_arg") + done + + if [ -n "$__e_first" ]; then + __e_val="$("${__e_cmd[@]}")" || __e_r=$? + else + __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$? + fi + __e_first= + done + [ -n "$__e_val" ] && echo "$__e_val" + return $__e_r +} +function setxx() { + local -a __s_args + if [ "$1" == -a ]; then __s_args=(-a); shift; fi + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + __s_args=("${__s_args[@]}" "$__s_var") + setx "${__s_args[@]}" evalx "$@" +} +function evalp() { + local __e_arg __e_cmd + + while [ $# -gt 0 ]; do + __e_arg="$1"; shift + if [ "$__e_arg" == // ]; then + __e_cmd="$__e_cmd |" + continue + elif [ "${__e_arg%//}" != "$__e_arg" ]; then + local __e_tmp="${__e_arg%//}" + if [ -z "${__e_tmp//\\/}" ]; then + __e_arg="${__e_arg#\\}" + fi + fi + __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\"" + done + eval "$__e_cmd" +} +function setxp() { + local -a __s_args + if [ "$1" == -a ]; then __s_args=(-a); shift; fi + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + __s_args=("${__s_args[@]}" "$__s_var") + setx "${__s_args[@]}" evalp "$@" +} +function testx() { + local __t_op="$1"; shift + local __t_val="$(evalx "$@")" + [ $__t_op "$__t_val" ] +} +function test2x() { + local __t_val1="$1"; shift + local __t_op="$1"; shift + local __t_val2="$(evalx "$@")" + [ "$__t_val1" $__t_op "$__t_val2" ] +} +function testrx() { + local __t_val1="$1"; shift + local __t_op="$1"; shift + local __t_val2="$(evalx "$@")" + eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]' +} +function testp() { + local __t_op="$1"; shift + local __t_val="$(evalp "$@")" + [ $__t_op "$__t_val" ] +} +function test2p() { + local __t_val1="$1"; shift + local __t_op="$1"; shift + local __t_val2="$(evalp "$@")" + [ "$__t_val1" $__t_op "$__t_val2" ] +} +function testrp() { + local __t_val1="$1"; shift + local __t_op="$1"; shift + local __t_val2="$(evalp "$@")" + eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]' +} + +function err2out() { + "$@" 2>&1 +} + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} +##@inc]base.core +##@inc[base.num +## Fonctions de base: gestion des valeurs numériques +uprovide base.num + +function isnum() { + [ ${#1} -gt 0 ] || return 1 + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 + v="${v//[0-9]/}" + [ -z "$v" ] +} +function ispnum() { + [ ${#1} -gt 0 ] || return 1 + [ -z "${1//[0-9]/}" ] +} +function isrnum() { + [ ${#1} -gt 0 ] || return 1 + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 + v="${v//./}" + v="${v//,/}" + v="${v//[0-9]/}" + [ -z "$v" ] +} +##@inc]base.num +##@inc[base.bool +## Fonctions de base: valeurs booléennes +##@inc[base.num +## Fonctions de base: gestion des valeurs numériques +uprovide base.num + +function isnum() { + [ ${#1} -gt 0 ] || return 1 + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 + v="${v//[0-9]/}" + [ -z "$v" ] +} +function ispnum() { + [ ${#1} -gt 0 ] || return 1 + [ -z "${1//[0-9]/}" ] +} +function isrnum() { + [ ${#1} -gt 0 ] || return 1 + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 + v="${v//./}" + v="${v//,/}" + v="${v//[0-9]/}" + [ -z "$v" ] +} +##@inc]base.num +uprovide base.bool +urequire base.num + +function is_yes() { + case "${1,,}" in + o|oui|y|yes|v|vrai|t|true|on) return 0;; + esac + isnum "$1" && [ "$1" -ne 0 ] && return 0 + return 1 +} +function is_no() { + case "${1,,}" in + n|non|no|f|faux|false|off) return 0;; + esac + isnum "$1" && [ "$1" -eq 0 ] && return 0 + return 1 +} +function yesval() { + is_yes "$1" && echo 1 +} + +function setb() { + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + local __s_r + if "$@" >/dev/null; then + eval "$__s_var=1" + else + __s_r=$? + eval "$__s_var=" + return $__s_r + fi +} +function _setb() { + local __s_var="$1"; shift + if "$@" >/dev/null; then + eval "$__s_var=1" + else + eval "$__s_var=" + fi +} + +function evalb() { + if evalx "$@" >/dev/null; then + echo 1 + else + return $? + fi +} +function setxb() { + local __s_var="$1"; shift + if [[ "$__s_var" == *=* ]]; then + set -- "${__s_var#*=}" "$@" + __s_var="${__s_var%%=*}" + fi + setx "$__s_var" evalb "$@" +} +##@inc]base.bool +##@inc[base.quote +## Fonctions de base: protection de valeurs chaine +uprovide base.quote +urequire base.core + +function _qawk() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s// +/\\n}" + recho_ "$s" +} +function qawk() { + echo -n \" + _qawk "$@" + echo \" +} +function qseds() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\//\\/}" + s="${s// +/\\n}" + recho "$s" +} +function _qform() { + local s="$*" + s="${s//\%/%25}" + s="${s//+/%2B}" + s="${s//&/%26}" + s="${s//=/%3D}" + s="${s// /+}" + recho_ "$s" +} +function qform() { + local s="$*" + if [[ "$s" == *=* ]]; then + _qform "${s%%=*}" + echo -n = + _qform "${s#*=}" + echo + else + _qform "$s" + echo + fi +} +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -5881,16 +10582,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -5952,6 +10643,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -6110,6 +10848,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -6117,21 +10933,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -6146,21 +10960,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -6250,6 +11062,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -6273,6 +11087,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -9511,16 +14333,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -9582,6 +14394,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -9740,6 +14599,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -9747,21 +14684,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -9776,21 +14711,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -9880,6 +14813,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -9903,6 +14838,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -13141,16 +18084,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -13212,6 +18145,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -13370,6 +18350,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -13377,21 +18435,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -13406,21 +18462,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -13510,6 +18564,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -13533,6 +18589,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -16771,16 +21835,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -16842,6 +21896,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -17000,6 +22101,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -17007,21 +22186,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -17036,21 +22213,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -17140,6 +22315,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -17163,6 +22340,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -20401,16 +25586,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -20472,6 +25647,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -20630,6 +25852,84 @@ function testrp() { function err2out() { "$@" 2>&1 } + +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] +} +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac + return 1 +} + +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" + else + eval "$1=(\"\${@:2}\")" + fi + fi +} +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" +} +function upvars() { + while [ $# -gt 0 ]; do + case "$1" in + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break + ;; + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 + ;; + *) + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift + ;; + esac + done +} + +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; + esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() +} +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac + shift + done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" +} +function buildcmd() { + local -a args + array_buildcmd args "$@" + qvals "${args[@]}" +} +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" +} ##@inc]base.core ##@inc[base.num ## Fonctions de base: gestion des valeurs numériques @@ -20637,21 +25937,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -20666,21 +25964,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -20770,6 +26066,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -20793,6 +26091,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -24031,16 +29337,6 @@ function recho_() { echo -n "$@" fi } -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} function _qval() { local s="$*" s="${s//\\/\\\\}" @@ -24102,6 +29398,53 @@ function qvals() { done [ -z "$first" ] && echo } +function qwc() { + local s="$*" + s="${s//\\/\\\\}" + s="${s//\"/\\\"}" + s="${s//\$/\\\$}" + s="${s//\`/\\\`}" + local r a b + while [ -n "$s" ]; do + if [[ "$s" == *\** ]]; then + if [[ "$s" == *\?* ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\**}" + s="${s#*\*}" + r="$r\"$a\"*" + fi + elif [[ "$s" == *\?* ]]; then + if [[ "$s" == *\** ]]; then + a="${s%%\**}" + b="${s%%\?*}" + if [ ${#a} -lt ${#b} ]; then + s="${s#*\*}" + r="$r\"$a\"*" + else + s="${s#*\?}" + r="$r\"$b\"?" + fi + else + a="${s%%\?*}" + s="${s#*\?}" + r="$r\"$a\"?" + fi + else + r="$r\"$s\"" + break + fi + done + recho_ "$r" +} function qlines() { sed "s/'/'\\\\''/g; s/.*/'&'/g" } @@ -24260,3635 +29603,83 @@ function testrp() { function err2out() { "$@" 2>&1 } -##@inc]base.core -##@inc[base.num -## Fonctions de base: gestion des valeurs numériques -uprovide base.num -function isnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" - v="${v//[0-9]/}" - [ -z "$v" ] +function is_defined() { + [ -n "$(declare -p "$1" 2>/dev/null)" ] } -function ispnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] -} -function isrnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" - v="${v//./}" - v="${v//,/}" - v="${v//[0-9]/}" - [ -z "$v" ] -} -##@inc]base.num -##@inc[base.bool -## Fonctions de base: valeurs booléennes -##@inc[base.num -## Fonctions de base: gestion des valeurs numériques -uprovide base.num - -function isnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" - v="${v//[0-9]/}" - [ -z "$v" ] -} -function ispnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] -} -function isrnum() { - [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" - v="${v//./}" - v="${v//,/}" - v="${v//[0-9]/}" - [ -z "$v" ] -} -##@inc]base.num -uprovide base.bool -urequire base.num - -function is_yes() { - case "${1,,}" in - o|oui|y|yes|v|vrai|t|true|on) return 0;; - esac - isnum "$1" && [ "$1" -ne 0 ] && return 0 +function is_array() { + case "$(declare -p "$1" 2>/dev/null)" in declare\ -a*) return 0;; esac return 1 } -function is_no() { - case "${1,,}" in - n|non|no|f|faux|false|off) return 0;; - esac - isnum "$1" && [ "$1" -eq 0 ] && return 0 - return 1 -} -function yesval() { - is_yes "$1" && echo 1 -} -function setb() { - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - local __s_r - if "$@" >/dev/null; then - eval "$__s_var=1" - else - __s_r=$? - eval "$__s_var=" - return $__s_r - fi -} -function _setb() { - local __s_var="$1"; shift - if "$@" >/dev/null; then - eval "$__s_var=1" - else - eval "$__s_var=" - fi -} - -function evalb() { - if evalx "$@" >/dev/null; then - echo 1 - else - return $? - fi -} -function setxb() { - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - setx "$__s_var" evalb "$@" -} -##@inc]base.bool -##@inc[base.quote -## Fonctions de base: protection de valeurs chaine -uprovide base.quote -urequire base.core - -function _qawk() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s// -/\\n}" - recho_ "$s" -} -function qawk() { - echo -n \" - _qawk "$@" - echo \" -} -function qseds() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\//\\/}" - recho "$s" -} -function _qform() { - local s="$*" - s="${s//\%/%25}" - s="${s//+/%2B}" - s="${s//&/%26}" - s="${s//=/%3D}" - s="${s// /+}" - recho_ "$s" -} -function qform() { - local s="$*" - if [[ "$s" == *=* ]]; then - _qform "${s%%=*}" - echo -n = - _qform "${s#*=}" - echo - else - _qform "$s" - echo - fi -} -##@inc]base.quote -uprovide base.compat -urequire base.core base.num base.bool base.quote - - -function setx2() { setxx "$@"; } -function rawecho() { recho "$@"; } -function rawecho_() { recho_ "$@"; } -function quote_arg() { _qval "$@"; } -function quoted_arg() { qvalm "$@"; } -function quoted_args() { qvals "$@"; } -function set_var() { setv "$@"; } -function set_var_cmd() { echo_setv "$@"; } -function set_var_literal() { eval "$1=$2"; } - -function quote_awk() { _qawk "$@"; } -function quoted_awk() { qawk "$@"; } -function quote_seds() { qseds "$@"; } -function quote_form() { _qform "$@"; } -function quoted_form() { qform "$@"; } - - -if [ -n "$BASH_VERSINFO" -a "${BASH_VERSINFO[0]}" -lt 4 ]; then - function strlower() { tr A-Z a-z <<<"$*"; } - function strlower1() { - local str="$*" - local h="${str:0:1}" r="${str:1}" - echo "$(tr A-Z a-z <<<"$h")$r" - } - function strlowers() { - local -a vs; local v - for v in "$@"; do - vs=("${vs[@]}" "$(strlower1 "$v")") - done - echo "${vs[*]}" - } - function strupper() { tr a-z A-Z <<<"$*"; } - function strupper1() { - local str="$*" - local h="${str:0:1}" r="${str:1}" - echo "$(tr a-z A-Z <<<"$h")$r" - } - function struppers() { - local -a vs; local v - for v in "$@"; do - vs=("${vs[@]}" "$(strupper1 "$v")") - done - echo "${vs[*]}" - } - - function is_yes() { - case "$1" in - o|oui|y|yes|v|vrai|t|true|on) return 0;; - O|OUI|Y|YES|V|VRAI|T|TRUE|ON) return 0;; - esac - isnum "$1" && [ "$1" -ne 0 ] && return 0 - case "$(strlower "$1")" in - o|oui|y|yes|v|vrai|t|true|on) return 0;; - esac - return 1 - } - function is_no() { - case "$1" in - n|non|no|f|faux|false|off) return 0;; - N|NON|NO|F|FAUX|FALSE|OFF) return 0;; - esac - isnum "$1" && [ "$1" -eq 0 ] && return 0 - case "$(strlower "$1")" in - n|non|no|f|faux|false|off) return 0;; - esac - return 1 - } -fi -##@inc]base.compat -uprovide base -urequire base.init base.core base.string base.num base.bool base.array base.quote base.split base.args base.tools base.compat - -UNAME_SYSTEM=`uname -s` -[ "${UNAME_SYSTEM#CYGWIN}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Cygwin -[ "${UNAME_SYSTEM#MINGW32}" != "$UNAME_SYSTEM" ] && UNAME_SYSTEM=Mingw -UNAME_MACHINE=`uname -m` -if [ -n "$UTOOLS_CHROOT" ]; then - [ -n "$UTOOLS_UNAME_SYSTEM" ] && eval "UNAME_SYSTEM=$UTOOLS_UNAME_SYSTEM" - [ -n "$UTOOLS_UNAME_MACHINE" ] && eval "UNAME_MACHINE=$UTOOLS_UNAME_MACHINE" -fi - -function setyesval() { - is_yes "$2" && set_var "$1" 1 || set_var "$1" "" -} -function normyesval() { - is_yes "${2:-"${!1}"}" && set_var "$1" 1 || set_var "$1" "" -} -function normyesvals() { - local __nyv_yesvar - for __nyv_yesvar in "$@"; do - is_yes "${!__nyv_yesvar}" && set_var "$__nyv_yesvar" 1 || set_var "$__nyv_yesvar" "" - done -} -function quote_in() { - sed 's/\\/\\\\/g -s/"/\\"/g -s/\$/\\$/g -s/`/\\`/g' -} -function quote_sin() { - sed "s/'/'\\\\''/g" -} -function quote_sarg() { - quote_sin <<<"$1" -} -function quoted_sarg() { - echo "'$(quote_sarg "$1")'" -} -function quoted_sargs() { - local a s - for a in "$@"; do - s="${s:+$s }$(quoted_sarg "$a")" - done - rawecho "$s" -} - -function set_array_cmd() { - [ $# -eq 1 ] && set -- "$1" "$1" - local __sac_s __sac_v __sac_f - __sac_s="$1=("; shift - if [ "$1" == "@" ]; then - shift - else - eval "set -- \"\${$1[@]}\"" - fi - __sac_f=1 - for __sac_v in "$@"; do - [ -n "$__sac_f" ] && __sac_f= || __sac_s="$__sac_s " - __sac_s="$__sac_s$(quoted_arg "$__sac_v")" - done - __sac_s="$__sac_s)" - echo "$__sac_s" -} -function set_array() { - eval "$(set_array_cmd "$@")" -} -function array_count() { - eval "echo \${#$1[*]}" -} -function array_isempty() { - [ $(array_count "$1") -eq 0 ] -} -function array_new() { - eval "$1=()" -} -function array_add() { - local __aa_a="$1"; shift - eval "$__aa_a=(\"\${$__aa_a[@]}\" \"\$@\")" -} -function array_ins() { - local __aa_a="$1"; shift - eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")" -} -function array_del() { - local __ad_v - local -a __ad_vs - eval 'for __ad_v in "${'"$1"'[@]}"; do - if [ "$__ad_v" != '"$(quoted_arg "$2")"' ]; then - array_add __ad_vs "$__ad_v" - fi -done' - array_copy "$1" __ad_vs -} -function array_addu() { - local __as_v - eval 'for __as_v in "${'"$1"'[@]}"; do - if [ "$__as_v" == '"$(quoted_arg "$2")"' ]; then - return 1 - fi -done' - array_add "$1" "$2" - return 0 -} -function array_set() { - array_addu "$@" -} -function array_insu() { - local __as_v - eval 'for __as_v in "${'"$1"'[@]}"; do - if [ "$__as_v" == '"$(quoted_arg "$2")"' ]; then - return 1 - fi -done' - array_ins "$1" "$2" - return 0 -} -function array_fillrange() { - local -a __af_vs - local __af_i="${2:-1}" __af_to="${3:-10}" __af_step="${4:-1}" - while [ "$__af_i" -le "$__af_to" ]; do - __af_vs=("${__af_vs[@]}" "$__af_i") - __af_i=$(($__af_i + $__af_step)) - done - array_copy "$1" __af_vs -} -function array_eq() { - local -a __ae_a1 __ae_a2 - array_copy __ae_a1 "$1" - array_copy __ae_a2 "$2" - [ ${#__ae_a1[*]} -eq ${#__ae_a2[*]} ] || return 1 - local __ae_v __ae_i=0 - for __ae_v in "${__ae_a1[@]}"; do - [ "$__ae_v" == "${__ae_a2[$__ae_i]}" ] || return 1 - __ae_i=$(($__ae_i + 1)) - done - return 0 -} -function array_contains() { - local __ac_v - eval 'for __ac_v in "${'"$1"'[@]}"; do - if [ "$__ac_v" == '"$(quoted_arg "$2")"' ]; then - return 0 - fi -done' - return 1 -} -function array_find() { - local __af_i __af_v - __af_i=0 - eval 'for __af_v in "${'"$1"'[@]}"; do - if [ "$__af_v" == '"$(quoted_arg "$2")"' ]; then - if [ -n "$3" ]; then - echo "${'"$3"'[$__af_i]}" - else - echo "$__af_i" - fi - return 0 - fi - __af_i=$(($__af_i + 1)) -done' - return 1 -} -function array_reverse() { - local -a __ar_vs - local __ar_v - array_copy __ar_vs "$1" - array_new "$1" - for __ar_v in "${__ar_vs[@]}"; do - array_ins "$1" "$__ar_v" - done -} - -function array_replace() { - local __ar_sn="$1"; shift - local __ar_f="$1"; shift - local -a __ar_s __ar_d - local __ar_v - array_copy __ar_s "$__ar_sn" - for __ar_v in "${__ar_s[@]}"; do - if [ "$__ar_v" == "$__ar_f" ]; then - __ar_d=("${__ar_d[@]}" "$@") +function upvar() { + if unset -v "$1"; then + if [ $# -eq 2 ]; then + eval "$1=\"\$2\"" else - __ar_d=("${__ar_d[@]}" "$__ar_v") - fi - done - array_copy "$__ar_sn" __ar_d -} -function array_each() { - local __ae_an="$1"; shift - local __ae_f="$1"; shift - local -a __ae_a - local __ae_v - array_copy __ae_a "$__ae_an" - for __ae_v in "${__ae_a[@]}"; do - "$__ae_f" "$__ae_v" "$@" - done -} -function array_map() { - local __am_an="$1"; shift - local __am_f="$1"; shift - local -a __am_a __am_vs - local __am_v - array_copy __am_a "$__am_an" - for __am_v in "${__am_a[@]}"; do - __am_vs=("${__am_vs[@]}" "$("$__am_f" "$__am_v" "$@")") - done - array_copy "$__am_an" __am_vs -} -function first_value() { - eval "rawecho \"\${$1[@]:0:1}\"" -} -function last_value() { - eval "rawecho \"\${$1[@]:\$((-1)):1}\"" -} -function array_copy() { - eval "$1=(\"\${$2[@]}\")" -} -function array_copy_firsts() { - eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")" -} -function array_del_last() { - array_copy_firsts "$1" -} -function array_copy_lasts() { - eval "$1=(\"\${${2:-$1}[@]:1}\")" -} -function array_del_first() { - array_copy_lasts "$1" -} -function array_extend() { - eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")" -} -function array_extendu() { - local __ae_v __ae_s=1 - eval 'for __ae_v in "${'"$2"'[@]}"; do - array_addu "$1" "$__ae_v" && __ae_s=0 -done' - return "$__ae_s" -} -function array_extend_firsts() { - eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")" -} -function array_extend_lasts() { - eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")" -} -function array_xsplit() { - eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' -{ - gsub(/'\''/, "'\'\\\\\'\''") - print "'\''" $0 "'\''" -}'))" #" -} -function array_split() { - eval "$1=($(recho_ "$2" | awkrun RS="${3:-:}" ' -/^$/ { next } -{ - gsub(/'\''/, "'\'\\\\\'\''") - print "'\''" $0 "'\''" -}'))" #" -} -function array_from_path() { - array_split "$1" "$2" ":" -} -function array_from_xlines() { - eval "$1=($(recho_ "$2" | _nl2lf | awk ' -{ - gsub(/'\''/, "'\'\\\\\'\''") - print "'\''" $0 "'\''" -}'))" #" -} -function array_from_lines() { - eval "$1=($(recho_ "$2" | _nl2lf | awk ' -/^$/ { next } -{ - gsub(/'\''/, "'\'\\\\\'\''") - print "'\''" $0 "'\''" -}'))" #" -} -function array_join() { - local __aj_an __aj_l __aj_j __aj_s="${2:-,}" __aj_pf __aj_sf - if [ "$1" == "@" ]; then - __aj_an="\$@" - shift; shift - else - __aj_an="\${$1[@]}" - __aj_pf="$4" - __aj_sf="$5" - fi - eval 'for __aj_l in "'"$__aj_an"'"; do - __aj_j="${__aj_j:+$__aj_j'"$__aj_s"'}$__aj_pf$__aj_l$__aj_sf" -done' - if [ -n "$__aj_j" ]; then - rawecho "$__aj_j" - elif [ "$__aj_an" != "\$@" -a -n "$3" ]; then - rawecho "$3" - fi -} -function array_mapjoin() { - local __amj_src="$1" __amj_func="$2" __amj_sep="$3" - shift; shift; shift - if [ "$__amj_src" == "@" ]; then - local -a __amj_tmpsrc - __amj_tmpsrc=("$@") - __amj_src=__amj_tmpsrc - set -- - fi - local -a __amj_tmp - array_copy __amj_tmp "$__amj_src" - array_map __amj_tmp "$__amj_func" - array_join __amj_tmp "$__amj_sep" "$@" -} -function array_to_lines() { - array_join "$1" " -" "$2" "$3" "$4" -} -function array_to_path() { - array_join "$1" ":" "$2" "$3" "$4" -} -function array_fix_paths() { - local __afp_an="$1" __afp_s="${2:-:}" - local -a __afp_vs - local __afp_v - array_copy __afp_vs "$__afp_an" - array_new "$__afp_an" - for __afp_v in "${__afp_vs[@]}"; do - array_split __afp_v "$__afp_v" "$__afp_s" - array_extend "$__afp_an" __afp_v - done -} - - -function get_date_rfc822() { - LC_TIME=C date +"%a, %d %b %Y %H:%M:%S %Z" -} -function get_date_fr() { - LC_TIME=C date +"%d/%m/%Y" -} -function get_time_fr() { - LC_TIME=C date +"%Hh%M" -} -function parse_date() { - local value="$1" type="${2:-date}" - local now="$(awk 'BEGIN { print mktime(strftime("%Y %m %d 00 00 00 +0400")) }')" - case "$value" in - +*) - value="$(($now + ${value#+} * 86400))" - ;; - *) - value="$(<<<"$value" awk -F/ '{ - nd = strftime("%d"); nm = strftime("%m"); ny = strftime("%Y") - d = $1 + 0; if (d < 1) d = nd; - m = $2 + 0; if (m < 1) m = nm; - if ($3 == "") y = ny; - else { y = $3 + 0; if (y < 100) y = y + 2000; } - print mktime(sprintf("%04i %02i %02i 00 00 00 +0400", y, m, d)); - }')" - esac - case "$type" in - d|date) awk '{ print strftime("%d/%m/%Y", $0 + 0) }' <<<"$value";; - l|ldap) awk '{ print strftime("%Y%m%d%H%M%S+0400", $0 + 0) }' <<<"$value";; - m|mysql) awk '{ print strftime("%Y-%m-%d", $0 + 0) }' <<<"$value";; - *) - rawecho "$value" - ;; - esac -} - - -function udelpath() { - local _qdir="${1//\//\\/}" - eval "export ${2:-PATH}; ${2:-PATH}"'="${'"${2:-PATH}"'#$1:}"; '"${2:-PATH}"'="${'"${2:-PATH}"'%:$1}"; '"${2:-PATH}"'="${'"${2:-PATH}"'//:$_qdir:/:}"; [ "$'"${2:-PATH}"'" == "$1" ] && '"${2:-PATH}"'=' -} -function uaddpath() { - local _qdir="${1//\//\\/}" - eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="${'"${2:-PATH}"':+$'"${2:-PATH}"':}$1"' -} -function uinspathm() { - local _qdir="${1//\//\\/}" - eval "export ${2:-PATH}; "'[ "${'"${2:-PATH}"'#$1:}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'%:$1}" == "$'"${2:-PATH}"'" -a "${'"${2:-PATH}"'//:$_qdir:/:}" == "$'"${2:-PATH}"'" -a "$'"${2:-PATH}"'" != "$1" ] && '"${2:-PATH}"'="$1${'"${2:-PATH}"':+:$'"${2:-PATH}"'}"' -} -function uinspath() { - udelpath "$@" - uinspathm "$@" -} - -function withpath() { - [ "${1#./}" != "$1" -o "${1#../}" != "$1" -o "${1#/}" != "$1" ] -} -function withext() { - local basename="$(basename -- "$1")" - [ "${basename%.*}" != "$basename" ] -} -function normpath() { - local -a parts - local part ap - array_split parts "$1" / - if [ "${1#/}" != "$1" ]; then - ap=/ - elif [ -n "$2" ]; then - ap="$2" - else - ap="$(pwd)" - fi - for part in "${parts[@]}"; do - if [ "$part" == "." ]; then - continue - elif [ "$part" == ".." ]; then - ap="${ap%/*}" - [ -n "$ap" ] || ap=/ - else - [ "$ap" != "/" ] && ap="$ap/" - ap="$ap$part" - fi - done - rawecho "$ap" -} -function abspath() { - local ap="$1" - if [ "${ap#/}" != "$ap" ]; then - __normpath "$ap" && return - else - local cwd - if [ -n "$2" ]; then - cwd="$(abspath "$2")" - else - cwd="$(pwd)" - fi - ap="$cwd/$ap" - __normpath "$ap" && return - fi - normpath "$ap" -} -function __normpath() { - if [ -d "$1" ]; then - if [ -x "$1" ]; then - (cd "$1"; pwd) - return 0 - fi - elif [ -f "$1" ]; then - local dn="$(dirname -- "$1")" bn="$(basename -- "$1")" - if [ -x "$dn" ]; then - (cd "$dn"; echo "$(pwd)/$bn") - return 0 - fi - fi - return 1 -} -function parentdirs() { - array_new "$1" - local __pd_d="$(abspath "$2")" - if [[ "$3" == r* ]]; then - while [ "$__pd_d" != "/" ]; do - array_ins "$1" "$__pd_d" - __pd_d="$(dirname "$__pd_d")" - done - else - while [ "$__pd_d" != "/" ]; do - array_add "$1" "$__pd_d" - __pd_d="$(dirname "$__pd_d")" - done - fi -} -function ppath() { - local path="$1" cwd="$2" - - path="$(abspath "$path")" # essayer de normaliser le chemin - [ -n "$cwd" ] || cwd="$(pwd)" - - [ "$path" = "$cwd" ] && path="." - [ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path/#$cwd\//}" - path="${path/#$HOME/~}" - - rawecho "$path" -} -function relpath() { - local p="$(abspath "$1" "$3")" cwd="$2" - if [ -z "$cwd" ]; then - cwd="$(pwd)" - else - cwd="$(abspath "$cwd" "$3")" - fi - if [ "$p" == "$cwd" ]; then - echo "" - elif [ "${p#$cwd/}" != "$p" ]; then - rawecho "${p#$cwd/}" - else - local rp - while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do - rp="${rp:+$rp/}.." - cwd="${cwd%/*}" - done - rp="$rp/${p#$cwd/}" - echo "${rp%//}" - fi -} -function relpathx() { - local p="$(relpath "$@")" - if [ -z "$p" ]; then - echo . - elif [ "${p#../}" != "$p" -o "${p#./}" != "$p" ]; then - echo "$p" - else - echo "./$p" - fi -} -function withinpath() { - local b="$1" p="$2" strict="${3:-N}" - b="$(abspath "$b")" - p="$(abspath "$p")" - if is_yes "$strict"; then - [ "${p#$b/}" != "$p" ] - else - [ "$p" == "$b" -o "${p#$b/}" != "$p" ] - fi -} -function safe_abspath() { - local p="$1" ba="$2" br="$3" - if [ -n "$ba" ]; then - ba="$(abspath "$ba")" - else - ba="$(pwd)" - fi - [ -n "$br" ] || br="$ba" - br="$(abspath "$br" "$ba")" - p="$(abspath "$p" "$ba")" - if [ "$p" == "$br" -o "${p#$br/}" != "$p" ]; then - echo "$p" - else - return 1 - fi -} -function safe_relpath() { - local p - if p="$(safe_abspath "$1" "$2" "$3")"; then - relpath "$p" "$2" "$(pwd)" - else - return 1 - fi -} -function splitwcs() { - local __sw_p="$1" - local __sw_dd="${2:-basedir}" __sw_df="${3:-filespec}" __sw_part __sw_d __sw_f - local -a __sw_parts - array_split __sw_parts "$__sw_p" "/" - for __sw_part in "${__sw_parts[@]}"; do - if [[ "$__sw_part" == *\** ]] || [[ "$__sw_part" == *\?* ]] || [ -n "$__sw_f" ]; then - __sw_f="${__sw_f:+$__sw_f/}$__sw_part" - else - __sw_d="${__sw_d:+$__sw_d/}$__sw_part" - fi - done - [ "${__sw_p#/}" != "$__sw_p" ] && __sw_d="/$__sw_d" - set_var "$__sw_dd" "$__sw_d" - set_var "$__sw_df" "$__sw_f" -} -function deref() { - local OENC="$UTF8" - - local max_deref=50 - local file="$1" - local basedir link - while [ -L "$file" ]; do - basedir="$(dirname "$file")" - link="$(readlink "$file")" - if first_char_is "$link" "/"; then - file="$link" - else - file="$basedir/$link" - fi - - max_deref=$(($max_deref - 1)) - [ $max_deref -eq 0 ] && die "Plus de 50 indirection. Le lien $file est-il récursif?" - done - abspath "$file" -} -function readlinka() { - if [ -L "$1" ]; then - local linkdir="$(dirname -- "$1")" - abspath "$(readlink "$1")" "$linkdir" - else - abspath "$1" - fi -} -function readlinkm() { - readlink -m "$1" -} -function path_if_test() { - local op="$1"; shift - local file="$1"; shift - local rel="$1" reldir=; shift - if beginswith "$rel" relative; then - reldir="${rel#relative}" - if beginswith "$reldir" :; then - reldir="${reldir#:}" - if [ -n "$reldir" ]; then - reldir="${reldir}/" - fi - else - reldir= - fi - else - rel= - fi - - while [ -n "$1" ]; do - local basedir="$1" - if [ $op "$basedir/$file" ]; then - if [ -n "$rel" ]; then - rawecho "$reldir$file" - else - rawecho "$basedir/$file" - fi - break - fi - shift - done -} -function update_link() { - [ -L "$2" ] || return 1 - local dest link="$2" - local linkdir="$(dirname "$link")" - local ldest="$(readlink "$link")" - if [ "${ldest#/}" != "$ldest" ]; then - dest="$(abspath "$1")" - else - dest="$(relpath "$1" "$linkdir")" - fi - if [ "$dest" == "$ldest" ]; then - : # pas besoin de mettre à jour - elif [ -d "$link" ]; then - rm -f "$link" && ln -s "$dest" "$link" - else - ln -sf "$dest" "$link" - fi -} -function update_links() { - [ -n "$1" ] || return 1 - local dest="$1"; shift - local r=0 link - for link in "$@"; do - update_link "$dest" "$link" || r=$? - done - return $r -} -function move_link() { - [ -n "$1" -a -n "$2" ] || return 1 - local link="$1" dest="$2" - [ -d "$dest" ] && dest="$dest/$(basename -- "$link")" - dest="$(abspath "$dest")" - if [ -L "$link" ]; then - link="$(abspath "$link")" - [ "$dest" == "$link" ] && return 0 - ldest="$(readlinka "$link")" - mv "$link" "$dest" || return 1 - update_link "$ldest" "$dest" - else - [ "$dest" == "$link" ] && return 0 - mv "$link" "$dest" - fi -} -function array_find_links() { - local -a __afl_links __afl_result - local __afl_dir="${3:-.}" - local __afl_dest __afl_destname __afl_link __afl_linkdir __afl_ldest - __afl_dest="$(abspath "$2")" - __afl_destname="${__afl_dest##*/}" - array_from_lines __afl_links "$(find "$__afl_dir" -type l)" - for __afl_link in "${__afl_links[@]}"; do - __afl_ldest="$(readlink "$__afl_link")" - if [ "$__afl_ldest" != "$__afl_destname" ]; then - [[ "$__afl_ldest" == */"$__afl_destname" ]] || continue - fi - __afl_link="$(abspath "$__afl_link" "$__afl_dir")" - __afl_linkdir="$(dirname -- "$__afl_link")" - __afl_ldest="$(abspath "$__afl_ldest" "$__afl_linkdir")" - if [ "$__afl_ldest" == "$__afl_dest" ]; then - array_add __afl_result "$__afl_link" - fi - done - array_copy "$1" __afl_result -} -function list_links() { - local -a links - array_find_links links "$@" - array_to_lines links -} -function move_file() { - [ -n "$1" -a -n "$2" ] || return 1 - local src="$1" dest="$2" link - shift; shift - [ -d "$dest" ] && dest="$dest/$(basename -- "$src")" - move_link "$src" "$dest" || return 1 - update_links "$dest" "$@" -} - -function get_nblines() { - [ -f "$1" ] && sed -ne '$=' "$1" || echo 0 -} -function mktempf() { - mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}" -} -function mktempd() { - mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}" -} -function mkdirof() { - mkdir -p "$(dirname -- "$1")" -} -function cp_a() { - /bin/cp -a "$@" -} -function cp_R() { - /bin/cp -pR "$@" -} -function quietgrep() { - grep -q "$@" 2>/dev/null -} -function quietdiff() { - diff -q "$@" >&/dev/null -} -function testsame() { - quietdiff "$@" -} -function testdiff() { - ! quietdiff "$@" -} -function testupdated() { - if [ -f "$2" ]; then - testdiff "$1" "$2" - else - return 0 - fi -} -function testnewer() { - test ! -e "$2" -o "$1" -nt "$2" -} -function ps_all() { - ps -axww -} -function progexists() { - test -n "$1" -a -x "$(which "$1" 2>/dev/null)" -} -function has_python() { - progexists python -} -function has_gawk() { - progexists gawk -} -function is_root() { - test `id -u` -eq 0 -} -function source_ifexists() { - if [ -f "$1" ]; then source "$1" || die; fi -} -function little_sleep { - LC_NUMERIC=C sleep 0.1 -} -function random_sleep { - sleep $(($RANDOM % ${1:-1800})) -} -function is_running() { - kill -0 "$1" >&/dev/null -} -function sedi() { - sed -i "$@" -} -function csort() { - LANG=C sort "$@" -} -function lsort() { sort "$@"; } -function cgrep() { - LANG=C grep "$@" -} -function lgrep() { grep "$@"; } -function csed() { - LANG=C sed "$@" -} -function lsed() { sed "$@"; } -function cawk() { - LANG=C awk "$@" -} -function lawk() { awk "$@"; } -function cdiff() { - LANG=C diff "$@" -} -function ldiff() { diff "$@"; } - - -function fix_mode() { - local file="$1" - [ -f "$file" ] || touch "$file" || return 1 - if [ ! -w "$file" ]; then - local mode="$(stat -c %a "$file")" - chmod ${mode:0:${#mode}-3}6${mode:${#mode}-2:2} "$file" - echo "$mode" - fi -} -function unfix_mode() { - [ -n "$2" ] && chmod "$2" "$1" -} -function get_mode() { - [ -f "$1" ] || touch "$1" || return 1 - stat -c %a "$1" -} -function rm_maybe() { - local parse_opts=1 arg rm - for arg in "$@"; do - if [ -n "$parse_opts" ]; then - if [ "$arg" == "--" ]; then - parse_opts= - elif [[ "$arg" == "-*" ]]; then - continue - elif [ -n "$arg" ]; then - rm=1 - break - fi - elif [ -n "$arg" ]; then - rm=1 - break - fi - done - [ -n "$rm" ] && /bin/rm "$@" -} -__CPDIR_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) -__CPDIR_RSYNC_ARGS=(-q) -function cpdir() { - - if progexists rsync; then - [ -d "$2" ] || mkdir -p "$2" || return 1 - if [ -d "$1" ]; then - rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1/" "$2/" - else - rsync -a ${__CPDIR_RSYNC_SLOW:+-c} "${__CPDIR_RSYNC_ARGS[@]}" "$1" "$2/" - fi - else - __cpdir "$@" - fi -} -function __cpdir() { - local src="$1" dest="$2" method="${3:-cp_a}" - - if [ -d "$src" ]; then - [ -d "$dest" ] || mkdir -p "$dest" || return 1 - - local prevdir="$(pwd)" - - dest="$(abspath "$dest")" - cd "$src" - if [ -n "$(/bin/ls -a1)" ]; then - [ -n "$(/bin/ls -1)" ] && "$method" * "$dest" - local i - for i in .*; do - [ "$i" == "." -o "$i" == ".." ] && continue - "$method" "$i" "$dest" - done - fi - cd "$prevdir" - else - if [ -f "$dest" ]; then - "$method" "$src" "$dest" - elif [ -d "$dest" ]; then - "$method" "$src" "$dest" - else - mkdir -p "$dest" - "$method" "$src" "$dest" + eval "$1=(\"\${@:2}\")" fi fi } -__CPNOVCS_RSYNC_SLOW=1 # synchro potentiellement plus lente, mais plus fidèle (option -c) -__CPNOVCS_RSYNC_ARGS=(-q) -function cpnovcs() { - local src="$1" destdir="$2" - [ -d "$destdir" ] || mkdir -p "$destdir" || return 1 - if progexists rsync; then - local gitexclude=/.git/ - if [ "${src%/}" == "$src" ]; then - gitexclude="/$(basename -- "$src")$gitexclude" - fi - rsync -a ${__CPNOVCS_RSYNC_SLOW:+-c} --exclude CVS/ --exclude .svn/ --exclude "$gitexclude" "${__CPNOVCS_RSYNC_ARGS[@]}" "$src" "$destdir/" - elif [ "${src%/}" != "$src" ]; then - __cpdir "$src" "$destdir" - else - local srcname="$(basename -- "$src")" - mkdir -p "$destdir/$srcname" - __cpdir "$src" "$destdir/$srcname" - fi +function array_upvar() { + unset -v "$1" && eval "$1=(\"\${@:2}\")" } -function cpdirnovcs() { - if [ -d "$1" ]; then - cpnovcs "$1/" "$2" - else - cpnovcs "$1" "$2" - fi -} -function doinplace() { - if [ -n "$1" -a "$1" != "-" ]; then - local __dip_file="$1"; shift - autoclean "$__dip_file.tmp.$$" - "$@" <"$__dip_file" >"$__dip_file.tmp.$$" - local s=$? - [ "$s" == 0 ] && /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" - /bin/rm -f "$__dip_file.tmp.$$" - return $s - else - shift - "$@" - fi -} -function doinplacef() { - if [ -n "$1" -a "$1" != "-" ]; then - local __dip_file="$1"; shift - autoclean "$__dip_file.tmp.$$" - "$@" <"$__dip_file" >"$__dip_file.tmp.$$" - local s=$? - /bin/cat "$__dip_file.tmp.$$" >"$__dip_file" - /bin/rm -f "$__dip_file.tmp.$$" - return $s - else - shift - "$@" - fi -} -function stripnl() { - tr -d '\r\n' -} -function _nl2lf() { - awk 'BEGIN {RS="\r|\r\n|\n"} {print}' -} -function nl2lf() { - doinplace "$1" _nl2lf -} -function _nl2crlf() { - awk 'BEGIN {RS="\r|\r\n|\n"} {print $0 "\r"}' -} -function nl2crlf() { - doinplace "$1" _nl2crlf -} -function _nl2cr() { - awk 'BEGIN {RS="\r|\r\n|\n"; ORS=""} {print $0 "\r"}' -} -function nl2cr() { - doinplace "$1" _nl2cr -} -function _latin1compat() { - LANG=fr_FR.UTF-8 sed $' -s/[‘’]/\x27/g -s/[«»“”]/"/g -s/[\xC2\xA0\xE2\x80\x87\xE2\x80\xAF\xE2\x81\xA0]/ /g -' -} -function _noaccents() { - LANG=fr_FR.UTF-8 sed ' -s/[à]/a/g -s/[éèêë]/e/g -s/[ïî]/i/g -s/[ôö]/o/g -s/[üû]/u/g -s/[ç]/c/g -s/[À]/A/g -s/[ÉÈÊË]/E/g -s/[ÏÎ]/I/g -s/[ÔÖ]/O/g -s/[ÜÛ]/U/g -s/[Ç]/C/g -' -} -function list_all() { - local curdir="$(pwd)" - local b="${1:-.}"; shift - - cd "$b" 2>/dev/null || return - eval "$(__la_cmd "$@")" | while read f; do - [ "$f" == "." -o "$f" == ".." ] && continue - rawecho "$f" - done - cd "$curdir" -} -function __la_cmd() { - [ $# -gt 0 ] || set '*' - local arg - local cmd="/bin/ls -1d" - for arg in "$@"; do - arg="$(_rval "$arg")" - cmd="$cmd $arg" - done - cmd="$cmd 2>/dev/null" - echo "$cmd" -} -function list_files() { - local f - local curdir="$(pwd)" - local b="${1:-.}"; shift - - cd "$b" 2>/dev/null || return - eval "$(__la_cmd "$@")" | while read f; do - [ -f "$f" ] && rawecho "$f" - done - cd "$curdir" -} -function list_dirs() { - local f - local curdir="$(pwd)" - local b="${1:-.}"; shift - - cd "$b" 2>/dev/null || return - eval "$(__la_cmd "$@")" | while read f; do - [ "$f" == "." -o "$f" == ".." ] && continue - [ -d "$f" ] && rawecho "$f" - done - cd "$curdir" -} -function __array_ls() { - local __al_l="list_${1:-all}"; shift - local __al_an="$1"; shift - local __al_d="${1:-.}"; shift - local -a __al_fs - array_from_lines __al_fs "$("$__al_l" "$__al_d" "$@")" - local __al_f - array_new "$__al_an" - for __al_f in "${__al_fs[@]}"; do - array_add "$__al_an" "$__al_d/$__al_f" - done -} -function array_lsall() { - __array_ls all "$@" -} -function array_lsdirs() { - __array_ls dirs "$@" -} -function array_lsfiles() { - __array_ls files "$@" -} -function filter_empty() { - sed '/^$/d' -} -function filter_vcspath() { - sed ' -/^.git$/d -/^.git\//d -/\/.git$/d -/\/.git\//d -/^.svn$/d -/^.svn\//d -/\/.svn$/d -/\/.svn\//d -' -} -function merge_contlines() { - awk 'substr($0, length($0)) == "\\" { - while (getline nextline) { - $0 = substr($0, 1, length($0) - 1) nextline - if (substr($0, length($0)) != "\\") break - } - print - next -} -{print}' -} -function filter_comment() { - local -a merge - [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) - awk ' - /^[ \t]*#/ { next } - /^[ \t]*$/ { next } - { print }' | "${merge[@]}" -} -function filter_conf() { - local -a merge - [ "$1" == -m ] && merge=(merge_contlines) || merge=(cat) - grep -v '^#' | grep -v '^$' | "${merge[@]}" -} -function is_archive() { - local name="${1%.zip}" - name="${name%.tgz}" - name="${name%.tbz2}" - name="${name%.tar.gz}" - name="${name%.tar.bz2}" - name="${name%.tar}" - name="${name%.jar}" - name="${name%.war}" - name="${name%.ear}" - [ "$name" != "$1" ] -} -function extract_archive() { - local arch="$1" destdir="${2:-.}" - shift; shift - if endswith "$arch" .zip; then - unzip -q -d "$destdir" "$arch" "$@" || return - elif endswith "$arch" .tgz || endswith "$arch" .tar.gz; then - tar xzf "$arch" -C "$destdir" "$@" || return - elif endswith "$arch" .tbz2 || endswith "$arch" .tar.bz2; then - tar xjf "$arch" -C "$destdir" "$@" || return - elif endswith "$arch" .tar; then - tar xf "$arch" -C "$destdir" "$@" || return - elif endswith "$arch" .jar || endswith "$arch" .war || endswith "$arch" .ear; then - ( - arch="$(abspath "$arch")" - cd "$destdir" - jar xf "$arch" "$@" - ) || return - else - return 1 - fi -} -function get_archive_basename() { - local basename="$(basename -- "$1")" - basename="${basename%.zip}" - basename="${basename%.tgz}" - basename="${basename%.tbz2}" - basename="${basename%.gz}" - basename="${basename%.bz2}" - basename="${basename%.tar}" - basename="${basename%.jar}" - basename="${basename%.war}" - basename="${basename%.ear}" - echo "$basename" -} -function get_archive_appname() { - local appname="$(basename -- "$1")" - appname="${appname%.zip}" - appname="${appname%.tgz}" - appname="${appname%.tbz2}" - appname="${appname%.gz}" - appname="${appname%.bz2}" - appname="${appname%.tar}" - appname="${appname%.jar}" - appname="${appname%.war}" - appname="${appname%.ear}" - echo "$appname" | awk '{ - if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { - print substr($0, 1, RSTART - 1) - } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/)) { - print substr($0, 1, RSTART - 1) - } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { - print substr($0, 1, RSTART - 1) - } else { - print $0 - } -}' -} -function get_archive_versionsuffix() { - local basename="$(get_archive_basename "$1")" - echo "$basename" | awk '{ - if (match($0, /([-_.][0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { - print vs["1"] - } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { - print vs["1"] - } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { - print vs["1"] - } -}' -} -function get_archive_version() { - local basename="$(get_archive_basename "$1")" - echo "$basename" | awk '{ - if (match($0, /[-_.]([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { - print vs["1"] - } else if (match($0, /([0-9]+([-_.][0-9]+)*([a-zA-Z][0-9]*|[-_.][0-9]+[a-zA-Z][0-9]*)?)$/, vs)) { - print vs["1"] - } else if (match($0, /([0-9]+[a-z][a-z][a-z0-9]?)$/, vs)) { - print vs["1"] - } -}' -} -function __dump_usernames() { - = 500 && $6 ~ /^\/home\// { print $1 }' -} -function dump_usernames() { - array_from_lines "${1:-usernames}" "$(__dump_usernames)" -} -function __resolv_ips() { - LANG=C host "$1" 2>/dev/null | awk '/address / { gsub(/^.*address /, ""); print }' -} -function resolv_ips() { - array_from_lines "${1:-ips}" "$(__resolv_ips "$2")" -} -function __resolv_hosts() { - LANG=C host "$1" 2>/dev/null | awk '/domain name pointer / { gsub(/^.*domain name pointer /, ""); gsub(/\.$/, ""); print }' -} -function resolv_hosts() { - array_from_lines "${1:-hosts}" "$(__resolv_hosts "$2")" -} -function runscript_as() { - local OENC="$UTF8" - local user="${1:-root}"; shift - local exec_maybe= - if [ "$1" = "exec" ]; then - exec_maybe=exec - shift - fi - - local cmd - cmd="\ -__estack=$(quoted_arg "$__estack") -__tlevel=$(quoted_args "$__tlevel") -export __estack __tlevel -exec ${BASH:-/bin/sh} $(quoted_args "$@")" - - if is_yes "$UTOOLS_USES_SU" || ! progexists sudo; then - eecho "Entrez le mot de passe de root" - $exec_maybe su "$user" -c "$cmd" - else - if [ "$user" == "root" ]; then - $exec_maybe sudo -p "Entrez le mot de passe de %u: " "${BASH:-/bin/sh}" -c "$cmd" - else - $exec_maybe sudo -p "Entrez le mot de passe de %u: " su "$user" -c "$cmd" - fi - fi -} -function runscript_as_root() { - if is_root; then - local exec_maybe= - if [ "$1" = "exec" ]; then - exec_maybe=exec - shift - fi - $exec_maybe "${BASH:-/bin/sh}" "$@" - else - runscript_as root "$@" - fi -} -function run_as() { - local user="${1:-root}"; shift - local exec_maybe=exec - if [ "$1" = "--noexec" ]; then - exec_maybe= - shift - fi - - runscript_as "$user" $exec_maybe "$0" "$@" -} -function run_as_root() { - is_root || run_as root "$@" -} -function check_user() { - local user - for user in "$@"; do - [ "$USER" == "$user" ] && return 0 - done - return 1 -} -function ensure_user() { - local -a users - while [ $# -gt 0 -a "$1" != "--" ]; do - array_add users "$1" - shift - done - [ "$1" == "--" ] && shift - - if ! check_user "${users[@]}"; then - if [ ${#users[*]} -gt 1 ]; then - ewarn "Cette commande doit être lancée avec l'un des users ${users[*]}" - else - ewarn "Cette commande doit être lancée avec le user ${users[0]}" - fi - if ask_yesno "Voulez-vous tenter de relancer la commande avec le bon user?" O; then - estep "Lancement du script avec le user ${users[0]}" - run_as "${users[0]}" "$@" - return 1 - elif is_root; then - return 11 - else - return 10 - fi - fi - return 0 -} -function check_hostname() { - local userhost user host path - for userhost in "$@"; do - splitfsep "$userhost" : userhost path - splituserhost "$userhost" user host - [ "$MYHOSTNAME" == "${host%%.*}" ] && return 0 - done - return 1 -} -function check_userhostname() { - local userhost path user host - for userhost in "$@"; do - if check_hostname "$userhost"; then - [[ "$userhost" == *@* ]] || return 0 - splitfsep "$userhost" : userhost path - splituserhost "$userhost" user host - check_user "$user" && return 0 - fi - done - return 1 -} -UTOOLS_ENSURE_HOSTNAME_SSH_OPTS=() -function ensure_hostname() { - local -a userhosts - while [ $# -gt 0 -a "$1" != "--" ]; do - array_add userhosts "$1" - shift - done - [ "$1" == "--" ] && shift - - local userhost user host path - if ! check_hostname "${userhosts[@]}"; then - if [ ${#userhosts[*]} -gt 1 ]; then - ewarn "Cette commande n'est valide que sur l'un des hôtes ${userhosts[*]}" - else - ewarn "Cette commande n'est valide que sur l'hôte ${userhosts[0]}" - fi - - enote "Vous pouvez tenter de relancer le script sur ${userhosts[0]}, mais cela requière que ce script ET les données dont il a besoin soient installés dans la même version et dans le même répertoire sur l'hôte distant" - if ask_yesno "Voulez-vous tenter de relancer le script sur l'hôte distant?" N; then - splitfsep "${userhosts[0]}" : userhost path - splituserhost "$userhost" user host - [ -n "$user" ] || user=root - - estep "Lancement de la commande sur l'hôte distant $user@$host" - local cmd - [ -n "$path" ] && cmd="$(quoted_args cd "$path"); " - cmd="$cmd$(quoted_args "$script" "$@")" - ssh -qt "${UTOOLS_ENSURE_HOSTNAME_SSH_OPTS[@]}" "$user@$host" "$cmd" - [ $? -eq 255 ] && return 12 - return 1 - else - return 11 - fi - fi - local userhost user host - for userhost in "${userhosts[@]}"; do - [[ "$userhost" == *@* ]] || continue - if check_hostname "$userhost"; then - splitfsep "$userhost" : userhost path - splituserhost "$userhost" user host - [ -n "$path" ] && cd "$path" - ensure_user "$user" -- "$@" - return $? - fi - done - return 0 -} - -__AWKDEF_FUNCTIONS=' -function quote_html(s) { - gsub(/&/, "\\&", s) - gsub(/"/, "\\"", s) - gsub(/>/, "\\>", s) - gsub(/", s) - gsub(/"/, "\"", s) - gsub(/&/, "\\&", s) - return s -} -function quote_value(s) {'" - gsub(/'/, \"'\\\\''\", s) - return \"'\" s \"'\" -"'} -function quoted_values( i, line) { - line = "" - for (i = 1; i <= NF; i++) { - if (i > 1) line = line " " - line = line quote_value($i) - } - return line -} -function quote_subrepl(s) { - gsub(/\\/, "\\\\", s) - gsub(/&/, "\\\\&", s) - return s -} -function quote_grep(s) { - gsub(/[[\\.^$*]/, "\\\\&", s) - return s -} -function quote_egrep(s) { - gsub(/[[\\.^$*+?()|{]/, "\\\\&", s) - return s -} -function quote_sql(s) {'" - gsub(/'/, \"''\", s) - return \"'\" s \"'\" -"'} -function unquote_mysqlcsv(s) { - gsub(/\\n/, "\n", s) - gsub(/\\t/, "\t", s) - gsub(/\\0/, "\0", s) - gsub(/\\\\/, "\\", s) - return s -} -function array_new(dest) { - dest[0] = 0 # forcer awk à considérer dest comme un tableau - delete dest -} -function array_newsize(dest, size, i) { - dest[0] = 0 # forcer awk à considérer dest comme un tableau - delete dest - size = int(size) - for (i = 1; i <= size; i++) { - dest[i] = "" - } -} -function array_len(values, count, i) { - count = 0 - for (i in values) { - count++ - } - return count -} -function mkindices(values, indices, i, j) { - array_new(indices) - j = 1 - for (i in values) { - indices[j++] = int(i) - } - return asort(indices) -} -function array_copy(dest, src, count, indices, i) { - array_new(dest) - count = mkindices(src, indices) - for (i = 1; i <= count; i++) { - dest[indices[i]] = src[indices[i]] - } -} -function array_getlastindex(src, count, indices) { - count = mkindices(src, indices) - if (count == 0) return 0 - return indices[count] -} -function array_add(dest, value, lastindex) { - lastindex = array_getlastindex(dest) - dest[lastindex + 1] = value -} -function array_deli(dest, i, l) { - i = int(i) - if (i == 0) return - l = array_len(dest) - while (i < l) { - dest[i] = dest[i + 1] - i++ - } - delete dest[l] -} -function array_del(dest, value, ignoreCase, i) { - do { - i = key_index(value, dest, ignoreCase) - if (i != 0) array_deli(dest, i) - } while (i != 0) -} -function array_extend(dest, src, count, lastindex, indices, i) { - lastindex = array_getlastindex(dest) - count = mkindices(src, indices) - for (i = 1; i <= count; i++) { - dest[lastindex + i] = src[indices[i]] - } -} -function array_fill(dest, i) { - array_new(dest) - for (i = 1; i <= NF; i++) { - dest[i] = $i - } -} -function array_getline(src, count, indices, i, j) { - $0 = "" - count = mkindices(src, indices) - for (i = 1; i <= count; i++) { - j = indices[i] - $j = src[j] - } -} -function array_appendline(src, count, indices, i, nf, j) { - count = mkindices(src, indices) - nf = NF - for (i = 1; i <= count; i++) { - j = nf + indices[i] - $j = src[indices[i]] - } -} -function in_array(value, values, ignoreCase, i) { - if (ignoreCase) { - value = tolower(value) - for (i in values) { - if (tolower(values[i]) == value) return 1 - } - } else { - for (i in values) { - if (values[i] == value) return 1 - } - } - return 0 -} -function key_index(value, values, ignoreCase, i) { - if (ignoreCase) { - value = tolower(value) - for (i in values) { - if (tolower(values[i]) == value) return int(i) - } - } else { - for (i in values) { - if (values[i] == value) return int(i) - } - } - return 0 -} -function array2s(values, prefix, sep, suffix, noindices, first, i, s) { - if (!prefix) prefix = "[" - if (!sep) sep = ", " - if (!suffix) suffix = "]" - s = prefix - first = 1 - for (i in values) { - if (first) first = 0 - else s = s sep - if (!noindices) s = s "[" i "]=" - s = s values[i] - } - s = s suffix - return s -} -function array2so(values, prefix, sep, suffix, noindices, count, indices, i, s) { - if (!prefix) prefix = "[" - if (!sep) sep = ", " - if (!suffix) suffix = "]" - s = prefix - count = mkindices(values, indices) - for (i = 1; i <= count; i++) { - if (i > 1) s = s sep - if (!noindices) s = s "[" indices[i] "]=" - s = s values[indices[i]] - } - s = s suffix - return s -} -function array_join(values, sep, prefix, suffix, count, indices, i, s) { - s = prefix - count = mkindices(values, indices) - for (i = 1; i <= count; i++) { - if (i > 1) s = s sep - s = s values[indices[i]] - } - s = s suffix - return s -} -function printto(s, output) { - if (output == "") { - print s - } else if (output ~ /^>>/) { - sub(/^>>/, "", output) - print s >>output - } else if (output ~ /^>/) { - sub(/^>/, "", output) - print s >output - } else { - print s >output - } -} -function find_line(input, field, value, orig, line) { - orig = $0 - line = "" - while ((getline 0) { - if ($field == value) { - line = $0 - break - } - } - close(input) - $0 = orig - return line -} -function merge_line(input, field, key, line) { - line = find_line(input, field, $key) - if (line != "") $0 = $0 FS line -} -function __csv_parse_quoted(line, destl, colsep, qchar, echar, pos, tmpl, nextc, resl) { - line = substr(line, 2) - resl = "" - while (1) { - pos = index(line, qchar) - if (pos == 0) { - resl = resl line - destl[0] = "" - destl[1] = 0 - return resl - } - if (echar != "" && pos > 1) { - prevc = substr(line, pos - 1, 1) - quotec = substr(line, pos, 1) - nextc = substr(line, pos + 1, 1) - if (prevc == echar) { - tmpl = substr(line, 1, pos - 2) - resl = resl tmpl quotec - line = substr(line, pos + 1) - continue - } - tmpl = substr(line, 1, pos - 1) - if (nextc == colsep || nextc == "") { - resl = resl tmpl - destl[0] = substr(line, pos + 2) - destl[1] = nextc == colsep - return resl - } else { - resl = resl tmpl quotec - line = substr(line, pos + 1) - } - } else { - tmpl = substr(line, 1, pos - 1) - quotec = substr(line, pos, 1) - nextc = substr(line, pos + 1, 1) - if (nextc == colsep || nextc == "") { - resl = resl tmpl - destl[0] = substr(line, pos + 2) - destl[1] = nextc == colsep - return resl - } else if (nextc == qchar) { - resl = resl tmpl quotec - line = substr(line, pos + 2) - } else { - resl = resl tmpl quotec - line = substr(line, pos + 1) - } - } - } -} -function __csv_parse_unquoted(line, destl, colsep, qchar, echar, pos) { - pos = index(line, colsep) - if (pos == 0) { - destl[0] = "" - destl[1] = 0 - return line - } else { - destl[0] = substr(line, pos + 1) - destl[1] = 1 - return substr(line, 1, pos - 1) - } -} -function __array_parsecsv(fields, line, nbfields, colsep, qchar, echar, shouldparse, destl, i) { - array_new(fields) - array_new(destl) - i = 1 - shouldparse = 0 - while (shouldparse || line != "") { - if (index(line, qchar) == 1) { - value = __csv_parse_quoted(line, destl, colsep, qchar, echar) - line = destl[0] - shouldparse = destl[1] - } else { - value = __csv_parse_unquoted(line, destl, colsep, qchar, echar) - line = destl[0] - shouldparse = destl[1] - } - fields[i] = value - i = i + 1 - } - if (nbfields) { - nbfields = int(nbfields) - i = array_len(fields) - while (i < nbfields) { - i++ - fields[i] = "" - } - } - return array_len(fields) -} -BEGIN { - DEFAULT_COLSEP = "," - DEFAULT_QCHAR = "\"" - DEFAULT_ECHAR = "" -} -function array_parsecsv2(fields, line, nbfields, colsep, qchar, echar) { - return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) -} -function array_parsecsv(fields, line, nbfields, colsep, qchar, echar) { - if (colsep == "") colsep = DEFAULT_COLSEP - if (qchar == "") qchar = DEFAULT_QCHAR - if (echar == "") echar = DEFAULT_ECHAR - return __array_parsecsv(fields, line, nbfields, colsep, qchar, echar) -} -function parsecsv(line, fields) { - array_parsecsv(fields, line) - array_getline(fields) - return NF -} -function getlinecsv(file, fields) { - if (file) { - getline 1) line = line colsep - if (qchar != "" && index(value, qchar) != 0) { - if (echar != "") gsub(qchar, quote_subrepl(echar) "&", value); - else gsub(qchar, "&&", value); - } - if (qchar != "" && (index(value, mvsep) != 0 || index(value, colsep) != 0 || index(value, qchar) != 0 || __csv_should_quote(value))) { - line = line qchar value qchar - } else { - line = line value - } - } - return line -} -function array_formatcsv(fields) { - return array_formatcsv2(fields, ",", ";", "\"", "") -} -function array_printcsv(fields, output) { - printto(array_formatcsv(fields), output) -} -function get_formatcsv( fields) { - array_fill(fields) - return array_formatcsv(fields) -} -function formatcsv() { - $0 = get_formatcsv() -} -function printcsv(output, fields) { - array_fill(fields) - array_printcsv(fields, output) -} -function array_findcsv(fields, input, field, value, nbfields, orig, found, i) { - array_new(orig) - array_fill(orig) - array_new(fields) - found = 0 - while ((getline 0) { - array_parsecsv(fields, $0, nbfields) - if (fields[field] == value) { - found = 1 - break - } - } - close(input) - array_getline(orig) - if (!found) { - delete fields - if (nbfields) { - nbfields = int(nbfields) - i = array_len(fields) - while (i < nbfields) { - i++ - fields[i] = "" - } - } - } - return found -} - -function __and(var, x, l_res, l_i) { - l_res=0; - for (l_i=0; l_i < 8; l_i++){ - if (var%2 == 1 && x%2 == 1) l_res=l_res/2 + 128; - else l_res/=2; - var=int(var/2); - x=int(x/2); - } - return l_res; -} -function __lshift(var, x) { - while(x > 0){ - var*=2; - x--; - } - return var; -} -function __rshift(var, x) { - while(x > 0){ - var=int(var/2); - x--; - } - return var; -} -BEGIN { - __BASE64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" -} -function b64decode(src, result, base1, base2, base3, base4) { - result = "" - while (length(src) > 0) { - base1 = substr(src, 1, 1) - base2 = substr(src, 2, 1) - base3 = substr(src, 3, 1); if (base3 == "") base3 = "=" - base4 = substr(src, 4, 1); if (base4 == "") base4 = "=" - byte1 = index(__BASE64, base1) - 1 - if (byte1 < 0) byte1 = 0 - byte2 = index(__BASE64, base2) - 1 - if (byte2 < 0) byte2 = 0 - byte3 = index(__BASE64, base3) - 1 - if (byte3 < 0) byte3 = 0 - byte4 = index(__BASE64, base4) - 1 - if (byte4 < 0) byte4 = 0 - result = result sprintf( "%c", __lshift(__and(byte1, 63), 2) + __rshift(__and(byte2, 48), 4) ) - if (base3 != "=") result = result sprintf( "%c", __lshift(__and(byte2, 15), 4) + __rshift(__and(byte3, 60), 2) ) - if (base4 != "=") result = result sprintf( "%c", __lshift(__and(byte3, 3), 6) + byte4 ) - src = substr(src, 5) - } - return result -} -' -function awkdef() { - - if [ "${1:0:3}" == "-f" ]; then - shift - echo "$__AWKDEF_FUNCTIONS" - fi - if [ $# -gt 0 ]; then - local __ad_arg __ad_vpos __ad_name __ad_value - echo "BEGIN {" - while [ -n "${1:0:1}" ]; do - __ad_arg="${1:0:256}" - local __ad_array= - if [ "${__ad_arg%\[@\]}" != "$__ad_arg" ]; then - __ad_array=1 - __ad_name="${__ad_arg%\[@\]}" - [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break - __ad_value="$__ad_name" - elif [[ "$__ad_arg" == *\[@\]=* ]]; then - __ad_array=1 - __ad_name="${__ad_arg%%\[@\]=*}" - [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break - __ad_vpos=$((${#__ad_name} + 4)) - __ad_value="${1:$__ad_vpos}" - [ ${#__ad_value} -ne 0 ] || __ad_value="$__ad_name" - elif [[ "$__ad_arg" == *=* ]]; then - local __ad_int= __ad_str= - __ad_name="${__ad_arg%%=*}" - __ad_vpos=$((${#__ad_name} + 1)) - if [ "${__ad_name%:int}" != "$__ad_name" ]; then - __ad_int=1 - __ad_name="${__ad_name%:int}" - elif [ "${__ad_name%:str}" != "$__ad_name" ]; then - __ad_str=1 - __ad_name="${__ad_name%:str}" - fi - [ -z "${__ad_name//[a-zA-Z0-9_]/}" ] || break - __ad_value="${1:$__ad_vpos}" - if [ -n "$__ad_int" ]; then - echo "$__ad_name = int($(quoted_awk "$__ad_value") + 0)" - elif [ -n "$__ad_str" ]; then - echo "$__ad_name = $(quoted_awk "$__ad_value")" - elif [ ${#__ad_value} -lt 256 ] && isnum "$__ad_value"; then - echo "$__ad_name = $__ad_value" - else - echo "$__ad_name = $(quoted_awk "$__ad_value")" - fi - else - break - fi - if [ -n "$__ad_array" ]; then - if [ "${__ad_value:0:2}" == $'<\n' ]; then - local -a __ad_values - array_from_lines __ad_values "${__ad_value:2}" - __ad_value=__ad_values - fi - __ad_value="${__ad_value}[@]" - local __ad_i=1 - echo "$__ad_name[0] = 0; delete $__ad_name" - for __ad_arg in "${!__ad_value}"; do - echo "$__ad_name[$__ad_i]=$(quoted_awk "$__ad_arg")" - __ad_i=$(($__ad_i + 1)) - done - eval "echo \"\${__ad_name}_count = \${#$__ad_value}\"" - fi - shift - done - echo "}" - for __ad_arg in "$@"; do - rawecho "$__ad_arg" - done - fi -} -function lawkrun() { - local -a __ar_defs __ar_args - while [ $# -gt 0 -a "$1" != "--" ]; do - __ar_defs=("${__ar_defs[@]}" "$1") - shift - done - shift +function upvars() { while [ $# -gt 0 ]; do - __ar_args=("${__ar_args[@]}" "$1") - shift - done - local __ar_script="$(awkdef "${__ar_defs[@]}")" - awk "$__ar_script" "${__ar_args[@]}" -} -function cawkrun() { LANG=C lawkrun "$@"; } -function awkrun() { LANG=C lawkrun "$@"; } - -function __lf_get_age() { - local y=$(date "+%Y") - local dy=$(date "+%j"); while [ "${dy#0}" != "$dy" ]; do dy="${dy#0}"; done - [ -n "$dy" ] || dy=0 - local h=$(date "+%H"); while [ "${h#0}" != "$h" ]; do h="${h#0}"; done - [ -n "$h" ] || h=0 - echo $((($y * 365 + $dy) * 24 + $h)) -} -function lf_trylock() { - local eoo lockfile max_hours=4 - while [ -n "$1" ]; do case "$1" in - -h) shift; max_hours="$1";; - --) shift; eoo=1;; - *) eoo=1;; - esac - [ -n "$eoo" ] && break - shift - done - - lockfile="$1" - [ -n "$lockfile" ] || die "il faut spécifier un fichier pour le verrou" - - local now="$(__lf_get_age)" - if (set -C; echo "$now" >"$lockfile") 2>/dev/null; then - return 0 - fi - local prev diff - if prev="$(<"$lockfile")"; then - diff="$(($now - $prev))" - if [ "$diff" -gt "$max_hours" ]; then - echo stale - else - echo locked - fi - elif [ -f "$lockfile" ]; then - echo retry - fi - return 1 -} -function pidfile_set() { - local eoo pidfile pid=$$ replace= - while [ -n "$1" ]; do - case "$1" in - -p) - shift - pid="$1" + -a) + unset -v "$2" && eval "$2=(\"\${@:3}\")" + break ;; - -r) - replace=1 - ;; - --) - shift - eoo=1 + -a*) + unset -v "$2" && eval "$2=(\"\${@:3:${1#-a}}\")" + shift $((${1#-a} + 2)) || return 1 ;; *) - eoo=1 + unset -v "$1" && eval "$1=\"\$2\"" + shift; shift ;; esac - [ -n "$eoo" ] && break - shift - done - - pidfile="$1" - [ -n "$pidfile" ] || return 10 - - if [ -f "$pidfile" ]; then - local curpid="$(<"$pidfile")" - if is_running "$curpid"; then - return 1 - elif [ -n "$replace" ]; then - /bin/rm -f "$pidfile" || return 10 - else - return 2 - fi - fi - - echo_ "$pid" >"$pidfile" || return 10 - autoclean "$pidfile" - return 0 -} -function pidfile_check() { - local pidfile="$1" - [ -n "$pidfile" ] || return 10 - - if [ -f "$pidfile" ]; then - [ -r "$pidfile" ] || return 10 - local pid="$(<"$pidfile")" - is_running "$pid" && return 0 - fi - return 1 -} -function page_maybe() { - if isatty; then - less -XF "$@" - else - cat - fi -} - - -function utools_local() { - local arg - [ $# -gt 0 ] || set -- opts verbosity interaction - for arg in "$@"; do - case "$arg" in - parse_opts|opts|o|args) echo "local -a args";; - verbosity|v) echo "local __verbosity='$__verbosity'";; - interaction|i) echo "local __interaction='$__interaction'";; - esac done } -function isatty() { - tty -s <&1 -} -function in_isatty() { - tty -s -} -function out_isatty() { - tty -s <&1 -} -function err_isatty() { - tty -s <&2 -} -function die() { [ $# -gt 0 ] && eerror "$@"; exit 1; } -function exit_with { if [ $# -gt 0 ]; then "$@"; fi; exit $?; } -function die_with { [ $# -gt 0 ] && eerror "$1"; shift; [ $# -gt 0 ] && "$@"; exit 1; } -function die_unless() { - local count=$# - if [ $count -eq 0 ]; then - exit 1 - elif [ $count -eq 1 ]; then - "$@" || exit $? - else - local m r - m="${@:$count}" - count=$(($count - 1)) - set -- "${@:1:$count}" - if "$@"; then - : - else - r=$? - eerror "$m" - exit $r - fi - fi -} -function eerror_unless() { - local count=$# - if [ $count -eq 0 ]; then - return 1 - elif [ $count -eq 1 ]; then - "$@" || return $? - else - local m r - m="${@:$count}" - count=$(($count - 1)) - set -- "${@:1:$count}" - if "$@"; then - : - else - r=$? - eerror "$m" - return $r - fi - fi -} -function die_if() { - local count=$# - if [ $count -eq 0 ]; then - : - elif [ $count -eq 1 ]; then - "$@" && exit 1 - else - local m r - m="${@:$count}" - count=$(($count - 1)) - set -- "${@:1:$count}" - if "$@"; then - eerror "$m" - exit 1 - fi - fi -} -function eerror_if() { - local count=$# - if [ $count -eq 0 ]; then - : - elif [ $count -eq 1 ]; then - "$@" && return 1 - else - local m r - m="${@:$count}" - count=$(($count - 1)) - set -- "${@:1:$count}" - if "$@"; then - eerror "$m" - return 1 - fi - fi -} - -TAB=$'\t' -LATIN1=iso-8859-1 -LATIN9=iso-8859-15 -UTF8=utf-8 -OENC="$UTF8" - -if ! progexists iconv; then - function iconv() { cat; } -fi - -function __lang_encoding() { - local lang="$(<<<"$LANG" awk '{ print tolower($0) }')" - case "$lang" in - *@euro) echo "iso-8859-15";; - *.utf-8|*.utf8) echo "utf-8";; - *) echo "iso-8859-1";; +function __ab_process_pending() { + local -a values + case "$mode" in + cmd) values="$("${pending[@]}")";; + ssplit) eval "values=($("${pending[@]}"))";; + lsplit) eval "values=($("${pending[@]}" | qlines))";; + add) values=("${pending[@]}");; esac + cmd=("${cmd[@]}" "${values[@]}") + pending=() } -function __norm_encoding() { - awk '{ - enc = tolower($0) - gsub(/^latin$/, "latin1", enc) - gsub(/^latin1$/, "iso-8859-1", enc) - gsub(/^latin9$/, "iso-8859-15", enc) - gsub(/[-_]/, "", enc) - if (enc == "iso8859" || enc == "iso88591" || enc == "8859" || enc == "88591") print "iso-8859-1" - else if (enc == "iso885915" || enc == "885915") print "iso-8859-15" - else if (enc == "utf" || enc == "utf8") print "utf-8" - else print $0 - }' <<<"$1" -} -function __init_encoding() { - local DEFAULT_ENCODING="$(__lang_encoding)" - [ -n "$DEFAULT_ENCODING" ] || DEFAULT_ENCODING=utf-8 - [ -n "$UTOOLS_OUTPUT_ENCODING" ] || UTOOLS_OUTPUT_ENCODING="$DEFAULT_ENCODING" - UTOOLS_OUTPUT_ENCODING="$(__norm_encoding "$UTOOLS_OUTPUT_ENCODING")" - [ -n "$UTOOLS_INPUT_ENCODING" ] || UTOOLS_INPUT_ENCODING="$UTOOLS_OUTPUT_ENCODING" - UTOOLS_INPUT_ENCODING="$(__norm_encoding "$UTOOLS_INPUT_ENCODING")" - [ -n "$UTOOLS_EDITOR_ENCODING" ] || UTOOLS_EDITOR_ENCODING="$UTOOLS_INPUT_ENCODING" - UTOOLS_EDITOR_ENCODING="$(__norm_encoding "$UTOOLS_EDITOR_ENCODING")" - - IENC="$UTOOLS_INPUT_ENCODING" - OENC="$UTOOLS_OUTPUT_ENCODING" -} - -if [ -n "$UTOOLS_LANG" -a -z "$LANG" ]; then - export UTOOLS_LANG - export LANG="$UTOOLS_LANG" -fi -__init_encoding - -function tooenc() { - local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" - if [ "$from" == "$to" ]; then - rawecho "$src" - else - iconv -f "$from" -t "$to" <<<"$src" - fi -} -function uecho() { - tooenc "$*" -} -function tooenc_() { - local src="$1" from="${2:-$OENC}" to="${3:-$UTOOLS_OUTPUT_ENCODING}" - if [ "$from" == "$to" ]; then - rawecho_ "$src" - else - rawecho_ "$src" | iconv -f "$from" -t "$to" - fi -} -function uecho_() { - tooenc_ "$*" -} -function toienc() { - local __tie_var="$1" __tie_to="${2:-$IENC}" __tie_from="${3:-$UTOOLS_INPUT_ENCODING}" - if [ "$__tie_from" != "$__tie_to" ]; then - set_var "$__tie_var" "$(iconv -f "$__tie_from" -t "$__tie_to" <<<"${!__tie_var}")" - fi -} -function uread() { - [ $# -gt 0 ] || set -- REPLY - local __r_var - read "$@" - for __r_var in "$@"; do - [ -z "$__r_var" -o "${__r_var:0:1}" == "-" ] && continue # ignorer les options - toienc "$__r_var" - done -} - -function stooenc() { - local from="${1:-$OENC}" to="${2:-$UTOOLS_OUTPUT_ENCODING}" - if [ "$from" == "$to" ]; then - cat - else - iconv -f "$from" -t "$to" - fi -} -function stoienc() { - local to="${1:-$IENC}" from="${2:-$UTOOLS_INPUT_ENCODING}" - if [ "$from" == "$to" ]; then - cat - else - iconv -f "$from" -t "$to" - fi -} - -export UTOOLS_EDATE -function __edate() { [ -n "$UTOOLS_EDATE" ] && date +"[%d/%m/%Y-%H:%M:%S] "; } - -export UTOOLS_ELOG_OVERWRITE -function __set_no_colors() { :; } -function elogto() { - UTOOLS_EDATE=1 - if [ -n "$1" -a -n "$2" ]; then - LANG=fr_FR.UTF8 - UTOOLS_OUTPUT_ENCODING="$UTF8" - __set_no_colors 1 - if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then - exec >"$1" 2>"$2" - else - exec >>"$1" 2>>"$2" - fi - elif [ -n "$1" ]; then - LANG=fr_FR.UTF8 - UTOOLS_OUTPUT_ENCODING="$UTF8" - __set_no_colors 1 - if [ -n "$UTOOLS_ELOG_OVERWRITE" ]; then - exec >"$1" 2>&1 - else - exec >>"$1" 2>&1 - fi - fi -} - -export __estack __tlevel -function __indent() { - if [ "${1/ -/}" != "$1" ]; then - sed "2,\$s/^/${__tlevel}/g" <<<"$1" - else - rawecho "$1" - fi -} -function __eerror() { tooenc "$(__edate)${__tlevel}* error: $(__indent "$1")"; } -function __ewarn() { tooenc "$(__edate)${__tlevel}* warning: $(__indent "$1")"; } -function __enote() { tooenc "$(__edate)${__tlevel}* note: $(__indent "$1")"; } -function __ebanner() { - local maxi="${COLUMNS:-80}" - local -a lines - local psfix line - - psfix="$(__edate)${__tlevel}" - while [ ${#psfix} -lt $maxi ]; do psfix="$psfix="; done - - tooenc "$psfix" - maxi=$(($maxi - 1)) - array_from_xlines lines "$1" - for line in "" "${lines[@]}" ""; do - line="$(__edate)${__tlevel}= $line" - if [ ${#line} -le $maxi ]; then - while [ ${#line} -lt $maxi ]; do line="$line "; done - line="$line=" - fi - tooenc "$line" - done - tooenc "$psfix" -} -function __eimportant() { tooenc "$(__edate)${__tlevel}* important: $(__indent "$1")"; } -function __eattention() { tooenc "$(__edate)${__tlevel}* attention: $(__indent "$1")"; } -function __einfo() { tooenc "$(__edate)${__tlevel}* info: $(__indent "$1")"; } -function __eecho() { tooenc "$(__edate)${__tlevel}$(__indent "$1")"; } -function __eecho_() { tooenc_ "$(__edate)${__tlevel}$(__indent "$1")"; } -function __edebug() { tooenc "$(__edate)${__tlevel}* debug: $(__indent "$1")"; } -function __estep() { tooenc "$(__edate)${__tlevel}* $(__indent "$1")"; } -function __estepe() { __estep "$@"; } -function __estepw() { __estep "$@"; } -function __estepn() { __estep "$@"; } -function __estepi() { __estep "$@"; } -function __estep_() { tooenc_ "$(__edate)${__tlevel}* $(__indent "$1")"; } -function __estepe_() { __estep_ "$@"; } -function __estepw_() { __estep_ "$@"; } -function __estepn_() { __estep_ "$@"; } -function __estepi_() { __estep_ "$@"; } -function __etitle() { tooenc "$(__edate)${__tlevel}+++ $(__indent "$1")"; } -function __ebegin() { tooenc_ "$(__edate)${__tlevel}* $(__indent "$1"): "; } -function __edoto() { echo_ "."; } -function __edotw() { echo_ "w"; } -function __edotx() { echo_ "x"; } -function __edotp() { echo_ "+"; } -function __edotd() { tooenc "($1)"; } -function __eendo() { echo "[ok]"; } -function __eendx() { echo "[error]"; } -PRETTYOPTS=() -function set_verbosity() { :;} -function set_interaction() { :;} -function show_error() { - return 0 -} -function show_warn() { - return 0 -} -function show_info() { - return 0 -} -function show_verbose() { - return 0 -} -function show_debug() { - [ -n "$DEBUG" ] -} -function check_verbosity() { - return 0 -} -function get_verbosity_option() { :;} -function check_interaction() { - return 0 -} -function is_interaction() { - return 1 -} -function get_interaction_option() { :;} -__epending= -function eflush() { - if [ -n "$__epending" ]; then rawecho "$__epending" 1>&2; __epending=; fi -} -function eclearp() { - __epending= -} -function eerror() { - show_error || return; eflush; __eerror "$*" 1>&2 -} -function ewarn() { - show_warn || return; eflush; __ewarn "$*" 1>&2 -} -function enote() { - show_info || return; eflush; __enote "$*" 1>&2 -} -function ebanner() { - show_error || return; eflush; __ebanner "$*" 1>&2; sleep 5 -} -function eimportant() { - show_error || return; eflush; __eimportant "$*" 1>&2 -} -function eattention() { - show_warn || return; eflush; __eattention "$*" 1>&2 -} -function einfo() { - show_info || return; eflush; __einfo "$*" 1>&2 -} -function eecho() { - show_info || return; eflush; __eecho "$*" 1>&2 -} -function eecho_() { - show_info || return; eflush; __eecho_ "$*" 1>&2 -} -function edebug() { - show_debug || return; eflush; __edebug "$*" 1>&2 -} -function trace() { - local r cmd="$(quoted_args "$@")" - show_info && { eflush; __eecho "\$ $cmd" 1>&2; } - "$@"; r=$? - if [ $r -ne 0 ]; then - if show_info; then - eflush; __eecho "^ [EC #$r]" 1>&2 - elif show_error; then - eflush; __eecho "^ $cmd [EC #$r]" 1>&2; - fi - fi - return $r -} -function trace_error() { - local r - "$@"; r=$? - if [ $r -ne 0 ]; then - local cmd="$(quoted_args "$@")" - show_error && { eflush; __eecho "^ $cmd [EC #$r]" 1>&2; } - fi - return $r -} - -function etitle() { - local __t_deferred= - __t_etitle "$@" -} -function etitled() { - local __t_deferred=1 - __t_etitle "$@" -} -function __t_etitle() { - local __t_eend=default - local __t_clearp= - while [ -n "$1" ]; do - if [ "$1" == "--" ]; then - shift - break - elif [ "$1" == "-s" ]; then - __t_eend= - shift - elif [ "$1" == "--eend" ]; then - __t_eend=1 - shift - elif [ "$1" == "-p" ]; then - __t_clearp=1 - shift - else - break - fi - done - local __t_title="$1"; shift - local __t_s=0 - [ -n "$__estack" ] && __tlevel="${__tlevel} " - __estack="$__estack:t" - if show_info; then - if [ -n "$__t_deferred" ]; then - __epending="${__epending:+$__epending -}$(__etitle "$__t_title")" - else - eflush - __etitle "$__t_title" 1>&2 - fi - fi - if [ $# -gt 0 ]; then - "$@" - __t_s=$? - [ "$__t_eend" == "default" ] && __t_eend=1 - fi - [ "$__t_eend" == "default" ] && __t_eend= - if [ -n "$__t_eend" ]; then - eend $__t_s - [ -n "$__t_clearp" ] && eclearp - fi - return $__t_s -} -function estep() { - show_info || return; eflush; __estep "$*" 1>&2 -} -function estepe() { - show_info || return; eflush; __estepe "$*" 1>&2 -} -function estepw() { - show_info || return; eflush; __estepw "$*" 1>&2 -} -function estepn() { - show_info || return; eflush; __estepn "$*" 1>&2 -} -function estepi() { - show_info || return; eflush; __estepi "$*" 1>&2 -} -function estep_() { - show_info || return; eflush; __estep_ "$*" 1>&2 -} -function estepe_() { - show_info || return; eflush; __estepe_ "$*" 1>&2 -} -function estepw_() { - show_info || return; eflush; __estepw_ "$*" 1>&2 -} -function estepn_() { - show_info || return; eflush; __estepn_ "$*" 1>&2 -} -function estepi_() { - show_info || return; eflush; __estepi_ "$*" 1>&2 -} -function ebegin() { - local __b_eend=default - while [ -n "$1" ]; do - if [ "$1" == "--" ]; then - shift - break - elif [ "$1" == "-s" ]; then - __b_eend= - shift - elif [ "$1" == "--eend" ]; then - __b_eend=1 - shift - else - break - fi - done - local __b_msg="$1"; shift - local __b_s=0 - __estack="$__estack:b" - if show_info; then - eflush - __ebegin "$__b_msg" 1>&2 - fi - if [ $# -gt 0 ]; then - "$@" - __b_s=$? - [ "$__b_eend" == "default" ] && __b_eend=1 - fi - [ "$__b_eend" == "default" ] && __b_eend= - [ -n "$__b_eend" ] && eend $__b_s - return $__b_s -} -function edot() { - local s=$? - show_info || return - eflush - [ -n "$1" ] && s="$1" - shift - if [ "$s" == "0" ]; then - __edoto 1>&2 - else - __edotx 1>&2 - fi - show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 - return $s -} -function edotw() { - local s=$? - show_info || return - eflush - [ -n "$1" ] && s="$1" - shift - __edotw 1>&2 - show_verbose && [ $# -gt 0 ] && __edotd "$*" 1>&2 - return $s -} -function ewait() { - [ -n "$1" ] || return 1 - if show_info; then - local count=2 - eflush - little_sleep # certains processus retournent tout de suite - while is_running "$1"; do - sleep 1 - if [ $count -gt 0 ]; then - count=$(($count - 1)) - else - __edotp 1>&2 - fi - done - __edoto 1>&2 - else - wait "$1" - fi -} -function eend() { - local s=$? - if [ "$1" == "-c" ]; then - __estack= - __tlevel= - elif [ "${__estack%:b}" != "$__estack" ]; then - __estack="${__estack%:b}" - show_info || return - eflush - [ -n "$1" ] && s="$1" - if [ "$s" == "0" ]; then - __eendo 1>&2 - else - __eendx 1>&2 - fi - elif [ "${__estack%:t}" != "$__estack" ]; then - __estack="${__estack%:t}" - __tlevel="${__tlevel% }" - fi -} -function ask_yesno() { - local interactive=1 - if [[ "$1" == -* ]]; then - if [ "$1" != -- ]; then - check_interaction "$1" || interactive= - fi +function array_buildcmd() { + local desta="$1"; shift; local "$desta" + local mode=add + local -a pending cmd + while [ $# -gt 0 ]; do + case "$1" in + ++c|++cmd|++) __ab_process_pending; mode=cmd;; + ++s|++ssplit) __ab_process_pending; mode=ssplit;; + ++l|++lsplit) __ab_process_pending; mode=lsplit;; + ++a|++add) __ab_process_pending; mode=add;; + *) pending=("${pending[@]}" "$1");; + esac shift - else - check_interaction -c || interactive= - fi - local default="${2:-N}" - if [ "$default" == "C" ]; then - [ -n "$interactive" ] && default=N || default=O - elif [ "$default" == "X" ]; then - [ -n "$interactive" ] && default=O || default=N - fi - if [ -n "$interactive" ]; then - eflush - local message="$1" - local prompt="[oN]" - local r - is_yes "$default" && prompt="[On]" - if [ -n "$message" ]; then - tooenc_ "$message" 1>&2 - else - tooenc_ "Voulez-vous continuer?" "$UTF8" 1>&2 - fi - tooenc_ " $prompt " "$UTF8" 1>&2 - uread r - is_yes "${r:-$default}" - else - is_yes "$default" - fi -} -function read_value() { - local -a __rv_opts __rv_readline=1 __rv_showdef=1 __rv_nl= - __rv_opts=() - [ -n "$UTOOLS_NO_READLINE" ] && __rv_readline= - __rv_read "$@" -} -function read_password() { - local -a __rv_opts __rv_readline= __rv_showdef= __rv_nl=1 - __rv_opts=(-s) - __rv_read "$@" -} -function __rv_read() { - local __rv_int=1 - if [[ "$1" == -* ]]; then - if [ "$1" != -- ]; then - check_interaction "$1" || __rv_int= - fi - shift - else - check_interaction -c || __rv_int= - fi - local __rv_msg="$1" __rv_v="${2:-value}" __rv_d="$3" __rv_re="${4:-O}" - if [ -z "$__rv_int" ]; then - if is_yes "$__rv_re" && [ -z "$__rv_d" ]; then - OENC="$UTF8" eerror "La valeur par défaut de $__rv_v doit être non vide" - return 1 - fi - set_var "$__rv_v" "$__rv_d" - return 0 - fi - - eflush - local __rv_r - while true; do - if [ -n "$__rv_msg" ]; then - tooenc_ "$__rv_msg" 1>&2 - else - tooenc_ "Entrez la valeur" "$UTF8" 1>&2 - fi - if [ -n "$__rv_readline" ]; then - tooenc_ ": " "$UTF8" 1>&2 - uread -e ${__rv_d:+-i"$__rv_d"} "${__rv_opts[@]}" __rv_r - else - if [ -n "$__rv_d" ]; then - if [ -n "$__rv_showdef" ]; then - tooenc_ " [$__rv_d]" 1>&2 - else - tooenc_ " [****]" 1>&2 - fi - fi - tooenc_ ": " "$UTF8" 1>&2 - uread "${__rv_opts[@]}" __rv_r - [ -n "$__rv_nl" ] && echo - fi - __rv_r="${__rv_r:-$__rv_d}" - if [ -n "$__rv_r" ] || ! is_yes "$__rv_re"; then - set_var "$__rv_v" "$__rv_r" - return 0 - fi done + __ab_process_pending + array_upvar "$desta" "${cmd[@]}" } -function simple_menu() { - local __sm_title= __sm_yourchoice= __sm_default= - local -a __sm_args - parse_opts -t: __sm_title= -m: __sm_yourchoice= -d: __sm_default= @ __sm_args -- "$@" && - set -- "${__sm_args[@]}" || ewarn "$__sm_args" - - local __sm_option_var="${1:-option}" __sm_options_var="${2:-options}" - local __sm_option __sm_options - __sm_options="$__sm_options_var[*]" - if [ -z "${!__sm_options}" ]; then - OENC="$UTF8" eerror "Le tableau $__sm_options_var doit être non vide" - return 1 - fi - [ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}" - - eflush - array_copy __sm_options "$__sm_options_var" - local __sm_c=0 __sm_i __sm_choice - while true; do - if [ "$__sm_c" == "0" ]; then - [ -n "$__sm_title" ] && tooenc "=== $__sm_title ===" 1>&2 - __sm_i=1 - for __sm_option in "${__sm_options[@]}"; do - if [ "$__sm_option" == "$__sm_default" ]; then - tooenc "$__sm_i*- $__sm_option" 1>&2 - else - tooenc "$__sm_i - $__sm_option" 1>&2 - fi - let __sm_i=$__sm_i+1 - done - fi - - if [ -n "$__sm_yourchoice" ]; then - tooenc_ "$__sm_yourchoice" 1>&2 - else - tooenc_ "Entrez le numéro de l'option choisie" "$UTF8" 1>&2 - fi - tooenc_ ": " "$UTF8" 1>&2 - uread __sm_choice - - if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then - __sm_option="$__sm_default" - break - fi - if [ -n "$__sm_choice" -a -z "${__sm_choice//[0-9]/}" ]; then - if [ "$__sm_choice" -gt 0 -a "$__sm_choice" -le "${#__sm_options[*]}" ]; then - __sm_option="${__sm_options[$(($__sm_choice - 1))]}" - break - else - OENC="$UTF8" eerror "Numéro d'option incorrect" - fi - else - OENC="$UTF8" eerror "Vous devez saisir le numéro de l'option choisie" - fi - - let __sm_c=$__sm_c+1 - if [ "$__sm_c" -eq 5 ]; then - tooenc "" "$UTF8" 1>&2 - __sm_c=0 - fi - done - set_var "$__sm_option_var" "$__sm_option" -} - -function actions_menu() { - local -a __am_action_descs __am_options __am_void_actions - local __am_tmp __am_select_action __am_select_option __am_title __am_optyc __am_actyc - local __am_default_action=auto __am_quit_action=auto - local __am_default_option= - local -a __am_args - parse_opts \ - -t: __am_title= \ - -m: __am_optyc= \ - -M: __am_actyc= \ - -e: __am_void_actions \ - -d: __am_default_action= \ - -q: __am_quit_action= \ - -o: __am_default_option= \ - @ __am_args -- "$@" && set -- "${__am_args[@]}" || { eerror "$__am_args"; return 1; } - - __am_tmp="${1:-action}"; __am_select_action="${!__am_tmp}" - __am_tmp="${2:-option}"; __am_select_option="${!__am_tmp}" - [ -n "$__am_default_option" ] && __am_select_option="$__am_default_option" - array_copy __am_action_descs "${3:-actions}" - array_copy __am_options "${4:-options}" - - eerror_unless [ ${#__am_action_descs[*]} -gt 0 ] "Vous devez spécifier le tableau des actions" || return - __actions_menu || return 1 - setv "${1:-action}" "$__am_select_action" - setv "${2:-option}" "$__am_select_option" -} -function __actions_menu() { - local title="$__am_title" - local optyc="$__am_optyc" actyc="$__am_actyc" - local default_action="$__am_default_action" - local quit_action="$__am_quit_action" - local select_action="$__am_select_action" - local select_option="$__am_select_option" - local -a action_descs options void_actions - array_copy action_descs __am_action_descs - array_copy options __am_options - array_copy void_actions __am_void_actions - - local no_options - array_isempty options && no_options=1 - - local -a actions - local tmp action name - for tmp in "${action_descs[@]}"; do - splitfsep2 "$tmp" : action name - [ -n "$action" ] || action="${name:0:1}" - action="$(strlower "$action")" - array_addu actions "$action" - done - - if [ "$default_action" == auto ]; then - default_action="$select_action" - if [ -n "$default_action" ]; then - array_contains actions "$default_action" || default_action= - fi - [ -n "$default_action" ] || default_action="${actions[0]}" - fi - default_action="${default_action:0:1}" - default_action="$(strlower "$default_action")" - - if [ "$quit_action" == auto ]; then - if [ ${#actions[*]} -gt 1 ]; then - quit_action="${actions[@]:$((-1)):1}" - array_addu void_actions "$quit_action" - fi - fi - quit_action="${quit_action:0:1}" - quit_action="$(strlower "$quit_action")" - - local action_title - for tmp in "${action_descs[@]}"; do - splitfsep2 "$tmp" : action name - [ -n "$action" ] || action="${name:0:1}" - [ -n "$name" ] || name="$action" - action="$(strlower "$action")" - if [ -n "$no_options" ]; then - if ! array_contains void_actions "$action"; then - array_del actions "$action" - continue - fi - fi - [ "$action" == "$default_action" ] && name="$name*" - action_title="${action_title:+$action_title/}$name" - done - if [ -n "$default_action" ]; then - array_contains actions "$default_action" || default_action= - fi - if [ -n "$quit_action" ]; then - array_contains actions "$quit_action" || quit_action= - fi - - if [ -n "$no_options" ]; then - if array_isempty void_actions; then - eerror "Aucune option n'est définie. Il faut définir le tableau des actions vides" - return 1 - fi - __void_actions_menu - else - __options_actions_menu - fi -} -function __void_actions_menu() { - eflush - local c=0 choice - while true; do - if [ $c -eq 0 ]; then - [ -n "$title" ] && __etitle "$title" 1>&2 - __eecho_ "=== Actions disponibles: " 1>&2 - tooenc "$action_title" 1>&2 - fi - if [ -n "$actyc" ]; then - __eecho_ "$actyc" 1>&2 - elif [ -n "$optyc" ]; then - __eecho_ "$optyc" 1>&2 - else - __eecho_ "Entrez l'action à effectuer" 1>&2 - fi - tooenc_ ": " 1>&2 - uread choice - if [ -z "$choice" -a -n "$default_action" ]; then - select_action="$default_action" - break - fi - - choice="${choice:0:1}" - choice="$(strlower "$choice")" - if array_contains actions "$choice"; then - select_action="$choice" - break - elif [ -n "$choice" ]; then - eerror "$choice: action incorrecte" - else - eerror "vous devez saisir l'action à effectuer" - fi - let c=$c+1 - if [ $c -eq 5 ]; then - tooenc "" 1>&2 - c=0 - fi - done - __am_select_action="$select_action" - __am_select_option= -} -function __options_actions_menu() { - eflush - local c=0 option choice action option - while true; do - if [ $c -eq 0 ]; then - [ -n "$title" ] && __etitle "$title" 1>&2 - i=1 - for option in "${options[@]}"; do - if [ "$option" == "$select_option" ]; then - tooenc "$i*- $option" 1>&2 - else - tooenc "$i - $option" 1>&2 - fi - let i=$i+1 - done - __estepn_ "Actions disponibles: " 1>&2 - tooenc "$action_title" 1>&2 - fi - if [ -n "$optyc" ]; then - __eecho_ "$optyc" 1>&2 - else - __eecho_ "Entrez l'action et le numéro de l'option choisie" 1>&2 - fi - tooenc_ ": " 1>&2 - uread choice - - if [ -z "$choice" -a -n "$default_action" ]; then - action="$default_action" - if array_contains void_actions "$action"; then - select_action="$action" - select_option= - break - elif [ -n "$select_option" ]; then - select_action="$action" - break - fi - fi - action="${choice:0:1}" - action="$(strlower "$action")" - if array_contains actions "$action"; then - if array_contains void_actions "$action"; then - select_action="$action" - select_option= - break - else - option="${choice:1}" - option="${option// /}" - if [ -z "$option" -a -n "$select_option" ]; then - select_action="$action" - break - elif [ -z "$option" ]; then - eerror "vous devez saisir le numéro de l'option" - elif isnum "$option"; then - if [ $option -gt 0 -a $option -le ${#options[*]} ]; then - select_action="$action" - select_option="${options[$(($option - 1))]}" - break - fi - else - eerror "$option: numéro d'option incorrecte" - fi - fi - elif isnum "$choice"; then - action="$default_action" - if [ -n "$action" ]; then - if array_contains void_actions "$action"; then - select_action="$action" - select_option= - break - else - option="${choice// /}" - if [ -z "$option" ]; then - eerror "vous devez saisir le numéro de l'option" - elif isnum "$option"; then - if [ $option -gt 0 -a $option -le ${#options[*]} ]; then - select_action="$action" - select_option="${options[$(($option - 1))]}" - break - fi - else - eerror "$option: numéro d'option incorrecte" - fi - fi - else - eerror "Vous devez spécifier l'action à effectuer" - fi - elif [ -n "$choice" ]; then - eerror "$choice: action et/ou option incorrecte" - else - eerror "vous devez saisir l'action à effectuer" - fi - let c=$c+1 - if [ $c -eq 5 ]; then - tooenc "" 1>&2 - c=0 - fi - done - __am_select_action="$select_action" - __am_select_option="$select_option" -} - - -function __ac_forgetall() { __ac_files=(); } -__ac_forgetall -function __ac_trap() { - local file - for file in "${__ac_files[@]}"; do - [ -e "$file" ] && rm -rf "$file" 2>/dev/null - done - __ac_forgetall -} -trap __ac_trap 1 3 15 EXIT -function autoclean() { - local file - for file in "$@"; do - [ -n "$file" ] && array_add __ac_files "$file" - done -} -function ac_cleanall() { - __ac_trap -} -function ac_clean() { - local file - for file in "$@"; do - if array_contains __ac_files "$file"; then - [ -e "$file" ] && rm -rf "$file" 2>/dev/null - array_del __ac_files "$file" - fi - done -} -function ac_set_tmpfile() { - local __acst_d - if show_debug; then - if [ -n "$5" ]; then - is_yes "${!5}" && __acst_d=1 - else - __acst_d=1 - fi - fi - if [ -n "$__acst_d" -a -n "$3" ]; then - set_var "$1" "$3" - [ -f "$3" -a "$4" == keep ] || >"$3" - else - local __acst_t="$(mktempf "$2")" - autoclean "$__acst_t" - set_var "$1" "$__acst_t" - fi -} -function ac_set_tmpdir() { - local __acst_d - if show_debug; then - if [ -n "$4" ]; then - is_yes "${!4}" && __acst_d=1 - else - __acst_d=1 - fi - fi - if [ -n "$__acst_d" -a -n "$3" ]; then - set_var "$1" "$3" - mkdir -p "$3" - else - local __acst_t="$(mktempd "$2")" - autoclean "$__acst_t" - set_var "$1" "$__acst_t" - fi -} -function debug_tee() { - if show_debug; then - tee "$@" - else - cat - fi -} - - -function get_defaults_files() { - local __gd_dest="${1:-defaults}"; shift - local -a __gd_fs - local __gd_f __gd_found - for __gd_f in "$@"; do - __gd_found= - if [ -r "/etc/default/$__gd_f" ]; then - __gd_fs=("${__gd_fs[@]}" "/etc/default/$__gd_f") - __gd_found=1 - fi - if [ -r "$HOME/etc/default/$__gd_f" ]; then - __gd_fs=("${__gd_fs[@]}" "$HOME/etc/default/$__gd_f") - __gd_found=1 - fi - if [ -z "$__gd_found" -a -r "$scriptdir/lib/default/$__gd_f" ]; then - __gd_fs=("${__gd_fs[@]}" "$scriptdir/lib/default/$__gd_f") - fi - done - array_copy "$__gd_dest" __gd_fs -} - -function set_defaults() { - local -a __sd_fs - local __sd_f - get_defaults_files __sd_fs "$@" - for __sd_f in "${__sd_fs[@]}"; do - source "$__sd_f" - done -} - - -: "${MYHOST:=$HOSTNAME}" -: "${MYHOSTNAME:=${MYHOST%%.*}}" -export MYHOST MYHOSTNAME - -function myhost() { - hostname -f 2>/dev/null || echo "$MYHOST" -} -function myhostname() { - hostname -s 2>/dev/null || echo "$MYHOSTNAME" -} -##@inc]base -uprovide base.tools -urequire base - -function base_umove() { +function buildcmd() { local -a args - local updatedir - args=(-d:,--updatedir: .) - parse_args_check "$@" || return; set -- "${args[@]}" - - eerror_unless [ -z "$updatedir" -o -d "$updatedir" ] "$updatedir: doit être un répertoire" || return - eerror_if [ $# -eq 0 ] "Vous devez spécifier les fichiers à déplacer" || return - eerror_if [ $# -eq 1 ] "Vous devez spécifier la destination" || return - - local -a srcs - local dest - - srcs=("$@") - setx dest=last_value srcs - array_del_last srcs - - if [ $# -eq 2 ]; then - if [ -d "$dest" ]; then - : # ce cas sera traité ci-dessous - elif [ -e "$dest" ]; then - eerror "$dest: refus d'écraser la destination" - return 1 - else - src="${srcs[0]}" - if [ -n "$updatedir" ]; then - if [ -L "$src" ]; then - ldest="$(readlinka "$src")" - array_find_links update_links "$ldest" "$updatedir" - else - array_find_links update_links "$src" "$updatedir" - fi - move_file "$src" "$dest" "${update_links[@]}" - else - move_link "$src" "$dest" - fi - return $? - fi - fi - - [ -d "$dest" ] || { - eerror "$dest: doit être un répertoire" - return 1 - } - for src in "${srcs[@]}"; do - if [ -n "$updatedir" ]; then - if [ -L "$src" ]; then - move_link "$src" "$dest" - else - array_find_links update_links "$src" "$updatedir" - move_file "$src" "$dest" "${update_links[@]}" - fi - else - move_link "$src" "$dest" - fi - done + array_buildcmd args "$@" + qvals "${args[@]}" } -##@inc]base.tools -##@inc[base.compat -## Fonctions de base: support des fonctions obsolètes et des versions de bash < 4.x -##@inc[base.core -## Fonctions de base: fondement -uprovide base.core - -function echo_() { - echo -n "$*" -} -function recho() { - if [[ "${1:0:2}" == -[eEn] ]]; then - echo -n - - local first="${1:1}"; shift - echo "$first$@" - else - echo "$@" - fi -} -function recho_() { - if [[ "${1:0:2}" == -[eEn] ]]; then - echo -n - - local first="${1:1}"; shift - echo -n "$first$@" - else - echo -n "$@" - fi -} -function _rval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\'/\'}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - s="${s// /\\ }" - recho_ "$s" -} -function _qval() { - local s="$*" - s="${s//\\/\\\\}" - s="${s//\"/\\\"}" - s="${s//\$/\\\$}" - s="${s//\`/\\\`}" - recho_ "$s" -} -function should_quote() { - local l="${#1}" - [ $l -eq 0 -o $l -gt 80 ] && return 0 - local s="${*//[a-zA-Z0-9]/}" - s="${s//,/}" - s="${s//./}" - s="${s//+/}" - s="${s//\//}" - s="${s//-/}" - s="${s//_/}" - s="${s//=/}" - [ -n "$s" ] -} -function qval() { - echo -n \" - _qval "$@" - echo \" -} -function qvalm() { - if should_quote "$*"; then - echo -n \" - _qval "$@" - echo \" - else - recho "$*" - fi -} -function qvalr() { - if [ -z "$*" ]; then - : - elif should_quote "$*"; then - echo -n \" - _qval "$@" - echo \" - else - recho "$*" - fi -} -function qvals() { - local arg first=1 - for arg in "$@"; do - [ -z "$first" ] && echo -n " " - if should_quote "$arg"; then - echo -n \" - _qval "$arg" - echo -n \" - else - recho_ "$arg" - fi - first= - done - [ -z "$first" ] && echo -} -function qlines() { - sed "s/'/'\\\\''/g; s/.*/'&'/g" -} -function setv() { - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - eval "$__s_var=\"\$*\"" -} -function _setv() { - local __s_var="$1"; shift - eval "$__s_var=\"\$*\"" -} -function echo_setv() { - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - echo "$__s_var=$(qvalr "$*")" -} -function setx() { - if [ "$1" == -a ]; then - shift - local __s_array="$1"; shift - if [[ "$__s_array" == *=* ]]; then - set -- "${__s_array#*=}" "$@" - __s_array="${__s_array%%=*}" - fi - eval "$__s_array=($("$@" | qlines))" - else - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - eval "$__s_var="'"$("$@")"' - fi -} -function _setvx() { - local __s_var="$1"; shift - eval "$__s_var="'"$("$@")"' -} -function _setax() { - local __s_array="$1"; shift - eval "$__s_array=($("$@" | qlines))" -} -function evalx() { - local __e_val __e_arg __e_r=0 - local -a __e_cmd - - local __e_first=1 - while [ $# -gt 0 ]; do - __e_cmd=() - while [ $# -gt 0 ]; do - __e_arg="$1"; shift - [ "$__e_arg" == // ] && break - if [ "${__e_arg%//}" != "$__e_arg" ]; then - local __e_tmp="${__e_arg%//}" - if [ -z "${__e_tmp//\\/}" ]; then - __e_arg="${__e_arg#\\}" - __e_cmd=("${__e_cmd[@]}" "$__e_arg") - continue - fi - fi - __e_cmd=("${__e_cmd[@]}" "$__e_arg") - done - - if [ -n "$__e_first" ]; then - __e_val="$("${__e_cmd[@]}")" || __e_r=$? - else - __e_val="$("${__e_cmd[@]}" "$__e_val")" || __e_r=$? - fi - __e_first= - done - [ -n "$__e_val" ] && echo "$__e_val" - return $__e_r -} -function setxx() { - local -a __s_args - if [ "$1" == -a ]; then __s_args=(-a); shift; fi - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - __s_args=("${__s_args[@]}" "$__s_var") - setx "${__s_args[@]}" evalx "$@" -} -function evalp() { - local __e_arg __e_cmd - - while [ $# -gt 0 ]; do - __e_arg="$1"; shift - if [ "$__e_arg" == // ]; then - __e_cmd="$__e_cmd |" - continue - elif [ "${__e_arg%//}" != "$__e_arg" ]; then - local __e_tmp="${__e_arg%//}" - if [ -z "${__e_tmp//\\/}" ]; then - __e_arg="${__e_arg#\\}" - fi - fi - __e_cmd="${__e_cmd:+$__e_cmd }\"$(_qval "$__e_arg")\"" - done - eval "$__e_cmd" -} -function setxp() { - local -a __s_args - if [ "$1" == -a ]; then __s_args=(-a); shift; fi - local __s_var="$1"; shift - if [[ "$__s_var" == *=* ]]; then - set -- "${__s_var#*=}" "$@" - __s_var="${__s_var%%=*}" - fi - __s_args=("${__s_args[@]}" "$__s_var") - setx "${__s_args[@]}" evalp "$@" -} -function testx() { - local __t_op="$1"; shift - local __t_val="$(evalx "$@")" - [ $__t_op "$__t_val" ] -} -function test2x() { - local __t_val1="$1"; shift - local __t_op="$1"; shift - local __t_val2="$(evalx "$@")" - [ "$__t_val1" $__t_op "$__t_val2" ] -} -function testrx() { - local __t_val1="$1"; shift - local __t_op="$1"; shift - local __t_val2="$(evalx "$@")" - eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]' -} -function testp() { - local __t_op="$1"; shift - local __t_val="$(evalp "$@")" - [ $__t_op "$__t_val" ] -} -function test2p() { - local __t_val1="$1"; shift - local __t_op="$1"; shift - local __t_val2="$(evalp "$@")" - [ "$__t_val1" $__t_op "$__t_val2" ] -} -function testrp() { - local __t_val1="$1"; shift - local __t_op="$1"; shift - local __t_val2="$(evalp "$@")" - eval '[[ "$__t_val1" '"$__t_op"' "$__t_val2" ]]' -} - -function err2out() { - "$@" 2>&1 +function evalcmd() { + local -a args + array_buildcmd args "$@" + "${args[@]}" } ##@inc]base.core ##@inc[base.num @@ -27897,21 +29688,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -27926,21 +29715,19 @@ uprovide base.num function isnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//[0-9]/}" [ -z "$v" ] } function ispnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v//[0-9]/}" - [ -z "$v" ] + [ -z "${1//[0-9]/}" ] } function isrnum() { [ ${#1} -gt 0 ] || return 1 - local v="$1" - v="${v#-}" + local v="${1#-}" + [ ${#v} -gt 0 ] || return 1 v="${v//./}" v="${v//,/}" v="${v//[0-9]/}" @@ -28030,6 +29817,8 @@ function qseds() { local s="$*" s="${s//\\/\\\\}" s="${s//\//\\/}" + s="${s// +/\\n}" recho "$s" } function _qform() { @@ -28053,6 +29842,14 @@ function qform() { echo fi } +function _qsql() { + local q="'" qq="''" + echo "${*//$q/$qq}" +} +function qsql() { + local q="'" qq="''" + echo "'${*//$q/$qq}'" +} ##@inc]base.quote uprovide base.compat urequire base.core base.num base.bool base.quote @@ -31269,9 +33066,9 @@ function __set_no_colors() { } __set_no_colors -function __eerror() { tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}* error:${COULEUR_NORMALE} $(__indent "$1")"; } -function __ewarn() { tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}* warning:${COULEUR_NORMALE} $(__indent "$1")"; } -function __enote() { tooenc "$(__edate)${__tlevel}${COULEUR_VERTE}* note:${COULEUR_NORMALE} $(__indent "$1")"; } +function __eerror() { local p; [ -z "$NO_COLORS" ] && p="E" || p="ERROR"; tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __ewarn() { local p; [ -z "$NO_COLORS" ] && p="W" || p="WARNING"; tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __enote() { local p; [ -z "$NO_COLORS" ] && p="N" || p="NOTE"; tooenc "$(__edate)${__tlevel}${COULEUR_VERTE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } function __ebanner() { local maxi="${COLUMNS:-80}" local -a lines @@ -31293,22 +33090,22 @@ function __ebanner() { done tooenc "$psfix$COULEUR_NORMALE" } -function __eimportant() { tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}* important:${COULEUR_NORMALE} $(__indent "$1")"; } -function __eattention() { tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}* attention:${COULEUR_NORMALE} $(__indent "$1")"; } -function __einfo() { tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}* info:${COULEUR_NORMALE} $(__indent "$1")"; } -function __edebug() { tooenc "$(__edate)${__tlevel}${COULEUR_BLANCHE}* debug:${COULEUR_NORMALE} $(__indent "$1")"; } -function __estep() { tooenc "$(__edate)${__tlevel}${COULEUR_BLANCHE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepe() { tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepw() { tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepn() { tooenc "$(__edate)${__tlevel}${COULEUR_VERTE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepi() { tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estep_() { tooenc_ "$(__edate)${__tlevel}${COULEUR_BLANCHE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepe_() { tooenc_ "$(__edate)${__tlevel}${COULEUR_ROUGE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepw_() { tooenc_ "$(__edate)${__tlevel}${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepn_() { tooenc_ "$(__edate)${__tlevel}${COULEUR_VERTE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __estepi_() { tooenc_ "$(__edate)${__tlevel}${COULEUR_BLEUE}*${COULEUR_NORMALE} $(__indent "$1")"; } -function __etitle() { tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}+++ $(get_color _)$(__indent "$1")${COULEUR_NORMALE}"; } -function __ebegin() { tooenc_ "$(__edate)${__tlevel}${COULEUR_BLANCHE}*${COULEUR_NORMALE} $(__indent "$1"): "; } +function __eimportant() { local p; [ -z "$NO_COLORS" ] && p="!" || p="IMPORTANT"; tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __eattention() { local p; [ -z "$NO_COLORS" ] && p="*" || p="ATTENTION"; tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __einfo() { local p; [ -z "$NO_COLORS" ] && p="I" || p="INFO"; tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __edebug() { local p; [ -z "$NO_COLORS" ] && p="D" || p="DEBUG"; tooenc "$(__edate)${__tlevel}${COULEUR_BLANCHE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estep() { local p; [ -z "$NO_COLORS" ] && p="." || p="."; tooenc "$(__edate)${__tlevel}${COULEUR_BLANCHE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepe() { local p; [ -z "$NO_COLORS" ] && p="." || p=".E"; tooenc "$(__edate)${__tlevel}${COULEUR_ROUGE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepw() { local p; [ -z "$NO_COLORS" ] && p="." || p=".W"; tooenc "$(__edate)${__tlevel}${COULEUR_JAUNE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepn() { local p; [ -z "$NO_COLORS" ] && p="." || p=".N"; tooenc "$(__edate)${__tlevel}${COULEUR_VERTE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepi() { local p; [ -z "$NO_COLORS" ] && p="." || p=".I"; tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estep_() { local p; [ -z "$NO_COLORS" ] && p="." || p="."; tooenc_ "$(__edate)${__tlevel}${COULEUR_BLANCHE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepe_() { local p; [ -z "$NO_COLORS" ] && p="." || p=".E"; tooenc_ "$(__edate)${__tlevel}${COULEUR_ROUGE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepw_() { local p; [ -z "$NO_COLORS" ] && p="." || p=".W"; tooenc_ "$(__edate)${__tlevel}${COULEUR_JAUNE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepn_() { local p; [ -z "$NO_COLORS" ] && p="." || p=".N"; tooenc_ "$(__edate)${__tlevel}${COULEUR_VERTE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __estepi_() { local p; [ -z "$NO_COLORS" ] && p="." || p=".I"; tooenc_ "$(__edate)${__tlevel}${COULEUR_BLEUE}${p}${COULEUR_NORMALE} $(__indent "$1")"; } +function __etitle() { local p; [ -z "$NO_COLORS" ] && p="T" || p="==="; tooenc "$(__edate)${__tlevel}${COULEUR_BLEUE}${p} $(get_color _)$(__indent "$1")${COULEUR_NORMALE}"; } +function __ebegin() { local p; [ -z "$NO_COLORS" ] && p="." || p="."; tooenc_ "$(__edate)${__tlevel}${COULEUR_BLANCHE}${p}${COULEUR_NORMALE} $(__indent "$1"): "; } function __edoto() { echo_ "."; } function __edotw() { echo_ "${COULEUR_JAUNE}w${COULEUR_NORMALE}"; } function __edotx() { echo_ "${COULEUR_ROUGE}x${COULEUR_NORMALE}"; } @@ -31422,16 +33219,16 @@ SYSDIST_ALIASES=( 10.7=lion 10.6=snowleopard 10.5=leopard 10.4=tiger 10.3=panther ) debianlike_SYSVERS=() -debian_SYSVERS=(wheezy squeeze lenny etch) +debian_SYSVERS=(stretch jessie wheezy squeeze lenny etch) ubuntu_SYSVERS=(oneiric natty maverick lucid karmic jaunty intrepid hardy) redhatlike_SYSVERS=() -rhel_SYSVERS=(rhel6 rhel5 rhel4 redhat6 redhat5 redhat4) +rhel_SYSVERS=(rhel7 rhel6 rhel5 rhel4 redhat7 redhat6 redhat5 redhat4) fedora_SYSVERS=(fedora14 fedora13 fedora12 fedora11) -centos_SYSVERS=(centos6 centos5 centos4 redhat6 redhat5 redhat4) +centos_SYSVERS=(centos7 centos6 centos5 centos4 redhat7 redhat6 redhat5 redhat4) suse_SYSVERS=() gentoo_SYSVERS=() SYSVER_ALIASES=( - 7=wheezy 6=squeeze 5=lenny 4=etch + 9=stretch 8=jessie 7=wheezy 6=squeeze 5=lenny 4=etch 11.10=oneiric 11.04=natty 10.10=maverick 10.04=lucid 9.10=karmic 9.04=jaunty 8.10=intrepid 8.04=hardy ) @@ -31456,92 +33253,158 @@ function __setup_ALL_SYSvars() { __setup_ALL_SYSvars unset -f __setup_ALL_SYSvars +function __compute_local_sysinfos_data() { + SYSINFOS_DATA=( + "$UNAME_SYSTEM" + "$UNAME_MACHINE" + "$([ -f /etc/debian_version ] && cat /etc/debian_version)" + "$([ -f /etc/gentoo-release ] && cat /etc/gentoo-release)" + "$([ -f /etc/redhat-release ] && cat /etc/redhat-release)" + "$([ -f /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist)" + "$([ -f /System/Library/Frameworks/CoreServices.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Resources/version.plist)" + ) +} +function __dump_remote_sysinfos_data() { + "${2:-ssh}" "$1" "\ +uname -s +echo .----------------. +uname -m +echo .----------------. +[ -f /etc/debian_version ] && cat /etc/debian_version +echo .----------------. +[ -f /etc/gentoo-release ] && cat /etc/gentoo-release +echo .----------------. +[ -f /etc/redhat-release ] && cat /etc/redhat-release +echo .----------------. +[ -f /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Frameworks/Metadata.framework/Resources/version.plist +echo .----------------. +[ -f /System/Library/Frameworks/CoreServices.framework/Resources/version.plist ] && cat /System/Library/Frameworks/CoreServices.framework/Resources/version.plist +echo .----------------." +} +function __build_sysinfos_data() { + awk ' +BEGIN { + data = "" + have_data = 0 + print "SYSINFOS_DATA=(" +} +function read_data() { + if (have_data) data = data "\n" + else have_data = 1 + data = data $0 +} +function dump_data() { + gsub(/'\''/, "'\'\\\\\'\''", data) + print "'\''" data "'\''" + data = "" + have_data = 0 +} +$0 == ".----------------." { dump_data(); next } +{ read_data() } +END { + dump_data() + print ")" +} +' +} +function __compute_sysinfos() { + local system="${SYSINFOS_DATA[0]}" + local machine="${SYSINFOS_DATA[1]}" + local debian_version="${SYSINFOS_DATA[2]}" + local gentoo_release="${SYSINFOS_DATA[3]}" + local redhat_release="${SYSINFOS_DATA[4]}" + local macosx_plist1="${SYSINFOS_DATA[5]}" + local macosx_plist2="${SYSINFOS_DATA[6]}" + + if [ "$system" == "Linux" ]; then + case "$machine" in + x86_64) MYSYSNAME=(linux64 linux); MYBITS=64;; + i386|i586|i686) MYSYSNAME=(linux32 linux); MYBITS=32;; + ppc) MYSYSNAME=(linuxppc32 linuxppc linux); MYBITS=32;; + ppc64) MYSYSNAME=(linuxppc64 linuxppc linux); MYBITS=64;; + arm*) MYSYSNAME=(linuxarm linux);; + *) MYSYSNAME=(linux);; + esac + if [ -n "$debian_version" ]; then + case "$debian_version" in + 9*|stretch*) MYSYSDIST=(debian debianlike); MYSYSVER=(stretch);; + 8*|jessie*) MYSYSDIST=(debian debianlike); MYSYSVER=(jessie);; + 7*|wheezy*) MYSYSDIST=(debian debianlike); MYSYSVER=(wheezy);; + 6*|squeeze*) MYSYSDIST=(debian debianlike); MYSYSVER=(squeeze);; + 5*) MYSYSDIST=(debian debianlike); MYSYSVER=(lenny);; + 4*) MYSYSDIST=(debian debianlike); MYSYSVER=(etch);; + *) MYSYSDIST=(debianlike);; + esac + elif [ -n "$gentoo_release" ]; then + MYSYSDIST=(gentoo) + elif [ -n "$redhat_release" ]; then + case "$redhat_release" in + Fedora*) MYSYSDIST=(fedora redhatlike);; + Red*Hat*Enterprise*Linux*) MYSYSDIST=(rhel redhatlike);; + CentOS*) MYSYSDIST=(centos redhatlike);; + *) MYSYSDIST=(redhatlike);; + esac + case "$redhat_release" in + Fedora*14*) MYSYSVER=(fedora14);; + Fedora*13*) MYSYSVER=(fedora13);; + Fedora*12*) MYSYSVER=(fedora12);; + Fedora*11*) MYSYSVER=(fedora11);; + Red*Hat*Enterprise*Linux*release\ 7*) MYSYSVER=(rhel7 redhat7);; + Red*Hat*Enterprise*Linux*release\ 6*) MYSYSVER=(rhel6 redhat6);; + Red*Hat*Enterprise*Linux*release\ 5*) MYSYSVER=(rhel5 redhat5);; + Red*Hat*Enterprise*Linux*release\ 4*) MYSYSVER=(rhel4 redhat4);; + CentOS*release\ 7*) MYSYSVER=(centos7 redhat7);; + CentOS*release\ 6*) MYSYSVER=(centos6 redhat6);; + CentOS*release\ 5*) MYSYSVER=(centos5 redhat5);; + CentOS*release\ 4*) MYSYSVER=(centos4 redhat4);; + esac + fi + elif [ "$system" == "Darwin" ]; then + function get_macosx_version() { + local plist + for plist in "$@"; do + [ -n "$plist" ] || continue + echo "$plist" | grep -A 1 CFBundleShortVersionString | grep string | sed 's/.*//g +s/<\/string>.*$//g' + break + done + } + MYSYSNAME=(macosx darwin) + case "$(get_macosx_version)" in + 10.7*) MYSYSDIST=(lion);; + 10.6*) MYSYSDIST=(snowleopard);; + 10.5*) MYSYSDIST=(leopard);; + 10.4*) MYSYSDIST=(tiger);; + 10.3*) MYSYSDIST=(panther);; + esac + fi +} + +function compute_local_sysinfos() { + local SYSINFOS_DATA + __compute_local_sysinfos_data + __compute_sysinfos + if [ -n "$UTOOLS_CHROOT" ]; then + [ -n "$UTOOLS_SYSNAME" ] && eval "MYSYSNAME=($UTOOLS_SYSNAME)" + [ -n "$UTOOLS_BITS" ] && eval "MYBITS=$UTOOLS_BITS" + [ -n "$UTOOLS_SYSDIST" ] && eval "MYSYSDIST=($UTOOLS_SYSDIST)" + [ -n "$UTOOLS_SYSVER" ] && eval "MYSYSVER=($UTOOLS_SYSVER)" + fi +} +function compute_remote_sysinfos() { + local SYSINFOS_DATA + eval "$(__dump_remote_sysinfos_data "$@" | __build_sysinfos_data)" + __compute_sysinfos +} + +SYSINFOSLOCALS="\ +local -a MYSYSNAME MYSYSDIST MYSYSVER +local MYBITS" MYSYSNAME=() MYBITS= MYSYSDIST=() MYSYSVER=() -if [ "$UNAME_SYSTEM" == "Linux" ]; then - case "$UNAME_MACHINE" in - x86_64) - MYSYSNAME=(linux64 linux) - MYBITS=64 - ;; - i386|i586|i686) - MYSYSNAME=(linux32 linux) - MYBITS=32 - ;; - ppc) - MYSYSNAME=(linuxppc32 linuxppc linux) - MYBITS=32 - ;; - ppc64) - MYSYSNAME=(linuxppc64 linuxppc linux) - MYBITS=64 - ;; - arm*) - MYSYSNAME=(linuxarm linux) - ;; - *) - MYSYSNAME=(linux) - ;; - esac - - if [ -f /etc/debian_version ]; then - case "$(//g -s/<\/string>.*$//g' - break - fi - done - } - MYSYSNAME=(macosx darwin) - case "$(get_macosx_version)" in - 10.7*) MYSYSDIST=(lion);; - 10.6*) MYSYSDIST=(snowleopard);; - 10.5*) MYSYSDIST=(leopard);; - 10.4*) MYSYSDIST=(tiger);; - 10.3*) MYSYSDIST=(panther);; - esac -fi -if [ -n "$UTOOLS_CHROOT" ]; then - [ -n "$UTOOLS_SYSNAME" ] && eval "MYSYSNAME=($UTOOLS_SYSNAME)" - [ -n "$UTOOLS_BITS" ] && eval "MYBITS=$UTOOLS_BITS" - [ -n "$UTOOLS_SYSDIST" ] && eval "MYSYSDIST=($UTOOLS_SYSDIST)" - [ -n "$UTOOLS_SYSVER" ] && eval "MYSYSVER=($UTOOLS_SYSVER)" -fi +compute_local_sysinfos function __get_sysdist_alias() { if ! array_contains ALL_SYSDISTS "$1"; then @@ -31634,11 +33497,28 @@ function ensure_sysinfos() { __fix_sysinfos_downward } +function get_sysinfos_desc() { + local sysname_="${1:-MYSYSNAME}"; sysname_="${!sysname_}" + local sysdist_="${2:-MYSYSDIST}"; sysdist_="${!sysdist_}" + local sysver_="${3:-MYSYSVER}"; sysver_="${!sysver_}" + echo "$sysname_${sysdist_:+/$sysdist_}${sysver_:+/$sysver_}" +} + function check_sysinfos() { - local sysnamevar_="MYSYSNAME" - local sysdistvar_="MYSYSDIST" - local sysvervar_="MYSYSVER" - local bitsvar_="MYBITS" + local sysnamevar_ sysdistvar_ sysvervar_ bitsvar_ + if [ "$1" == --vars ]; then + shift + if [[ "$1" != -* ]]; then sysnamevar_="${1:-MYSYSNAME}"; shift; fi + if [[ "$1" != -* ]]; then sysdistvar_="${1:-MYSYSDIST}"; shift; fi + if [[ "$1" != -* ]]; then sysvervar_="${1:-MYSYSVER}"; shift; fi + if [[ "$1" != -* ]]; then bitsvar_="${1:-MYBITS}"; shift; fi + else + sysnamevar_="MYSYSNAME" + sysdistvar_="MYSYSDIST" + sysvervar_="MYSYSVER" + bitsvar_="MYBITS" + fi + local check_=sysname r_=0 while [ -n "$1" ]; do if [[ "$1" == -* ]]; then @@ -31682,6 +33562,7 @@ function check_sysinfos() { if array_contains "$sysdistvar_" "$sysdist_"; then r_=0 check_=skip + [ "$sysdist_" == "$value_" ] && break elif [ "$sysdist_" == "$value_" ]; then break fi @@ -31721,6 +33602,7 @@ function check_sysinfos() { if array_contains "$sysvervar_" "$sysver_"; then r_=0 check_=skip + [ "$sysver_" == "$value_" ] && break elif [ "$sysver_" == "$value_" ]; then break fi @@ -31749,6 +33631,59 @@ function check_sysinfos() { done return $r_ } + +function on_debian() { + NUTOOLS_ON_DEBIAN= + if check_sysinfos -d debian; then + urequire debian + if [ $# -gt 0 ]; then + NUTOOLS_ON_DEBIAN=debian + "$@" + else + NUTOOLS_ON_DEBIAN=1 + fi + return 0 + else + return 1 + fi +} +function on_debian:() { on_debian "$@"; } +function __on_debian() { + [ -z "$NUTOOLS_ON_DEBIAN" -o "$NUTOOLS_ON_DEBIAN" != 1 ] && return 1 + local sysver="$1"; shift + if [ $# -gt 0 ]; then + if check_sysinfos -d debian -v "$sysver"; then + NUTOOLS_ON_DEBIAN="$sysver" + "$@" + return 0 + else + return 1 + fi + else + if check_sysinfos -d debian -v "$sysver+"; then + NUTOOLS_ON_DEBIAN="$sysver" + return 0 + fi + fi +} + +function on_stretch() { __on_debian stretch "$@"; } +function on_jessie() { __on_debian jessie "$@"; } +function on_wheezy() { __on_debian wheezy "$@"; } +function on_squeeze() { __on_debian squeeze "$@"; } +function on_default() { + if [ "$NUTOOLS_ON_DEBIAN" == 1 ]; then + if [ $# -gt 0 ]; then + "$@" + return 0 + else + return 0 + fi + elif [ -n "$NUTOOLS_ON_DEBIAN" ]; then + return 1 + fi + return 1 +} ##@inc]sysinfos ##@inc[compat # Code de support pour les architectures autre que Linux @@ -32149,7 +34084,11 @@ function enable_in_crontab() { fi } function ctnow() { - date +"%-M %-H %-d %-m %u" + if [ -n "$1" ]; then + echo "$1" | awk '{ print strftime("%-M %-H %-d %-m %u", $0) }' + else + awk 'BEGIN { print strftime("%-M %-H %-d %-m %u", systime()) }' + fi } __CTRESOLVE_CTNOW="" function ctresolve() { @@ -32338,13 +34277,18 @@ function __compute_crontab_prefixes() { __crontab_prefixes=1 } -UTOOLS_PREFIXES=("${UTOOLS_PREFIXES[@]}" CRONTABDIR) +CRONTAB_PREFIXES=(CRONTABDIR) +UTOOLS_PREFIXES=("${UTOOLS_PREFIXES[@]}" "${CRONTAB_PREFIXES[@]}") function compute_crontab_prefixes() { __compute_crontab_prefixes } function recompute_crontab_prefixes() { + local v + for v in "${CRONTAB_PREFIXES[@]}"; do + eval "$v=" + done __crontab_prefixes= __compute_crontab_prefixes } From 739a94d44d6fca516331a5264be3413c4d15b1b2 Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:26:14 +0400 Subject: [PATCH 38/39] Init changelog & version 6.0.0 --- CHANGES.md | 41 +++++++++++++++++++++++++++++++++++++++++ VERSION.txt | 2 +- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 925395c..e1654aa 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,44 @@ +## Version 6.0.0 du 30/12/2016-18:26 + +* `a7767f7` Intégration de la branche better-apacheconfig + * `683d5df` pour simplifier le débuggage, set -x est désactivé pour les fonction urequire(), parse_args() et awkdef() + * `ed1633a` module template: support de variables supplémentaires avec TEMPLATE_USER_VARS et de la description des variables dans le fichier de configuration + * `b920641` l'installation distante de modules uinst par rruns se fait par défaut sans confirmation + * `fd9105f` bug dans le module base.num: 'is*num -' était vrai + * `6366256` module ipcalc: ajout de ipcalc_network() + * `fbc1a5c` mise à jour du module debian + * `e3cd3ce` diverses modification de apacheconfig et apache.tools +* `c552d2d` corriger le module python ulib.ext.xpath +* `62a59e5` kill-ssh-user-sessions.service: laisser un peu de temps pour que le client soit notifié +* `4b7d4f4` ajouter une option pour installer le service kill-ssh-user-sessions +* `c95d1cd` ajouter un service pour tuer les sessions ssh lors de l'arrêt de la machine +* `4e1e5a4` ruinst: tracer la collecte d'informations sur l'hôte distant +* `5aa60a3` désactiver la création du lien pour xpathtool.py, qui ne fonctionne qu'à partir de lib/ulib/support +* `6a4e6a7` foreach: ajouter une syntaxe alternative pour lister directement les éléments +* `24a8839` update-nutools: possibilité de cloner la branche develop avec l'option --develop +* `24812c1` initialiser PYTHONIOENCODING pour faciliter l'exécution de scripts python +* `390233a` modification pour afficher l'aide même si certains outils requis sont absents +* `2425cbe` maj de l'arborescence de la doc +* `96b1866` prel: support du fichier .prel-noauto pour interdire les releases automatiques +* `b278b17` prel: mise à jour de la commande par défaut pour les dépendances +* `9983e49` pver: support des aliases D et P pour .pver-map +* `e37cc8f` pver et prel utilisent -p par défaut pour le support maven +* `2980d15` pver supporte la gestion des versions maven +* `190519d` pxs ne requière plus la présence du remote origin +* `4a4094c` déplacement de is_defined() et is_array() dans base.core +* `d735b7b` ulib: strops: ajout des opération mid et repl +* `8d486dc` ulib: ajouter la fonction strops +* `db24c70` ulib: ajout de la fonction is_array() +* `3ccd4ed` apacheconfig: ne pas chercher à utiliser site-certs.conf s'il n'y a pas les variables @@{cert,key,ca}@@ +* `b9dc5ca` bug dans apacheconfig +* `b4ade73` TODO de branche +* `44fe622` chrono.py: chronomètre graphique +* `2a6d089` ulib/base: implémenter ask_any() +* `94615de` conversion de CHANGES.txt en CHANGES.md +* `d2a56d6` prel: ajout de l'option --uc pour convertir CHANGES.txt en CHANGES.md +* `f3ba5e3` prel: générer un fichier CHANGES.md par défaut avec une syntaxe un peu différente +* `40babfe` sqlcsv: l'option -l supporte une url jdbc, et la recherche de sqlcsv.properties se fait dans tous les répertoires parent jusqu'à $HOME + ## Version 5.3.0 du 28/09/2016-23:31 * `5c466d8` ldif et ldap: match des suffixes sans tenir compte de la casse. modrdn prend un nouvel argument SUFFIX diff --git a/VERSION.txt b/VERSION.txt index 03f488b..09b254e 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.0 +6.0.0 From 96447b943097922229e0e5eda8595c4f17f2c62f Mon Sep 17 00:00:00 2001 From: Jephte Clain Date: Fri, 30 Dec 2016 18:26:36 +0400 Subject: [PATCH 39/39] maj version ulib --- lib/ulib/.ulibver | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ulib/.ulibver b/lib/ulib/.ulibver index a56eaf2..aa19da0 100644 --- a/lib/ulib/.ulibver +++ b/lib/ulib/.ulibver @@ -1 +1 @@ -013003000 +014000000