Compare commits

...

151 Commits
master ... wip

Author SHA1 Message Date
Jephté Clain 5000baa6a1 renommer rundk en devuser 2024-09-19 23:33:29 +04:00
Jephté Clain 2adaaf5d1e support _rundk 2024-09-19 21:53:19 +04:00
Jephté Clain 1bc0534780 modifs.mineures sans commentaires 2024-09-17 15:54:55 +04:00
Jephté Clain 3bbcdd1f8b supprimer rundk 2024-09-17 15:03:13 +04:00
Jephté Clain 2e6c8219df finaliser runphp 2024-09-17 15:01:58 +04:00
Jephté Clain f465b3b175 modifs.mineures sans commentaires 2024-09-17 13:43:21 +04:00
Jephté Clain 94e4af7012 début runphp 2024-09-17 13:19:18 +04:00
Jephté Clain 5a5f0e9c93 build -r ne rebuild rundk que si c'est nécessaire 2024-07-19 19:41:26 +04:00
Jephté Clain d68cf2b052 maj nur/sery 2024-07-19 16:38:19 +04:00
Jephté Clain 73432809ad forcer LANG si pas défini 2024-07-19 16:37:19 +04:00
Jephté Clain 54ec7e7d94 cosmetic 2024-07-02 23:51:07 +04:00
Jephté Clain 13de7abd40 modifs.mineures sans commentaires 2024-06-09 12:30:03 +04:00
Jephté Clain a8d8acaa3c modifs.mineures sans commentaires 2024-06-04 09:01:24 +04:00
Jephté Clain b8c9e7d334 modifs.mineures sans commentaires 2024-06-04 08:49:34 +04:00
Jephté Clain c1bd66d91b modifs.mineures sans commentaires 2024-06-04 08:48:11 +04:00
Jephté Clain a590cd1c17 maj nur-sery 2024-05-26 19:51:13 +04:00
Jephté Clain 416d0a44d4 cosmetic 2024-05-23 18:31:21 +04:00
Jephté Clain d2d77bca0b migration vers legacytools 2024-05-23 18:26:33 +04:00
Jephté Clain fa8fd6e7c3 supprimer schema qui n'est pas encore prêt 2024-05-23 08:16:02 +04:00
Jephté Clain 350622997c modifs.mineures sans commentaires 2024-05-22 21:07:58 +04:00
Jephté Clain ae96d97566 modifs.mineures sans commentaires 2024-05-22 20:49:59 +04:00
Jephté Clain 6755323cc5 maj nur-sery 2024-05-22 20:49:05 +04:00
Jephté Clain 08a1ce49c5 un seul répertoire src/ 2024-05-22 20:14:07 +04:00
Jephté Clain 286fce6cca forcer maj build 2024-05-16 10:08:29 +04:00
Jephté Clain cd09a07cda support multiples fichiers de config 2024-05-15 17:27:48 +04:00
Jephté Clain ac4b03dff7 ajout adminer et postgres15 2024-05-15 16:37:30 +04:00
Jephté Clain 455f658243 modifs.mineures sans commentaires 2024-05-15 16:31:45 +04:00
Jephté Clain 42253c963e support build local et build flavour 2024-05-15 16:23:53 +04:00
Jephté Clain 44e0a784ca modifs.mineures sans commentaires 2024-05-15 15:35:30 +04:00
Jephté Clain a78d93e510 rundk tient compte de $RUNDK_FORCE_BUILDENV 2024-05-13 11:45:44 +04:00
Jephté Clain 5e095a5579 modifs.mineures sans commentaires 2024-05-13 11:20:18 +04:00
Jephté Clain c2408f768f possibilité de spécifier un fichier d'environnement pour le build 2024-05-13 10:51:06 +04:00
Jephté Clain c8aee44c2a cosmetic 2024-05-12 23:46:40 +04:00
Jephté Clain 006964df45 modifs.mineures sans commentaires 2024-05-12 23:41:53 +04:00
Jephté Clain e78becb92a s'assurer que NDIST est calculé 2024-05-12 22:09:50 +04:00
Jephté Clain 1e481044eb modifs.mineures sans commentaires 2024-05-12 18:37:40 +04:00
Jephté Clain 34f48d4714 modifs.mineures sans commentaires 2024-05-12 18:17:25 +04:00
Jephté Clain f94db2a69d améliorer la prise en charge dans les projets 2024-05-12 15:07:31 +04:00
Jephté Clain f42dcb978a ajouter quelques fonctions de base.path.sh 2024-05-12 14:15:37 +04:00
Jephté Clain cbe26be1dc modifs.mineures sans commentaires 2024-04-26 20:49:09 +04:00
Jephté Clain badf5e109c modifs.mineures sans commentaires 2024-04-25 17:47:07 +04:00
Jephté Clain 5460903a9f modifs.mineures sans commentaires 2024-04-25 17:46:18 +04:00
Jephté Clain 451b2768e6 ajout des fonctions q* 2024-04-24 14:56:09 +04:00
Jephté Clain fa2dffdfbe modifs.mineures sans commentaires 2024-04-19 12:52:26 +04:00
Jephté Clain ff1fe2c103 modifs.mineures sans commentaires 2024-04-19 12:27:08 +04:00
Jephté Clain 2f1f401846 modifs.mineures sans commentaires 2024-04-19 12:19:14 +04:00
Jephté Clain aa4fae3b97 modifs.mineures sans commentaires 2024-04-19 12:10:58 +04:00
Jephté Clain 037faa4786 modifs.mineures sans commentaires 2024-04-19 12:03:22 +04:00
Jephté Clain b8241a2994 modifs.mineures sans commentaires 2024-04-19 11:59:16 +04:00
Jephté Clain fa7eeb0237 modifs.mineures sans commentaires 2024-04-19 11:53:37 +04:00
Jephté Clain 171a6703db maj rundk 2024-04-19 11:46:31 +04:00
Jephté Clain df391263d4 modifs.mineures sans commentaires 2024-04-08 18:46:01 +04:00
Jephté Clain 5d9495b1ac découper rundk en 3 parties dont une utilisateur 2024-04-08 18:38:24 +04:00
Jephté Clain 9b0a98acc3 ajout templ.sql 2024-04-05 18:14:51 +04:00
Jephté Clain b12949d3ea templates: support des fichiers locaux 2024-04-04 14:59:11 +04:00
Jephté Clain f572282784 modifs.mineures sans commentaires 2024-04-02 09:33:39 +04:00
Jephté Clain da641d87ae tests 2024-03-29 12:04:23 +04:00
Jephté Clain bc14f4b4ca autodebug et autohelp 2024-03-29 12:04:00 +04:00
Jephté Clain a02d25f8d1 modifs.mineures sans commentaires 2024-03-27 21:39:01 +04:00
Jephté Clain 55bcbacb7c ajout template 2024-03-27 20:44:37 +04:00
Jephté Clain 0673acfc3a ajouter le support des proxies 2024-03-27 19:40:54 +04:00
Jephté Clain c838581ac2 modifs.mineures sans commentaires 2024-03-27 19:32:24 +04:00
Jephté Clain 635e513a8b modifs.mineures sans commentaires 2024-03-27 18:19:14 +04:00
Jephté Clain 2ca4332063 modifs.mineures sans commentaires 2024-03-27 15:33:59 +04:00
Jephté Clain e79e74d379 ajout rundk en support 2024-03-27 12:40:36 +04:00
Jephté Clain ce9e9e5d02 modifs.mineures sans commentaires 2024-03-27 04:47:26 +04:00
Jephté Clain 75de50f5d8 modifs.mineures sans commentaires 2024-03-27 04:37:42 +04:00
Jephté Clain c470c00c34 support affichage actions 2024-03-27 04:25:09 +04:00
Jephté Clain 739aed8f5e maj projet 2024-03-26 14:56:16 +04:00
Jephté Clain e34adce260 maj deps et projet 2024-03-25 18:22:58 +04:00
Jephté Clain 01ed9758d5 modifs.mineures sans commentaires 2024-03-22 13:19:28 +04:00
Jephté Clain eead65706d implémentation minimale de fndate 2024-03-18 17:32:16 +04:00
Jephté Clain 0a868dd179 modifs.mineures sans commentaires 2024-03-14 17:21:22 +04:00
Jephté Clain 52f54e1751 template: ajouter @@:- et @@:+ 2024-03-14 09:46:39 +04:00
Jephté Clain ba33c7815f modifs pour faciliter l'override 2024-03-13 16:23:52 +04:00
Jephté Clain f659f293cb ajout de template.sh 2024-03-11 22:09:59 +04:00
Jephté Clain 7cbddf02a3 supprimer l'appel à eflush 2024-03-11 18:18:26 +04:00
Jephté Clain 12344f7b14 bug 2024-03-06 08:55:06 +04:00
Jephté Clain e723f9bd3e ajout base.tools.awk 2024-03-06 08:19:24 +04:00
Jephté Clain 3f47c40a0e modifs.mineures sans commentaires 2024-03-05 23:52:57 +04:00
Jephté Clain 9fea3c603b afficher --help++ dans l'aide 2024-03-05 21:57:51 +04:00
Jephté Clain b7586b92d0 extension automatique 2024-03-05 20:56:51 +04:00
Jephté Clain b8f3614d0a ajouter quietc_echo 2024-02-27 12:04:07 +04:00
Jephté Clain dc5c078213 renommer quiet_unless en quietc 2024-02-27 10:21:44 +04:00
Jephté Clain e4518ebe81 modifs.mineures sans commentaires 2024-02-23 10:30:23 +04:00
Jephté Clain 5988637a41 modifs.mineures sans commentaires 2024-02-10 23:35:15 +04:00
Jephté Clain 4b60232dfb ajouter nlshell 2024-02-10 15:05:45 +04:00
Jephté Clain 41f217fd76 améliorer ls_* 2024-02-10 15:05:39 +04:00
Jephté Clain 537263216e ajout templ.md et .yml 2024-02-06 23:57:22 +04:00
Jephté Clain 3734e0b3c7 modifs.mineures sans commentaires 2024-01-30 21:44:28 +04:00
Jephté Clain a9ce46da89 renommer newfile.sh en templ.sh 2024-01-30 18:04:16 +04:00
Jephté Clain d5cae312cc templates/ n'est plus nécessaire 2024-01-30 18:02:15 +04:00
Jephté Clain 6f6aad9c05 ajout newfile.sh 2024-01-30 18:01:52 +04:00
Jephté Clain 4a16469559 modifs.mineures sans commentaires 2024-01-22 16:33:11 +04:00
Jephté Clain 71035b2b59 ajout quietgrep, testsame, testdiff 2024-01-06 15:09:20 +04:00
Jephté Clain 2964153e54 modifs.mineures sans commentaires 2023-12-31 22:37:04 +04:00
Jephté Clain 48d2c4aabf modifs.mineures sans commentaires 2023-12-31 19:34:12 +04:00
Jephté Clain dd9a8e8256 modifs.mineures sans commentaires 2023-12-31 18:54:23 +04:00
Jephté Clain 3f357c9d45 modifs.mineures sans commentaires 2023-12-30 18:50:40 +04:00
Jephté Clain b4de22cc74 modifs.mineures sans commentaires 2023-12-30 14:51:27 +04:00
Jephté Clain a5d1485ecf modifs.mineures sans commentaires 2023-12-28 19:33:05 +04:00
Jephté Clain 506fbb1390 modifs.mineures sans commentaires 2023-12-28 17:49:17 +04:00
Jephté Clain 255a68315a modifs.mineures sans commentaires 2023-12-28 17:47:16 +04:00
Jephté Clain b817ad6c98 intégration output et web 2023-12-28 17:32:27 +04:00
Jephté Clain 080ccc4f4d modifs.mineures sans commentaires 2023-12-28 16:06:53 +04:00
Jephté Clain 43b7b046fa modifs.mineures sans commentaires 2023-12-28 12:33:06 +04:00
Jephté Clain 8cf225ea1e modifs.mineures sans commentaires 2023-12-27 11:38:57 +04:00
Jephté Clain 3def66748b support --opt et --no-opt automatique 2023-12-16 16:12:53 +04:00
Jephté Clain d7323d7d45 modifs.mineures sans commentaires 2023-12-15 11:45:24 +04:00
Jephté Clain 69f3ab76b1 corriger simple_menu et actions_menu 2023-12-14 15:10:18 +04:00
Jephté Clain 04e25b5700 modifs.mineures sans commentaires 2023-12-13 15:26:01 +04:00
Jephté Clain d44937132a support des options avancées 2023-12-12 14:43:54 +04:00
Jephté Clain 3fc5f05f5d amélioration parse_args 2023-12-11 10:04:02 +04:00
Jephté Clain 2b10acacb2 ajout de base.tools 2023-12-05 19:11:43 +04:00
Jephté Clain 5afd3f6666 modifs.mineures sans commentaires 2023-11-27 18:57:22 +04:00
Jephté Clain 42b18bf663 modifs.mineures sans commentaires 2023-11-24 16:50:16 +04:00
Jephté Clain ae65a36505 modifs.mineures sans commentaires 2023-11-17 11:11:03 +04:00
Jephté Clain 0009fe55d5 modifs.mineures sans commentaires 2023-11-17 06:46:07 +04:00
Jephté Clain 2b90be29f8 modifs.mineures sans commentaires 2023-11-16 16:42:28 +04:00
Jephté Clain b605477c54 squelette pu 2023-11-16 08:20:24 +04:00
Jephté Clain 5760be32e4 ajout nlman 2023-11-16 07:20:27 +04:00
Jephté Clain 633b554547 ajout abspath 2023-11-08 16:08:12 +04:00
Jephté Clain af332ea961 modifs.mineures sans commentaires 2023-11-04 00:59:42 +04:00
Jephté Clain a1f88e9d38 parse_args: usage peut maintenant être sur plusieurs lignes 2023-11-04 00:29:06 +04:00
Jephté Clain 7cac433cbc modifs.mineures sans commentaires 2023-11-03 22:49:44 +04:00
Jephté Clain e978d18fc8 modifs.mineures sans commentaires 2023-11-03 08:57:09 +04:00
Jephté Clain 861d39974f modifs.mineures sans commentaires 2023-11-02 21:42:34 +04:00
Jephté Clain 1d53f41ef4 modifs.mineures sans commentaires 2023-11-01 10:24:20 +04:00
Jephté Clain 1aee701fef modifs.mineures sans commentaires 2023-10-25 16:49:28 +04:00
Jephté Clain 4a0006724e ajouter les nano-secondes à la date 2023-10-21 20:07:32 +04:00
Jephté Clain 2e037cfa68 ajout base.num et base.bool 2023-10-21 19:49:20 +04:00
Jephté Clain f4fa9fa28c ajout esection 2023-10-21 19:49:06 +04:00
Jephté Clain 19ca7cfbd3 autohelp en version courte 2023-10-20 10:49:41 +04:00
Jephté Clain 6e088c51a2 modifs.mineures sans commentaires 2023-10-20 10:31:32 +04:00
Jephté Clain 2e8d0ac010 maj bash 2023-10-20 10:30:12 +04:00
Jephté Clain 5f15e1ea8c correction php 2023-10-20 10:25:20 +04:00
Jephté Clain b6a95259a7 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain 4b15b8f600 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain f9f16ee0f1 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain 375dbc5042 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain bfb184be58 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain e9bbc93a96 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain 65446a33b4 implémenter la gestion des arguments 2023-10-20 10:01:18 +04:00
Jephté Clain ce5b166f59 modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain 9f0d97fc0a modifs.mineures sans commentaires 2023-10-20 10:01:18 +04:00
Jephté Clain e47fa53336 maj setup pour compat docker 2023-10-20 10:01:18 +04:00
Jephté Clain 9f06ffa6f4 maj projet 2023-10-20 10:01:18 +04:00
Jephté Clain 5c2dd610ef cstr::join accepte un iterable 2023-10-20 10:01:16 +04:00
Jephté Clain 8d55a931d2 modifs.mineures sans commentaires 2023-10-20 10:00:23 +04:00
Jephté Clain 5d86f60b11 ajout librairies awk 2023-10-20 10:00:23 +04:00
Jephté Clain eba69bea41 début migration nulib 2023-10-20 10:00:23 +04:00
290 changed files with 23633 additions and 311 deletions

View File

@ -1,8 +1,8 @@
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
composer_php_min: '7.3'
composer_php_min: '7.4'
composer_php_max: '8.0'
composer_registry: pubdocker.univ-reunion.fr
composer_image: image/phpbuilder:d10
composer_image: image/phpbuilder:d11
require:
branch:
master:

8
.idea/.gitignore vendored
View File

@ -1,8 +0,0 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

15
.idea/codeception.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Codeception">
<option name="configurations">
<list>
<Configuration>
<option name="path" value="$PROJECT_DIR$/tests" />
</Configuration>
<Configuration>
<option name="path" value="$PROJECT_DIR$/tests" />
</Configuration>
</list>
</option>
</component>
</project>

View File

@ -2,8 +2,8 @@
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/php/src_base" isTestSource="false" packagePrefix="nulib\" />
<sourceFolder url="file://$MODULE_DIR$/php/tests" isTestSource="true" packagePrefix="nulib\" />
<sourceFolder url="file://$MODULE_DIR$/php/src" isTestSource="false" packagePrefix="nulib\" />
<excludeFolder url="file://$MODULE_DIR$/vendor" />
</content>
<orderEntry type="inheritedJdk" />

View File

@ -12,39 +12,39 @@
</component>
<component name="PhpIncludePathManager">
<include_path>
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phar-io/manifest" />
<path value="$PROJECT_DIR$/vendor/nikic/php-parser" />
<path value="$PROJECT_DIR$/vendor/phar-io/version" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/sebastian/resource-operations" />
<path value="$PROJECT_DIR$/vendor/doctrine/instantiator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-timer" />
<path value="$PROJECT_DIR$/vendor/composer" />
<path value="$PROJECT_DIR$/vendor/myclabs/deep-copy" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-file-iterator" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-text-template" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-invoker" />
<path value="$PROJECT_DIR$/vendor/phpunit/php-code-coverage" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-reflector" />
<path value="$PROJECT_DIR$/vendor/phpunit/phpunit" />
<path value="$PROJECT_DIR$/vendor/sebastian/exporter" />
<path value="$PROJECT_DIR$/vendor/sebastian/environment" />
<path value="$PROJECT_DIR$/vendor/sebastian/recursion-context" />
<path value="$PROJECT_DIR$/vendor/sebastian/global-state" />
<path value="$PROJECT_DIR$/vendor/sebastian/object-enumerator" />
<path value="$PROJECT_DIR$/vendor/sebastian/complexity" />
<path value="$PROJECT_DIR$/vendor/sebastian/cli-parser" />
<path value="$PROJECT_DIR$/vendor/nulib/tests" />
<path value="$PROJECT_DIR$/vendor/sebastian/diff" />
<path value="$PROJECT_DIR$/vendor/sebastian/lines-of-code" />
<path value="$PROJECT_DIR$/vendor/sebastian/type" />
<path value="$PROJECT_DIR$/vendor/sebastian/version" />
<path value="$PROJECT_DIR$/vendor/sebastian/comparator" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/mur/tests" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.3" />
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>

13
.idea/phpspec.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPSpec">
<suites>
<PhpSpecSuiteConfiguration>
<option name="myPath" value="$PROJECT_DIR$" />
</PhpSpecSuiteConfiguration>
<PhpSpecSuiteConfiguration>
<option name="myPath" value="$PROJECT_DIR$" />
</PhpSpecSuiteConfiguration>
</suites>
</component>
</project>

10
.idea/phpunit.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PHPUnit">
<option name="directories">
<list>
<option value="$PROJECT_DIR$/tests" />
</list>
</option>
</component>
</project>

View File

@ -2,6 +2,5 @@
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

30
.udir Normal file
View File

@ -0,0 +1,30 @@
# -*- 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="librairies de base pour scripts bash, awk, php, python"
udir_note=""
udir_types=(uinst)
uinc=release
uinc_options=()
uinc_args=()
preconfig_scripts=()
configure_variables=(dest)
configure_dest_for=(lib/profile.d/nulib)
config_scripts=(lib/uinst/conf)
install_profiles=true
profiledir=lib/profile.d
bashrcdir=lib/bashrc.d
defaultdir=lib/default
workdir_rsync_options=()
workdir_excludes=()
workdir_includes=()
copy_files=true
destdir=/opt
destdir_override_userhost=
destdir_ssh=
destdir_force_remote=
srcdir=.
files=()
owner=root:
modes=(u=rwX,g=rX,o=rX)
root_scripts=(lib/uinst/rootconf)

15
TODO.md Normal file
View File

@ -0,0 +1,15 @@
# nulib
* runners
* [ ] rnlphp -- lancer un programme php avec la bonne version (+docker le cas échéant)
* [ ] utilisable en shebang
* [ ] utilisable en tant que lien: lance `../php/bin/$MYNAME.php`
* [ ] frontend pour composer
* [ ] rnljava -- lancer un programme java avec la bonne version (+docker le cas échéant)
* [ ] frontend pour maven
* [ ] rnlawk -- lancer un script awk
* [ ] rnlpy3 -- lancer un script python3
* [ ] rnlsh -- lancer un shell avec les librairies bash / lancer un script
* MYTRUEDIR, MYTRUENAME, MYTRUESELF -- résoudre les liens symboliques
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

157
awk/src/base.array.awk Normal file
View File

@ -0,0 +1,157 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
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) {
# length(array) a un bug sur awk 3.1.5
# cette version est plus lente mais fonctionne toujours
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
}

5
awk/src/base.awk Normal file
View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
@include "base.core.awk"
@include "base.array.awk"
@include "base.date.awk"
@include "base.tools.awk"

141
awk/src/base.core.awk Normal file
View File

@ -0,0 +1,141 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
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(/&/, "\\&amp;", s)
gsub(/"/, "\\&quot;", s)
gsub(/>/, "\\&gt;", s)
gsub(/</, "\\&lt;", s)
return s
}
function unquote_html(s) {
gsub(/&lt;/, "<", s)
gsub(/&gt;/, ">", s)
gsub(/&quot;/, "\"", s)
gsub(/&amp;/, "\\&", s)
return s
}
function qawk(s) {
gsub(/\\/, "\\\\", s)
gsub(/"/, "\\\"", s)
gsub(/\n/, "\\n", 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) {
suffix = suffix != ""? " " suffix: ""
if (s == "") return s
else return "," s suffix
}
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 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 <input) > 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
}

52
awk/src/base.date.awk Normal file
View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
function date__parse_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))
} else if (match(date, /([0-9][0-9]?)\/([0-9][0-9]?)\/([0-9][0-9])/, parts)) {
basey = int(strftime("%Y")); basey = basey - basey % 100
y = basey + 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 date__parse_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 date__parse_any(date, serial) {
serial = date__parse_fr(date)
if (serial == -1) serial = date__parse_mysql(date)
return serial
}
function date_serial(date) {
return date__parse_any(date)
}
function date_parse(date, serial) {
serial = date__parse_any(date)
if (serial == -1) return date
return strftime("%d/%m/%Y", serial)
}
function date_monday(date, serial, dow) {
serial = date__parse_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 = date__parse_any(date)
if (serial == -1) return date
serial += nbdays * 86400
return strftime("%d/%m/%Y", serial)
}

20
awk/src/base.tools.awk Normal file
View File

@ -0,0 +1,20 @@
BEGIN {
srand()
}
function get_random_password( password, max, LETTERS) {
LETTERS = "AZERTYUIOPQSDFGHJKLMWXCVBNazertyuiopqsdfghjklmwxcvbn0123456789"
max = length(LETTERS)
password = ""
for (i = 0; i < 16; i++) {
password = password substr(LETTERS, int(rand() * max), 1)
}
return password
}
function should_generate_password() {
return $0 ~ /XXXRANDOMXXX/
}
function generate_password() {
sub(/XXXRANDOMXXX/, get_random_password())
}

201
awk/src/csv.awk Normal file
View File

@ -0,0 +1,201 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
@include "base.core.awk"
@include "base.array.awk"
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) {
# chaine mal terminee
resl = resl line
destl[0] = ""
destl[1] = 0
return resl
}
if (echar != "" && pos > 1) {
# tenir compte du fait qu"un caratère peut être mis en échappement
prevc = substr(line, pos - 1, 1)
quotec = substr(line, pos, 1)
nextc = substr(line, pos + 1, 1)
if (prevc == echar) {
# qchar en échappement
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 == "") {
# fin de champ ou fin de ligne
resl = resl tmpl
destl[0] = substr(line, pos + 2)
destl[1] = nextc == colsep
return resl
} else {
# erreur de syntaxe: guillemet non mis en échappement
# ignorer cette erreur et prendre le guillemet quand meme
resl = resl tmpl quotec
line = substr(line, pos + 1)
}
} else {
# pas d"échappement pour qchar. il est éventuellement doublé
tmpl = substr(line, 1, pos - 1)
quotec = substr(line, pos, 1)
nextc = substr(line, pos + 1, 1)
if (nextc == colsep || nextc == "") {
# fin de champ ou fin de ligne
resl = resl tmpl
destl[0] = substr(line, pos + 2)
destl[1] = nextc == colsep
return resl
} else if (nextc == qchar) {
# qchar en echappement
resl = resl tmpl quotec
line = substr(line, pos + 2)
} else {
# erreur de syntaxe: guillemet non mis en échappement
# ignorer cette erreur et prendre le guillemet quand meme
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 csv__array_parse(fields, line, nbfields, colsep, qchar, echar, shouldparse, destl, i) {
array_new(fields)
array_new(destl)
i = 1
shouldparse = 0
# shouldparse permet de gérer le cas où un champ vide est en fin de ligne.
# en effet, après "," il faut toujours parser, même si line==""
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 csv__array_parse(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 csv__array_parse(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 <file
} else {
getline
}
return parsecsv($0)
}
function csv__should_quote(s) {
if (s ~ /^[[:blank:][:cntrl:][:space:]]/) return 1
if (s ~ /[[:blank:][:cntrl:][:space:]]$/) return 1
return 0
}
function array_formatcsv2(fields, colsep, mvsep, qchar, echar, count, indices, line, i, value) {
line = ""
count = mkindices(fields, indices)
for (i = 1; i <= count; i++) {
value = fields[indices[i]]
if (i > 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 <input) > 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
}

57
awk/src/enc.base64.awk Normal file
View File

@ -0,0 +1,57 @@
# -*- coding: utf-8 mode: awk -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
function base64__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
}
# Rotate bytevalue left x times
function base64__lshift(var, x) {
while(x > 0) {
var *= 2
x--
}
return var
}
# Rotate bytevalue right x times
function base64__rshift(var, x) {
while(x > 0) {
var = int(var/2)
x--
}
return var
}
BEGIN {
BASE64__BYTES = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
}
function b64decode(src, result, base1, base2, base3, base4) {
result = ""
while (length(src) > 0) {
# Specify byte values
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 = "="
# Now find numerical position in BASE64 string
byte1 = index(BASE64__BYTES, base1) - 1
if (byte1 < 0) byte1 = 0
byte2 = index(BASE64__BYTES, base2) - 1
if (byte2 < 0) byte2 = 0
byte3 = index(BASE64__BYTES, base3) - 1
if (byte3 < 0) byte3 = 0
byte4 = index(BASE64__BYTES, base4) - 1
if (byte4 < 0) byte4 = 0
# Reconstruct ASCII string
result = result sprintf( "%c", base64__lshift(base64__and(byte1, 63), 2) + base64__rshift(base64__and(byte2, 48), 4) )
if (base3 != "=") result = result sprintf( "%c", base64__lshift(base64__and(byte2, 15), 4) + base64__rshift(base64__and(byte3, 60), 2) )
if (base4 != "=") result = result sprintf( "%c", base64__lshift(base64__and(byte3, 3), 6) + byte4 )
# Decrease incoming string with 4
src = substr(src, 5)
}
return result
}

45
bash/TODO.md Normal file
View File

@ -0,0 +1,45 @@
# nulib/bash
## template
* [x] pour tout fichier source `.file.template`, considérer avant
`file.template.local` s'il existe, ce qui permet à un utilisateur de
remplacer le modèle livré.
cela a-t-il du sens de supporter aussi file.dist.local? vu que ça ne sert
qu'une seule fois? ça ne mange pas de pain...
## args
* [x] support des couples d'options --option et --no-option qui mettent à jour
tous les deux la variables option. ceci:
~~~
--option .
--no-option .
~~~
est équivalent à ceci:
~~~
--option '$inc@ option'
--no-option '$dec@ option'
~~~
dec@ est une nouvelle fonction qui décrémente et remplace par une chaine vide
quand on arrive à zéro
* [x] args: support des noms d'argument pour améliorer l'affichage de l'aide.
par exemple la définition
~~~
-f:file,--input input= "spécifier le fichier en entrée"
~~~
donnera cette aide:
~~~
-f, --input FILE
spécifier le fichier
~~~
* [ ] args: après le support des noms d'arguments, ajouter la génération
automatique de l'auto-complétion basée sur ces informations. certains noms
seraient normalisés: `file` pour un fichier, `dir` pour un répertoire, `env`
pour une variable d'environnement, etc.
on pourrait même considérer mettre des patterns pour la sélection, e.g
~~~
"-C,--config:file:*.conf *.cnf" input= "spécifier le fichier de configuration"
~~~
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary

4
bash/src/TEMPLATE Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: TEMPLATE "DESCRIPTION"

77
bash/src/_output_color.sh Normal file
View File

@ -0,0 +1,77 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
function __esection() {
local -a lines
local lsep prefix="$(__edate)$(__eindent0)"
local length="${COLUMNS:-80}"
setx lsep=__complete "$prefix" "$length" -
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
[ -n "$*" ] || return 0
length=$((length - 1))
setx -a lines=echo "$1"
for line in "${lines[@]}"; do
setx line=__complete "$prefix- $line" "$length"
tooenc "$COULEUR_BLEUE$line-$COULEUR_NORMALE"
done
tooenc "$COULEUR_BLEUE$lsep$COULEUR_NORMALE"
}
function __etitle() {
local -a lines; local maxlen=0
local prefix="$(__edate)$(__eindent0)"
setx -a lines=echo "$1"
for line in "${lines[@]}"; do
[ ${#line} -gt $maxlen ] && maxlen=${#line}
tooenc "${prefix}${COULEUR_BLEUE}T $line$COULEUR_NORMALE"
done
maxlen=$((maxlen + 2))
tooenc "${prefix}${COULEUR_BLEUE}T$(__complete "" $maxlen -)${COULEUR_NORMALE}"
}
function __edesc() {
local -a lines
local prefix="$(__edate)$(__eindent0)"
setx -a lines=echo "$1"
for line in "${lines[@]}"; do
tooenc "${prefix}${COULEUR_BLEUE}>${COULEUR_NORMALE} $line"
done
}
function __ebanner() {
local -a lines
local lsep prefix="$(__edate)$(__eindent0)"
local length="${COLUMNS:-80}"
setx lsep=__complete "$prefix" "$length" =
tooenc "$COULEUR_ROUGE$lsep"
length=$((length - 1))
setx -a lines=echo "$1"
for line in "" "${lines[@]}" ""; do
setx line=__complete "$prefix= $line" "$length"
tooenc "$line="
done
tooenc "$lsep$COULEUR_NORMALE"
}
function __eimportant() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}!${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __eattention() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}*${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __eerror() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}E${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __ewarn() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}W${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __enote() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}N${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __einfo() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}I${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __edebug() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}D${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estep() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepe() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepw() { tooenc "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepn() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepi() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estep_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepe_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_ROUGE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepw_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_JAUNE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepn_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_VERTE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __estepi_() { tooenc_ "$(__edate)$(__eindent0)${COULEUR_BLEUE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __action() { tooenc "$(__edate)$(__eindent0)${COULEUR_BLANCHE}.${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __asuccess() { tooenc "$(__edate)$(__eindent0)${COULEUR_VERTE}${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __afailure() { tooenc "$(__edate)$(__eindent0)${COULEUR_ROUGE}${COULEUR_NORMALE} $(__eindent "$1" " ")"; }
function __adone() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }

View File

@ -0,0 +1,66 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
function __esection() {
local -a lines
local lsep prefix="$(__edate)$(__eindent0)"
local length="${COLUMNS:-80}"
setx lsep=__complete "$prefix" "$length" -
tooenc "$lsep"
[ -n "$*" ] || return 0
length=$((length - 1))
setx -a lines=echo "$1"
for line in "${lines[@]}"; do
setx line=__complete "$prefix- $line" "$length"
tooenc "$line-"
done
tooenc "$lsep"
}
function __etitle() {
local p="TITLE: " i=" "
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
}
function __edesc() {
local p="DESC: " i=" "
tooenc "$(__edate)$(__eindent0)${p}$(__eindent "$1" "$i")"
}
function __ebanner() {
local -a lines
local lsep prefix="$(__edate)$(__eindent0)"
local length="${COLUMNS:-80}"
setx lsep=__complete "$prefix" "$length" =
tooenc "$lsep"
length=$((length - 1))
setx -a lines=echo "$1"
for line in "" "${lines[@]}" ""; do
setx line=__complete "$prefix= $line" "$length"
tooenc "$line="
done
tooenc "$lsep"
}
function __eimportant() { tooenc "$(__edate)$(__eindent0)IMPORTANT! $(__eindent "$1" " ")"; }
function __eattention() { tooenc "$(__edate)$(__eindent0)ATTENTION! $(__eindent "$1" " ")"; }
function __eerror() { tooenc "$(__edate)$(__eindent0)ERROR: $(__eindent "$1" " ")"; }
function __ewarn() { tooenc "$(__edate)$(__eindent0)WARNING: $(__eindent "$1" " ")"; }
function __enote() { tooenc "$(__edate)$(__eindent0)NOTE: $(__eindent "$1" " ")"; }
function __einfo() { tooenc "$(__edate)$(__eindent0)INFO: $(__eindent "$1" " ")"; }
function __edebug() { tooenc "$(__edate)$(__eindent0)DEBUG: $(__eindent "$1" " ")"; }
function __eecho() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }
function __eecho_() { tooenc_ "$(__edate)$(__eindent0)$(__eindent "$1")"; }
function __estep() { tooenc "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
function __estepe() { tooenc "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
function __estepw() { tooenc "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
function __estepn() { tooenc "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
function __estepi() { tooenc "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
function __estep_() { tooenc_ "$(__edate)$(__eindent0). $(__eindent "$1" " ")"; }
function __estepe_() { tooenc_ "$(__edate)$(__eindent0).E $(__eindent "$1" " ")"; }
function __estepw_() { tooenc_ "$(__edate)$(__eindent0).W $(__eindent "$1" " ")"; }
function __estepn_() { tooenc_ "$(__edate)$(__eindent0).N $(__eindent "$1" " ")"; }
function __estepi_() { tooenc_ "$(__edate)$(__eindent0).I $(__eindent "$1" " ")"; }
function __action() { tooenc "$(__edate)$(__eindent0)ACTION: $(__eindent "$1" " ")"; }
function __asuccess() { tooenc "$(__edate)$(__eindent0)(OK) $(__eindent "$1" " ")"; }
function __afailure() { tooenc "$(__edate)$(__eindent0)(KO) $(__eindent "$1" " ")"; }
function __adone() { tooenc "$(__edate)$(__eindent0)$(__eindent "$1")"; }

486
bash/src/base.args.sh Normal file
View File

@ -0,0 +1,486 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.args "Fonctions de base: analyse d'arguments"
function: local_args "Afficher des commandes pour rendre locales des variables utilisées par parse_args()
Cela permet d'utiliser parse_args() à l'intérieur d'une fonction."
function local_args() {
echo "local -a args"
echo "local NULIB_ARGS_ONERROR_RETURN=1"
echo "local NULIB_VERBOSITY=\"\$NULIB_VERBOSITY\""
echo "local NULIB_INTERACTION=\"\$NULIB_INTERACTION\""
}
function: parse_args "Analyser les arguments de la ligne de commande à partir des définitions du tableau args
Cette fonction s'utilise ainsi:
~~~"'
args=(
[desc]
[usage]
[+|-]
-o,--longopt action [optdesc]
-a:,--mandarg: action [optdesc]
-b::,--optarg:: action [optdesc]
)
parse_args "$@"; set -- "${args[@]}"
~~~'"
au retour de la fonction, args contient les arguments qui n'ont pas été traités
automatiquement.
les options --help et --help++ sont automatiquement gérées. avec --help, seules
les options standards sont affichées. --help++ affiche toutes les options. les
descriptions sont utilisées pour l'affichage de l'aide. une option avancée est
identifiée par une description qui commence par ++
desc
: description de l'objet du script ou de la fonction. cette valeur est
facultative
usage
: description des arguments du script, sans le nom du script. par exemple la
valeur '[options] FILE' générera le texte d'aide suivant:
~~~
USAGE
\$MYNAME [options] FILE
~~~
Peut contenir autant de lignes que nécessaire. Chaque ligne est préfixée du
nom du script, jusqu'à la première ligne vide. Ensuite, les lignes sont
affichées telles quelles.
Le premier espace est ignoré, ce qui permet de spécifier des USAGEs avec une
option, e.g ' -c VALUE'
+|-
: méthode d'analyse des arguments.
* Par défaut, les options sont valides n'importe où sur la ligne de commande.
* Avec '+', l'analyse s'arrête au premier argument qui n'est pas une option.
* Avec '-', les options sont valides n'importe où sur la ligne de commande,
mais les arguments ne sont pas réordonnés, et apparaissent dans l'ordre de
leur mention. IMPORTANT: dans ce cas, aucun argument ni option n'est traité,
c'est à la charge de l'utilisateur. Au retour de la fonction, args contient
l'ensemble des arguments tels qu'analysés par getopt
-o, --longopt
: option sans argument
-a:, --mandarg:
: option avec argument obligatoire
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
-a:file pour un fichier. cette valeur est mise en majuscule lors de
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
-b::, --optarg::
: option avec argument facultatif
l'option peut être suivi d'une valeur qui décrit l'argument attendu e.g
-b::file pour un fichier. cette valeur est mise en majuscule lors de
l'affichage de l'aide. pour le moment, cette valeur n'est pas signifiante.
action
: action à effectuer si cette option est utilisée. plusieurs syntaxes sont valides:
* 'NAME' met à jour la variable en fonction du type d'argument: l'incrémenter
pour une option sans argument, lui donner la valeur spécifiée pour une
option avec argument, ajouter la valeur spécifiée au tableau si l'option est
spécifiée plusieurs fois.
la valeur spéciale '.' calcule une valeur de NAME en fonction du nom de
l'option la plus longue. par exemple, les deux définitions suivantes sont
équivalentes:
~~~
-o,--short,--very-long .
-o,--short,--very-long very_long
~~~
De plus, la valeur spéciale '.' traite les options de la forme --no-opt
comme l'inverse des option --opt. par exemple, les deux définitions
suivantes sont équivalentes:
~~~
--opt . --no-opt .
--opt opt --no-opt '\$dec@ opt'
~~~
* 'NAME=VALUE' pour une option sans argument, forcer la valeur spécifiée; pour
une option avec argument, prendre la valeur spécifiée comme valeur par
défaut si la valeur de l'option est vide
* '\$CMD' CMD est évalué avec eval *dès* que l'option est rencontrée.
les valeurs suivantes sont initialisées:
* option_ est l'option utilisée, e.g --long-opt
* value_ est la valeur de l'option
les fonctions suivantes sont définies:
* 'inc@ NAME' incrémente la variable NAME -- c'est le comportement de set@ si
l'option est sans argument
* 'dec@ NAME' décrémente la variable NAME, et la rend vide quand le compte
arrive à zéro
* 'res@ NAME VALUE' initialise la variable NAME avec la valeur de l'option (ou
VALUE si la valeur de l'option est vide) -- c'est le comportement de set@
si l'option prend un argument
* 'add@ NAME VALUE' ajoute la valeur de l'option (ou VALUE si la valeur de
l'option est vide) au tableau NAME.
* 'set@ NAME' met à jour la variable NAME en fonction de la définition de
l'option (avec ou sans argument, ajout ou non à un tableau)
optdesc
: description de l'option. cette valeur est facultative. si la description
commence par ++, c'est une option avancée qui n'est pas affichée par défaut."
function parse_args() {
eval "$NULIB__DISABLE_SET_X"
local __r=
local __DIE='[ -n "$NULIB_ARGS_ONERROR_RETURN" ] && return 1 || die'
if ! is_array args; then
eerror "Invalid args definition: args must be defined"
__r=1
fi
# distinguer les descriptions des définition d'arguments
local __USAGE __DESC
local -a __DEFS __ARGS
__ARGS=("$@")
set -- "${args[@]}"
[ "${1#-}" == "$1" ] && { __DESC="$1"; shift; }
[ "${1#-}" == "$1" ] && { __USAGE="$1"; shift; }
if [ -n "$__r" ]; then
:
elif [ $# -gt 0 -a "$1" != "+" -a "${1#-}" == "$1" ]; then
eerror "Invalid args definition: third arg must be an option"
__r=1
else
__DEFS=("$@")
__parse_args || __r=1
fi
eval "$NULIB__ENABLE_SET_X"
if [ -n "$__r" ]; then
eval "$__DIE"
fi
}
function __parse_args() {
## tout d'abord, construire la liste des options
local __AUTOH=1 __AUTOHELP=1 # faut-il gérer automatiquement l'affichage de l'aide?
local __AUTOD=1 __AUTODEBUG=1 # faut-il rajouter les options -D et --debug
local __ADVHELP # y a-t-il des options avancées?
local __popt __sopts __lopts
local -a __defs
set -- "${__DEFS[@]}"
while [ $# -gt 0 ]; do
case "$1" in
+) __popt="$1"; shift; continue;;
-) __popt="$1"; shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
*) eerror "Invalid arg definition: expected option, got '$1'"; eval "$__DIE";;
esac
# est-ce que l'option prend un argument?
local __def __witharg __valdesc
__witharg=
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
fi
done
# définitions __sopts et __lopts
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
if [[ "$__def" == --* ]]; then
# --longopt
__def="${__def#--}"
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
# -o
__def="${__def#-}"
__sopts="$__sopts$__def$__witharg"
[ "$__def" == h ] && __AUTOH=
[ "$__def" == D ] && __AUTOD=
else
# -longopt ou longopt
__def="${__def#-}"
__lopts="$__lopts${__lopts:+,}$__def$__witharg"
fi
[ "$__def" == help -o "$__def" == help++ ] && __AUTOHELP=
[ "$__def" == debug ] && __AUTODEBUG=
done
# sauter l'action
shift
# sauter la description le cas échéant
if [ "${1#-}" == "$1" ]; then
[ "${1#++}" != "$1" ] && __ADVHELP=1
shift
fi
done
[ -n "$__AUTOH" ] && __sopts="${__sopts}h"
[ -n "$__AUTOHELP" ] && __lopts="$__lopts${__lopts:+,}help,help++"
[ -n "$__AUTOD" ] && __sopts="${__sopts}D"
[ -n "$__AUTODEBUG" ] && __lopts="$__lopts${__lopts:+,}debug"
__sopts="$__popt$__sopts"
local -a __getopt_args
__getopt_args=(-n "$MYNAME" ${__sopts:+-o "$__sopts"} ${__lopts:+-l "$__lopts"} -- "${__ARGS[@]}")
## puis analyser et normaliser les arguments
if args="$(getopt -q "${__getopt_args[@]}")"; then
eval "set -- $args"
else
# relancer pour avoir le message d'erreur
LANG=C getopt "${__getopt_args[@]}" 2>&1 1>/dev/null
eval "$__DIE"
fi
## puis traiter les options
local __defname __resvalue __decvalue __defvalue __add __action option_ name_ value_
function inc@() {
eval "[ -n \"\$$1\" ] || let $1=0"
eval "let $1=$1+1"
}
function dec@() {
eval "[ -n \"\$$1\" ] && let $1=$1-1"
eval "[ \"\$$1\" == 0 ] && $1="
}
function res@() {
local __value="${value_:-$2}"
eval "$1=\"\$__value\""
}
function add@() {
local __value="${value_:-$2}"
eval "$1+=(\"\$__value\")"
}
function set@() {
if [ -n "$__resvalue" ]; then
res@ "$@"
elif [ -n "$__witharg" ]; then
if is_array "$1"; then
add@ "$@"
elif ! is_defined "$1"; then
# première occurrence: variable
res@ "$@"
else
# deuxième occurence: tableau
[ -z "${!1}" ] && eval "$1=()"
add@ "$@"
fi
elif [ -n "$__decvalue" ]; then
dec@ "$@"
else
inc@ "$@"
fi
}
function showhelp@() {
local help="$MYNAME" showadv="$1"
if [ -n "$__DESC" ]; then
help="$help: $__DESC"
fi
local first usage nl=$'\n'
local prefix=" $MYNAME "
local usages="${__USAGE# }"
[ -n "$usages" ] || usages="[options]"
help="$help
USAGE"
first=1
while [ -n "$usages" ]; do
usage="${usages%%$nl*}"
if [ "$usage" != "$usages" ]; then
usages="${usages#*$nl}"
else
usages=
fi
if [ -n "$first" ]; then
first=
[ -z "$usage" ] && continue
else
[ -z "$usage" ] && prefix=
fi
help="$help
$prefix$usage"
done
set -- "${__DEFS[@]}"
first=1
while [ $# -gt 0 ]; do
case "$1" in
+) shift; continue;;
-) shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
esac
if [ -n "$first" ]; then
first=
help="$help${nl}${nl}OPTIONS"
if [ -n "$__AUTOHELP" -a -n "$__ADVHELP" ]; then
help="$help
--help++
Afficher l'aide avancée"
fi
fi
# est-ce que l'option prend un argument?
__witharg=
__valdesc=value
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
[ -n "${__def#*::}" ] && __valdesc="[${__def#*::}]"
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
[ -n "${__def#*:}" ] && __valdesc="${__def#*:}"
fi
done
# description de l'option
local first=1 thelp tdesc
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
if [[ "$__def" == --* ]]; then
: # --longopt
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
: # -o
else
# -longopt ou longopt
__def="--${__def#-}"
fi
if [ -n "$first" ]; then
first=
thelp="${nl} "
else
thelp="$thelp, "
fi
thelp="$thelp$__def"
done
[ -n "$__witharg" ] && thelp="$thelp ${__valdesc^^}"
# sauter l'action
shift
# prendre la description le cas échéant
if [ "${1#-}" == "$1" ]; then
tdesc="$1"
if [ "${tdesc#++}" != "$tdesc" ]; then
# option avancée
if [ -n "$showadv" ]; then
tdesc="${tdesc#++}"
else
thelp=
fi
fi
[ -n "$thelp" ] && thelp="$thelp${nl} ${tdesc//$nl/$nl }"
shift
fi
[ -n "$thelp" ] && help="$help$thelp"
done
uecho "$help"
exit 0
}
if [ "$__popt" != - ]; then
while [ $# -gt 0 ]; do
if [ "$1" == -- ]; then
shift
break
fi
[[ "$1" == -* ]] || break
option_="$1"; shift
__parse_opt "$option_"
if [ -n "$__witharg" ]; then
# l'option prend un argument
value_="$1"; shift
else
# l'option ne prend pas d'argument
value_=
fi
eval "$__action"
done
fi
unset -f inc@ res@ add@ set@ showhelp@
args=("$@")
}
function __parse_opt() {
# $1 est l'option spécifiée
local option_="$1"
set -- "${__DEFS[@]}"
while [ $# -gt 0 ]; do
case "$1" in
+) shift; continue;;
-) shift; continue;;
-*) IFS=, read -a __defs <<<"$1"; shift;;
esac
# est-ce que l'option prend un argument?
__witharg=
for __def in "${__defs[@]}"; do
if [ "${__def%::*}" != "$__def" ]; then
[ "$__witharg" != : ] && __witharg=::
elif [ "${__def%:*}" != "$__def" ]; then
__witharg=:
fi
done
# nom le plus long
__defname=
local __found=
for __def in "${__defs[@]}"; do
__def="${__def%%:*}"
[ "$__def" == "$option_" ] && __found=1
if [[ "$__def" == --* ]]; then
# --longopt
__def="${__def#--}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
elif [[ "$__def" == -* ]] && [ ${#__def} -eq 2 ]; then
# -o
__def="${__def#-}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
else
# -longopt ou longopt
__def="${__def#-}"
[ ${#__def} -gt ${#__defname} ] && __defname="$__def"
fi
done
__defname="${__defname//-/_}"
# analyser l'action
__decvalue=
if [ "${1#\$}" != "$1" ]; then
name_="$__defname"
__resvalue=
__defvalue=
__action="${1#\$}"
else
if [ "$1" == . ]; then
name_="$__defname"
__resvalue=
__defvalue=
if [ "${name_#no_}" != "$name_" ]; then
name_="${name_#no_}"
__decvalue=1
fi
elif [[ "$1" == *=* ]]; then
name_="${1%%=*}"
__resvalue=1
__defvalue="${1#*=}"
else
name_="$1"
__resvalue=
__defvalue=
fi
__action="$(qvals set@ "$name_" "$__defvalue")"
fi
shift
# sauter la description le cas échéant
[ "${1#-}" == "$1" ] && shift
[ -n "$__found" ] && return 0
done
if [ -n "$__AUTOH" -a "$option_" == -h ]; then
__action="showhelp@"
return 0
fi
if [ -n "$__AUTOHELP" ]; then
if [ "$option_" == --help ]; then
__action="showhelp@"
return 0
elif [ "$option_" == --help++ ]; then
__action="showhelp@ ++"
return 0
fi
fi
if [ -n "$__AUTOD" -a "$option_" == -D ]; then
__action=set_debug
return 0
fi
if [ -n "$__AUTODEBUG" -a "$option_" == --debug ]; then
__action=set_debug
return 0
fi
# ici, l'option n'a pas été trouvée, on ne devrait pas arriver ici
eerror "Unexpected option '$option_'"; eval "$__DIE"
}

360
bash/src/base.array.sh Normal file
View File

@ -0,0 +1,360 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.array "Fonctions de base: gestion des variables tableaux"
function: array_count "retourner le nombre d'éléments du tableau \$1"
function array_count() {
eval "echo \${#$1[*]}"
}
function: array_isempty "tester si le tableau \$1 est vide"
function array_isempty() {
eval "[ \${#$1[*]} -eq 0 ]"
}
function: array_new "créer un tableau vide dans la variable \$1"
function array_new() {
eval "$1=()"
}
function: array_copy "copier le contenu du tableau \$2 dans le tableau \$1"
function array_copy() {
eval "$1=(\"\${$2[@]}\")"
}
function: array_add "ajouter les valeurs \$2..@ à la fin du tableau \$1"
function array_add() {
local __aa_a="$1"; shift
eval "$__aa_a+=(\"\$@\")"
}
function: array_ins "insérer les valeurs \$2..@ au début du tableau \$1"
function array_ins() {
local __aa_a="$1"; shift
eval "$__aa_a=(\"\$@\" \"\${$__aa_a[@]}\")"
}
function: array_del "supprimer *les* valeurs \$2 du tableau \$1"
function array_del() {
local __ad_v
local -a __ad_vs
eval '
for __ad_v in "${'"$1"'[@]}"; do
if [ "$__ad_v" != "$2" ]; then
__ad_vs=("${__ad_vs[@]}" "$__ad_v")
fi
done'
array_copy "$1" __ad_vs
}
function: array_addu "ajouter la valeur \$2 au tableau \$1, si la valeur n'y est pas déjà
Retourner vrai si la valeur a été ajoutée"
function array_addu() {
local __as_v
eval '
for __as_v in "${'"$1"'[@]}"; do
[ "$__as_v" == "$2" ] && return 1
done'
array_add "$1" "$2"
return 0
}
function: array_insu "insérer la valeur \$2 au début du tableau tableau \$1, si la valeur n'y est pas déjà
Retourner vrai si la valeur a été ajoutée."
function array_insu() {
local __as_v
eval '
for __as_v in "${'"$1"'[@]}"; do
[ "$__as_v" == "$2" ] && return 1
done'
array_ins "$1" "$2"
return 0
}
function: array_fillrange "Initialiser le tableau \$1 avec les nombres de \$2(=1) à \$3(=10) avec un step de \$4(=1)"
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 "tester l'égalité des tableaux \$1 et \$2"
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 "tester si le tableau \$1 contient la valeur \$2"
function array_contains() {
local __ac_v
eval '
for __ac_v in "${'"$1"'[@]}"; do
[ "$__ac_v" == "$2" ] && return 0
done'
return 1
}
function: array_icontains "tester si le tableau \$1 contient la valeur \$2, sans tenir compte de la casse"
function array_icontains() {
local __ac_v
eval '
for __ac_v in "${'"$1"'[@]}"; do
[ "${__ac_v,,} == "${2,,}" ] && return 0
done'
return 1
}
function: array_find "si le tableau \$1 contient la valeur \$2, afficher l'index de la valeur. Si le tableau \$3 est spécifié, afficher la valeur à l'index dans ce tableau"
function array_find() {
local __af_i __af_v
__af_i=0
eval '
for __af_v in "${'"$1"'[@]}"; do
if [ "$__af_v" == "$2" ]; then
if [ -n "$3" ]; then
recho "${'"$3"'[$__af_i]}"
else
echo "$__af_i"
fi
return 0
fi
__af_i=$(($__af_i + 1))
done'
return 1
}
function: array_reverse "Inverser l'ordre des élément du tableau \$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 "dans le tableau \$1, remplacer toutes les occurences de \$2 par \$3..*"
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 "Pour chacune des valeurs ITEM du tableau \$1, appeler la fonction \$2 avec les arguments (\$3..@ ITEM)"
function array_each() {
local __ae_v
local -a __ae_a
array_copy __ae_a "$1"; shift
for __ae_v in "${__ae_a[@]}"; do
"$@" "$__ae_v"
done
}
function: array_map "Pour chacune des valeurs ITEM du tableau \$1, appeler la fonction \$2 avec les arguments (\$3..@ ITEM), et remplacer la valeur par le résultat de la fonction"
function array_map() {
local __am_v
local -a __am_a __am_vs
local __am_an="$1"; shift
local __am_f="$1"; shift
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: array_first "afficher la première valeur du tableau \$1"
function array_first() {
eval "recho \"\${$1[@]:0:1}\""
}
function: array_last "afficher la dernière valeur du tableau \$1"
function array_last() {
eval "recho \"\${$1[@]: -1:1}\""
}
function: array_copy_firsts "copier toutes les valeurs du tableau \$2(=\$1) dans le tableau \$1, excepté la dernière"
function array_copy_firsts() {
eval "$1=(\"\${${2:-$1}[@]:0:\$((\${#${2:-$1}[@]}-1))}\")"
}
function: array_copy_lasts "copier toutes les valeurs du tableau \$2(=\$1) dans le tableau \$1, excepté la première"
function array_copy_lasts() {
eval "$1=(\"\${${2:-$1}[@]:1}\")"
}
function: array_extend "ajouter le contenu du tableau \$2 au tableau \$1"
function array_extend() {
eval "$1=(\"\${$1[@]}\" \"\${$2[@]}\")"
}
function: 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"
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 "ajouter toutes les valeurs du tableau \$2 dans le tableau \$1, excepté la dernière"
function array_extend_firsts() {
eval "$1=(\"\${$1[@]}\" \"\${$2[@]:0:\$((\${#$2[@]}-1))}\")"
}
function: array_extend_lasts "ajouter toutes les valeurs du tableau \$2 dans le tableau \$1, excepté la première"
function array_extend_lasts() {
eval "$1=(\"\${$1[@]}\" \"\${$2[@]:1}\")"
}
function: 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)"
function array_xsplit() {
eval "$1=($(recho_ "$2" | lawk -v RS="${3:-:}" '
{
gsub(/'\''/, "'\'\\\\\'\''")
print "'\''" $0 "'\''"
}'))" #"
}
function: array_xsplitc "variante de array_xsplit() où le séparateur est ',' par défaut"
function array_xsplitc() {
array_xsplit "$1" "$2" "${3:-,}"
}
function: 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\""
function array_split() {
eval "$1=($(recho_ "$2" | lawk -v RS="${3:-:}" '
/^$/ { next }
{
gsub(/'\''/, "'\'\\\\\'\''")
print "'\''" $0 "'\''"
}'))" #"
}
function: array_splitc "variante de array_split() où le séparateur est ',' par défaut"
function array_splitc() {
array_split "$1" "$2" "${3:-,}"
}
function: array_xsplitl "créer le tableau \$1 avec chaque ligne de \$2"
function array_xsplitl() {
eval "$1=($(recho_ "$2" | nl2lf | lawk '
{
gsub(/'\''/, "'\'\\\\\'\''")
print "'\''" $0 "'\''"
}'))" #"
}
function: array_splitl "créer le tableau \$1 avec chaque ligne de \$2
Les lignes vides sont ignorés."
function array_splitl() {
eval "$1=($(recho_ "$2" | nl2lf | lawk '
/^$/ { next }
{
gsub(/'\''/, "'\'\\\\\'\''")
print "'\''" $0 "'\''"
}'))" #"
}
function: array_join "afficher le contenu du tableau \$1 sous forme d'une liste de valeurs séparées par \$2 (qui vaut ':' par défaut)
* 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"
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
recho "$__aj_j"
elif [ "$__aj_an" != "\$@" -a -n "$3" ]; then
recho "$3"
fi
}
function: array_joinc "afficher les éléments du tableau \$1 séparés par ','"
function array_joinc() {
array_join "$1" , "$2" "$3" "$4"
}
function: array_joinl "afficher les éléments du tableau \$1 à raison d'un élément par ligne"
function array_joinl() {
array_join "$1" "
" "$2" "$3" "$4"
}
function: 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)"
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_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)"
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
}

50
bash/src/base.bool.sh Normal file
View File

@ -0,0 +1,50 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.bool "Fonctions de base: valeurs booléennes"
function: is_yes 'retourner vrai si $1 est une valeur "oui"'
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 'retourner vrai si $1 est une valeur "non"'
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: normyesval 'remplacer les valeurs des variables $1..* par la valeur normalisée de leur valeur "oui"'
function normyesval() {
while [ $# -gt 0 ]; do
is_yes "${!1}" && _setv "$1" 1 || _setv "$1" ""
shift
done
}
function: 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'
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
}

458
bash/src/base.core.sh Normal file
View File

@ -0,0 +1,458 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.core "Fonctions de base: fondement"
function: echo_ "afficher la valeur \$* sans passer à la ligne"
function echo_() { echo -n "$*"; }
function: recho "afficher une valeur brute.
contrairement à la commande echo, ne reconnaitre aucune option (i.e. -e, -E, -n
ne sont pas signifiants)"
function recho() {
if [[ "${1:0:2}" == -[eEn] ]]; then
local first="${1:1}"; shift
echo -n -
echo "$first" "$@"
else
echo "$@"
fi
}
function: 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)"
function recho_() {
if [[ "${1:0:2}" == -[eEn] ]]; then
local first="${1:1}"; shift
echo -n -
echo -n "$first" "$@"
else
echo -n "$@"
fi
}
function: _qval "Dans la chaine \$*, remplacer:
~~~
\\ par \\\\
\" par \\\"
\$ par \\\$
\` par \\\`
~~~
Cela permet de quoter une chaine à mettre entre guillements.
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"
function _qval() {
local s="$*"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//\$/\\\$}"
s="${s//\`/\\\`}"
recho_ "$s"
}
function: should_quote "Tester si la chaine \$* doit être mise entre quotes"
function should_quote() {
# pour optimiser, toujours mettre entre quotes si plusieurs arguments sont
# spécifiés ou si on spécifie une chaine vide ou de plus de 80 caractères
[ $# -eq 0 -o $# -gt 1 -o ${#1} -eq 0 -o ${#1} -gt 80 ] && return 0
# sinon, tester si la chaine contient des caractères spéciaux
local s="$*"
s="${s//[a-zA-Z0-9]/}"
s="${s//,/}"
s="${s//./}"
s="${s//+/}"
s="${s//\//}"
s="${s//-/}"
s="${s//_/}"
s="${s//=/}"
[ -n "$s" ]
}
function: qval "Afficher la chaine \$* quotée avec \""
function qval() {
echo -n \"
_qval "$@"
echo \"
}
function: qvalm "Afficher la chaine \$* quotée si nécessaire avec \""
function qvalm() {
if should_quote "$@"; then
echo -n \"
_qval "$@"
echo \"
else
recho "$@"
fi
}
function: qvalr "Afficher la chaine \$* quotée si nécessaire avec \", sauf si elle est vide"
function qvalr() {
if [ -z "$*" ]; then
:
elif should_quote "$@"; then
echo -n \"
_qval "$@"
echo \"
else
recho "$@"
fi
}
function: qvals "Afficher chaque argument de cette fonction quotée le cas échéant avec \", chaque valeur étant séparée par un espace"
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 "Dans la chaine \$*, remplacer:
~~~
\\ par \\\\
\" par \\\"
\$ par \\\$
\` par \\\`
~~~
puis quoter la chaine avec \", sauf les wildcards *, ? et [class]
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"
function qwc() {
local s="$*"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//\$/\\\$}"
s="${s//\`/\\\`}"
local r a b c
while [ -n "$s" ]; do
a=; b=; c=
a=; [[ "$s" == *\** ]] && { a="${s%%\**}"; a=${#a}; }
b=; [[ "$s" == *\?* ]] && { b="${s%%\?*}"; b=${#b}; }
c=; [[ "$s" == *\[* ]] && { c="${s%%\[*}"; c=${#c}; }
if [ -z "$a" -a -z "$b" -a -z "$c" ]; then
r="$r\"$s\""
break
fi
if [ -n "$a" ]; then
[ -n "$b" ] && [ $a -lt $b ] && b=
[ -n "$c" ] && [ $a -lt $c ] && c=
fi
if [ -n "$b" ]; then
[ -n "$a" ] && [ $b -lt $a ] && a=
[ -n "$c" ] && [ $b -lt $c ] && c=
fi
if [ -n "$c" ]; then
[ -n "$a" ] && [ $c -lt $a ] && a=
[ -n "$b" ] && [ $c -lt $b ] && b=
fi
if [ -n "$a" ]; then # PREFIX*
a="${s%%\**}"
s="${s#*\*}"
[ -n "$a" ] && r="$r\"$a\""
r="$r*"
elif [ -n "$b" ]; then # PREFIX?
a="${s%%\?*}"
s="${s#*\?}"
[ -n "$a" ] && r="$r\"$a\""
r="$r?"
elif [ -n "$c" ]; then # PREFIX[class]
a="${s%%\[*}"
b="${s#*\[}"; b="${b%%\]*}"
s="${s:$((${#a} + ${#b} + 2))}"
[ -n "$a" ] && r="$r\"$a\""
r="$r[$b]"
fi
done
recho_ "$r"
}
function: qlines "Traiter chaque ligne de l'entrée standard pour en faire des chaines quotées avec '"
function qlines() {
sed "s/'/'\\\\''/g; s/.*/'&'/g"
}
function: 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"
function setv() {
local s__var="$1"; shift
if [[ "$s__var" == *=* ]]; then
set -- "${s__var#*=}" "$@"
s__var="${s__var%%=*}"
fi
eval "$s__var=\"\$*\""
}
function: _setv "Comme la fonction setv() mais ne supporte que la syntaxe '_setv var values...'
Cette fonction est légèrement plus rapide que setv()"
function _setv() {
local s__var="$1"; shift
eval "$s__var=\"\$*\""
}
function: echo_setv "Afficher la commande qui serait lancée par setv \"\$@\""
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: echo_setv2 "Afficher la commande qui recrée la variable \$1.
Equivalent à
~~~
echo_setv \"\$1=\${!1}\"
~~~
Si d'autres arguments que le nom de la variable sont spécifiés, cette fonction
se comporte comme echo_setv()"
function echo_setv2() {
local s__var="$1"; shift
if [[ "$s__var" == *=* ]]; then
set -- "${s__var#*=}" "$@"
s__var="${s__var%%=*}"
fi
if [ $# -eq 0 ]; then
echo_setv "$s__var" "${!s__var}"
else
echo_setv "$s__var" "$@"
fi
}
function: seta "initialiser le tableau \$1 avec les valeurs \$2..@
note: en principe, la syntaxe est 'seta array values...'. cependant, la syntaxe
'seta array=values...' est supportée aussi"
function seta() {
local s__array="$1"; shift
if [[ "$s__array" == *=* ]]; then
set -- "${s__array#*=}" "$@"
s__array="${s__array%%=*}"
fi
eval "$s__array=(\"\$@\")"
}
function: _seta "Comme la fonction seta() mais ne supporte que la syntaxe '_seta array values...'
Cette fonction est légèrement plus rapide que seta()"
function _seta() {
local s__array="$1"; shift
eval "$s__array=(\"\$@\")"
}
function: echo_seta "Afficher la commande qui serait lancée par seta \"\$@\""
function echo_seta() {
local s__var="$1"; shift
if [[ "$s__var" == *=* ]]; then
set -- "${s__var#*=}" "$@"
s__var="${s__var%%=*}"
fi
echo "$s__var=($(qvals "$@"))"
}
function: echo_seta2 "Afficher la commande qui recrée le tableau \$1
Si d'autres arguments que le nom de tableau sont spécifiés, cette fonction se
comporte comme echo_seta()"
function echo_seta2() {
local s__var="$1"; shift
if [[ "$s__var" == *=* ]]; then
set -- "${s__var#*=}" "$@"
s__var="${s__var%%=*}"
elif [ $# -eq 0 ]; then
eval "set -- \"\${$s__var[@]}\""
fi
echo "$s__var=($(qvals "$@"))"
}
function: setx "Initialiser une variable avec le résultat d'une commande
* syntaxe 1: initialiser la variable \$1 avec le résultat de la commande \"\$2..@\"
~~~
setx var cmd
~~~
note: en principe, la syntaxe est 'setx var cmd args...'. cependant, la syntaxe
'setx var=cmd args...' est supportée aussi
* syntaxe 2: initialiser le tableau \$1 avec le résultat de la commande
\"\$2..@\", chaque ligne du résultat étant un élément du tableau
~~~
setx -a array cmd
~~~
note: en principe, la syntaxe est 'setx -a array cmd args...'. cependant, la
syntaxe 'setx -a array=cmd args...' est supportée aussi"
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 "Comme la fonction setx() mais ne supporte que l'initialisation d'une variable scalaire avec la syntaxe '_setvx var cmd args...' pour gagner (un peu) en rapidité d'exécution."
function _setvx() {
local s__var="$1"; shift
eval "$s__var="'"$("$@")"'
}
function: _setax "Comme la fonction setx() mais ne supporte que l'initialisation d'un tableau avec la syntaxe '_setax array cmd args...' pour gagner (un peu) en rapidité d'exécution."
function _setax() {
local s__array="$1"; shift
eval "$s__array=($("$@" | qlines))"
}
function: is_defined "tester si la variable \$1 est définie"
function is_defined() {
[ -n "$(declare -p "$1" 2>/dev/null)" ]
}
function: is_array "tester si la variable \$1 est un tableau"
function is_array() {
[[ "$(declare -p "$1" 2>/dev/null)" =~ declare\ -[^\ ]*a[^\ ]*\ ]]
}
function: array_local "afficher les commandes pour faire une copie dans la variable locale \$1 du tableau \$2"
function array_local() {
if [ "$1" == "$2" ]; then
declare -p "$1" 2>/dev/null || echo "local -a $1"
else
echo "local -a $1; $1=(\"\${$2[@]}\")"
fi
}
function: upvar "Implémentation de upvar() de http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference
USAGE
~~~
local varname && upvar varname values...
~~~
* @param varname Variable name to assign value to
* @param values Value(s) to assign. If multiple values (> 1), an array is
assigned, otherwise a single value is assigned."
function upvar() {
if unset -v "$1"; then
if [ $# -lt 2 ]; then
eval "$1=\"\$2\""
else
eval "$1=(\"\${@:2}\")"
fi
fi
}
function: array_upvar "Comme upvar() mais force la création d'un tableau, même s'il y a que 0 ou 1 argument"
function array_upvar() {
unset -v "$1" && eval "$1=(\"\${@:2}\")"
}
function: upvars "Implémentation modifiée de upvars() de http://www.fvue.nl/wiki/Bash:_Passing_variables_by_reference
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 varnames... && upvars [varname value | -aN varname values...]...
~~~
* @param -a assigns remaining values to varname as array
* @param -aN assigns next N values to varname as array. Returns 1 if wrong
number of options occurs"
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: set_debug "Passer en mode DEBUG"
function set_debug() {
export NULIB_DEBUG=1
}
function: is_debug "Tester si on est en mode DEBUG"
function is_debug() {
[ -n "$NULIB_DEBUG" ]
}
function: lawk "Lancer GNUawk avec la librairie 'base'"
function lawk() {
gawk -i base.awk "$@"
}
function: cawk "Lancer GNUawk avec LANG=C et la librairie 'base'
Le fait de forcer la valeur de LANG permet d'éviter les problèmes avec la locale"
function cawk() {
LANG=C gawk -i base.awk "$@"
}
function: lsort "Lancer sort avec support de la locale courante"
function: csort "Lancer sort avec LANG=C pour désactiver le support de la locale
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."
function lsort() { sort "$@"; }
function csort() { LANG=C sort "$@"; }
function: lgrep "Lancer grep avec support de la locale courante"
function: cgrep "Lancer grep avec LANG=C pour désactiver le support de la locale"
function lgrep() { grep "$@"; }
function cgrep() { LANG=C grep "$@"; }
function: lsed "Lancer sed avec support de la locale courante"
function: csed "Lancer sed avec LANG=C pour désactiver le support de la locale"
function lsed() { sed "$@"; }
function csed() { LANG=C sed "$@"; }
function: ldiff "Lancer diff avec support de la locale courante"
function: cdiff "Lancer diff avec LANG=C pour désactiver le support de la locale"
function ldiff() { diff "$@"; }
function cdiff() { LANG=C diff "$@"; }

53
bash/src/base.init.sh Normal file
View File

@ -0,0 +1,53 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.init "Fonctions de base: initialiser l'environnement"
if [ -z "$NULIB_NO_INIT_ENV" ]; then
# Emplacement du script courant
if [ "$0" == "-bash" ]; then
MYNAME=
MYDIR=
MYSELF=
elif [ ! -f "$0" -a -f "${0#-}" ]; then
MYNAME="$(basename -- "${0#-}")"
MYDIR="$(dirname -- "${0#-}")"
MYDIR="$(cd "$MYDIR"; pwd)"
MYSELF="$MYDIR/$MYNAME"
else
MYNAME="$(basename -- "$0")"
MYDIR="$(dirname -- "$0")"
MYDIR="$(cd "$MYDIR"; pwd)"
MYSELF="$MYDIR/$MYNAME"
fi
[ -n "$NULIBDIR" ] || NULIBDIR="$MYDIR"
# Repertoire temporaire
[ -z "$TMPDIR" -a -d "$HOME/tmp" ] && TMPDIR="$HOME/tmp"
[ -z "$TMPDIR" ] && TMPDIR="${TMP:-${TEMP:-/tmp}}"
export TMPDIR
# User
[ -z "$USER" -a -n "$LOGNAME" ] && export USER="$LOGNAME"
# Le fichier nulibrc doit être chargé systématiquement
[ -f /etc/debian_chroot ] && NULIB_CHROOT=1
[ -f /etc/nulibrc ] && . /etc/nulibrc
[ -f ~/.nulibrc ] && . ~/.nulibrc
# Type de système sur lequel tourne le script
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 "$NULIB_CHROOT" ]; then
# Dans un chroot, il est possible de forcer les valeurs
[ -n "$NULIB_UNAME_SYSTEM" ] && eval "UNAME_SYSTEM=$NULIB_UNAME_SYSTEM"
[ -n "$NULIB_UNAME_MACHINE" ] && eval "UNAME_MACHINE=$NULIB_UNAME_MACHINE"
fi
# Nom d'hôte respectivement avec et sans domaine
# contrairement à $HOSTNAME, cette valeur peut être spécifiée, comme par ruinst
[ -n "$MYHOST" ] || MYHOST="$HOSTNAME"
[ -n "$MYHOSTNAME" ] || MYHOSTNAME="${HOSTNAME%%.*}"
export MYHOST MYHOSTNAME
fi

581
bash/src/base.input.sh Normal file
View File

@ -0,0 +1,581 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.input "Fonctions de base: saisie"
function toienc() {
# $1 étant une variable contenant une chaine encodée dans l'encoding d'entrée $2
# (qui vaut par défaut $NULIB_INPUT_ENCODING), transformer cette chaine en
# utf-8
local __var="$1" __from="${2:-$NULIB_INPUT_ENCODING}"
if [ "$__from" != "NULIB__UTF8" ]; then
_setv "$__var" "$(iconv -f "$__from" -t utf-8 <<<"${!__var}")"
fi
}
function 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 $NULIB_INPUT_ENCODING
[ $# -gt 0 ] || set -- REPLY
local __var
read "$@"
for __var in "$@"; do
[ -z "$__var" -o "${__var:0:1}" == "-" ] && continue # ignorer les options
toienc "$__var"
done
}
function set_interaction() { :;}
function is_interaction() { return 1; }
function check_interaction() { return 0; }
function get_interaction_option() { :;}
function 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
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
local message="$1"
local prompt="[oN]"
local r
is_yes "$default" && prompt="[On]"
if [ -n "$message" ]; then
__eecho_ "$message" 1>&2
else
__eecho_ "Voulez-vous continuer?" 1>&2
fi
tooenc_ " $prompt " 1>&2
uread r
is_yes "${r:-$default}"
else
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
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
[ -z "${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
f="${format:$i:1}"
[ -z "${f/[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
# 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.
local -a __rv_opts; local __rv_readline=1 __rv_showdef=1 __rv_nl=
__rv_opts=()
[ -n "$NULIB_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
# En mode non interactif, retourner la valeur par défaut
if is_yes "$__rv_re" && [ -z "$__rv_d" ]; then
eerror "La valeur par défaut de $__rv_v doit être non vide"
return 1
fi
_setv "$__rv_v" "$__rv_d"
return 0
fi
local __rv_r
while true; do
if [ -n "$__rv_msg" ]; then
__eecho_ "$__rv_msg" 1>&2
else
__eecho_ "Entrez la valeur" 1>&2
fi
if [ -n "$__rv_readline" ]; then
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
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
_setv "$__rv_v" "$__rv_r"
return 0
fi
done
}
function 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)
eval "$(local_args)"
local __sm_title= __sm_yourchoice= __sm_default=
args=(
-t:,--title __sm_title=
-m:,--prompt __sm_yourchoice=
-d:,--default __sm_default=
)
parse_args "$@"; set -- "${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
eerror "Le tableau $__sm_options_var doit être non vide"
return 1
fi
[ -z "$__sm_default" ] && __sm_default="${!__sm_option_var}"
array_copy __sm_options "$__sm_options_var"
local __sm_c=0 __sm_i __sm_choice
while true; do
if [ "$__sm_c" == "0" ]; then
# Afficher le menu
[ -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
# Afficher les choix
if [ -n "$__sm_yourchoice" ]; then
__eecho_ "$__sm_yourchoice" 1>&2
else
__eecho_ "Entrez le numéro de l'option choisie" 1>&2
fi
tooenc_ ": " 1>&2
uread __sm_choice
# Valeur par défaut
if [ -z "$__sm_choice" -a -n "$__sm_default" ]; then
__sm_option="$__sm_default"
break
fi
# Vérifier la saisie
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
eerror "Numéro d'option incorrect"
fi
else
eerror "Vous devez saisir le numéro de l'option choisie"
fi
let __sm_c=$__sm_c+1
if [ "$__sm_c" -eq 5 ]; then
# sauter une ligne toutes les 4 tentatives
tooenc "" 1>&2
__sm_c=0
fi
done
_setv "$__sm_option_var" "$__sm_option"
}
function 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)
eval "$(local_args)"
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=
args=(
-t:,--title __am_title=
-m:,--prompt __am_optyc=
-M:,--no-prompt __am_actyc=
-e: __am_void_actions
-d: __am_default_action=
-q: __am_quit_action=
-o: __am_default_option=
)
parse_args "$@"; set -- "${args[@]}"
__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
# Calculer la liste des actions valides
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
# Calculer l'action par défaut
if [ "$default_action" == auto ]; then
# si action par défaut non spécifiée, alors prendre la première action
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")"
# Calculer l'action quitter par défaut
if [ "$quit_action" == auto ]; then
# si action par défaut non spécifiée, alors prendre la dernière action,
# s'il y a au moins 2 actions
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")"
# Calculer la ligne des actions à afficher
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
# si action par défaut invalide, alors pas d'action par défaut
array_contains actions "$default_action" || default_action=
fi
if [ -n "$quit_action" ]; then
# si action quitter invalide, alors pas d'action quitter
array_contains actions "$quit_action" || quit_action=
fi
# Type de menu
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() {
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
# vérifier la saisie
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
# sauter une ligne toutes les 4 tentatives
tooenc "" 1>&2
c=0
fi
done
__am_select_action="$select_action"
__am_select_option=
}
function __options_actions_menu() {
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
# vérifier la saisie
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
# on commence par un code d'action valide. cool :-)
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
# on a simplement donné un numéro d'option
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
# sauter une ligne toutes les 4 tentatives
tooenc "" 1>&2
c=0
fi
done
__am_select_action="$select_action"
__am_select_option="$select_option"
}

30
bash/src/base.num.sh Normal file
View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.num "Fonctions de base: gestion des valeurs numériques"
function: isnum 'retourner vrai si $1 est une valeur numérique entière (positive ou négative)'
function isnum() {
[ ${#1} -gt 0 ] || return 1
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'
function ispnum() {
[ ${#1} -gt 0 ] || return 1
[ -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 ,'
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" ]
}

601
bash/src/base.output.sh Normal file
View File

@ -0,0 +1,601 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.output "Fonctions de base: affichage"
nulib__load: _output_vanilla
NULIB__TAB=$'\t'
NULIB__LATIN1=iso-8859-1
NULIB__LATIN9=iso-8859-15
NULIB__UTF8=utf-8
[ -n "$LANG" ] || export LANG=fr_FR.UTF-8
if [ ! -x "$(which iconv 2>/dev/null)" ]; then
function iconv() { cat; }
fi
function nulib__lang_encoding() {
case "${LANG,,}" in
*@euro) echo "iso-8859-15";;
*.utf-8|*.utf8) echo "utf-8";;
*) echo "iso-8859-1";;
esac
}
function nulib__norm_encoding() {
local enc="${1,,}"
enc="${enc//[-_]/}"
case "$enc" in
latin|latin1|iso8859|iso88591|8859|88591) echo "iso-8859-1";;
latin9|iso885915|885915) echo "iso-8859-15";;
utf|utf8) echo "utf-8";;
*) echo "$1";;
esac
}
function nulib__init_encoding() {
local default_encoding="$(nulib__lang_encoding)"
[ -n "$default_encoding" ] || default_encoding=utf-8
[ -n "$NULIB_OUTPUT_ENCODING" ] || NULIB_OUTPUT_ENCODING="$default_encoding"
NULIB_OUTPUT_ENCODING="$(nulib__norm_encoding "$NULIB_OUTPUT_ENCODING")"
[ -n "$NULIB_INPUT_ENCODING" ] || NULIB_INPUT_ENCODING="$NULIB_OUTPUT_ENCODING"
NULIB_INPUT_ENCODING="$(nulib__norm_encoding "$NULIB_INPUT_ENCODING")"
}
[ -n "$NULIB_LANG" -a -z "$LANG" ] && export NULIB_LANG LANG="$NULIB_LANG"
nulib__init_encoding
function noerror() {
# lancer la commande "$@" et masquer son code de retour
[ $# -gt 0 ] || set :
"$@" || return 0
}
function noout() {
# lancer la commande "$@" en supprimant sa sortie standard
[ $# -gt 0 ] || return 0
"$@" >/dev/null
}
function noerr() {
# lancer la commande "$@" en supprimant sa sortie d'erreur
[ $# -gt 0 ] || return 0
"$@" 2>/dev/null
}
function isatty() {
# tester si STDOUT n'est pas une redirection
tty -s <&1
}
function in_isatty() {
# tester si STDIN n'est pas une redirection
tty -s
}
function out_isatty() {
# tester si STDOUT n'est pas une redirection. identique à isatty()
tty -s <&1
}
function err_isatty() {
# tester si STDERR n'est pas une redirection
tty -s <&2
}
################################################################################
function tooenc() {
# $1 étant une chaine encodée en utf-8, l'afficher dans l'encoding de sortie $2
# qui vaut par défaut $NULIB_OUTPUT_ENCODING
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
if [ "$to" == "$NULIB__UTF8" ]; then
recho "$value"
else
iconv -f -utf-8 -t "$to" <<<"$value"
fi
}
function uecho() { tooenc "$*"; }
function tooenc_() {
# $1 étant une chaine encodée en utf-8, l'afficher sans passer à la ligne dans
# l'encoding de sortie $2 qui vaut par défaut $NULIB_OUTPUT_ENCODING
local value="$1" to="${2:-$NULIB_OUTPUT_ENCODING}"
if [ "$to" == "$NULIB__UTF8" ]; then
recho_ "$value"
else
recho_ "$value" | iconv -f utf-8 -t "$to"
fi
}
function uecho_() { tooenc_ "$*"; }
export NULIB_QUIETLOG
export NULIB__TMPLOG
function: quietc "\
N'afficher la sortie de la commande \$@ que si on est en mode DEBUG ou si elle se termine en erreur"
function quietc() {
local r
[ -z "$NULIB__TMPLOG" ] && ac_set_tmpfile NULIB__TMPLOG
"$@" >&"$NULIB__TMPLOG"; r=$?
[ -n "$NULIB_QUIETLOG" ] && cat "$NULIB__TMPLOG" >>"$NULIB_QUIETLOG"
[ $r -ne 0 -o -n "$NULIB_DEBUG" ] && cat "$NULIB__TMPLOG"
return $r
}
function: quietc_logto "\
Si quietc est utilisé, sauvegarder quand même la sortie dans le fichier \$1
Utiliser l'option -a pour ajouter au fichier au lieu de l'écraser e.g
quietc_logto -a path/to/logfile
Tous les autres arguments sont du contenu ajoutés au fichier, e.g
quietc_logto -a path/to/logfile \"\\
================================================================================
= \$(date +%F-%T)\""
function quietc_logto() {
local append
if [ "$1" == -a ]; then
shift
append=1
fi
NULIB_QUIETLOG="$1"; shift
[ -n "$NULIB_QUIETLOG" ] || return
if [ -z "$append" ]; then
>"$NULIB_QUIETLOG"
fi
if [ $# -gt 0 ]; then
echo "$*" >>"$NULIB_QUIETLOG"
fi
}
function: quietc_echo "Ajouter \$* dans le fichier mentionné par quietc_logto() le cas échéant"
function quietc_echo() {
if [ -n "$NULIB_QUIETLOG" ]; then
echo "$*" >>"$NULIB_QUIETLOG"
fi
}
# faut-il dater les messages des fonctions e* et action?
# Faire NULIB_ELOG_DATE=1 en début de script pour activer cette fonctionnalité
# faut-il rajouter aussi le nom du script? (nécessite NULIB_ELOG_DATE)
# Faire NULIB_ELOG_MYNAME=1 en début de script pour activer cette fonctionnalité
export NULIB_ELOG_DATE NULIB_ELOG_MYNAME
function __edate() {
[ -n "$NULIB_ELOG_DATE" ] || return
local prefix="$(date +"[%F %T.%N] ")"
[ -n "$NULIB_ELOG_MYNAME" ] && prefix="$prefix$MYNAME "
echo "$prefix"
}
export NULIB_ELOG_OVERWRITE
function __set_no_colors() { :; }
function elogto() {
# Activer NULIB_ELOG_DATE 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 NULIB_ELOG_OVERWRITE=1, alors le fichier en sortie est écrasé. Sinon, les
# lignes en sortie lui sont ajoutées
NULIB_ELOG_DATE=1
NULIB_ELOG_MYNAME=1
if [ -n "$1" -a -n "$2" ]; then
LANG=fr_FR.UTF8
NULIB_OUTPUT_ENCODING="$NULIB__UTF8"
__set_no_colors 1
if [ -n "$NULIB_ELOG_OVERWRITE" ]; then
exec >"$1" 2>"$2"
else
exec >>"$1" 2>>"$2"
fi
elif [ -n "$1" ]; then
LANG=fr_FR.UTF8
NULIB_OUTPUT_ENCODING="$NULIB__UTF8"
__set_no_colors 1
if [ -n "$NULIB_ELOG_OVERWRITE" ]; then
exec >"$1" 2>&1
else
exec >>"$1" 2>&1
fi
fi
}
# variables utilisées pour l'affichage indenté des messages et des titres
# NULIB__ESTACK est la liste des invocations de 'etitle' et 'action' en cours
export NULIB__ESTACK NULIB__INDENT=
function __eindent0() {
# afficher le nombre d'espaces correspondant à l'indentation
local indent="${NULIB__ESTACK//?/ }"
indent="${indent% }$NULIB__INDENT"
[ -n "$indent" ] && echo "$indent"
}
function __eindent() {
# indenter les lignes de $1, sauf la première
local -a lines; local line first=1
local indent="$(__eindent0)$2"
setx -a lines=echo "$1"
for line in "${lines[@]}"; do
if [ -n "$first" ]; then
recho "$line"
first=
else
recho "$indent$line"
fi
done
}
function __complete() {
# compléter $1 avec $3 jusqu'à obtenir une taille de $2 caractères
local columns="${COLUMNS:-80}"
local line="$1" maxi="${2:-$columns}" sep="${3:- }"
while [ ${#line} -lt $maxi ]; do
line="$line$sep"
done
echo "$line"
}
PRETTYOPTS=()
function set_verbosity() { :;}
function check_verbosity() { return 0; }
function get_verbosity_option() { :;}
# tester respectivement si on doit afficher les messages d'erreur,
# d'avertissement, d'information, de debug
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" ]; }
# note: toutes les fonctions d'affichage e* écrivent sur stderr
function esection() {
# Afficher une section. Toutes les indentations sont remises à zéro
show_info || return
eval "$NULIB__DISABLE_SET_X"
NULIB__ESTACK=
__esection "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function etitle() {
# Afficher le titre $1. Le contenu sous des titres imbriqués est affiché
# indenté.
# - si $2..$* est spécifié, c'est une commande qui est lancée dans le contexte
# du titre, ensuite le titre est automatiquement terminé
# - sinon il faut terminer le titre explicitement avec eend
local title="$1"; shift
# etitle
NULIB__ESTACK="$NULIB__ESTACK:t"
if show_info; then
eval "$NULIB__DISABLE_SET_X"
__etitle "$title" 1>&2
eval "$NULIB__ENABLE_SET_X"
fi
# commande
local r=0
if [ $# -gt 0 ]; then
"$@"; r=$?
eend
fi
return $r
}
function eend() {
# Terminer un titre
if [ "${NULIB__ESTACK%:t}" != "$NULIB__ESTACK" ]; then
NULIB__ESTACK="${NULIB__ESTACK%:t}"
fi
}
function edesc() {
# Afficher une description sous le titre courant
show_info || return
eval "$NULIB__DISABLE_SET_X"
__edesc "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function ebanner() {
# Afficher un message très important encadré, puis attendre 5 secondes
show_error || return
eval "$NULIB__DISABLE_SET_X"
__ebanner "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
sleep 5
}
function eimportant() {
# Afficher un message très important
show_error || return
eval "$NULIB__DISABLE_SET_X"
__eimportant "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qimportant() { eimportant "$*"; quietc_echo "IMPORTANT: $*"; }
function eattention() {
# Afficher un message important
show_warn || return
eval "$NULIB__DISABLE_SET_X"
__eattention "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qattention() { eattention "$*"; quietc_echo "ATTENTION: $*"; }
function eerror() {
# Afficher un message d'erreur
show_error || return
eval "$NULIB__DISABLE_SET_X"
__eerror "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qerror() { eerror "$*"; quietc_echo "ERROR: $*"; }
function eerror_unless() {
# Afficher $1 avec eerror() si la commande $2..@ retourne FAUX. dans tous les cas, retourner le code de retour de la commande.
local msg="$1"; shift
local r=1
if [ $# -eq 0 ]; then
[ -n "$msg" ] && eerror "$msg"
else
"$@"; r=$?
if [ $r -ne 0 -a -n "$msg" ]; then
eerror "$msg"
fi
fi
return $r
}
function eerror_if() {
# Afficher $1 avec eerror() si la commande $2..@ retourne VRAI. dans tous les cas, retourner le code de retour de la commande.
local msg="$1"; shift
local r=0
if [ $# -gt 0 ]; then
"$@"; r=$?
if [ $r -eq 0 -a -n "$msg" ]; then
eerror "$msg"
fi
fi
return $r
}
function set_die_return() {
NULIB__DIE="return 1"
}
function die() {
[ $# -gt 0 ] && eerror "$@"
local die="${NULIB__DIE:-exit 1}"
eval "$die" || return
}
function die_with {
[ $# -gt 0 ] && eerror "$1"
shift
[ $# -gt 0 ] && "$@"
local die="${NULIB__DIE:-exit 1}"
eval "$die" || return
}
function die_unless() {
# Afficher $1 et quitter le script avec die() si la commande $2..@ retourne FAUX
local msg="$1"; shift
local die="${NULIB__DIE:-exit 1}"
local r=1
if [ $# -eq 0 ]; then
[ -n "$msg" ] && eerror "$msg"
else
"$@"; r=$?
if [ $r -ne 0 -a -n "$msg" ]; then
eerror "$msg"
fi
fi
if [ $r -ne 0 ]; then
eval "${die% *} $r" || return $r
fi
return $r
}
function die_if() {
# Afficher $1 et quitter le script avec die() si la commande $2..@ retourne VRAI. sinon, retourner le code de retour de la commande
local msg="$1"; shift
local die="${NULIB__DIE:-exit 1}"
local r=0
if [ $# -gt 0 ]; then
"$@"; r=$?
if [ $r -eq 0 -a -n "$msg" ]; then
eerror "$msg"
fi
fi
if [ $r -eq 0 ]; then
eval "${die% *} $r" || return $r
fi
return $r
}
function exit_with {
if [ $# -gt 0 ]; then "$@"; fi
exit $?
}
function ewarn() {
# Afficher un message d'avertissement
show_warn || return
eval "$NULIB__DISABLE_SET_X"
__ewarn "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qwarn() { ewarn "$*"; quietc_echo "WARN: $*"; }
function enote() {
# Afficher un message d'information de même niveau qu'un avertissement
show_info || return
eval "$NULIB__DISABLE_SET_X"
__enote "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qnote() { enote "$*"; quietc_echo "NOTE: $*"; }
function einfo() {
# Afficher un message d'information
show_info || return
eval "$NULIB__DISABLE_SET_X"
__einfo "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qinfo() { einfo "$*"; quietc_echo "INFO: $*"; }
function eecho() {
# Afficher un message d'information sans préfixe
show_info || return
eval "$NULIB__DISABLE_SET_X"
__eecho "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function qecho() { eecho "$*"; quietc_echo "$*"; }
function eecho_() {
show_info || return
eval "$NULIB__DISABLE_SET_X"
__eecho_ "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
function trace() {
# Afficher la commande $1..@, la lancer, puis afficher son code d'erreur si une
# erreur se produit
local r cmd="$(qvals "$@")"
show_info && { __eecho "\$ $cmd" 1>&2; }
"$@"; r=$?
if [ $r -ne 0 ]; then
if show_info; then
__eecho "^ [EC #$r]" 1>&2
elif show_error; then
__eecho "^ $cmd [EC #$r]" 1>&2
fi
fi
return $r
}
function 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.
local r
"$@"; r=$?
if [ $r -ne 0 ]; then
local cmd="$(qvals "$@")"
if show_error; then
__eecho "^ $cmd [EC #$r]" 1>&2
fi
fi
return $r
}
function edebug() {
# Afficher un message de debug
show_debug || return
eval "$NULIB__DISABLE_SET_X"
__edebug "$*" 1>&2
eval "$NULIB__ENABLE_SET_X"
}
# 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.
function estep() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estep "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepe() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepe "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepw() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepw "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepn() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepn "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepi() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepi "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estep_() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estep_ "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepe_() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepe_ "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepw_() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepw_ "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepn_() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepn_ "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function estepi_() { show_info || return; eval "$NULIB__DISABLE_SET_X"; __estepi_ "$*" 1>&2; eval "$NULIB__ENABLE_SET_X"; }
function qstep() { estep "$*"; quietc_echo "* $*"; }
function action() {
# commencer l'action $1
# - si $2..$* est spécifié, c'est une commande qui est lancée dans le contexte
# de l'action, ensuite l'action est terminée en succès ou en échec suivant le
# code de retour. ne pas afficher la sortie de la commande comme avec quietc()
# - sinon il faut terminer le titre explicitement avec eend
eval "$NULIB__DISABLE_SET_X"
local action="$1"; shift
local r=0
if [ $# -gt 0 ]; then
[ -z "$NULIB__TMPLOG" ] && ac_set_tmpfile NULIB__TMPLOG
[ -n "$action" ] && quietc_echo "$(__edate) ACTION: $action:"
"$@" >&"$NULIB__TMPLOG"; r=$?
[ -n "$NULIB_QUIETLOG" ] && cat "$NULIB__TMPLOG" >>"$NULIB_QUIETLOG"
if [ $r -ne 0 -o -n "$NULIB_DEBUG" ]; then
NULIB__ESTACK="$NULIB__ESTACK:a"
[ -n "$action" ] && action="$action:"
__action "$action" 1>&2
cat "$NULIB__TMPLOG"
aresult $r
else
if [ $r -eq 0 ]; then
[ -n "$action" ] || action="succès"
__asuccess "$action" 1>&2
else
[ -n "$action" ] || action="échec"
__afailure "$action" 1>&2
fi
fi
else
NULIB__ESTACK="$NULIB__ESTACK:a"
[ -n "$action" ] && action="$action:"
__action "$action" 1>&2
fi
eval "$NULIB__ENABLE_SET_X"
return $r
}
function asuccess() {
# terminer l'action en cours avec le message de succès $*
[ "${NULIB__ESTACK%:a}" != "$NULIB__ESTACK" ] || return
eval "$NULIB__DISABLE_SET_X"
[ -n "$*" ] || set -- "succès"
NULIB__INDENT=" " __asuccess "$*" 1>&2
NULIB__ESTACK="${NULIB__ESTACK%:a}"
eval "$NULIB__ENABLE_SET_X"
return 0
}
function afailure() {
# terminer l'action en cours avec le message d'échec $*
[ "${NULIB__ESTACK%:a}" != "$NULIB__ESTACK" ] || return
eval "$NULIB__DISABLE_SET_X"
[ -n "$*" ] || set -- "échec"
NULIB__INDENT=" " __afailure "$*" 1>&2
NULIB__ESTACK="${NULIB__ESTACK%:a}"
eval "$NULIB__ENABLE_SET_X"
return 1
}
function aresult() {
# terminer l'action en cours avec un message de succès ou d'échec $2..* en
# fonction du code de retour $1 (0=succès, sinon échec)
[ "${NULIB__ESTACK%:a}" != "$NULIB__ESTACK" ] || return
eval "$NULIB__DISABLE_SET_X"
local r="${1:-0}"; shift
if [ "$r" == 0 ]; then
[ -n "$*" ] || set -- "succès"
NULIB__INDENT=" " __asuccess "$*" 1>&2
else
[ -n "$*" ] || set -- "échec"
NULIB__INDENT=" " __afailure "$*" 1>&2
fi
NULIB__ESTACK="${NULIB__ESTACK%:a}"
eval "$NULIB__ENABLE_SET_X"
return $r
}
function adone() {
# terminer l'action en cours avec le message neutre $*
[ "${NULIB__ESTACK%:a}" != "$NULIB__ESTACK" ] || return
eval "$NULIB__DISABLE_SET_X"
[ -n "$*" ] && NULIB__INDENT=" " __adone "$*" 1>&2
NULIB__ESTACK="${NULIB__ESTACK%:a}"
eval "$NULIB__ENABLE_SET_X"
return 0
}

304
bash/src/base.path.sh Normal file
View File

@ -0,0 +1,304 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.path "Fonctions de base: gestion des chemins et des fichiers"
function: in_path "tester l'existence d'un programme dans le PATH"
function in_path() {
[ -n "$1" -a -x "$(which "$1" 2>/dev/null)" ]
}
function: delpath "supprimer le chemin \$1 de \$2(=PATH)"
function delpath() {
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: addpath "Ajouter le chemin \$1 à la fin, dans \$2(=PATH), s'il n'y existe pas déjà"
function addpath() {
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: inspathm "Ajouter le chemin \$1 au début, dans \$2(=PATH), s'il n'y existe pas déjà"
function inspathm() {
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: inspath "S'assurer que le chemin \$1 est au début de \$2(=PATH)"
function inspath() {
delpath "$@"
inspathm "$@"
}
function: push_cwd "enregistrer le répertoire courant dans la variable \$2(=cwd) et se placer dans le répertoire \$1"
function push_cwd() {
eval "${2:-cwd}"'="$(pwd)"'
cd "$1"
}
function: pop_cwd "se placer dans le répertoire \${!\$2}(=\$cwd) puis retourner le code d'erreur \$1(=0)"
function pop_cwd() {
eval 'cd "$'"${2:-cwd}"'"'
return "${1:-0}"
}
################################################################################
## fichiers temporaires
function: mktempf "générer un fichier temporaire et retourner son nom"
function mktempf() {
mktemp "${1:-"$TMPDIR/tmp.XXXXXX"}"
}
function: mktempd "générer un répertoire temporaire et retourner son nom"
function mktempd() {
mktemp -d "${1:-"$TMPDIR/tmp.XXXXXX"}"
}
function ac__forgetall() { NULIB__AC_FILES=(); }
ac__forgetall
function ac__trap() {
local file
for file in "${NULIB__AC_FILES[@]}"; do
[ -e "$file" ] && rm -rf "$file" 2>/dev/null
done
ac__forgetall
}
trap ac__trap 1 3 15 EXIT
function: autoclean "\
Ajouter les fichiers spécifiés à la liste des fichiers à supprimer à la fin du
programme"
function autoclean() {
local file
for file in "$@"; do
[ -n "$file" ] && NULIB__AC_FILES=("${NULIB__AC_FILES[@]}" "$file")
done
}
function: ac_cleanall "\
Supprimer *tous* les fichiers temporaires gérés par autoclean tout de suite."
function ac_cleanall() {
ac__trap
}
function: 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()"
function ac_clean() {
local file acfile found
local -a acfiles
for acfile in "${NULIB__AC_FILES[@]}"; do
found=
for file in "$@"; do
if [ "$file" == "$acfile" ]; then
found=1
[ -e "$file" ] && rm -rf "$file" 2>/dev/null
break
fi
done
[ -z "$found" ] && acfiles=("${acfiles[@]}" "$acfile")
done
NULIB__AC_FILES=("${acfiles[@]}")
}
function: 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."
function ac_set_tmpfile() {
local se__d
if is_debug; then
if [ -n "$5" ]; then
is_yes "${!5}" && se__d=1
else
se__d=1
fi
fi
if [ -n "$se__d" -a -n "$3" ]; then
_setv "$1" "$3"
[ -f "$3" -a "$4" == keep ] || >"$3"
else
local se__t="$(mktempf "$2")"
autoclean "$se__t"
_setv "$1" "$se__t"
fi
}
function: 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"
function ac_set_tmpdir() {
local sr__d
if is_debug; then
if [ -n "$4" ]; then
is_yes "${!4}" && sr__d=1
else
sr__d=1
fi
fi
if [ -n "$sr__d" -a -n "$3" ]; then
_setv "$1" "$3"
mkdir -p "$3"
else
local sr__t="$(mktempd "$2")"
autoclean "$sr__t"
_setv "$1" "$sr__t"
fi
}
################################################################################
## manipulation de chemins
#XXX repris tel quel depuis nutools, à migrer
function normpath() {
# normaliser le chemin $1, qui est soit absolu, soit relatif à $2 (qui vaut
# $(pwd) par défaut)
local -a parts
local part ap
IFS=/ read -a 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
echo "$ap"
}
function __normpath() {
# normaliser dans les cas simple le chemin absolu $1. sinon retourner 1.
# cette fonction est utilisée par abspath()
if [ -d "$1" ]; then
if [ -x "$1" ]; then
# le cas le plus simple: un répertoire dans lequel on peut entrer
(cd "$1"; pwd)
return 0
fi
elif [ -f "$1" ]; then
local dn="$(dirname -- "$1")" bn="$(basename -- "$1")"
if [ -x "$dn" ]; then
# autre cas simple: un fichier situé dans un répertoire dans lequel
# on peut entrer
(cd "$dn"; echo "$(pwd)/$bn")
return 0
fi
fi
return 1
}
function 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.
local ap="$1"
if [ "${ap#/}" != "$ap" ]; then
# chemin absolu. on peut ignorer $2
__normpath "$ap" && return
else
# chemin relatif. il faut exprimer le chemin par rapport à $2
local cwd
if [ -n "$2" ]; then
cwd="$(abspath "$2")"
else
cwd="$(pwd)"
fi
ap="$cwd/$ap"
__normpath "$ap" && return
fi
# dans les cas spéciaux, il faut calculer "manuellement" le répertoire absolu
normpath "$ap"
}
function 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
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}"
echo "$path"
}
function ppath2() {
# Comme ppath() mais afficher '.' comme '. ($dirname)' pour la joliesse
local path="$1" cwd="$2"
path="$(abspath "$path")" # essayer de normaliser le chemin
[ -n "$cwd" ] || cwd="$(pwd)"
if [ "$path" == "$cwd" ]; then
path=". ($(basename -- "$path"))"
else
[ "$cwd" != "/" -a "$cwd" != "$HOME" ] && path="${path#$cwd/}"
[ "${path#$HOME/}" != "$path" ] && path="~${path#$HOME}"
fi
echo "$path"
}
function 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
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
echo "${p#$cwd/}"
else
local rp
while [ -n "$cwd" -a "${p#$cwd/}" == "$p" ]; do
rp="${rp:+$rp/}.."
cwd="${cwd%/*}"
done
rp="$rp/${p#$cwd/}"
# ${rp%//} traite le cas $1==/
echo "${rp%//}"
fi
}
function 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
local p="$(relpath "$@")"
if [ -z "$p" ]; then
echo .
elif [ "${p#../}" != "$p" -o "${p#./}" != "$p" ]; then
echo "$p"
else
echo "./$p"
fi
}

22
bash/src/base.sh Normal file
View File

@ -0,0 +1,22 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
# shim pour les fonctions de nulib.sh au cas où ce module n'est pas chargée
if [ -z "$NULIBDIR" -o "$NULIBDIR" != "$NULIBINIT" ]; then
function module:() { :; }
function function:() { :; }
function require:() { :; }
fi
##@include base.init.sh
##@include base.core.sh
##@include base.str.sh
##@include base.num.sh
##@include base.bool.sh
##@include base.array.sh
##@include base.split.sh
##@include base.path.sh
##@include base.args.sh
##@include base.tools.sh
##@include base.input.sh
##@include base.output.sh
module: base "Chargement de tous les modules base.*"
require: base.init base.core base.str base.num base.bool base.array base.split base.path base.args base.tools base.input base.output

188
bash/src/base.split.sh Normal file
View File

@ -0,0 +1,188 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.split "Fonctions de base: analyse et découpage de valeurs"
function: 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."
function splitfsep() {
if [[ "$1" == *"$2"* ]]; then
setv "${3:-first}" "${1%%$2*}"
setv "${4:-second}" "${1#*$2}"
else
setv "${3:-first}" "$1"
setv "${4:-second}"
fi
}
function: 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."
function splitfsep2() {
if [[ "$1" == *"$2"* ]]; then
setv "${3:-first}" "${1%%$2*}"
setv "${4:-second}" "${1#*$2}"
else
setv "${3:-first}"
setv "${4:-second}" "$1"
fi
}
function: 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."
function splitlsep() {
if [[ "$1" == *"$2"* ]]; then
setv "${3:-first}" "${1%$2*}"
setv "${4:-second}" "${1##*$2}"
else
setv "${3:-first}" "$1"
setv "${4:-second}"
fi
}
function: 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."
function splitlsep2() {
if [[ "$1" == *"$2"* ]]; then
setv "${3:-first}" "${1%$2*}"
setv "${4:-second}" "${1##*$2}"
else
setv "${3:-first}"
setv "${4:-second}" "$1"
fi
}
function: 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)"
function splitvar() {
splitfsep "$1" = "${2:-name}" "${3:-value}"
}
function: 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)"
function splitpath() {
splitlsep2 "$1" / "${2:-dir}" "${3:-name}"
}
function: 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é."
function splitname() {
splitlsep "$1" . "${2:-basename}" "${3:-ext}"
}
function: 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)"
function splithost() {
splitfsep "$1" . "${2:-hostname}" "${3:-domain}"
}
function: 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)"
function splituserhost() {
splitfsep2 "$1" @ "${2:-user}" "${3:-host}"
}
function: 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)"
function splitpair() {
splitfsep "$1" : "${2:-src}" "${3:-dest}"
}
function: splitproxy "\
Découper \$1 de la forme http://[user:password@]host[:port]/ entre les valeurs
\$2(=host), \$3(=port), \$4(=user), \$5(=password)
S'il n'est pas spécifié, port vaut 3128 par défaut"
function splitproxy() {
local sy__tmp sy__host sy__port sy__creds sy__user sy__password
sy__tmp="${1#http://}"
if [[ "$sy__tmp" == *@* ]]; then
sy__creds="${sy__tmp%%@*}"
sy__tmp="${sy__tmp#${sy__creds}@}"
splitpair "$sy__creds" sy__user sy__password
fi
sy__tmp="${sy__tmp%%/*}"
splitpair "$sy__tmp" sy__host sy__port
[ -n "$sy__port" ] || sy__port=3128
setv "${2:-host}" "$sy__host"
setv "${3:-port}" "$sy__port"
setv "${4:-user}" "$sy__user"
setv "${5:-password}" "$sy__password"
}
function: 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)
S'il n'est pas spécifié, port vaut 80 pour http, 443 pour https, 21 pour ftp"
function spliturl() {
local sl__tmp sl__scheme sl__creds sl__user sl__password sl__host sl__port sl__path
sl__scheme="${1%%:*}"
sl__tmp="${1#${sl__scheme}://}"
if [[ "$sl__tmp" == */* ]]; then
sl__path="${sl__tmp#*/}"
sl__tmp="${sl__tmp%%/*}"
fi
if [[ "$sl__tmp" == *@* ]]; then
sl__creds="${sl__tmp%%@*}"
sl__tmp="${sl__tmp#${sl__creds}@}"
splitpair "$sl__creds" sl__user sl__password
fi
splitpair "$sl__tmp" sl__host sl__port
if [ -z "$sl__port" ]; then
[ "$sl__scheme" == "http" ] && sl__port=80
[ "$sl__scheme" == "https" ] && sl__port=443
[ "$sl__scheme" == "ftp" ] && sl__port=21
fi
setv "${2:-scheme}" "$sl__scheme"
setv "${3:-user}" "$sl__user"
setv "${4:-password}" "$sl__password"
setv "${5:-host}" "$sl__host"
setv "${6:-port}" "$sl__port"
setv "${7:-path}" "$sl__path"
}
function: 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)"
function splitwcs() {
local ss__p="$1"
local ss__dd="${2:-basedir}" ss__df="${3:-filespec}" ss__part ss__d ss__f
local -a ss__parts
array_split ss__parts "$ss__p" "/"
for ss__part in "${ss__parts[@]}"; do
if [[ "$ss__part" == *\** ]] || [[ "$ss__part" == *\?* ]] || [ -n "$ss__f" ]; then
ss__f="${ss__f:+$ss__f/}$ss__part"
else
ss__d="${ss__d:+$ss__d/}$ss__part"
fi
done
[ "${ss__p#/}" != "$ss__p" ] && ss__d="/$ss__d"
_setv "$ss__dd" "$ss__d"
_setv "$ss__df" "$ss__f"
}

140
bash/src/base.str.sh Normal file
View File

@ -0,0 +1,140 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.str "Fonctions de base: gestion des valeurs chaines"
function: 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"
function strmid() {
local range="$1"; shift
local str="$*"
if [[ "$range" == *:-* ]]; then
local max=${#str}
[ $max -eq 0 ] && return
local start="${range%%:*}"
[ -n "$start" ] || start=0
while [ "$start" -lt 0 ]; do
start=$(($max$start))
done
max=$(($max-$start))
local length="${range#*:}"
while [ "$length" -lt 0 ]; do
length=$(($max$length))
done
range="$start:$length"
fi
eval 'echo "${str:'" $range"'}"'
}
function: 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"
function strrepl() {
local pattern="$1"; shift
local repl="$1"; shift
local str="$*"
local cmd='echo "${str/'
if [ "${pattern#/}" != "$pattern" ]; then
pattern="${pattern#/}"
cmd="$cmd/"
elif [ "${pattern#\#}" != "$pattern" ]; then
pattern="${pattern#\#}"
cmd="$cmd#"
elif [ "${pattern#%}" != "$pattern" ]; then
pattern="${pattern#%}"
cmd="$cmd%"
fi
cmd="$cmd"'$pattern/$repl}"'
eval "$cmd"
}
function: strlcomp "transformer dans le flux en entrée en UTF-8 certains caractères en leur équivalent transformable en latin1.
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function strlcomp() {
if [ $# -gt 0 ]; then strlcomp <<<"$*"
else 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/[\xE2\x80\xA6]/.../g
s/[œ]/oe/g
s/[Œ]/OE/g
s/[æ]/ae/g
s/[Æ]/AE/g
s/a\xCC\x80/à/g
s/e\xCC\x81/é/g; s/e\xCC\x80/è/g; s/e\xCC\x82/ê/g; s/e\xCC\x88/ë/g
s/i\xCC\x88/ï/g; s/i\xCC\x82/î/g
s/o\xCC\x82/ô/g; s/o\xCC\x88/ö/g
s/u\xCC\x88/ü/g; s/u\xCC\x82/û/g
s/c\xCC\xA7/ç/g
s/A\xCC\x80/À/g
s/E\xCC\x81/É/g; s/E\xCC\x80/È/g; s/E\xCC\x82/Ê/g; s/E\xCC\x88/Ë/g
s/I\xCC\x88/Ï/g; s/I\xCC\x82/Î/g
s/O\xCC\x82/Ô/g; s/O\xCC\x88/Ö/g
s/U\xCC\x88/Ü/g; s/U\xCC\x82/Û/g
s/C\xCC\xA7/Ç/g
'
fi
}
function: strnacc "supprimer les accents dans le flux en entrée en UTF-8
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function strnacc() {
if [ $# -gt 0 ]; then strnacc <<<"$*"
else 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
'
fi
}
function: stripnl "Supprimer dans le flux en entrée les caractères de fin de ligne
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function stripnl() {
if [ $# -gt 0 ]; then stripnl <<<"$*"
else tr -d '\r\n'
fi
}
function: nl2lf "transformer dans le flux en entrée les fins de ligne en LF
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function nl2lf() {
if [ $# -gt 0 ]; then nl2lf <<<"$*"
else lawk 'BEGIN {RS="\r|\r\n|\n"} {print}'
fi
}
function: nl2crlf "transformer dans le flux en entrée les fins de ligne en CRLF
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function nl2crlf() {
if [ $# -gt 0 ]; then nl2crlf <<<"$*"
else lawk 'BEGIN {RS="\r|\r\n|\n"} {print $0 "\r"}'
fi
}
function: nl2cr "transformer dans le flux en entrée les fins de ligne en CR
si cette fonction est appelée avec des arguments, prendre \$* comme valeur du flux en entrée."
function nl2cr() {
if [ $# -gt 0 ]; then nl2cr <<<"$*"
else lawk 'BEGIN {RS="\r|\r\n|\n"; ORS=""} {print $0 "\r"}'
fi
}

101
bash/src/base.tools.sh Normal file
View File

@ -0,0 +1,101 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: base.tools "Fonctions de base: outils divers"
function: mkdirof 'Créer le répertoire correspondant au fichier $1'
function mkdirof() {
mkdir -p "$(dirname -- "$1")"
}
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: ls_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
Seuls les noms des fichiers sont listés. Utiliser l'\''option -p pour inclure
les chemins'
function ls_all() {
local withp f b
if [ "$1" == -p ]; then withp=1; shift; fi
b="${1:-.}"; shift
(
cd "$b" || exit
eval "$(__la_cmd "$@")" | while read f; do
[ "$f" == "." -o "$f" == ".." ] && continue
recho "${withp:+$b/}$f"
done
)
}
function: ls_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
Seuls les noms des fichiers sont listés. Utiliser l'\''option -p pour inclure
les chemins'
function ls_files() {
local withp f b
if [ "$1" == -p ]; then withp=1; shift; fi
b="${1:-.}"; shift
(
cd "$b" || exit
eval "$(__la_cmd "$@")" | while read f; do
[ -f "$f" ] && recho "${withp:+$b/}$f"
done
)
}
function: ls_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
Seuls les noms des répertoires sont listés. Utiliser l'\''option -p pour
inclure les chemins'
function ls_dirs() {
local withp f b
if [ "$1" == -p ]; then withp=1; shift; fi
b="${1:-.}"; shift
(
cd "$b" || exit
eval "$(__la_cmd "$@")" | while read f; do
[ "$f" == "." -o "$f" == ".." ] && continue
[ -d "$f" ] && recho "${withp:+$b/}$f"
done
)
}
function: quietgrep "tester la présence d'un pattern dans un fichier en mode silencieux"
function quietgrep() { grep -q "$@" 2>/dev/null; }
function: testsame "tester si deux fichiers sont identiques en mode silencieux"
function testsame() { diff -q "$@" >&/dev/null; }
function: testdiff "tester si deux fichiers sont différents en mode silencieux"
function testdiff() { ! diff -q "$@" >&/dev/null; }
function: should_update "faut-il mettre à jour le \$1 qui est construit à partir de \$2..@"
function should_update() {
local dest="$1"; shift
local source
for source in "$@"; do
[ -f "$source" ] || continue
[ "$source" -nt "$dest" ] && return 0
done
return 1
}

4
bash/src/donk.build.sh Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: donk.build "construire des images docker"
require: donk.common

3
bash/src/donk.common.sh Normal file
View File

@ -0,0 +1,3 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: donk.common "fonctions communes"

41
bash/src/donk.help.sh Normal file
View File

@ -0,0 +1,41 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: donk.help "aide de donk"
DONK_VALID_ACTIONS=(
dump:d
build:b
clean:k
)
dump_SUMMARY="afficher les valeurs des variables et ce que ferait l'action build"
build_SUMMARY="construire les images"
clean_SUMMARY="nettoyer le projet des fichiers créés avec 'copy gitignore=', en utilisant la commande 'git clean -dX'"
DONK_HELP_SECTIONS=(
base:b
reference:ref:r
)
function donk_help() {
:
}
function _donk_show_actions() {
local action summary
echo "
ACTIONS"
for action in "${DONK_VALID_ACTIONS[@]}"; do
IFS=: read -a action <<<"$action"; action="${action[0]}"
summary="${action}_SUMMARY"; summary="${!summary}"
echo "\
$action
$summary"
done
}
function _donk_show_help() {
if [ -z "$value_" ]; then showhelp@
else donk_help "$value_"
fi
exit $?
}

45
bash/src/fndate.sh Normal file
View File

@ -0,0 +1,45 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: fndate "gestion de fichiers dont le nom contient la date"
function: fndate_verifix "\
corriger le chemin \$1 pour ajouter le cas échéant une date au nom du fichier
le fichier n'existe peut-être pas au moment où cette fonction est appelée
\$2 est l'extension finale du fichier, à ignorer si elle est présente
(elle n'est cependant pas ajoutée si elle n'est pas présente)
\$3 est la date à sélectionner (par défaut c'est la date du jour)
XXX à implémenter:
- gestion de la date
- ajout d'un suffixe .N le cas échéant (i.e YYMMDD.NN)
"
function fndate_verifix() {
local dir filename ext date
if [[ "$1" == */* ]]; then
dir="$(dirname -- "$1")"
filename="$(basename -- "$1")"
else
dir=
filename="$1"
fi
ext="$2"
if [ -n "$ext" ]; then
ext=".${2#.}"
if [ "${filename%$ext}" != "$filename" ]; then
filename="${filename%$ext}"
else
ext=
fi
fi
date="$3"
[ -n "$date" ] || date="$(date +%y%m%d)"
case "$filename" in
~~-*) filename="$date-${filename#~~-}";;
~~*) filename="$date-${filename#~~}";;
*-~~) filename="${filename%-~~}-$date";;
*~~) filename="${filename%~~}-$date";;
esac
echo "${dir:+$dir/}$filename$ext"
}

217
bash/src/git.sh Normal file
View File

@ -0,0 +1,217 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
##@require nulib.sh
##@require base
module: git "Fonctions pour faciliter l'utilisation de git"
require: nulib base
function: git_geturl ""
function git_geturl() {
git config --get remote.origin.url
}
function: git_have_annex ""
function git_have_annex() {
[ -n "$(git config --get annex.uuid)" ]
}
NULIB_GIT_FUNCTIONS=(
git_check_gitvcs git_ensure_gitvcs
git_list_branches git_list_rbranches
git_have_branch git_have_rbranch
git_get_branch git_is_branch
git_have_remote git_track_branch
git_check_cleancheckout git_ensure_cleancheckout
git_is_ancestor git_should_ff git_should_push
git_is_merged
)
NULIB_GIT_FUNCTIONS_MAP=(
cg:git_check_gitvcs eg:git_ensure_gitvcs
lbs:git_list_branches rbs:git_list_rbranches
hlb:git_have_branch hrb:git_have_rbranch
gb:git_get_branch ib:git_is_branch
hr:git_have_remote tb:git_track_branch
cc:git_check_cleancheckout ec:git_ensure_cleancheckout
ia:git_is_ancestor sff:git_should_ff spu:git_should_push
im:git_is_merged
)
function: git_check_gitvcs ""
function git_check_gitvcs() {
git rev-parse --show-toplevel >&/dev/null
}
function: git_ensure_gitvcs ""
function git_ensure_gitvcs() {
git_check_gitvcs || edie "Ce n'est pas un dépôt git" || return
}
function: git_list_branches ""
function git_list_branches() {
git for-each-ref refs/heads/ --format='%(refname:short)' | csort
}
function: git_list_rbranches ""
function git_list_rbranches() {
git for-each-ref "refs/remotes/${1:-origin}/" --format='%(refname:short)' | csort
}
function: 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"
function git_list_pbranches() {
local prefix="${1:-origin}/"
{
git for-each-ref refs/heads/ --format='%(refname:short)'
git for-each-ref "refs/remotes/$prefix" --format='%(refname:short)' | grep -F "$prefix" | cut -c $((${#prefix} + 1))-
} | grep -vF HEAD | csort -u
}
function: git_have_branch ""
function git_have_branch() {
git_list_branches | grep -qF "$1"
}
function: git_have_rbranch ""
function git_have_rbranch() {
git_list_rbranches "${2:-origin}" | grep -qF "$1"
}
function: git_get_branch ""
function git_get_branch() {
git rev-parse --abbrev-ref HEAD 2>/dev/null
}
function: git_get_branch_remote ""
function git_get_branch_remote() {
local branch="$1"
[ -n "$branch" ] || branch="$(git_get_branch)"
[ -n "$branch" ] || return
git config --get "branch.$branch.remote"
}
function: git_get_branch_merge ""
function git_get_branch_merge() {
local branch="$1"
[ -n "$branch" ] || branch="$(git_get_branch)"
[ -n "$branch" ] || return
git config --get "branch.$branch.merge"
}
function: git_get_branch_rbranch ""
function git_get_branch_rbranch() {
local branch="$1" remote="$2" merge
[ -n "$branch" ] || branch="$(git_get_branch)"
[ -n "$branch" ] || return
[ -n "$remote" ] || remote="$(git_get_branch_remote "$branch")"
[ -n "$remote" ] || return
merge="$(git_get_branch_merge "$branch")"
[ -n "$merge" ] || return
echo "refs/remotes/$remote/${merge#refs/heads/}"
}
function: git_is_branch ""
function git_is_branch() {
[ "$(git_get_branch)" == "${1:-master}" ]
}
function: git_have_remote ""
function git_have_remote() {
[ -n "$(git config --get remote.${1:-origin}.url)" ]
}
function: git_track_branch ""
function git_track_branch() {
local branch="$1" origin="${2:-origin}"
[ -n "$branch" ] || return
git_have_remote "$origin" || return
[ "$(git config --get branch.$branch.remote)" == "$origin" ] && return
if git_have_rbranch "$branch" "$origin"; then
if git_have_branch "$branch"; then
git branch -u "$origin/$branch" "$branch"
else
git branch -t "$branch" "$origin/$branch"
fi
elif git_have_branch "$branch"; then
git push -u "$origin" "$branch" || return
fi
}
function: git_ensure_branch "
@return 0 si la branche a été créée, 1 si elle existait déjà, 2 en cas d'erreur"
function git_ensure_branch() {
local branch="$1" source="${2:-master}" origin="${3:-origin}"
[ -n "$branch" ] || return 2
git_have_branch "$branch" && return 1
if git_have_rbranch "$branch" "$origin"; then
# une branche du même nom existe dans l'origine. faire une copie de cette branche
git branch -t "$branch" "$origin/$branch" || return 2
else
# créer une nouvelle branche du nom spécifié
git_have_branch "$source" || return 2
git branch "$branch" "$source" || return 2
if [ -z "$NULIB_GIT_OFFLINE" ]; then
git_have_remote "$origin" && git_track_branch "$branch" "$origin"
fi
fi
return 0
}
function: git_check_cleancheckout "vérifier qu'il n'y a pas de modification locales dans le dépôt correspondant au répertoire courant."
function git_check_cleancheckout() {
[ -z "$(git status --porcelain 2>/dev/null)" ]
}
function: git_ensure_cleancheckout ""
function git_ensure_cleancheckout() {
git_check_cleancheckout ||
edie "Vous avez des modifications locales. Enregistrez ces modifications avant de continuer" || return
}
function git__init_ff() {
o="${3:-origin}"
b="$1" s="${2:-refs/remotes/$o/$1}"
b="$(git rev-parse --verify --quiet "$b")" || return 1
s="$(git rev-parse --verify --quiet "$s")" || return 1
return 0
}
function git__can_ff() {
[ "$1" == "$(git merge-base "$1" "$2")" ]
}
function: 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"
function git_is_ancestor() {
local o b s; git__init_ff "$@" || return
git__can_ff "$b" "$s"
}
function: 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"
function git_should_ff() {
local o b s; git__init_ff "$@" || return
[ "$b" != "$s" ] || return 1
git__can_ff "$b" "$s"
}
function: 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."
function git_should_push() {
git_should_ff "refs/remotes/${2:-origin}/$1" "$1"
}
function: 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"
function git_fast_forward() {
local o b s; git__init_ff "$@" || return
[ "$b" != "$s" ] || return 1
local head="$(git rev-parse HEAD)"
[ "$head" == "$b" ] || return 1
git__can_ff "$b" "$s" || return 1
git merge --ff-only "$s"
}
function: 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"
function git_is_merged() {
local b="$1" d="$2"
b="$(git rev-parse --verify --quiet "$b")" || return 1
d="$(git rev-parse --verify --quiet "$d")" || return 1
[ -n "$(git merge-base "$b" "$d")" ] || return 1
[ -z "$(git rev-list "$d..$b")" ]
}

1
bash/src/nulib.sh Symbolic link
View File

@ -0,0 +1 @@
../../load.sh

194
bash/src/pretty.sh Normal file
View File

@ -0,0 +1,194 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: pretty "Affichage en couleur"
require: base
################################################################################
# Gestion des couleurs
function __get_color() {
[ -z "$*" ] && set RESET
echo_ $'\e['
local sep
while [ -n "$1" ]; do
[ -n "$sep" ] && echo_ ";"
case "$1" in
z|RESET) echo_ "0";;
o|BLACK) echo_ "30";;
r|RED) echo_ "31";;
g|GREEN) echo_ "32";;
y|YELLOW) echo_ "33";;
b|BLUE) echo_ "34";;
m|MAGENTA) echo_ "35";;
c|CYAN) echo_ "36";;
w|WHITE) echo_ "37";;
DEFAULT) echo_ "39";;
O|BLACK_BG) echo_ "40";;
R|RED_BG) echo_ "41";;
G|GREEN_BG) echo_ "42";;
Y|YELLOW_BG) echo_ "43";;
B|BLUE_BG) echo_ "44";;
M|MAGENTA_BG) echo_ "45";;
C|CYAN_BG) echo_ "46";;
W|WHITE_BG) echo_ "47";;
DEFAULT_BG) echo_ "49";;
@|BOLD) echo_ "1";;
-|FAINT) echo_ "2";;
_|UNDERLINED) echo_ "4";;
~|REVERSE) echo_ "7";;
n|NORMAL) echo_ "22";;
esac
sep=1
shift
done
echo_ "m"
}
function get_color() {
[ -n "$NO_COLORS" ] && return
__get_color "$@"
}
function __set_no_colors() {
if [ -z "$1" ]; then
if [ -n "$NULIB_NO_COLORS" ]; then NO_COLORS=1
elif out_isatty && err_isatty; then NO_COLORS=
else NO_COLORS=1
fi
else
is_yes "$1" && NO_COLORS=1 || NO_COLORS=
fi
COULEUR_ROUGE="$(get_color RED BOLD)"
COULEUR_VERTE="$(get_color GREEN BOLD)"
COULEUR_JAUNE="$(get_color YELLOW BOLD)"
COULEUR_BLEUE="$(get_color BLUE BOLD)"
COULEUR_BLANCHE="$(get_color WHITE BOLD)"
COULEUR_NORMALE="$(get_color RESET)"
if [ -n "$NO_COLORS" ]; then
nulib__load: _output_vanilla
else
nulib__load: _output_color
fi
}
__set_no_colors
# 5=afficher les messages de debug; 4=afficher les message verbeux;
# 3=afficher les message informatifs; 2=afficher les warnings et les notes;
# 1=afficher les erreurs; 0=ne rien afficher
export __verbosity
[ -z "$__verbosity" ] && __verbosity=3
function set_verbosity() {
[ -z "$__verbosity" ] && __verbosity=3
case "$1" in
-Q|--very-quiet) __verbosity=0;;
-q|--quiet) [ "$__verbosity" -gt 0 ] && let __verbosity=$__verbosity-1;;
-v|--verbose) [ "$__verbosity" -lt 5 ] && let __verbosity=$__verbosity+1;;
-c|--default) __verbosity=3;;
-D|--debug) __verbosity=5; DEBUG=1;;
*) return 1;;
esac
return 0
}
# 3=interaction maximale; 2=interaction par défaut
# 1= interaction minimale; 0=pas d'interaction
export __interaction
[ -z "$__interaction" ] && __interaction=2
function set_interaction() {
[ -z "$__interaction" ] && __interaction=2
case "$1" in
-b|--batch) __interaction=0;;
-y|--automatic) [ "$__interaction" -gt 0 ] && let __interaction=$__interaction-1;;
-i|--interactive) [ "$__interaction" -lt 3 ] && let __interaction=$__interaction+1;;
-c|--default) __interaction=2;;
*) return 1;;
esac
return 0
}
# Variable à inclure pour lancer automatiquement set_verbosity et
# set_interaction en fonction des arguments de la ligne de commande. A utiliser
# de cette manière:
# parse_opts ... "${PRETTYOPTS[@]}" @ args -- ...
PRETTYOPTS=(
-L:,--log-to: '$elogto $value_'
-Q,--very-quiet,-q,--quiet,-v,--verbose,-D,--debug '$set_verbosity $option_'
-b,--batch,-y,--automatic,-i,--interactive '$set_interaction $option_'
)
function show_error() { [ "$__verbosity" -ge 1 ]; }
function show_warn() { [ "$__verbosity" -ge 2 ]; }
function show_info() { [ "$__verbosity" -ge 3 ]; }
function show_verbose() { [ "$__verbosity" -ge 4 ]; }
function show_debug() { [ -n "$DEBUG" -o "$__verbosity" -ge 5 ]; }
# Vérifier le niveau de verbosité actuel par rapport à l'argument. $1 peut valoir:
# -Q retourner true si on peut afficher les messages d'erreur
# -q retourner true si on peut afficher les messages d'avertissement
# -c retourner true si on peut afficher les message normaux
# -v retourner true si on peut afficher les messages verbeux
# -D retourner true si on peut afficher les messages de debug
function check_verbosity() {
case "$1" in
-Q|--very-quiet) [ "$__verbosity" -ge 1 ];;
-q|--quiet) [ "$__verbosity" -ge 2 ];;
-c|--default) [ "$__verbosity" -ge 3 ];;
-v|--verbose) [ "$__verbosity" -ge 4 ];;
-D|--debug) [ -n "$DEBUG" -o "$__verbosity" -ge 5 ];;
*) return 0;;
esac
}
# Retourner l'option correspondant au niveau de verbosité actuel
function get_verbosity_option() {
case "$__verbosity" in
0) echo --very-quiet;;
1) echo --quiet --quiet;;
2) echo --quiet;;
4) echo --verbose;;
5) echo --debug;;
esac
}
# Vérifier le niveau d'interaction autorisé par rapport à l'argument. Par
# exemple, 'check_interaction -y' signifie "Il ne faut interagir avec
# l'utilisateur qu'à partir du niveau d'interaction -y. Suis-je dans les
# condition voulues pour autoriser l'interaction?"
# $1 peut valoir:
# -b retourner true
# -y retourner true si on est au moins en interaction minimale
# -c retourner true si on est au moins en interaction normale
# -i retourner true si on est au moins en interaction maximale
function check_interaction() {
case "$1" in
-b|--batch) return 0;;
-y|--automatic) [ -n "$__interaction" -a "$__interaction" -ge 1 ];;
-c|--default) [ -n "$__interaction" -a "$__interaction" -ge 2 ];;
-i|--interactive) [ -n "$__interaction" -a "$__interaction" -ge 3 ];;
*) return 0;;
esac
}
# Vérifier le niveau d'interaction dans lequel on se trouve actuellement. $1
# peut valoir:
# -b retourner true si l'une des options -b ou -yy a été spécifiée
# -Y retourner true si l'une des options -b, -yy, ou -y a été spécifiée
# -y retourner true si l'option -y a été spécifiée
# -c retourner true si aucune option n'a été spécifiée
# -i retourner true si l'option -i a été spécifiée
# -C retourner true si aucune option ou l'option -i ont été spécifiés
function is_interaction() {
case "$1" in
-b|--batch) [ -n "$__interaction" -a "$__interaction" -eq 0 ];;
-Y) [ -n "$__interaction" -a "$__interaction" -le 1 ];;
-y|--automatic) [ -n "$__interaction" -a "$__interaction" -eq 1 ];;
-c|--default) [ -n "$__interaction" -a "$__interaction" -eq 2 ];;
-i|--interactive) [ -n "$__interaction" -a "$__interaction" -eq 3 ];;
-C) [ -n "$__interaction" -a "$__interaction" -ge 2 ];;
*) return 1;;
esac
}
# Retourner l'option correspondant au niveau d'interaction actuel
function get_interaction_option() {
case "$__interaction" in
0) echo --batch;;
1) echo --automatic;;
3) echo --interactive;;
esac
}

4
bash/src/sysinfos.sh Normal file
View File

@ -0,0 +1,4 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: sysinfos "Informations sur le système courant"
require: base

225
bash/src/template.sh Normal file
View File

@ -0,0 +1,225 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: template "Mise à jour de templates à partir de modèles"
function: template_locals "\
Afficher les variables qui doivent être locales
Utiliser de cette façon:
~~~
eval \$(template_locals)
~~~"
function template_locals() {
echo "local -a userfiles; local updated"
}
function: template_copy_replace "\
Copier \$1 vers \$2 de façon inconditionnelle
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
et \$2 vaudra alors 'file'
si un fichier \${2#.}.local existe, prendre ce fichier à la place comme source
Ajouter file au tableau userfiles"
function template_copy_replace() {
local src="$1" dest="$2"
local srcdir srcname lsrcname
setx srcdir=dirname "$src"
setx srcname=basename "$src"
if [ -z "$dest" ]; then
dest="${srcname#.}"
dest="${dest%.*}"
dest="$srcdir/$dest"
fi
lsrcname="${srcname#.}.local"
[ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname"
userfiles+=("$dest")
cp -P "$src" "$dest"
return 0
}
function: template_copy_missing "\
Copier \$1 vers \$2 si ce fichier n'existe pas déjà
Si \$2 n'est pas spécifié, on assume que \$1 est de la forme '.file.ext'
et \$2 vaudra alors 'file'
si un fichier \${2#.}.local existe, prendre ce fichier à la place comme source
Ajouter file au tableau userfiles"
function template_copy_missing() {
local src="$1" dest="$2"
local srcdir srcname lsrcname
setx srcdir=dirname "$src"
setx srcname=basename "$src"
if [ -z "$dest" ]; then
dest="${srcname#.}"
dest="${dest%.*}"
dest="$srcdir/$dest"
fi
userfiles+=("$dest")
if [ ! -e "$dest" ]; then
lsrcname="${srcname#.}.local"
[ -e "$srcdir/$lsrcname" ] && src="$srcdir/$lsrcname"
cp -P "$src" "$dest"
return 0
fi
return 1
}
function: template_dump_vars "\
Lister les variables mentionnées dans les fichiers \$@
Seules sont prises en compte les variables dont le nom est de la forme
[A-Z][A-Za-z_]*
Cette fonction est utilisée par template_source_envs(). Elle utilise la
fonction outil _template_dump_vars() qui peut être redéfinie si nécessaire."
function template_dump_vars() {
_template_dump_vars "$@"
}
function _template_dump_vars() {
[ $# -gt 0 ] || return 0
cat "$@" |
grep -E '^[A-Z][A-Za-z_]*=' |
sed 's/=.*//' |
sort -u
}
function: template_source_envs "\
Cette fonction doit être implémentée par l'utilisateur et doit:
- initialiser le tableau template_vars qui donne la liste des variables scalaires
- initialiser te tableau template_lists qui donne la liste des variables listes
- charger ces variables depuis les fichiers \$@
Cette fonction utilise la fonction outil _template_source_envs() qui peut être
redéfinie si nécessaire."
function template_source_envs() {
_template_source_envs "$@"
}
function _template_source_envs() {
local e_
for e_ in "$@"; do
[ -f "$e_" ] && source "$e_"
done
setx -a template_vars=template_dump_vars "$@"
template_lists=()
}
function: template_resolve_scripts "\
Générer le script awk \$1 et le script sed \$2 qui remplacent dans les fichiers
destination les marqueurs @@VAR@@ par la valeur de la variable \$VAR
correspondante
Les fichiers \$3..@ contiennent les valeurs des variables
Les marqueurs supportés sont les suivants et sont évalués dans cet ordre:
- XXXRANDOMXXX remplacer cette valeur par une chaine de 16 caractères au hasard
- @@FOR:VARS@@ VARS étant une liste de valeurs séparées par des espaces:
dupliquer la ligne autant de fois qu'il y a de valeurs dans \$VARS
dans chaque ligne, remplacer les occurrences de @@VAR@@ par la valeur
de l'itération courante
- #@@IF:VAR@@ afficher la ligne si VAR est non vide, supprimer la ligne sinon
- #@@UL:VAR@@ afficher la ligne si VAR est vide, supprimer la ligne sinon
- #@@if:VAR@@
- #@@ul:VAR@@ variantes qui ne suppriment pas la ligne mais sont remplacées par #
- @@VAR:-string@@ remplacer par 'string' si VAR a une valeur vide ou n'est pas défini, \$VAR sinon
- @@VAR:+string@@ remplacer par 'string' si VAR est défini a une valeur non vide
"
function template_generate_scripts() {
local awkscript="$1"; shift
local sedscript="$1"; shift
(
template_source_envs "$@"
NL=$'\n'
# random, for
exec >"$awkscript"
echo '@include "base.tools.awk"'
echo 'BEGIN {'
for list in "${template_lists[@]}"; do
items="${!list}"; read -a items <<<"${items//
/ }"
let i=0
echo " $list[0] = 0; delete $list"
for item in "${items[@]}"; do
item="${item//\\/\\\\}"
item="${item//\"/\\\"}"
echo " $list[$i] = \"$item\""
let i=i+1
done
done
echo '}'
echo '{ if (should_generate_password()) { generate_password() } }'
for list in "${template_lists[@]}"; do
items="${!list}"; read -a items <<<"${items//
/ }"
echo "/@@FOR:$list@@/ {"
if [ ${#items[*]} -gt 0 ]; then
if [ "${list%S}" != "$list" ]; then item="${list%S}"
elif [ "${list%s}" != "$list" ]; then item="${list%s}"
else item="$list"
fi
echo " sub(/@@FOR:$list@@/, \"\")"
echo " for (i in $list) {"
echo " print gensub(/@@$item@@/, $list[i], \"g\")"
echo " }"
fi
echo " next"
echo "}"
done
echo '{ print }'
# if, ul, var
exec >"$sedscript"
for var in "${template_vars[@]}"; do
value="${!var}"
value="${value//\//\\\/}"
value="${value//[/\\[}"
value="${value//\*/\\\*}"
value="${value//$NL/\\n}"
if [ -n "$value" ]; then
echo "s/#@@IF:${var}@@//g"
echo "s/#@@if:${var}@@//g"
echo "/#@@UL:${var}@@/d"
echo "s/#@@ul:${var}@@/#/g"
echo "s/@@${var}:-([^@]*)@@/${value}/g"
echo "s/@@${var}:+([^@]*)@@/\\1/g"
else
echo "/#@@IF:${var}@@/d"
echo "s/#@@if:${var}@@/#/g"
echo "s/#@@UL:${var}@@//g"
echo "s/#@@ul:${var}@@//g"
echo "s/@@${var}:-([^@]*)@@/\\1/g"
echo "s/@@${var}:+([^@]*)@@//g"
fi
echo "s/@@${var}@@/${value}/g"
done
)
#etitle "awkscript" cat "$awkscript"
#etitle "sedscript" cat "$sedscript"
}
function template_process_userfiles() {
local awkscript sedscript workfile userfile
ac_set_tmpfile awkscript
ac_set_tmpfile sedscript
template_generate_scripts "$awkscript" "$sedscript" "$@"
ac_set_tmpfile workfile
for userfile in "${userfiles[@]}"; do
if cat "$userfile" | awk -f "$awkscript" | sed -rf "$sedscript" >"$workfile"; then
if testdiff "$workfile" "$userfile"; then
# n'écrire le fichier que s'il a changé
cat "$workfile" >"$userfile"
fi
fi
done
ac_clean "$awkscript" "$sedscript" "$workfile"
}

160
bash/src/tests.sh Normal file
View File

@ -0,0 +1,160 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
##@cooked nocomments
module: tests "tests unitaires"
require: base
function tests__get_line() {
# obtenir le nom du script depuis lequel les fonctions de ce module ont été
# appelées
local mysource="${BASH_SOURCE[0]}"
local i=1
while [ "${BASH_SOURCE[$i]}" == "$mysource" ]; do
let i=i+1
done
echo "${BASH_SOURCE[$i]}:${BASH_LINENO[$((i-1))]}"
}
function tests__set_message() {
if [ "$1" == -m ]; then
message="$2"
return 2
elif [[ "$1" == -m* ]]; then
message="${1#-m}"
return 1
else
return 0
fi
}
function: assert_ok "faire un test unitaire. la syntaxe est
assert_ok [-m message] cmd
la commande doit retourner vrai pour que le test soit réussi"
function assert_ok() {
local message; tests__set_message "$@" || shift $?
"$@" && return 0
[ -n "$message" ] && message="$message: "
message="${message}test failed at $(tests__get_line)"
die "$message"
}
function assert() { assert_ok "$@"; }
function: assert_ko "faire un test unitaire. la syntaxe est
assert_ko [-m message] cmd
la commande doit retourner faux pour que le test soit réussi"
function assert_ko() {
local message; tests__set_message "$@" || shift $?
"$@" || return 0
[ -n "$message" ] && message="$message: "
message="${message}test failed at $(tests__get_line)"
die "$message"
}
function tests__assert() {
local message="$1"; shift
"assert_${1#assert_}" -m "$message" "${@:2}"
}
function assert_n() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="value is empty"
tests__assert "$message" ok [ -n "$1" ]
}
function assert_z() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="value is not empty"
tests__assert "$message" ok [ -z "$1" ]
}
function assert_same() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' and '$2' are different"
tests__assert "$message" ok [ "$1" == "$2" ]
}
function assert_diff() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' and '$2' are the same"
tests__assert "$message" ok [ "$1" != "$2" ]
}
function assert_eq() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' and '$2' are not equals"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -eq "$2" ]
}
function assert_ne() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' and '$2' are equals"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -ne "$2" ]
}
function assert_gt() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' is not greater than '$2'"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -gt "$2" ]
}
function assert_ge() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' is not greater than or equals to '$2'"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -ge "$2" ]
}
function assert_lt() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' is not less than '$2'"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -lt "$2" ]
}
function assert_le() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$3"
[ -n "$message" ] || message="'$1' is not less than or equals to '$2'"
tests__assert "'$1' must be a number" ok isnum "$1"
tests__assert "$message" ok [ "$1" -le "$2" ]
}
function assert_is_defined() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="'$1' is not defined"
tests__assert "$message" ok is_defined "$1"
}
function assert_not_defined() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="'$1' is defined"
tests__assert "$message" ko is_defined "$1"
}
function assert_is_array() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="'$1' is not an array"
tests__assert "$message" ok is_array "$1"
}
function assert_not_array() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="$2"
[ -n "$message" ] || message="'$1' is an array"
tests__assert "$message" ko is_array "$1"
}
function assert_array_same() {
local message; tests__set_message "$@" || shift $?
[ -n "$message" ] || message="'$1' is not an array or not equals to (${*:2})"
assert_is_array "$1" "$message"
eval "actual=\"\$(qvals \"\${$1[@]}\")\""; shift
eval "expected=\"\$(qvals \"\$@\")\""
assert_same "$actual" "$expected" "$message"
}

1
bash/tests/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/template-dest.txt

View File

@ -0,0 +1,21 @@
---
PROFILES vaut prod test
c'est à dire, si on fait un par ligne:
- prod
- test
---
PASSWORD is vaeRL6ADYKmWndEA
---
hosts:
- first
- second
---
---
IF valeur
if valeur
#ul valeur
---
#if
UL
ul
---

View File

@ -0,0 +1,23 @@
---
PROFILES vaut @@PROFILES@@
c'est à dire, si on fait un par ligne:
@@FOR:PROFILES@@- @@PROFILE@@
---
PASSWORD is XXXRANDOMXXX
---
#@@IF:HOSTS@@hosts:
@@FOR:HOSTS@@- @@HOST@@
---
#@@IF:VALUES@@values:
@@FOR:VALUES@@- @@VALUE@@
---
#@@IF:PLEIN@@IF @@PLEIN@@
#@@if:PLEIN@@if @@PLEIN@@
#@@UL:PLEIN@@UL @@PLEIN@@
#@@ul:PLEIN@@ul @@PLEIN@@
---
#@@IF:VIDE@@IF @@VIDE@@
#@@if:VIDE@@if @@VIDE@@
#@@UL:VIDE@@UL @@VIDE@@
#@@ul:VIDE@@ul @@VIDE@@
---

View File

@ -0,0 +1,19 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
echo "\
source ./_template-values.env
template_vars=(
PROFILES
PASSWORD
HOSTS
VALUES
PLEIN
VIDE
)
template_lists=(
PROFILES
HOSTS
VALUES
)
"

View File

@ -0,0 +1,12 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
PROFILES="prod test"
HOSTS="
first
second
"
VALUES=
PLEIN=valeur
VIDE=

View File

@ -0,0 +1,17 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
#NULIB_NO_DISABLE_SET_X=1
args=(
"tester autodebug"
#-D x=1 "désactiver l'option automatique -D"
#--debug x=1 "désactiver l'option automatique --debug"
)
parse_args "$@"; set -- "${args[@]}"
if is_debug; then
echo "on est en mode debug"
else
echo "on n'est pas en mode debug, relancer avec -D ou --debug"
fi

View File

@ -0,0 +1,40 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
#NULIB_NO_DISABLE_SET_X=1
args=("tester l'affichage de l'aide")
case "$1" in
s|std)
# NB: seul l'affichage standard est disponible...
args+=(
-h,--help,--hstd '$showhelp@' "afficher l'aide de base"
)
shift
;;
a|adv)
# NB: seul l'affichage avancé est disponible...
args+=(
-H,--help++,--hadv '$showhelp@ ++' "afficher l'aide avancée"
)
shift
;;
sa|std+adv)
args+=(
-h,--help,--hstd '$showhelp@' "afficher l'aide de base"
-H,--help++,--hadv '$showhelp@ ++' "afficher l'aide avancée"
)
shift
;;
esac
args+=(
-a,--std . "cette option apparait dans les options standards"
-b,--adv . "++cette option apparait dans les options avancées"
)
parse_args "$@"; set -- "${args[@]}"
enote "lancer le script
- avec --help pour afficher les options standards uniquement
- avec --help++ pour afficher toutes les options"

187
bash/tests/test-args-base.sh Executable file
View File

@ -0,0 +1,187 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
require: tests
#NULIB_NO_DISABLE_SET_X=1
function pa() {
unset count fixed mopt dmopt oopt doopt autoinc autoval a1 a2 a3 a4
count=
fixed=
efixed=1
mopt=
dmopt=
oopt=
doopt=
autoinc=
autoval=
unset a1
a2=()
a3=
a4=x
args=(
"tester la gestion des arguments"
-o,--eopt count "incrémenter count"
-f,--fixed fixed=42 "spécifier fixed"
-e,--efixed efixed= "spécifier efixed"
-a:,--mopt mopt= "spécifier mopt"
-A:,--dmopt dmopt=default "spécifier dmopt"
-b::,--oopt oopt= "spécifier oopt"
-B::,--doopt doopt=default "spécifier doopt"
-n,--autoinc . "incrémenter autoinc"
-N,--no-autoinc . "décrémenter autoinc"
-v:,--autoval . "spécifier autoval"
-x: a1 "autoadd a1 qui n'est pas défini"
-y: a2 "autoadd a2 qui est défini à ()"
-z: a3 "autoadd a3 qui est défini à vide"
-t: a4 "autoadd a4 qui est défini à une valeur non vide"
-s,--sans-arg '$echo "sans_arg option=$option_, name=$name_, value=$value_"'
-S::,--avec-arg '$echo "avec_arg option=$option_, name=$name_, value=$value_"'
)
parse_args "$@"
}
pa
assert_z "$count"
assert_z "$fixed"
assert_eq "$efixed" 1
assert_z "$mopt"
assert_z "$dmopt"
assert_z "$oopt"
assert_z "$doopt"
assert_z "$autoinc"
assert_z "$autoval"
assert_not_defined a1
assert_is_array a2
assert_not_array a3
assert_z "$a3"
assert_not_array a4
assert_same x "$a4"
assert_eq "${#args[*]}" 0
pa -o
assert_eq "$count" 1
pa -oo
assert_eq "$count" 2
pa -ooo
assert_eq "$count" 3
pa -f
assert_eq "$fixed" 42
pa -ff
assert_eq "$fixed" 42
pa -fff
assert_eq "$fixed" 42
assert_same "$efixed" "1"
pa -e
assert_same "$efixed" ""
pa -ee
assert_same "$efixed" ""
pa -eee
assert_same "$efixed" ""
pa -a ""
assert_not_array mopt
assert_same "$mopt" ""
pa -a abc
assert_not_array mopt
assert_same "$mopt" abc
pa -a abc -a xyz
assert_not_array mopt
assert_same "$mopt" xyz
pa -A ""
assert_not_array dmopt
assert_same "$dmopt" default
pa -A abc
assert_not_array dmopt
assert_same "$dmopt" abc
pa -A abc -A xyz
assert_not_array dmopt
assert_same "$dmopt" xyz
pa -b
assert_not_array oopt
assert_same "$oopt" ""
pa -babc
assert_not_array oopt
assert_same "$oopt" abc
pa -babc -bxyz
assert_not_array oopt
assert_same "$oopt" xyz
pa -B
assert_not_array doopt
assert_same "$doopt" default
pa -Babc
assert_not_array doopt
assert_same "$doopt" abc
pa -Babc -Bxyz
assert_not_array doopt
assert_same "$doopt" xyz
pa -n
assert_eq "$autoinc" 1
pa -nn
assert_eq "$autoinc" 2
pa -nnn
assert_eq "$autoinc" 3
pa -nN
assert_z "$autoinc"
pa -nnN
assert_eq "$autoinc" 1
pa -nnnNN
assert_eq "$autoinc" 1
pa -v ""
assert_is_array autoval
assert_array_same autoval ""
pa -v abc
assert_is_array autoval
assert_array_same autoval abc
pa -v abc -v xyz
assert_is_array autoval
assert_array_same autoval abc xyz
pa -xa
assert_not_array a1
assert_same "$a1" a
pa -xa -xb
assert_is_array a1
assert_array_same a1 a b
pa -ya
assert_is_array a2
assert_array_same a2 a
pa -ya -yb
assert_is_array a2
assert_array_same a2 a b
pa -za
assert_is_array a3
assert_array_same a3 a
pa -za -zb
assert_is_array a3
assert_array_same a3 a b
pa -ta
assert_is_array a4
assert_array_same a4 x a
pa -ta -tb
assert_is_array a4
assert_array_same a4 x a b
assert_same "$(pa -s)" "sans_arg option=-s, name=sans_arg, value="
assert_same "$(pa --sans-arg)" "sans_arg option=--sans-arg, name=sans_arg, value="
assert_same "$(pa -S)" "avec_arg option=-S, name=avec_arg, value="
assert_same "$(pa -Sx)" "avec_arg option=-S, name=avec_arg, value=x"
assert_same "$(pa --avec-arg)" "avec_arg option=--avec-arg, name=avec_arg, value="
assert_same "$(pa --avec-arg=x)" "avec_arg option=--avec-arg, name=avec_arg, value=x"
pa x
assert_array_same args x
enote "tests successful"

18
bash/tests/test-args-help.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
#NULIB_NO_DISABLE_SET_X=1
args=(
"tester l'affichage de l'aide"
-f:,--input input= "spécifier le fichier en entrée
il est possible de spécifier aussi un répertoire auquel cas un fichier par défaut est chargé
nb: l'aide pour cette option doit faire 3 lignes indentées"
-a,--std . "cette option apparait dans les options standards"
-b,--adv . "++cette option apparait dans les options avancées"
)
parse_args "$@"; set -- "${args[@]}"
enote "lancer le script
- avec --help pour afficher les options standards uniquement
- avec --help++ pour afficher toutes les options"

18
bash/tests/test-input.sh Executable file
View File

@ -0,0 +1,18 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
#NULIB_NO_DISABLE_SET_X=1
args=(
"tester diverses fonctions de saisie"
)
parse_args "$@"; set -- "${args[@]}"
choices=(first second third)
choice=
simple_menu -t "choix sans valeur par défaut" -m "le prompt" choice choices
enote "vous avez choisi choice=$choice"
choice=second
simple_menu -t "choix avec valeur par défaut" -m "le prompt" choice choices
enote "vous avez choisi choice=$choice"

169
bash/tests/test-output.sh Executable file
View File

@ -0,0 +1,169 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
#NULIB_NO_DISABLE_SET_X=1
Multiline=
Banner=
args=(
"afficher divers messages avec les fonctions e*"
-D,--debug '$set_debug'
-d,--date NULIB_ELOG_DATE=1
-m,--myname NULIB_ELOG_MYNAME=1
-n,--nc,--no-color '$__set_no_colors 1'
--ml Multiline=1
-b Banner=1
)
parse_args "$@"; set -- "${args[@]}"
if [ -n "$Multiline" ]; then
############################################################################
[ -n "$Banner" ] && ebanner $'multi-line\nbanner'
esection $'multi-line\nsection'
etitle $'multi-line\ntitle'
etitle $'another\ntitle'
edesc $'multi-line\ndesc'
[ -n "$Banner" ] && ebanner $'multi-line\nbanner'
eimportant $'multi-line\nimportant'
eattention $'multi-line\nattention'
eerror $'multi-line\nerror'
ewarn $'multi-line\nwarn'
enote $'multi-line\nnote'
einfo $'multi-line\ninfo'
eecho $'multi-line\necho'
edebug $'multi-line\ndebug'
action $'multi-line\naction'
asuccess
action $'multi-line\naction'
estep $'multi-line\nstep'
afailure
action $'multi-line\naction'
estep $'multi-line\nstep'
asuccess $'multi-line\nsuccess'
action $'multi-line\naction'
estep $'multi-line\nstep'
adone $'multi-line\nneutral'
eend
eend
else
############################################################################
[ -n "$Banner" ] && ebanner "banner"
eimportant "important"
eattention "attention"
eerror "error"
ewarn "warn"
enote "note"
einfo "info"
eecho "echo"
edebug "debug"
estep "step"
estepe "stepe"
estepw "stepw"
estepn "stepn"
estepi "stepi"
esection "section"
eecho "content"
etitle "title0"
etitle "title1"
eecho "print under title1"
eend
eecho "print under title0"
eend
edesc "action avec step"
action "action avec step"
estep "step"
asuccess "action success"
action "action avec step"
estep "step"
afailure "action failure"
action "action avec step"
estep "step"
adone "action neutral"
edesc "actions sans step"
action "action sans step"
asuccess "action success"
action "action sans step"
afailure "action failure"
action "action sans step"
adone "action neutral"
edesc "actions imbriquées"
action "action0"
action "action1"
action "action2"
asuccess "action2 success"
asuccess "action1 success"
asuccess "action0 success"
edesc "action avec step, sans messages"
action "action avec step, sans messages, success"
estep "step"
asuccess
action "action avec step, sans messages, failure"
estep "step"
afailure
action "action avec step, sans messages, done"
estep "step"
adone
edesc "action sans step, sans messages"
action "action sans step, sans messages, success"
asuccess
action "action sans step, sans messages, failure"
afailure
action "action sans step, sans messages, done"
adone
edesc "actions imbriquées, sans messages"
action "action0"
action "action1"
action "action2"
asuccess
asuccess
asuccess
function vtrue() {
echo "commande qui se termine avec succès"
}
function vfalse() {
echo "commande qui se termine en échec"
return 1
}
edesc "action avec commande"
action "commande true" vtrue
action "commande false" vfalse
edesc "action avec commande et aresult sans message"
action "commande true"
vtrue; aresult $?
action "commande false"
vfalse; aresult $?
edesc "action avec commande et aresult"
action "commande true"
vtrue; aresult $? "résultat de la commande"
action "commande false"
vfalse; aresult $? "résultat de la commande"
fi

29
bash/tests/test-template.sh Executable file
View File

@ -0,0 +1,29 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../src/nulib.sh" || exit 1
require: template
#NULIB_NO_DISABLE_SET_X=1
args=(
"description"
#"usage"
)
parse_args "$@"; set -- "${args[@]}"
function template__source_envs() {
eval "$("$MYDIR/_template-source_envs")"
}
cd "$MYDIR"
#template_generate_scripts \
# /tmp/awkscript /tmp/sedscript \
# template_values.env
#
#for i in awk sed; do
# etitle "$i" cat "/tmp/${i}script"
#done
template_copy_replace _template-source.txt _template-dest.txt
template_process_userfiles _template_values.env
cat _template-dest.txt

25
bin/donk Executable file
View File

@ -0,0 +1,25 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: donk.help
# par défaut, c'est l'action build
case "$1" in
-h*|--help|--help=*|--help++) ;;
-*) set -- build "$@";;
esac
args=(
"construire des images docker"
"ACTION [options]
$(_donk_show_actions)"
+
-h::section,--help '$_donk_show_help' "Afficher l'aide de la section spécifiée.
Les sections valides sont: ${DONK_HELP_SECTIONS[*]%%:*}"
--help++ '$_donk_show_help' "++Afficher l'aide"
)
parse_args "$@"; set -- "${args[@]}"
action="$1"; shift
require: "donk.$action" || die
"donk_$action" "$@"

69
bin/nlman Executable file
View File

@ -0,0 +1,69 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
NULIB_NO_IMPORT_DEFAULTS=1
source "$(dirname -- "$0")/../load.sh" || exit 1
LIST_FUNCS=
SHOW_MODULE=
SHOW_FUNCS=()
function nulib__define_functions() {
function nulib_check_loaded() {
local module
for module in "${NULIB_LOADED_MODULES[@]}"; do
[ "$module" == "$1" ] && return 0
done
return 1
}
function module:() {
if [ -n "$LIST_FUNCS" ]; then
esection "@$1: $2"
fi
local module
SHOW_MODULE=
for module in "${SHOW_FUNCS[@]}"; do
if [ "$module" == "@$1" ]; then
SHOW_MODULE=1
fi
done
NULIB_MODULE="$1"
if ! nulib_check_loaded "$1"; then
NULIB_LOADED_MODULES+=("$1")
fi
}
function function:() {
if [ -n "$LIST_FUNCS" ]; then
eecho "$1"
elif [ -n "$SHOW_MODULE" ]; then
eecho "$COULEUR_BLEUE>>> $1 <<<$COULEUR_NORMALE"
eecho "$2"
else
local func
for func in "${SHOW_FUNCS[@]}"; do
if [ "$func" == "$1" ]; then
esection "$1"
eecho "$2"
fi
done
fi
}
}
require: DEFAULTS
modules=()
args=(
"afficher l'aide d'une fonction nulib"
"FUNCTIONS|@MODULES..."
-m:,--module: modules "charger des modules supplémentaires"
-l,--list LIST_FUNCS=1 "lister les fonctions pour lesquelles une aide existe"
)
parse_args "$@"; set -- "${args[@]}"
for func in "$@"; do
SHOW_FUNCS+=("$func")
done
NULIB_LOADED_MODULES=(nulib)
require: DEFAULTS
for module in "${modules[@]}"; do
require: "$module"
done

38
bin/nlshell Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
force_reload=
args=(
"lancer un shell avec les fonctions de nulib préchargées"
-r,--force-reload force_reload=1 "forcer le rechargement des modules"
)
parse_args "$@"; set -- "${args[@]}"
ac_set_tmpfile bashrc
echo >"$bashrc" "\
if ! grep -q '/etc/bash.bashrc' /etc/profile; then
[ -f /etc/bash.bashrc ] && source /etc/bash.bashrc
fi
if ! grep -q '~/.bashrc' ~/.bash_profile; then
[ -f ~/.bashrc ] && source ~/.bashrc
fi
[ -f /etc/profile ] && source /etc/profile
[ -f ~/.bash_profile ] && source ~/.bash_profile
# Modifier le PATH. Ajouter aussi le chemin vers les uapps python
PATH=$(qval "$NULIBDIR/bin:$PATH")
if [ -n '$DEFAULT_PS1' ]; then
DEFAULT_PS1=$(qval "[nlshell] $DEFAULT_PS1")
else
if [ -z '$PS1' ]; then
PS1='\\u@\\h \\w \\$ '
fi
PS1=\"[nlshell] \$PS1\"
fi
$(qvals source "$NULIBDIR/load.sh")
NULIB_FORCE_RELOAD=$(qval "$force_reload")"
"$SHELL" --rcfile "$bashrc" -i -- "$@"

61
bin/np Executable file
View File

@ -0,0 +1,61 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git
function git_status() {
local status r cwd
status="$(git status "$@" 2>&1)"; r=$?
if [ -n "$status" ]; then
setx cwd=ppath2 "$(pwd)" "$OrigCwd"
etitle "$cwd"
if [ $r -eq 0 ]; then
echo "$status"
else
eerror "$status"
fi
eend
fi
}
chdir=
all=
args=(
"afficher l'état du dépôt"
"[-d chdir] [-a patterns...]
Si l'option -a est utilisée, ce script accepte comme arguments une liste de patterns permettant de filtrer les répertoires concernés"
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
-a,--all all=1 "faire l'opération sur tous les sous-répertoires de BASEDIR qui sont des dépôts git"
)
parse_args "$@"; set -- "${args[@]}"
setx OrigCwd=pwd
if [ -n "$chdir" ]; then
cd "$chdir" || die
fi
if [ -n "$all" ]; then
# liste de sous répertoires
if [ $# -gt 0 ]; then
# si on a une liste de patterns, l'utiliser
setx -a dirs=ls_dirs . "$@"
else
dirs=()
for dir in */.git; do
[ -d "$dir" ] || continue
dirs+=("${dir%/.git}")
done
fi
setx cwd=pwd
for dir in "${dirs[@]}"; do
cd "$dir" || die
git_status --porcelain
cd "$cwd"
done
else
# répertoire courant uniquement
args=()
isatty || args+=(--porcelain)
git_status "${args[@]}"
fi

30
bin/npci Executable file
View File

@ -0,0 +1,30 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git
projdir=
remote=
what=auto
push=auto
clobber=ask
args=(
"\
valider les modifications locales
si la branche courante est une branche wip, écraser les modifications distantes éventuelles après un avertissement.
sinon, ne mettre à jour la branche locale qu'en mode fast-forward"
"MESSAGE [FILES...]"
-d:,--projdir projdir= "spécifier le projet dans lequel faire la mise à jour"
-o:,--remote remote= "spécifier le remote depuis lequel faire le fetch et vers lequel pousser les modifications"
--auto what=auto "calculer les modifications à valider: soit les fichiers mentionnés, soit ceux de l'index, soit les fichiers modifiés. c'est l'option par défaut"
-a,--all what=all "valider les modifications sur les fichiers modifiés uniquement"
-A,--all-new what=new "valider les modifications sur les fichiers modifiés et rajouter aussi les nouveaux fichiers"
--current push=auto "pousser les modifications sur la branche courante après validation. c'est l'option par défaut"
-p,--push push=1 "pousser les modifications de toutes les branches après la validation"
-l,--no-push push= "ne pas pousser les modifications après la validation"
--clobber clobber=1 "écraser les modifications distantes si la branche courante est une branche wip"
-n,--no-clobber clobber= "ne jamais écraser les modifications distantes, même si la branche courante est une branche wip"
)
parse_args "$@"; set -- "${args[@]}"

22
bin/npp Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git
projdir=
remote=
clobber=ask
args=(
"\
pousser les modifications locales
si la branche courante est une branche wip, écraser les modifications distantes éventuelles après un avertissement.
sinon, ne mettre à jour la branche locale qu'en mode fast-forward"
"MESSAGE [FILES...]"
-d:,--projdir projdir= "spécifier le projet dans lequel faire la mise à jour"
-o:,--remote remote= "spécifier le remote depuis lequel faire le fetch et vers lequel pousser les modifications"
--clobber clobber=1 "écraser les modifications distantes si la branche courante est une branche wip"
-n,--no-clobber clobber= "ne jamais écraser les modifications distantes, même si la branche courante est une branche wip"
)
parse_args "$@"; set -- "${args[@]}"

147
bin/npu Executable file
View File

@ -0,0 +1,147 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git
function _git_update() {
local branch
local -a prbranches crbranches dbranches
setx -a prbranches=git_list_rbranches
git fetch -p "$@" || return
setx -a crbranches=git_list_rbranches
# vérifier s'il y a des branches distantes qui ont été supprimées
for branch in "${prbranches[@]}"; do
if ! array_contains crbranches "$branch"; then
array_add dbranches "${branch#*/}"
fi
done
if [ ${#dbranches[*]} -gt 0 ]; then
setx -a branches=git_list_branches
setx branch=git_get_branch
eimportant "One or more distant branches where deleted"
if git_check_cleancheckout; then
einfo "Delete the obsolete local branches with these commands:"
else
ewarn "Take care of uncommitted local changes first"
einfo "Then delete the obsolete local branches with these commands:"
fi
if array_contains dbranches "$branch"; then
# si la branche courante est l'une des branches à supprimer, il faut
# basculer vers develop ou master
local swto
if [ -z "$swto" ] && array_contains branches develop && ! array_contains dbranches develop; then
swto=develop
fi
if [ -z "$swto" ] && array_contains branches master && ! array_contains dbranches master; then
swto=master
fi
[ -n "$swto" ] && qvals git checkout "$swto"
fi
qvals git branch -D "${dbranches[@]}"
return 1
fi
# intégrer les modifications des branches locales
if ! git_check_cleancheckout; then
setx branch=git_get_branch
setx remote=git_get_branch_remote "$branch"
setx rbranch=git_get_branch_rbranch "$branch" "$remote"
pbranch="${rbranch#refs/remotes/}"
if git merge -q --ff-only "$rbranch"; then
enote "There are uncommitted local changes: only CURRENT branch were updated"
fi
return 0
fi
setx -a branches=git_list_branches
restore_branch=
for branch in "${branches[@]}"; do
setx remote=git_get_branch_remote "$branch"
setx rbranch=git_get_branch_rbranch "$branch" "$remote"
pbranch="${rbranch#refs/remotes/}"
[ -n "$remote" -a -n "$rbranch" ] || continue
if git_is_ancestor "$branch" "$rbranch"; then
if git_should_ff "$branch" "$rbranch"; then
einfo "Fast-forwarding $branch -> $pbranch"
git checkout -q "$branch"
git merge -q --ff-only "$rbranch"
restore_branch=1
fi
else
if [ "$branch" == "$orig_branch" ]; then
echo "* Cannot fast-forward CURRENT branch $branch from $pbranch
Try to merge manually with: git merge $pbranch"
else
echo "* Cannot fast-forward local branch $branch from $pbranch
You can merge manually with: git checkout $branch; git merge $pbranch"
fi
fi
done
[ -n "$restore_branch" ] && git checkout -q "$orig_branch"
return 0
}
function git_update() {
local cwd r
setx cwd=ppath2 "$(pwd)" "$OrigCwd"
etitle "$cwd"
_git_update "$@"; r=$?
eend
return $r
}
chdir=
all=
Remote=
Autoff=1
Reset=ask
args=(
"\
mettre à jour les branches locales
si la branche courante est une branche wip, écraser les modifications locales éventuelles après un avertissement.
sinon, ne mettre à jour la branche locale qu'en mode fast-forward"
#"usage"
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
-a,--all all=1 "faire l'opération sur tous les sous-répertoires de BASEDIR qui sont des dépôts git"
-o:,--remote Remote= "spécifier le remote depuis lequel faire le fetch"
--autoff Autoff=1 "s'il n'y a pas de modifications locales, faire un fast-forward de toutes les branches traquées. c'est l'option par défaut."
-l,--no-autoff Autoff= "ne pas faire de fast-forward automatique des branches traquées. seule la branche courante est mise à jour"
--reset Reset=1 "écraser les modifications locales si la branche courante est une branche wip"
-n,--no-reset Reset= "ne jamais écraser les modifications locales, même si la branche courante est une branche wip"
)
parse_args "$@"; set -- "${args[@]}"
setx OrigCwd=pwd
if [ -n "$chdir" ]; then
cd "$chdir" || die
fi
if [ -n "$all" ]; then
# liste de sous répertoires
if [ $# -gt 0 ]; then
# si on a une liste de patterns, l'utiliser
setx -a dirs=ls_dirs . "$@"
else
dirs=()
for dir in */.git; do
[ -d "$dir" ] || continue
dirs+=("${dir%/.git}")
done
fi
setx cwd=pwd
for dir in "${dirs[@]}"; do
cd "$dir" || die
git_update || die
cd "$cwd"
done
else
# répertoire courant uniquement
args=()
isatty || args+=(--porcelain)
git_update "${args[@]}"
fi

View File

@ -1,7 +1,6 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
MYDIR="$(dirname -- "$0")"; MYNAME="$(basename -- "$0")"
function die() { echo 1>&2 "ERROR: $*"; exit 1; }
source "$(dirname -- "$0")/../load.sh" || exit 1
case "$MYNAME" in
runphp) ;;

38
bin/templ.md Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: fndate
: ${EDITOR:=vim}
autoext=1
args=(
"créer un nouveau fichier .markdown"
"<files...>"
-j,--no-autoext autoext= "ne pas rajouter l'extension .yaml ni .yml"
)
parse_args "$@"; set -- "${args[@]}"
[ $# -gt 0 ] || die "vous devez spécifier les noms des fichiers à créer"
for file in "$@"; do
setx file=fndate_verifix "$file" .md
setx filename=basename -- "$file"
if [[ "$filename" == *.* ]]; then
: # y'a déjà une extension, ne rien faire
elif [ -z "$autoext" ]; then
: # ne pas rajouter d'extension
elif [ -f "$file.markdown" ]; then
file="$file.markdown"
else
file="$file.md"
fi
[ -e "$file" ] && die "$file: fichier existant"
estep "Création de $file"
echo -n >"$file" "\
-*- coding: utf-8 mode: markdown -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8:noeol:binary"
"$EDITOR" "$file"
done

56
bin/templ.sh Executable file
View File

@ -0,0 +1,56 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: fndate
: ${EDITOR:=vim}
executable=1
autoext=1
args=(
"créer un nouveau fichier .sh"
"<files...>"
-x,--exec executable=1 "créer un script exécutable"
-n,--no-exec executable= "créer un fichier non exécutable"
-j,--no-autoext autoext= "ne pas rajouter l'extension .sh"
)
parse_args "$@"; set -- "${args[@]}"
[ $# -gt 0 ] || die "vous devez spécifier les noms des fichiers à créer"
for file in "$@"; do
setx file=fndate_verifix "$file" .sh
setx filename=basename -- "$file"
if [[ "$filename" == *.* ]]; then
: # y'a déjà une extension, ne rien faire
elif [ -z "$autoext" ]; then
: # ne pas rajouter d'extension
else
file="$file.sh"
fi
[ -e "$file" ] && die "$file: fichier existant"
estep "Création de $file"
if [ -n "$executable" ]; then
cat >"$file" <<EOF
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source /etc/nulib.sh || exit 1
args=(
"description"
#"usage"
)
parse_args "\$@"; set -- "\${args[@]}"
EOF
chmod +x "$file"
"$EDITOR" +6 "$file"
else
cat >"$file" <<EOF
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
EOF
"$EDITOR" +2 "$file"
fi
done

40
bin/templ.sql Executable file
View File

@ -0,0 +1,40 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: fndate
: ${EDITOR:=vim}
autoext=1
args=(
"créer un nouveau fichier .sql"
"<files...>"
-j,--no-autoext autoext= "ne pas rajouter l'extension .sql"
)
parse_args "$@"; set -- "${args[@]}"
[ $# -gt 0 ] || die "vous devez spécifier les noms des fichiers à créer"
for file in "$@"; do
setx file=fndate_verifix "$file" .sql
setx filename=basename -- "$file"
if [[ "$filename" == *.* ]]; then
: # y'a déjà une extension, ne rien faire
elif [ -z "$autoext" ]; then
: # ne pas rajouter d'extension
else
file="$file.sql"
fi
[ -e "$file" ] && die "$file: fichier existant"
estep "Création de $file"
cat >"$file" <<EOF
-- -*- coding: utf-8 mode: sql -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=$encoding
-- @database xxx
start transaction;
commit;
EOF
"$EDITOR" +2 "$file"
done

38
bin/templ.yml Executable file
View File

@ -0,0 +1,38 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: fndate
: ${EDITOR:=vim}
autoext=1
args=(
"créer un nouveau fichier .yaml"
"<files...>"
-j,--no-autoext autoext= "ne pas rajouter l'extension .yaml ni .yml"
)
parse_args "$@"; set -- "${args[@]}"
[ $# -gt 0 ] || die "vous devez spécifier les noms des fichiers à créer"
for file in "$@"; do
setx file=fndate_verifix "$file" .yml
setx filename=basename -- "$file"
if [[ "$filename" == *.* ]]; then
: # y'a déjà une extension, ne rien faire
elif [ -z "$autoext" ]; then
: # ne pas rajouter d'extension
elif [ -f "$file.yaml" ]; then
file="$file.yaml"
else
file="$file.yml"
fi
[ -e "$file" ] && die "$file: fichier existant"
estep "Création de $file"
echo >"$file" "\
# -*- coding: utf-8 mode: yaml -*- vim:sw=2:sts=2:et:ai:si:sta:fenc=utf-8
"
"$EDITOR" +3 "$file"
done

View File

@ -9,14 +9,17 @@
}
],
"require": {
"php": ">=7.3"
"php": ">=7.4"
},
"require-dev": {
"nulib/tests": "7.3"
"nulib/tests": "7.4",
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*"
},
"autoload": {
"psr-4": {
"nulib\\": "php/src_base"
"nulib\\": "php/src"
}
},
"autoload-dev": {

190
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "a83db90dff9c8a1e44abc608738042c3",
"content-hash": "0f0743123abf677caf20c5e71ece9616",
"packages": [],
"packages-dev": [
{
@ -138,25 +138,27 @@
},
{
"name": "nikic/php-parser",
"version": "v4.17.1",
"version": "v5.0.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d"
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13",
"reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13",
"shasum": ""
},
"require": {
"ext-ctype": "*",
"ext-json": "*",
"ext-tokenizer": "*",
"php": ">=7.0"
"php": ">=7.4"
},
"require-dev": {
"ircmaxell/php-yacc": "^0.0.7",
"phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0"
"phpunit/phpunit": "^7.0 || ^8.0 || ^9.0"
},
"bin": [
"bin/php-parse"
@ -164,7 +166,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.9-dev"
"dev-master": "5.0-dev"
}
},
"autoload": {
@ -188,17 +190,17 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1"
"source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2"
},
"time": "2023-08-13T19:53:39+00:00"
"time": "2024-03-05T20:51:40+00:00"
},
{
"name": "nulib/tests",
"version": "7.3",
"version": "7.4",
"source": {
"type": "git",
"url": "https://git.univ-reunion.fr/sda-php/nulib-tests.git",
"reference": "8902035bef6ddfe9864675a00844dd14872f6d13"
"reference": "6758a43c241cf51ab3ecff3d2260a583f20c0b19"
},
"require": {
"php": ">=7.3",
@ -207,12 +209,12 @@
"type": "library",
"autoload": {
"psr-4": {
"mur\\tests\\": "src"
"nulib\\tests\\": "src"
}
},
"autoload-dev": {
"psr-4": {
"mur\\tests\\": "tests"
"nulib\\tests\\": "tests"
}
},
"authors": [
@ -222,24 +224,25 @@
}
],
"description": "fonctions et classes pour les tests",
"time": "2023-10-01T11:57:55+00:00"
"time": "2023-10-20T06:17:30+00:00"
},
{
"name": "phar-io/manifest",
"version": "2.0.3",
"version": "2.0.4",
"source": {
"type": "git",
"url": "https://github.com/phar-io/manifest.git",
"reference": "97803eca37d319dfa7826cc2437fc020857acb53"
"reference": "54750ef60c58e43759730615a392c31c80e23176"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53",
"reference": "97803eca37d319dfa7826cc2437fc020857acb53",
"url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176",
"reference": "54750ef60c58e43759730615a392c31c80e23176",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-phar": "*",
"ext-xmlwriter": "*",
"phar-io/version": "^3.0.1",
@ -280,9 +283,15 @@
"description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)",
"support": {
"issues": "https://github.com/phar-io/manifest/issues",
"source": "https://github.com/phar-io/manifest/tree/2.0.3"
"source": "https://github.com/phar-io/manifest/tree/2.0.4"
},
"time": "2021-07-20T11:28:43+00:00"
"funding": [
{
"url": "https://github.com/theseer",
"type": "github"
}
],
"time": "2024-03-03T12:33:53+00:00"
},
{
"name": "phar-io/version",
@ -337,23 +346,23 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.29",
"version": "9.2.31",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76"
"reference": "48c34b5d8d983006bd2adc2d0de92963b9155965"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/6a3a87ac2bbe33b25042753df8195ba4aa534c76",
"reference": "6a3a87ac2bbe33b25042753df8195ba4aa534c76",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/48c34b5d8d983006bd2adc2d0de92963b9155965",
"reference": "48c34b5d8d983006bd2adc2d0de92963b9155965",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.15",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -403,7 +412,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.29"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.31"
},
"funding": [
{
@ -411,7 +420,7 @@
"type": "github"
}
],
"time": "2023-09-19T04:57:46+00:00"
"time": "2024-03-02T06:37:42+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -656,16 +665,16 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.13",
"version": "9.6.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be"
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/f3d767f7f9e191eab4189abe41ab37797e30b1be",
"reference": "f3d767f7f9e191eab4189abe41ab37797e30b1be",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/a1a54a473501ef4cdeaae4e06891674114d79db8",
"reference": "a1a54a473501ef4cdeaae4e06891674114d79db8",
"shasum": ""
},
"require": {
@ -739,7 +748,7 @@
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"security": "https://github.com/sebastianbergmann/phpunit/security/policy",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.13"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.19"
},
"funding": [
{
@ -755,20 +764,20 @@
"type": "tidelift"
}
],
"time": "2023-09-19T05:39:22+00:00"
"time": "2024-04-05T04:35:58+00:00"
},
{
"name": "sebastian/cli-parser",
"version": "1.0.1",
"version": "1.0.2",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/cli-parser.git",
"reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2"
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"reference": "442e7c7e687e42adc03470c7b668bc4b2402c0b2",
"url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
"reference": "2b56bea83a09de3ac06bb18b92f068e60cc6f50b",
"shasum": ""
},
"require": {
@ -803,7 +812,7 @@
"homepage": "https://github.com/sebastianbergmann/cli-parser",
"support": {
"issues": "https://github.com/sebastianbergmann/cli-parser/issues",
"source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.1"
"source": "https://github.com/sebastianbergmann/cli-parser/tree/1.0.2"
},
"funding": [
{
@ -811,7 +820,7 @@
"type": "github"
}
],
"time": "2020-09-28T06:08:49+00:00"
"time": "2024-03-02T06:27:43+00:00"
},
{
"name": "sebastian/code-unit",
@ -1000,20 +1009,20 @@
},
{
"name": "sebastian/complexity",
"version": "2.0.2",
"version": "2.0.3",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/complexity.git",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88"
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/739b35e53379900cc9ac327b2147867b8b6efd88",
"reference": "739b35e53379900cc9ac327b2147867b8b6efd88",
"url": "https://api.github.com/repos/sebastianbergmann/complexity/zipball/25f207c40d62b8b7aa32f5ab026c53561964053a",
"reference": "25f207c40d62b8b7aa32f5ab026c53561964053a",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.7",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3"
},
"require-dev": {
@ -1045,7 +1054,7 @@
"homepage": "https://github.com/sebastianbergmann/complexity",
"support": {
"issues": "https://github.com/sebastianbergmann/complexity/issues",
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.2"
"source": "https://github.com/sebastianbergmann/complexity/tree/2.0.3"
},
"funding": [
{
@ -1053,20 +1062,20 @@
"type": "github"
}
],
"time": "2020-10-26T15:52:27+00:00"
"time": "2023-12-22T06:19:30+00:00"
},
{
"name": "sebastian/diff",
"version": "4.0.5",
"version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/diff.git",
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131"
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131",
"url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/ba01945089c3a293b01ba9badc29ad55b106b0bc",
"reference": "ba01945089c3a293b01ba9badc29ad55b106b0bc",
"shasum": ""
},
"require": {
@ -1111,7 +1120,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/diff/issues",
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.5"
"source": "https://github.com/sebastianbergmann/diff/tree/4.0.6"
},
"funding": [
{
@ -1119,7 +1128,7 @@
"type": "github"
}
],
"time": "2023-05-07T05:35:17+00:00"
"time": "2024-03-02T06:30:58+00:00"
},
{
"name": "sebastian/environment",
@ -1186,16 +1195,16 @@
},
{
"name": "sebastian/exporter",
"version": "4.0.5",
"version": "4.0.6",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/exporter.git",
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d"
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"reference": "ac230ed27f0f98f597c8a2b6eb7ac563af5e5b9d",
"url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/78c00df8f170e02473b682df15bfcdacc3d32d72",
"reference": "78c00df8f170e02473b682df15bfcdacc3d32d72",
"shasum": ""
},
"require": {
@ -1251,7 +1260,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/exporter/issues",
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.5"
"source": "https://github.com/sebastianbergmann/exporter/tree/4.0.6"
},
"funding": [
{
@ -1259,20 +1268,20 @@
"type": "github"
}
],
"time": "2022-09-14T06:03:37+00:00"
"time": "2024-03-02T06:33:00+00:00"
},
{
"name": "sebastian/global-state",
"version": "5.0.6",
"version": "5.0.7",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/global-state.git",
"reference": "bde739e7565280bda77be70044ac1047bc007e34"
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bde739e7565280bda77be70044ac1047bc007e34",
"reference": "bde739e7565280bda77be70044ac1047bc007e34",
"url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
"reference": "bca7df1f32ee6fe93b4d4a9abbf69e13a4ada2c9",
"shasum": ""
},
"require": {
@ -1315,7 +1324,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/global-state/issues",
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.6"
"source": "https://github.com/sebastianbergmann/global-state/tree/5.0.7"
},
"funding": [
{
@ -1323,24 +1332,24 @@
"type": "github"
}
],
"time": "2023-08-02T09:26:13+00:00"
"time": "2024-03-02T06:35:11+00:00"
},
{
"name": "sebastian/lines-of-code",
"version": "1.0.3",
"version": "1.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/lines-of-code.git",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc"
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"reference": "c1c2e997aa3146983ed888ad08b15470a2e22ecc",
"url": "https://api.github.com/repos/sebastianbergmann/lines-of-code/zipball/e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"reference": "e1e4a170560925c26d424b6a03aed157e7dcc5c5",
"shasum": ""
},
"require": {
"nikic/php-parser": "^4.6",
"nikic/php-parser": "^4.18 || ^5.0",
"php": ">=7.3"
},
"require-dev": {
@ -1372,7 +1381,7 @@
"homepage": "https://github.com/sebastianbergmann/lines-of-code",
"support": {
"issues": "https://github.com/sebastianbergmann/lines-of-code/issues",
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.3"
"source": "https://github.com/sebastianbergmann/lines-of-code/tree/1.0.4"
},
"funding": [
{
@ -1380,7 +1389,7 @@
"type": "github"
}
],
"time": "2020-11-28T06:42:11+00:00"
"time": "2023-12-22T06:20:34+00:00"
},
{
"name": "sebastian/object-enumerator",
@ -1559,16 +1568,16 @@
},
{
"name": "sebastian/resource-operations",
"version": "3.0.3",
"version": "3.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/resource-operations.git",
"reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8"
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"reference": "0f4443cb3a1d92ce809899753bc0d5d5a8dd19a8",
"url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
"reference": "05d5692a7993ecccd56a03e40cd7e5b09b1d404e",
"shasum": ""
},
"require": {
@ -1580,7 +1589,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.0-dev"
"dev-main": "3.0-dev"
}
},
"autoload": {
@ -1601,8 +1610,7 @@
"description": "Provides a list of PHP built-in functions that operate on resources",
"homepage": "https://www.github.com/sebastianbergmann/resource-operations",
"support": {
"issues": "https://github.com/sebastianbergmann/resource-operations/issues",
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.3"
"source": "https://github.com/sebastianbergmann/resource-operations/tree/3.0.4"
},
"funding": [
{
@ -1610,7 +1618,7 @@
"type": "github"
}
],
"time": "2020-09-28T06:45:17+00:00"
"time": "2024-03-14T16:00:52+00:00"
},
{
"name": "sebastian/type",
@ -1723,16 +1731,16 @@
},
{
"name": "theseer/tokenizer",
"version": "1.2.1",
"version": "1.2.3",
"source": {
"type": "git",
"url": "https://github.com/theseer/tokenizer.git",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e"
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/34a41e998c2183e22995f158c581e7b5e755ab9e",
"reference": "34a41e998c2183e22995f158c581e7b5e755ab9e",
"url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2",
"shasum": ""
},
"require": {
@ -1761,7 +1769,7 @@
"description": "A small library for converting tokenized PHP source code into XML and potentially other formats",
"support": {
"issues": "https://github.com/theseer/tokenizer/issues",
"source": "https://github.com/theseer/tokenizer/tree/1.2.1"
"source": "https://github.com/theseer/tokenizer/tree/1.2.3"
},
"funding": [
{
@ -1769,7 +1777,7 @@
"type": "github"
}
],
"time": "2021-07-28T10:34:58+00:00"
"time": "2024-03-03T12:36:25+00:00"
}
],
"aliases": [],
@ -1778,8 +1786,12 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": ">=7.3"
"php": ">=7.4"
},
"platform-dev": [],
"plugin-api-version": "2.6.0"
"platform-dev": {
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*"
},
"plugin-api-version": "2.2.0"
}

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=php /g/ /g/
RUN /g/build -a @adminer
EXPOSE 80
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/instantclient AS instantclient
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
COPY --from=instantclient /g/ /g/
COPY --from=instantclient /src/ /src/
RUN /g/build _instantclient_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=php /g/ /g/
RUN /g/build -a @adminer
COPY --from=instantclient /g/ /g/
COPY --from=builder /opt/oracle/ /opt/oracle/
RUN /g/build instantclient
EXPOSE 80
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,19 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/mariadb AS mariadb
FROM $REGISTRY/src/legacytools AS legacytools
FROM mariadb:10
ARG APT_PROXY TIMEZONE
ENV APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=mariadb /g/ /g/
RUN /g/build -a @base @mariadb
COPY --from=legacytools /g/ /g/
RUN /g/build nutools servertools
EXPOSE 3306
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,31 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=php /g/ /g/
RUN /g/build -a @apache-php-cas php-utils
EXPOSE 80 443
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,44 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/legacytools AS legacytools
FROM $REGISTRY/src/instantclient AS instantclient
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
COPY --from=instantclient /g/ /g/
COPY --from=instantclient /src/ /src/
RUN /g/build _instantclient_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=legacytools /g/ /g/
RUN /g/build nutools
COPY --from=php /g/ /g/
RUN /g/build -a @apache-php-cas php-utils
COPY --from=instantclient /g/ /g/
COPY --from=builder /opt/oracle/ /opt/oracle/
RUN /g/build instantclient
EXPOSE 80 443
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,30 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=php /g/ /g/
RUN /g/build @php-cli php-utils
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,43 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG NDIST=12
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/legacytools AS legacytools
FROM $REGISTRY/src/instantclient AS instantclient
FROM $REGISTRY/src/php AS php
################################################################################
FROM debian:${NDIST}-slim AS builder
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=base /src/ /src/
RUN /g/build core lite _builder
RUN /g/build _su-exec_builder
COPY --from=instantclient /g/ /g/
COPY --from=instantclient /src/ /src/
RUN /g/build _instantclient_builder
################################################################################
FROM debian:${NDIST}-slim
ARG APT_MIRROR SEC_MIRROR APT_PROXY TIMEZONE
ENV APT_MIRROR=$APT_MIRROR SEC_MIRROR=$SEC_MIRROR APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=builder /src/su-exec/su-exec /g/
RUN /g/build
COPY --from=legacytools /g/ /g/
RUN /g/build nutools
COPY --from=php /g/ /g/
RUN /g/build @php-cli php-utils
COPY --from=instantclient /g/ /g/
COPY --from=builder /opt/oracle/ /opt/oracle/
RUN /g/build instantclient
ENTRYPOINT ["/g/entrypoint"]

View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG REGISTRY=pubdocker.univ-reunion.fr
FROM $REGISTRY/src/base AS base
FROM $REGISTRY/src/postgres AS postgres
FROM postgres:15-bookworm
ARG APT_PROXY TIMEZONE
ENV APT_PROXY=$APT_PROXY TIMEZONE=$TIMEZONE
COPY --from=base /g/ /g/
COPY --from=postgres /g/ /g/
RUN /g/build -a @base @postgres
RUN /g/pkg i @ssl @git
EXPOSE 5432
ENTRYPOINT ["/g/entrypoint"]

2
lib/profile.d/nulib Normal file
View File

@ -0,0 +1,2 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
__uaddpath "@@dest@@/bin" PATH

11
lib/setup.sh Executable file
View File

@ -0,0 +1,11 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
[ "$(id -u)" -eq 0 ] || die "Ce script doit être lancé avec les droits root"
cd "$MYDIR/.."
[ -n "$1" ] && dest="$1" || dest="$(pwd)"
estep "Maj /etc/nulib.sh"
sed "s|@@""dest""@@|$dest|g" load.sh >/etc/nulib.sh

6
lib/uinst/conf Normal file
View File

@ -0,0 +1,6 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$@" || exit 1
# supprimer les fichiers de VCS
rm -rf "$srcdir/.git"

5
lib/uinst/rootconf Normal file
View File

@ -0,0 +1,5 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$@" || exit 1
"$srcdir/lib/setup.sh" "$dest"

182
load.sh Normal file
View File

@ -0,0 +1,182 @@
##@cooked comments # -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## Charger nulib et rendre disponible les modules bash, awk, php et python
##@cooked nocomments
# Ce fichier doit être sourcé en premier. Si ce fichier n'est pas sourcé, alors
# le répertoire nulib doit être disponible dans le répertoire du script qui
# inclue ce fichier.
# Une fois ce fichier sourcé, les autres modules peuvent être importés avec
# require:() e.g.
# source /etc/nulib.sh || exit 1
# require: other_modules
# ou pour une copie locale de nulib:
# source "$(dirname "$0")/nulib/load.sh" || exit 1
# require: other_modules
# vérifier version minimum de bash
if [ "x$BASH" = "x" ]; then
echo "ERROR: nulib: this script requires bash"
exit 1
fi
function eerror() { echo "ERROR: $*" 1>&2; }
function die() { [ $# -gt 0 ] && eerror "$*"; exit 1; }
function edie() { [ $# -gt 0 ] && eerror "$*"; return 1; }
function delpath() { 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 addpath() { 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 inspathm() { 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 inspath() { delpath "$@"; inspathm "$@"; }
if [ ${BASH_VERSINFO[0]} -ge 5 -o \( ${BASH_VERSINFO[0]} -eq 4 -a ${BASH_VERSINFO[1]} -ge 1 \) ]; then :
elif [ -n "$NULIB_IGNORE_BASH_VERSION" ]; then :
else die "nulib: bash 4.1+ is required"
fi
# Calculer emplacement de nulib
NULIBDIR="@@dest@@"
if [ "$NULIBDIR" = "@@""dest""@@" ]; then
# La valeur "@@"dest"@@" n'est remplacée que dans la copie de ce script
# faite dans /etc. Sinon, il faut toujours faire le calcul. Cela permet de
# déplacer la librairie n'importe où sur le disque, ce qui est
# particulièrement intéressant quand on fait du déploiement.
NULIBDIR="${BASH_SOURCE[0]}"
if [ -f "$NULIBDIR" -a "$(basename -- "$NULIBDIR")" == load.sh ]; then
# Fichier sourcé depuis nulib/
NULIB_SOURCED=1
NULIBDIR="$(dirname -- "$NULIBDIR")"
elif [ -f "$NULIBDIR" -a "$(basename -- "$NULIBDIR")" == nulib.sh ]; then
# Fichier sourcé depuis nulib/bash/src
NULIB_SOURCED=1
NULIBDIR="$(dirname -- "$NULIBDIR")/../.."
else
# Fichier non sourcé. Tout exprimer par rapport au script courant
NULIB_SOURCED=
NULIBDIR="$(dirname -- "$0")"
if [ -d "$NULIBDIR/nulib" ]; then
NULIBDIR="$NULIBDIR/nulib"
elif [ -d "$NULIBDIR/lib/nulib" ]; then
NULIBDIR="$NULIBDIR/lib/nulib"
fi
fi
elif [ "${BASH_SOURCE[0]}" = /etc/nulib.sh ]; then
# Fichier chargé depuis /etc/nulib.sh
NULIB_SOURCED=1
fi
NULIBDIR="$(cd "$NULIBDIR" 2>/dev/null; pwd)"
NULIBDIRS=("$NULIBDIR/bash/src")
# marqueur pour vérifier que nulib a réellement été chargé. il faut avoir $NULIBINIT == $NULIBDIR
# utilisé par le module base qui doit pouvoir être inclus indépendamment
NULIBINIT="$NULIBDIR"
## Modules bash
NULIB_LOADED_MODULES=(nulib)
NULIB_DEFAULT_MODULES=(base pretty sysinfos)
# Si cette variable est non vide, require: recharge toujours le module, même
# s'il a déjà été chargé. Cette valeur n'est pas transitive: il faut toujours
# recharger explicitement tous les modules désirés
NULIB_FORCE_RELOAD=
function nulib__define_functions() {
function nulib_check_loaded() {
local module
for module in "${NULIB_LOADED_MODULES[@]}"; do
[ "$module" == "$1" ] && return 0
done
return 1
}
function module:() {
NULIB_MODULE="$1"
if ! nulib_check_loaded "$1"; then
NULIB_LOADED_MODULES+=("$1")
fi
}
function function:() {
:
}
}
function nulib__load:() {
local nl__module nl__nulibdir nl__found
[ $# -gt 0 ] || set DEFAULTS
for nl__module in "$@"; do
nl__found=
for nl__nulibdir in "${NULIBDIRS[@]}"; do
if [ -f "$nl__nulibdir/$nl__module.sh" ]; then
source "$nl__nulibdir/$nl__module.sh" || die
nl__found=1
break
fi
done
[ -n "$nl__found" ] || die "nulib: unable to find module $nl__module in (${NULIBDIRS[*]})"
done
}
function nulib__require:() {
local nr__module nr__nulibdir nr__found
[ $# -gt 0 ] || set DEFAULTS
# sauvegarder valeurs globales
local nr__orig_module="$NULIB_MODULE"
NULIB_MODULE=
# garder une copie de la valeur originale et casser la transitivité
local nr__force_reload="$NULIB_FORCE_RELOAD"
local NULIB_FORCE_RELOAD
for nr__module in "$@"; do
nr__found=
for nr__nulibdir in "${NULIBDIRS[@]}"; do
if [ -f "$nr__nulibdir/$nr__module.sh" ]; then
nr__found=1
if [ -n "$nr__force_reload" ] || ! nulib_check_loaded "$nr__module"; then
NULIB_LOADED_MODULES+=("$nr__module")
source "$nr__nulibdir/$nr__module.sh" || die
fi
break
fi
done
if [ -z "$nr__found" -a "$nr__module" == DEFAULTS ]; then
for nr__module in "${NULIB_DEFAULT_MODULES[@]}"; do
if [ -f "$nr__nulibdir/$nr__module.sh" ]; then
nr__found=1
if [ -n "$nr__force_reload" ] || ! nulib_check_loaded "$nr__module"; then
NULIB_LOADED_MODULES+=("$nr__module")
source "$nr__nulibdir/$nr__module.sh" || die
fi
else
break
fi
done
fi
[ -n "$nr__found" ] || die "nulib: unable to find module $nr__module in (${NULIBDIRS[*]})"
done
# restaurer valeurs globales
NULIB_MODULE="$nr__orig_module"
}
# désactiver set -x
NULIB__DISABLE_SET_X='local NULIB__SET_X; [ -z "$NULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; NULIB__SET_X=1; }'
NULIB__ENABLE_SET_X='[ -n "$NULIB__SET_X" ] && set -x'
# désactiver set -x de manière réentrante
NULIB__RDISABLE_SET_X='[ -z "$NULIB_NO_DISABLE_SET_X" ] && [[ $- == *x* ]] && { set +x; local NULIB_REQUIRE_SET_X=1; }; if [ -n "$NULIB_REQUIRE_SET_X" ]; then [ -n "$NULIB_REQUIRE_SET_X_RL1" ] || local NULIB_REQUIRE_SET_X_RL1; local NULIB_REQUIRE_SET_X_RL2=$RANDOM; [ -n "$NULIB_REQUIRE_SET_X_RL1" ] || NULIB_REQUIRE_SET_X_RL1=$NULIB_REQUIRE_SET_X_RL2; fi'
NULIB__RENABLE_SET_X='[ -n "$NULIB_REQUIRE_SET_X" -a "$NULIB_REQUIRE_SET_X_RL1" == "$NULIB_REQUIRE_SET_X_RL2" ] && set -x'
function require:() {
eval "$NULIB__RDISABLE_SET_X"
nulib__define_functions
nulib__require: "$@"
eval "$NULIB__RENABLE_SET_X"
return 0
}
## Autres modules
[ -d "$NULIBDIR/awk/src" ] && inspath "$NULIBDIR/awk/src" AWKPATH; export AWKPATH
[ -d "$NULIBDIR/python3/src" ] && inspath "$NULIBDIR/python3/src" PYTHONPATH; export PYTHONPATH
## Auto import DEFAULTS
nulib__define_functions
if [ -n "$NULIB_SOURCED" -a -z "$NULIB_NO_IMPORT_DEFAULTS" ]; then
require: DEFAULTS
fi

216
php/src/A.php Normal file
View File

@ -0,0 +1,216 @@
<?php
namespace nulib;
use Traversable;
/**
* Class A: gestion de tableaux ou d'instances de {@link IArrayWrapper}
*
* contrairement à {@link cl}, les méthodes de cette classes sont plutôt conçues
* pour modifier le tableau en place
*/
class A {
/**
* s'assurer que $array est un array non null. retourner true si $array n'a
* pas été modifié (s'il était déjà un array), false sinon.
*/
static final function ensure_array(&$array): bool {
if (is_array($array)) return true;
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
if ($array === null || $array === false) $array = [];
elseif ($array instanceof Traversable) $array = cl::all($array);
else $array = [$array];
return false;
}
/**
* s'assurer que $array est un array s'il est non null. retourner true si
* $array n'a pas été modifié (s'il était déjà un array ou s'il valait null).
*/
static final function ensure_narray(&$array): bool {
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
if ($array === null || is_array($array)) return true;
if ($array === false) $array = [];
elseif ($array instanceof Traversable) $array = cl::all($array);
else $array = [$array];
return false;
}
/**
* s'assurer que $array est un tableau de $size éléments, en complétant avec
* des occurrences de $default si nécessaire
*
* @return bool true si le tableau a été modifié, false sinon
*/
static final function ensure_size(?array &$array, int $size, $default=null): bool {
$modified = false;
if ($array === null) {
$array = [];
$modified = true;
}
if ($size < 0) return $modified;
$count = count($array);
if ($count == $size) return $modified;
if ($count < $size) {
# agrandir le tableau
while ($count++ < $size) {
$array[] = $default;
}
return true;
}
# rétrécir le tableau
$tmparray = [];
foreach ($array as $key => $value) {
if ($size-- == 0) break;
$tmparray[$key] = $value;
}
$array = $tmparray;
return true;
}
static function merge(&$dest, ...$merges): void {
self::ensure_narray($dest);
$dest = cl::merge($dest, ...$merges);
}
static function merge2(&$dest, ...$merges): void {
self::ensure_narray($dest);
$dest = cl::merge2($dest, ...$merges);
}
static final function select(&$dest, ?array $mappings, bool $inverse=false): void {
self::ensure_narray($dest);
$dest = cl::select($dest, $mappings, $inverse);
}
static final function selectm(&$dest, ?array $mappings, ?array $merge=null): void {
self::ensure_narray($dest);
$dest = cl::selectm($dest, $mappings, $merge);
}
static final function mselect(&$dest, ?array $merge, ?array $mappings): void {
self::ensure_narray($dest);
$dest = cl::mselect($dest, $merge, $mappings);
}
static final function pselect(&$dest, ?array $pkeys): void {
self::ensure_narray($dest);
$dest = cl::pselect($dest, $pkeys);
}
static final function pselectm(&$dest, ?array $pkeys, ?array $merge=null): void {
self::ensure_narray($dest);
$dest = cl::pselectm($dest, $pkeys, $merge);
}
static final function mpselect(&$dest, ?array $merge, ?array $pkeys): void {
self::ensure_narray($dest);
$dest = cl::mpselect($dest, $merge, $pkeys);
}
static final function set_nn(&$dest, $key, $value) {
self::ensure_narray($dest);
if ($value !== null) {
if ($key === null) $dest[] = $value;
else $dest[$key] = $value;
}
return $value;
}
static final function append_nn(&$dest, $value) {
return self::set_nn($dest, null, $value);
}
static final function set_nz(&$dest, $key, $value) {
self::ensure_narray($dest);
if ($value !== null && $value !== false) {
if ($key === null) $dest[] = $value;
else $dest[$key] = $value;
}
return $value;
}
static final function append_nz(&$dest, $value) {
self::ensure_narray($dest);
return self::set_nz($dest, null, $value);
}
static final function prepend_nn(&$dest, $value) {
self::ensure_narray($dest);
if ($value !== null) {
if ($dest === null) $dest = [];
array_unshift($dest, $value);
}
return $value;
}
static final function prepend_nz(&$dest, $value) {
self::ensure_narray($dest);
if ($value !== null && $value !== false) {
if ($dest === null) $dest = [];
array_unshift($dest, $value);
}
return $value;
}
static final function replace_nx(&$dest, $key, $value) {
self::ensure_narray($dest);
if ($dest !== null && !array_key_exists($key, $dest)) {
return $dest[$key] = $value;
} else {
return $dest[$key] ?? null;
}
}
static final function replace_n(&$dest, $key, $value) {
self::ensure_narray($dest);
$pvalue = $dest[$key] ?? null;
if ($pvalue === null) $dest[$key] = $value;
return $pvalue;
}
static final function replace_z(&$dest, $key, $value) {
self::ensure_narray($dest);
$pvalue = $dest[$key] ?? null;
if ($pvalue === null || $pvalue === false) $dest[$key] = $value;
return $pvalue;
}
static final function pop(&$dest, $key, $default=null) {
if ($dest === null) return $default;
self::ensure_narray($dest);
if ($key === null) return array_pop($dest);
$value = $dest[$key] ?? $default;
unset($dest[$key]);
return $value;
}
static final function popx(&$dest, ?array $keys): array {
$values = [];
if ($dest === null) return $values;
self::ensure_narray($dest);
if ($keys === null) return $values;
foreach ($keys as $key) {
$values[$key] = self::pop($dest, $key);
}
return $values;
}
static final function filter_if(&$dest, callable $cond): void {
self::ensure_narray($dest);
$dest = cl::filter_if($dest, $cond);
}
static final function filter_z($dest): void { self::filter_if($dest, [cv::class, "z"]);}
static final function filter_nz($dest): void { self::filter_if($dest, [cv::class, "nz"]);}
static final function filter_n($dest): void { self::filter_if($dest, [cv::class, "n"]);}
static final function filter_nn($dest): void { self::filter_if($dest, [cv::class, "nn"]);}
static final function filter_t($dest): void { self::filter_if($dest, [cv::class, "t"]);}
static final function filter_f($dest): void { self::filter_if($dest, [cv::class, "f"]);}
static final function filter_pt($dest): void { self::filter_if($dest, [cv::class, "pt"]);}
static final function filter_pf($dest): void { self::filter_if($dest, [cv::class, "pf"]);}
static final function filter_equals($dest, $value): void { self::filter_if($dest, cv::equals($value)); }
static final function filter_not_equals($dest, $value): void { self::filter_if($dest, cv::not_equals($value)); }
static final function filter_same($dest, $value): void { self::filter_if($dest, cv::same($value)); }
static final function filter_not_same($dest, $value): void { self::filter_if($dest, cv::not_same($value)); }
}

View File

@ -1,13 +1,18 @@
<?php
namespace nulib;
use Exception;
/**
* Class AccessException: indiquer que la resource ou l'objet auquel on veut
* accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur
*/
class AccessException extends Exception {
class AccessException extends UserException {
static final function read_only(?string $dest=null, ?string $prefix=null): self {
if ($prefix) $prefix = "$prefix: ";
if ($dest === null) $dest = "this property";
$message = "$dest is read-only";
return new static($prefix.$message);
}
static final function immutable_object(?string $dest=null, ?string $prefix=null): self {
if ($prefix) $prefix = "$prefix: ";
if ($dest === null) $dest = "this object";

101
php/src/ExceptionShadow.php Normal file
View File

@ -0,0 +1,101 @@
<?php
namespace nulib;
use Throwable;
/**
* Class ExceptionShadow: une classe qui capture les informations d'une
* exception afin de pouvoir les sérialiser
*/
class ExceptionShadow {
protected static function extract_trace(array $trace): array {
$frames = [];
foreach ($trace as $frame) {
$file = cl::get($frame, "file");
$line = cl::get($frame, "line");
$class = cl::get($frame, "class");
$function = cl::get($frame, "function");
$type = cl::get($frame, "type");
$frames[] = [
"file" => $file,
"line" => $line,
"class" => $class,
"object" => null,
"type" => $type,
"function" => $function,
"args" => [],
];
}
return $frames;
}
function __construct(Throwable $exception) {
$this->class = get_class($exception);
$this->message = $exception->getMessage();
$this->code = $exception->getCode();
$this->file = $exception->getFile();
$this->line = $exception->getLine();
$this->trace = self::extract_trace($exception->getTrace());
$previous = $exception->getPrevious();
if ($previous !== null) $this->previous = new static($previous);
}
/** @var string */
protected $class;
function getClass(): string {
return $this->class;
}
/** @var string */
protected $message;
function getMessage(): string {
return $this->message;
}
/** @var mixed */
protected $code;
function getCode() {
return $this->code;
}
/** @var string */
protected $file;
function getFile(): string {
return $this->file;
}
/** @var int */
protected $line;
function getLine(): int {
return $this->line;
}
/** @var array */
protected $trace;
function getTrace(): array {
return $this->trace;
}
function getTraceAsString(): string {
$lines = [];
foreach ($this->trace as $index => $frame) {
$lines[] = "#$index $frame[file]($frame[line]): $frame[class]$frame[type]$frame[function]()";
}
$index++;
$lines[] = "#$index {main}";
return implode("\n", $lines);
}
/** @var ExceptionShadow */
protected $previous;
function getPrevious(): ?ExceptionShadow {
return $this->previous;
}
}

31
php/src/ExitError.php Normal file
View File

@ -0,0 +1,31 @@
<?php
namespace nulib;
use Error;
use Throwable;
/**
* Class ExitException: une exception qui indique que l'application souhaite
* quitter normalement, avec éventuellement un code d'erreur.
*/
class ExitError extends Error {
function __construct(int $exitcode=0, $userMessage=null, Throwable $previous=null) {
parent::__construct(null, $exitcode, $previous);
$this->userMessage = $userMessage;
}
function isError(): bool {
return $this->getCode() !== 0;
}
/** @var ?string */
protected $userMessage;
function haveMessage(): bool {
return $this->userMessage !== null;
}
function getUserMessage(): ?string {
return $this->userMessage;
}
}

11
php/src/IArrayWrapper.php Normal file
View File

@ -0,0 +1,11 @@
<?php
namespace nulib;
/**
* Interface IArrayWrapper: un objet qui encapsule un array auquel on peut
* accéder
*/
interface IArrayWrapper {
/** retourne une référence sur l'array encapsulé */
function &wrappedArray(): ?array;
}

View File

@ -0,0 +1,10 @@
<?php
namespace nulib;
use RuntimeException;
/**
* Class NoMoreDataException: indiquer que plus aucune donnée n'est disponible
*/
class NoMoreDataException extends RuntimeException {
}

View File

@ -1,13 +1,13 @@
<?php
namespace nulib;
use Exception;
use LogicException;
/**
* Class StateException: indiquer que l'état dans lequel on se trouve est
* inattendu: il s'agit donc d'un bug
*/
class StateException extends Exception {
class StateException extends LogicException {
static final function not_implemented(?string $method=null, ?string $prefix=null): self {
if ($method === null) $method = "this method";
$message = "$method is not implemented";

12
php/src/StopException.php Normal file
View File

@ -0,0 +1,12 @@
<?php
namespace nulib;
use Exception;
/**
* Class StopException: une exception qui par convention est envoyée à un
* générateur pour indiquer qu'il doit s'arrêter "proprement". cela peut être
* utilisé pour implémenter des itérateur "closeable"
*/
class StopException extends Exception {
}

90
php/src/UserException.php Normal file
View File

@ -0,0 +1,90 @@
<?php
namespace nulib;
use RuntimeException;
use Throwable;
/**
* Class UserException: une exception qui peut en plus contenir un message
* utilisateur
*/
class UserException extends RuntimeException {
/** @param Throwable|ExceptionShadow $e */
static function get_user_message($e): ?string {
if ($e instanceof self) return $e->getUserMessage();
else return null;
}
/** @param Throwable|ExceptionShadow $e */
static final function get_user_summary($e): string {
$parts = [];
$first = true;
while ($e !== null) {
$message = self::get_user_message($e);
if (!$message) $message = "(no message)";
if ($first) $first = false;
else $parts[] = "caused by ";
$parts[] = get_class($e) . ": " . $message;
$e = $e->getPrevious();
}
return implode(", ", $parts);
}
/** @param Throwable|ExceptionShadow $e */
static function get_message($e): ?string {
$message = $e->getMessage();
if (!$message && $e instanceof self) $message = $e->getUserMessage();
return $message;
}
/** @param Throwable|ExceptionShadow $e */
static final function get_summary($e): string {
$parts = [];
$first = true;
while ($e !== null) {
$message = self::get_message($e);
if (!$message) $message = "(no message)";
if ($first) $first = false;
else $parts[] = "caused by ";
if ($e instanceof ExceptionShadow) $class = $e->getClass();
else $class = get_class($e);
$parts[] = "$class: $message";
$e = $e->getPrevious();
}
return implode(", ", $parts);
}
/** @param Throwable|ExceptionShadow $e */
static final function get_traceback($e): string {
$tbs = [];
$previous = false;
while ($e !== null) {
if (!$previous) {
$efile = $e->getFile();
$eline = $e->getLine();
$tbs[] = "at $efile($eline)";
} else {
$tbs[] = "~~ caused by: " . self::get_summary($e);
}
$tbs[] = $e->getTraceAsString();
$e = $e->getPrevious();
$previous = true;
#XXX il faudrait ne pas réinclure les lignes communes aux exceptions qui
# ont déjà été affichées
}
return implode("\n", $tbs);
}
function __construct($userMessage, $techMessage=null, $code=0, ?Throwable $previous=null) {
$this->userMessage = $userMessage;
if ($techMessage === null) $techMessage = $userMessage;
parent::__construct($techMessage, $code, $previous);
}
/** @var ?string */
protected $userMessage;
function getUserMessage(): ?string {
return $this->userMessage;
}
}

View File

@ -0,0 +1,71 @@
<?php
namespace nulib;
/**
* Class ValueException: indiquer qu'une valeur est invalide
*/
class ValueException extends UserException {
private static function value($value): string {
if (is_object($value)) {
return "<".get_class($value).">";
} elseif (is_array($value)) {
$values = $value;
$parts = [];
$index = 0;
foreach ($values as $key => $value) {
if ($key === $index) {
$index++;
$parts[] = self::value($value);
} else {
$parts[] = "$key=>".self::value($value);
}
}
return "[" . implode(", ", $parts) . "]";
} elseif (is_string($value)) {
return $value;
} else {
return var_export($value, true);
}
}
private static function message($value, ?string $message, ?string $kind, ?string $prefix, ?string $suffix): string {
if ($kind === null) $kind = "value";
if ($message === null) $message = "$kind$suffix";
if ($value !== null) {
$value = self::value($value);
if ($prefix) $prefix = "$prefix: $value";
else $prefix = $value;
}
if ($prefix) $prefix = "$prefix: ";
return $prefix.$message;
}
static final function null(?string $kind=null, ?string $prefix=null, ?string $message=null): self {
return new static(self::message(null, $message, $kind, $prefix, " should not be null"));
}
static final function invalid_kind($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self {
return new static(self::message($value, $message, $kind, $prefix, " is invalid"));
}
static final function invalid_key($value, ?string $prefix=null, ?string $message=null): self {
return self::invalid_kind($value, "key", $prefix, $message);
}
static final function invalid_value($value, ?string $prefix=null, ?string $message=null): self {
return self::invalid_kind($value, "value", $prefix, $message);
}
static final function invalid_type($value, string $expected_type): self {
return new static(self::message($value, null, "type", null, " is invalid, expected $expected_type"));
}
static final function invalid_class($class, string $expected_class): self {
if (is_object($class)) $class = get_class($class);
return new static(self::message($class, null, "class", null, " is invalid, expected $expected_class"));
}
static final function forbidden($value=null, ?string $kind=null, ?string $prefix=null, ?string $message=null): self {
return new static(self::message($value, $message, $kind, $prefix, " is forbidden"));
}
}

89
php/src/app/LockFile.php Normal file
View File

@ -0,0 +1,89 @@
<?php
namespace nulib\app;
use nulib\cl;
use nulib\file\SharedFile;
use nulib\output\msg;
use nulib\php\time\DateTime;
/**
* Class LockFile: une classe qui permet à une application de verrouiller
* certaines actions
*/
class LockFile {
const NAME = null;
const TITLE = null;
function __construct($file, ?string $name=null, ?string $title=null) {
$this->file = new SharedFile($file);
$this->name = $name ?? static::NAME;
$this->title = $title ?? static::TITLE;
}
/** @var SharedFile */
protected $file;
/** @var ?string */
protected $name;
/** @var ?string */
protected $title;
protected function initData(): array {
return [
"name" => $this->name,
"title" => $this->title,
"locked" => false,
"date_lock" => null,
"date_release" => null,
];
}
function read(bool $close=true): array {
$data = $this->file->unserialize(null, $close);
if (!is_array($data)) $data = $this->initData();
return $data;
}
function isLocked(?array &$data=null): bool {
$data = $this->read();
return $data["locked"];
}
function warnIfLocked(?array $data=null): bool {
if ($data === null) $data = $this->read();
if ($data["locked"]) {
msg::warning("$data[name]: possède le verrou depuis $data[date_lock] -- $data[title]");
return true;
}
return false;
}
function lock(?array &$data=null): bool {
$file = $this->file;
$data = $this->read(false);
if ($data["locked"]) {
$file->close();
return false;
} else {
$file->ftruncate();
$file->serialize(cl::merge($data, [
"locked" => true,
"date_lock" => new DateTime(),
"date_release" => null,
]));
return true;
}
}
function release(?array &$data=null): void {
$file = $this->file;
$data = $this->read(false);
$file->ftruncate();
$file->serialize(cl::merge($data, [
"locked" => false,
"date_release" => new DateTime(),
]));
}
}

354
php/src/app/RunFile.php Normal file
View File

@ -0,0 +1,354 @@
<?php
namespace nulib\app;
use nulib\cl;
use nulib\file\SharedFile;
use nulib\os\path;
use nulib\output\msg;
use nulib\php\time\DateTime;
use nulib\str;
/**
* Class RunFile: une classe permettant de suivre le fonctionnement d'une
* application qui tourne en tâche de fond
*/
class RunFile {
const RUN_EXT = ".run";
const LOCK_EXT = ".lock";
const NAME = null;
function __construct(?string $name, string $file, ?string $logfile=null) {
$file = path::ensure_ext($file, self::RUN_EXT);
$this->name = $name ?? static::NAME;
$this->file = new SharedFile($file);
$this->logfile = $logfile;
}
protected ?string $name;
protected SharedFile $file;
protected ?string $logfile;
function getLogfile(): ?string {
return $this->logfile;
}
protected static function merge(array $data, array $merge): array {
return cl::merge($data, [
"serial" => $data["serial"] + 1,
], $merge);
}
protected function initData(bool $forStart=true): array {
if ($forStart) {
$pid = posix_getpid();
$dateStart = new DateTime();
} else {
$pid = $dateStart = null;
}
return [
"name" => $this->name,
"id" => bin2hex(random_bytes(16)),
"pg_pid" => null,
"pid" => $pid,
"serial" => 0,
# lock
"locked" => false,
"date_lock" => null,
"date_release" => null,
# run
"logfile" => $this->logfile,
"date_start" => $dateStart,
"date_stop" => null,
"exitcode" => null,
"is_done" => null,
# action
"action" => null,
"action_date_start" => null,
"action_current_step" => null,
"action_max_step" => null,
"action_date_step" => null,
];
}
function read(): array {
$data = $this->file->unserialize();
if (!is_array($data)) $data = $this->initData(false);
return $data;
}
protected function willWrite(): array {
$file = $this->file;
$file->lockWrite();
$data = $file->unserialize(null, false, true);
if (!is_array($data)) {
$data = $this->initData(false);
$file->ftruncate();
$file->serialize($data, false, true);
}
return [$file, $data];
}
protected function serialize(SharedFile $file, array $data, ?array $merge=null): void {
$file->ftruncate();
$file->serialize(self::merge($data, $merge), true, true);
}
protected function update(callable $func): void {
/** @var SharedFile$file */
[$file, $data] = $this->willWrite();
$merge = call_user_func($func, $data);
if ($merge !== null && $merge !== false) {
$this->serialize($file, $data, $merge);
} else {
$file->cancelWrite();
}
}
function haveWorked(int $serial, ?int &$currentSerial=null, ?array $data=null): bool {
$data ??= $this->read();
$currentSerial = $data["serial"];
return $serial !== $currentSerial;
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# verrouillage par défaut
function isLocked(?array &$data=null): bool {
$data = $this->read();
return $data["locked"];
}
function warnIfLocked(?array $data=null): bool {
$data ??= $this->read();
if ($data["locked"]) {
msg::warning("$data[name]: possède le verrou depuis $data[date_lock]");
return true;
}
return false;
}
function lock(): bool {
$this->update(function ($data) use (&$locked) {
if ($data["locked"]) {
$locked = false;
return null;
} else {
$locked = true;
return [
"locked" => true,
"date_lock" => new DateTime(),
"date_release" => null,
];
}
});
return $locked;
}
function release(): void {
$this->update(function ($data) {
return [
"locked" => false,
"date_release" => new DateTime(),
];
});
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# cycle de vie de l'application
/**
* indiquer que l'application démarre. l'état est entièrement réinitialisé,
* sauf le PID du leader qui est laissé en l'état
*/
function wfStart(): void {
$this->update(function (array $data) {
return cl::merge($this->initData(), [
"pg_pid" => $data["pg_pid"],
]);
});
}
/** tester si l'application a déjà été démarrée au moins une fois */
function wasStarted(?array $data=null): bool {
$data ??= $this->read();
return $data["date_start"] !== null;
}
/** tester si l'application est démarrée et non arrêtée */
function isStarted(?array $data=null): bool {
$data ??= $this->read();
return $data["date_start"] !== null && $data["date_stop"] === null;
}
/**
* vérifier si l'application marquée comme démarrée tourne réellement
*/
function isRunning(?array $data=null): bool {
$data ??= $this->read();
if ($data["date_start"] === null) return false;
if ($data["date_stop"] !== null) return false;
if (!posix_kill($data["pid"], 0)) {
switch (posix_get_last_error()) {
case 1: #PCNTL_EPERM:
# process auquel on n'a pas accès?! est-ce un autre process qui a
# réutilisé le PID?
return false;
case 3: #PCNTL_ESRCH:
# process inexistant
return false;
case 22: #PCNTL_EINVAL:
# ne devrait pas se produire
return false;
}
}
# process existant auquel on a accès
return true;
}
/** indiquer que l'application s'arrête */
function wfStop(): void {
$this->update(function (array $data) {
return ["date_stop" => new DateTime()];
});
}
/** tester si l'application est déjà été stoppée au moins une fois */
function wasStopped(?array $data=null): bool {
$data ??= $this->read();
return $data["date_stop"] !== null;
}
/** tester si l'application a été démarrée puis arrêtée */
function isStopped(?array $data=null): bool {
$data ??= $this->read();
return $data["date_start"] !== null && $data["date_stop"] !== null;
}
/** après l'arrêt de l'application, mettre à jour le code de retour */
function wfStopped(int $exitcode): void {
$this->update(function (array $data) use ($exitcode) {
return [
"pg_pid" => null,
"date_stop" => $data["date_stop"] ?? new DateTime(),
"exitcode" => $exitcode,
];
});
}
/**
* comme {@link self::isStopped()} mais ne renvoie true qu'une seule fois si
* $updateDone==true
*/
function isDone(?array &$data=null, bool $updateDone=true): bool {
$done = false;
$this->update(function (array $ldata) use (&$done, &$data, $updateDone) {
$data = $ldata;
if ($data["date_start"] === null || $data["date_stop"] === null || $data["is_done"]) {
return false;
}
$done = true;
if ($updateDone) return ["is_done" => $done];
else return null;
});
return $done;
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# gestion des actions
/** indiquer le début d'une action */
function action(?string $title, ?int $maxSteps=null): void {
$this->update(function (array $data) use ($title, $maxSteps) {
return [
"action" => $title,
"action_date_start" => new DateTime(),
"action_max_step" => $maxSteps,
"action_current_step" => 0,
];
});
}
/** indiquer qu'une étape est franchie dans l'action en cours */
function step(int $nbSteps=1): void {
$this->update(function (array $data) use ($nbSteps) {
return [
"action_date_step" => new DateTime(),
"action_current_step" => $data["action_current_step"] + $nbSteps,
];
});
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Divers
function getLockFile(?string $name=null, ?string $title=null): LockFile {
$ext = self::LOCK_EXT;
if ($name !== null) $ext = ".$name$ext";
$file = path::ensure_ext($this->file->getFile(), $ext, self::RUN_EXT);
$name = str::join("/", [$this->name, $name]);
return new LockFile($file, $name, $title);
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Gestionnaire de tâches (tm_*)
/** démarrer un groupe de process dont le process courant est le leader */
function tm_startPg(): void {
$this->update(function (array $data) {
posix_setsid();
return [
"pg_pid" => posix_getpid(),
];
});
}
/**
* vérifier si on est dans le cas la tâche devrait tourner mais en réalité
* ce n'est pas le cas
*/
function tm_isUndead(?int $pid=null): bool {
$data = $this->read();
if ($data["date_start"] === null) return false;
if ($data["date_stop"] !== null) return false;
$pid ??= $data["pid"];
if (!posix_kill($pid, 0)) {
switch (posix_get_last_error()) {
case 1: #PCNTL_EPERM:
# process auquel on n'a pas accès?! est-ce un autre process qui a
# réutilisé le PID?
return false;
case 3: #PCNTL_ESRCH:
# process inexistant
return true;
case 22: #PCNTL_EINVAL:
# ne devrait pas se produire
return false;
}
}
# process existant auquel on a accès
return false;
}
function tm_isReapable(): bool {
$data = $this->read();
return $data["date_stop"] !== null && $data["exitcode"] === null;
}
/** marquer la tâche comme terminée */
function tm_reap(?int $pid=null): void {
$data = $this->read();
$pid ??= $data["pid"];
pcntl_waitpid($pid, $status);
$exitcode = pcntl_wifexited($status)? pcntl_wexitstatus($status): 127;
$this->update(function (array $data) use ($exitcode) {
return [
"pg_pid" => null,
"date_stop" => $data["date_stop"] ?? new DateTime(),
"exitcode" => $data["exitcode"] ?? $exitcode,
];
});
}
}

136
php/src/app/launcher.php Normal file
View File

@ -0,0 +1,136 @@
<?php
namespace nulib\app;
use nulib\cl;
use nulib\file\TmpfileWriter;
use nulib\os\path;
use nulib\os\proc\Cmd;
use nulib\output\msg;
use nulib\StateException;
use nulib\str;
use nulib\wip\app\app;
class launcher {
/**
* transformer une liste d'argument de la forme
* - ["myArg" => $value] devient ["--my-arg", "$value"]
* - ["myOpt" => true] devient ["--my-opt"]
* - ["myOpt" => false] est momis
* - les valeurs séquentielles sont prises telles quelles
*/
static function verifix_args(array $args): array {
if (!cl::is_list($args)) {
$fixedArgs = [];
$index = 0;
foreach ($args as $arg => $value) {
if ($arg === $index) {
$index++;
$fixedArgs[] = $value;
continue;
} elseif ($value === false) {
continue;
}
$arg = str::us2camel($arg);
$arg = str::camel2us($arg, false, "-");
$arg = str_replace("_", "-", $arg);
$fixedArgs[] = "--$arg";
if ($value !== true) $fixedArgs[] = "$value";
}
$args = $fixedArgs;
}
# corriger le chemin de l'application pour qu'il soit absolu et normalisé
$args[0] = path::abspath($args[0]);
return $args;
}
static function launch(string $appClass, array $args): int {
$app = app::get();
$vendorBindir = $app->getVendorbindir();
$launch_php = "$vendorBindir/_launch.php";
if (!file_exists($launch_php)) {
$launch_php = __DIR__."/../../lib/_launch.php";
}
$tmpfile = new TmpfileWriter();
$tmpfile->keep()->serialize($app->getParams());
$args = self::verifix_args($args);
$cmd = new Cmd([
$launch_php,
"--internal-use", $tmpfile->getFile(),
$appClass, "--", ...$args,
]);
$cmd->addRedir("both", "/tmp/nulib_app_launcher-launch.log");
$cmd->passthru($exitcode);
# attendre un peu que la commande aie le temps de s'initialiser
sleep(1);
$tmpfile->close();
return $exitcode;
}
static function _start(array $args, Runfile $runfile): bool {
if ($runfile->warnIfLocked()) return false;
$pid = pcntl_fork();
if ($pid == -1) {
# parent, impossible de forker
throw new StateException("unable to fork");
} elseif ($pid) {
# parent, fork ok
return true;
} else {
## child, fork ok
# Créer un groupe de process, pour pouvoir tuer tous les enfants en même temps
$runfile->tm_startPg();
$logfile = $runfile->getLogfile() ?? "/tmp/nulib_app_launcher-_start.log";
$pid = posix_getpid();
$exitcode = -776;
try {
# puis lancer la commande
$cmd = new Cmd($args);
$cmd->addSource("/g/init.env");
$cmd->addRedir("both", $logfile, true);
msg::debug("$pid: launching\n".$cmd->getCmd());
$cmd->fork_exec($exitcode);
msg::debug("$pid: exitcode=$exitcode");
return true;
} finally {
$runfile->wfStopped($exitcode);
}
}
}
static function _stop(Runfile $runfile): void {
$data = $runfile->read();
$pid = $data["pg_pid"];
if ($pid === null) {
msg::warning("$data[name]: groupe de process inconnu");
return;
}
msg::action("kill $pid");
if (!posix_kill(-$pid, SIGKILL)) {
switch (posix_get_last_error()) {
case PCNTL_ESRCH:
msg::afailure("process inexistant");
break;
case PCNTL_EPERM:
msg::afailure("process non accessible");
break;
case PCNTL_EINVAL:
msg::afailure("signal invalide");
break;
}
return;
}
$timeout = 10;
while ($runfile->tm_isUndead($pid)) {
sleep(1);
if (--$timeout == 0) {
msg::afailure("impossible d'arrêter la tâche");
return;
}
}
$runfile->wfStopped(-778);
msg::asuccess();
}
}

View File

@ -2,50 +2,111 @@
namespace nulib;
use ArrayAccess;
use nulib\php\func;
use Traversable;
/**
* Class cl: gestion de tableau de valeurs scalaires
* Class cl: gestion de tableaux ou d'instances de {@link ArrayAccess} le cas
* échéant
*
* contrairement à {@link A}, les méthodes de cette classes sont plutôt conçues
* pour retourner un nouveau tableau
*/
class cl {
/**
* retourner un array avec les éléments retournés par l'itérateur. les clés
* numériques sont réordonnées, les clés chaine sont laissées en l'état
*/
static final function all(?iterable $iterable): array {
if ($iterable === null) return [];
if (is_array($iterable)) return $iterable;
$array = [];
foreach ($iterable as $key => $value) {
if (is_int($key)) $array[] = $value;
else $array[$key] = $value;
}
return $array;
}
/**
* retourner la première valeur de $array ou $default si le tableau est null
* ou vide
*/
static final function first(?iterable $iterable, $default=null) {
if (is_array($iterable)) {
$key = array_key_first($iterable);
if ($key === null) return $default;
return $iterable[$key];
}
if (is_iterable($iterable)) {
foreach ($iterable as $value) {
return $value;
}
}
return $default;
}
/**
* retourner la dernière valeur de $array ou $default si le tableau est null
* ou vide
*/
static final function last(?iterable $iterable, $default=null) {
if (is_array($iterable)) {
$key = array_key_last($iterable);
if ($key === null) return $default;
return $iterable[$key];
}
$value = $default;
if (is_iterable($iterable)) {
foreach ($iterable as $value) {
# parcourir tout l'iterateur pour avoir le dernier élément
}
}
return $value;
}
/** retourner un array non null à partir de $array */
static final function with($array): array {
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
if (is_array($array)) return $array;
elseif ($array === null || $array === false) return [];
elseif ($array instanceof Traversable) return iterator_to_array($array);
elseif ($array instanceof Traversable) return self::all($array);
else return [$array];
}
/** retourner un array à partir de $array, ou null */
static final function withn($array): ?array {
if ($array instanceof IArrayWrapper) $array = $array->wrappedArray();
if (is_array($array)) return $array;
elseif ($array === null || $array === false) return null;
elseif ($array instanceof Traversable) return iterator_to_array($array);
elseif ($array instanceof Traversable) return self::all($array);
else return [$array];
}
/**
* s'assurer que $array est un array non null. retourner true si $array n'a
* pas été modifié (s'il était déjà un array), false sinon.
*/
static final function ensure_array(&$array): bool {
if (is_array($array)) return true;
elseif ($array === null || $array === false) $array = [];
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
else $array = [$array];
/** tester si $array a au moins une clé numérique */
static final function have_num_keys(?array $array): bool {
if ($array === null) return false;
foreach ($array as $key => $value) {
if (is_int($key)) return true;
}
return false;
}
/**
* s'assurer que $array est un array s'il est non null. retourner true si
* $array n'a pas été modifié (s'il était déjà un array ou s'il valait null).
* tester si $array est une liste, c'est à dire un tableau non null avec
* uniquement des clés numériques séquentielles commençant à zéro
*
* NB: is_list(null) === false
* et is_list([]) === true
*/
static final function ensure_narray(&$array): bool {
if ($array === null || is_array($array)) return true;
elseif ($array === false) $array = [];
elseif ($array instanceof Traversable) $array = iterator_to_array($array);
else $array = [$array];
return false;
static final function is_list(?array $array): bool {
if ($array === null) return false;
$index = -1;
foreach ($array as $key => $value) {
++$index;
if ($key !== $index) return false;
}
return true;
}
/**
@ -76,6 +137,128 @@ class cl {
return $default;
}
/**
* retourner un tableau construit à partir des clés de $keys
* - [$to => $from] --> $dest[$to] = self::get($array, $from)
* - [$to => null] --> $dest[$to] = null
* - [$to => false] --> NOP
* - [$to] --> $dest[$to] = self::get($array, $to)
* - [null] --> $dest[] = null
* - [false] --> NOP
*
* Si $inverse===true, le mapping est inversé:
* - [$to => $from] --> $dest[$from] = self::get($array, $to)
* - [$to => null] --> $dest[$to] = self::get($array, $to)
* - [$to => false] --> NOP
* - [$to] --> $dest[$to] = self::get($array, $to)
* - [null] --> NOP (XXX que faire dans ce cas?)
* - [false] --> NOP
*
* notez que l'ordre est inversé par rapport à {@link self::rekey()} qui
* attend des mappings [$from => $to], alors que cette méthode attend des
* mappings [$to => $from]
*/
static final function select($array, ?array $mappings, bool $inverse=false): array {
$dest = [];
$index = 0;
if (!$inverse) {
foreach ($mappings as $to => $from) {
if ($to === $index) {
$index++;
$to = $from;
if ($to === false) continue;
elseif ($to === null) $dest[] = null;
else $dest[$to] = self::get($array, $to);
} elseif ($from === false) {
continue;
} elseif ($from === null) {
$dest[$to] = null;
} else {
$dest[$to] = self::get($array, $from);
}
}
} else {
foreach ($mappings as $to => $from) {
if ($to === $index) {
$index++;
$to = $from;
if ($to === false) continue;
elseif ($to === null) continue;
else $dest[$to] = self::get($array, $to);
} elseif ($from === false) {
continue;
} elseif ($from === null) {
$dest[$to] = self::get($array, $to);
} else {
$dest[$from] = self::get($array, $to);
}
}
}
return $dest;
}
/**
* obtenir la liste des clés finalement obtenues après l'appel à
* {@link self::select()} avec le mapping spécifié
*/
static final function selected_keys(?array $mappings): array {
if ($mappings === null) return [];
$keys = [];
$index = 0;
foreach ($mappings as $to => $from) {
if ($to === $index) {
if ($from === false) continue;
elseif ($from === null) $keys[] = $index;
else $keys[] = $from;
$index++;
} elseif ($from === false) {
continue;
} else {
$keys[] = $to;
}
}
return $keys;
}
/**
* méthode de convenance qui sélectionne certaines clés de $array avec
* {@link self::select()} puis merge le tableau $merge au résultat.
*/
static final function selectm($array, ?array $mappings, ?array $merge=null): array {
return cl::merge(self::select($array, $mappings), $merge);
}
/**
* méthode de convenance qui merge $merge dans $array puis sélectionne
* certaines clés avec {@link self::select()}
*/
static final function mselect($array, ?array $merge, ?array $mappings): array {
return self::select(cl::merge($array, $merge), $mappings);
}
/**
* construire un sous-ensemble du tableau $array en sélectionnant les clés de
* $includes qui ne sont pas mentionnées dans $excludes.
*
* - si $includes===null && $excludes===null, retourner le tableau inchangé
* - si $includes vaut null, prendre toutes les clés
*
*/
static final function xselect($array, ?array $includes, ?array $excludes=null): ?array {
if ($array === null) return null;
$array = self::withn($array);
if ($includes === null && $excludes === null) return $array;
if ($includes === null) $includes = array_keys($array);
if ($excludes === null) $excludes = [];
$result = [];
foreach ($array as $key => $value) {
if (!in_array($key, $includes)) continue;
if (in_array($key, $excludes)) continue;
$result[$key] = $value;
}
return $result;
}
/**
* si $array est un array ou une instance de ArrayAccess, créer ou modifier
* l'élément dont la clé est $key
@ -119,29 +302,63 @@ class cl {
/**
* Fusionner tous les tableaux spécifiés. Les valeurs null sont ignorées.
* IMPORTANT: les clés numériques sont réordonnées.
* Retourner null si aucun tableau n'est fourni ou s'ils étaient tous null.
*/
static final function merge(...$arrays): ?array {
$merges = [];
foreach ($arrays as $array) {
self::ensure_narray($array);
A::ensure_narray($array);
if ($array !== null) $merges[] = $array;
}
return $merges? array_merge(...$merges): null;
}
/**
* Fusionner tous les tableaux spécifiés. Les valeurs null sont ignorées.
* IMPORTANT: les clés numériques NE SONT PAS réordonnées.
* Retourner null si aucun tableau n'est fourni ou s'ils étaient tous null.
*/
static final function merge2(...$arrays): ?array {
$merged = null;
foreach ($arrays as $array) {
foreach (self::with($array) as $key => $value) {
$merged[$key] = $value;
}
}
return $merged;
}
#############################################################################
static final function map(callable $callback, ?iterable $array): array {
$result = [];
if ($array !== null) {
$ctx = func::_prepare($callback);
foreach ($array as $key => $value) {
$result[$key] = func::_call($ctx, [$value, $key]);
}
}
return $result;
}
#############################################################################
/**
* vérifier que le chemin $keys existe dans le tableau $array
*
* si $keys est vide ou null, retourner true
* si $pkey est vide ou null, retourner true
*/
static final function phas($array, $pkey): bool {
if ($pkey !== null && !is_array($pkey)) {
# optimisations
if ($pkey === null || $pkey === []) {
return true;
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
return self::has($array, $pkey);
} elseif (!is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
if ($pkey === null || $pkey === []) return true;
# phas
$first = true;
foreach($pkey as $key) {
if ($key === "" && $first) {
@ -174,13 +391,18 @@ class cl {
/**
* obtenir la valeur correspondant au chemin $keys dans $array
*
* si $keys est vide ou null, retourner $default
* si $pkey est vide ou null, retourner $default
*/
static final function pget($array, $pkey, $default=null) {
if ($pkey !== null && !is_array($pkey)) {
# optimisations
if ($pkey === null || $pkey === []) return $default;
elseif ($pkey === "") return $array;
elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
return self::get($array, $pkey, $default);
} elseif (!is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
if ($pkey === null || $pkey === []) return true;
# pget
$value = $array;
$first = true;
foreach($pkey as $key) {
@ -211,25 +433,78 @@ class cl {
return $result;
}
/**
* retourner un tableau construit à partir des chemins de clé de $pkeys
* ces chemins peuvent être exprimés de plusieurs façon:
* - [$key => $pkey] --> $dest[$key] = self::pget($array, $pkey)
* - [$key => null] --> $dest[$key] = null
* - [$pkey] --> $dest[$key] = self::pget($array, $pkey)
* avec $key = implode("__", $pkey))
* - [null] --> $dest[] = null
* - [false] --> NOP
*/
static final function pselect($array, ?array $pkeys): array {
$dest = [];
$index = 0;
foreach ($pkeys as $key => $pkey) {
if ($key === $index) {
$index++;
if ($pkey === null) continue;
$value = self::pget($array, $pkey);
if (!is_array($pkey)) $pkey = explode(".", strval($pkey));
$key = implode("__", $pkey);
} elseif ($pkey === null) {
$value = null;
} else {
$value = self::pget($array, $pkey);
}
$dest[$key] = $value;
}
return $dest;
}
/**
* méthode de convenance qui sélectionne certaines clés de $array avec
* {@link self::pselect()} puis merge le tableau $merge au résultat.
*/
static final function pselectm($array, ?array $pkeys, ?array $merge=null): array {
return cl::merge(self::pselect($array, $pkeys), $merge);
}
/**
* méthode de convenance qui merge $merge dans $array puis sélectionne
* certaines clés avec {@link self::pselect()}
*/
static final function mpselect($array, ?array $merge, ?array $mappings): array {
return self::pselect(cl::merge($array, $merge), $mappings);
}
/**
* modifier la valeur au chemin de clé $keys dans le tableau $array
*
* utiliser la clé "" (chaine vide) en dernière position pour rajouter à la fin, e.g
* - _pset($array, [""], $value) est équivalent à $array[] = $value
* - _pset($array, ["a", "b", ""], $value) est équivalent à $array["a"]["b"][] = $value
* - pset($array, [""], $value) est équivalent à $array[] = $value
* - pset($array, ["a", "b", ""], $value) est équivalent à $array["a"]["b"][] = $value
* la clé "" n'a pas de propriété particulière quand elle n'est pas en dernière position
*
* si $keys est vide ou null, $array est remplacé par $value
* si $pkey est vide ou null, $array est remplacé par $value
*/
static final function pset(&$array, $pkey, $value): void {
if ($pkey !== null && !is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
# optimisations
if ($pkey === null || $pkey === []) {
$array = $value;
return;
} elseif ($pkey === "") {
$array[] = $value;
return;
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
self::set($array, $pkey, $value);
return;
} elseif (!is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
self::ensure_array($array);
# pset
A::ensure_array($array);
$current =& $array;
$key = null;
$last = count($pkey) - 1;
@ -245,7 +520,7 @@ class cl {
$current = [$current];
}
} else {
self::ensure_array($current[$key]);
A::ensure_array($current[$key]);
$current =& $current[$key];
}
$i++;
@ -262,28 +537,27 @@ class cl {
}
}
/**
* supprimer la valeur au chemin $keys fourni sous forme de tableau
*/
static final function pdel_a(&$array, ?array $pkey): void {
}
/**
* supprimer la valeur au chemin de clé $keys dans $array
*
* si $array vaut null ou false, sa valeur est inchangée.
* si $keys est vide ou null, $array devient null
* si $pkey est vide ou null, $array devient null
*/
static final function pdel(&$array, $pkey): void {
if ($array === false || $array === null) return;
if ($pkey !== null && !is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
if ($pkey === null || $pkey === []) {
# optimisations
if ($array === null || $array === false) {
return;
} elseif ($pkey === null || $pkey === []) {
$array = null;
return;
} elseif (is_int($pkey) || (is_string($pkey) && strpos($pkey, ".") === false)) {
self::del($array, $pkey);
return;
} elseif (!is_array($pkey)) {
$pkey = explode(".", strval($pkey));
}
self::ensure_array($array);
# pdel
A::ensure_array($array);
$current =& $array;
$key = null;
$last = count($pkey) - 1;
@ -322,9 +596,12 @@ class cl {
/**
* retourner le tableau $array en "renommant" les clés selon le tableau
* $mappings qui contient des associations de la forme [$from => $to]
*
* Si $inverse===true, renommer dans le sens $to => $from
*/
static function rekey(?array $array, ?array $mappings): ?array {
static function rekey(?array $array, ?array $mappings, bool $inverse=false): ?array {
if ($array === null || $mappings === null) return $array;
if ($inverse) $mappings = array_flip($mappings);
$mapped = [];
foreach ($array as $key => $value) {
if (array_key_exists($key, $mappings)) $key = $mappings[$key];
@ -333,6 +610,19 @@ class cl {
return $mapped;
}
/**
* indiquer si {@link self::rekey()} modifierai le tableau indiqué (s'il y a
* des modifications à faire)
*/
static function would_rekey(?array $array, ?array $mappings, bool $inverse=false): bool {
if ($array === null || $mappings === null) return false;
if ($inverse) $mappings = array_flip($mappings);
foreach ($array as $key => $value) {
if (array_key_exists($key, $mappings)) return true;
}
return false;
}
#############################################################################
/** tester si tous les éléments du tableau satisfont la condition */
@ -445,10 +735,10 @@ class cl {
static final function compare(array $keys): callable {
return function ($a, $b) use ($keys) {
foreach ($keys as $key) {
if (cstr::del_prefix($key, "+")) $w = 1;
elseif (cstr::del_prefix($key, "-")) $w = -1;
elseif (cstr::del_suffix($key, "|asc")) $w = 1;
elseif (cstr::del_suffix($key, "|desc")) $w = -1;
if (str::del_prefix($key, "+")) $w = 1;
elseif (str::del_prefix($key, "-")) $w = -1;
elseif (str::del_suffix($key, "|asc")) $w = 1;
elseif (str::del_suffix($key, "|desc")) $w = -1;
else $w = 1;
if ($c = $w * cv::compare(cl::get($a, $key), cl::get($b, $key))) {
return $c;

View File

@ -79,6 +79,15 @@ class cv {
#############################################################################
/** échanger les deux valeurs */
static final function swap(&$a, &$b): void {
$tmp = $a;
$a = $b;
$b = $tmp;
}
#############################################################################
/** mettre à jour $dest avec $value si $cond($value) est vrai */
static final function set_if(&$dest, $value, callable $cond) {
if ($cond($value)) $dest = $value;
@ -181,7 +190,7 @@ class cv {
$index = is_int($value)? $value : null;
$key = is_string($value)? $value : null;
if ($index === null && $key === null && $throw_exception) {
throw ValueException::invalid($value, "key", $prefix);
throw ValueException::invalid_kind($value, "key", $prefix);
} else {
return [$index, $key];
}
@ -198,7 +207,7 @@ class cv {
$string = is_string($value)? $value : null;
$array = is_array($value)? $value : null;
if ($bool === null && $string === null && $array === null && $throw_exception) {
throw ValueException::invalid($value, "value", $prefix);
throw ValueException::invalid_kind($value, "value", $prefix);
} else {
return [$bool, $string, $array];
}

173
php/src/db/Capacitor.php Normal file
View File

@ -0,0 +1,173 @@
<?php
namespace nulib\db;
use nulib\php\func;
use nulib\ValueException;
use Traversable;
/**
* Class Capacitor: un objet permettant d'attaquer un canal spécifique d'une
* instance de {@link CapacitorStorage}
*/
class Capacitor implements ITransactor {
function __construct(CapacitorStorage $storage, CapacitorChannel $channel, bool $ensureExists=true) {
$this->storage = $storage;
$this->channel = $channel;
$this->channel->setCapacitor($this);
if ($ensureExists) $this->ensureExists();
}
/** @var CapacitorStorage */
protected $storage;
function getStorage(): CapacitorStorage {
return $this->storage;
}
function db(): IDatabase {
return $this->getStorage()->db();
}
/** @var CapacitorChannel */
protected $channel;
function getChannel(): CapacitorChannel {
return $this->channel;
}
function getTableName(): string {
return $this->getChannel()->getTableName();
}
/** @var CapacitorChannel[] */
protected ?array $subChannels = null;
protected ?array $subManageTransactions = null;
function willUpdate(...$channels): self {
if ($this->subChannels === null) {
# désactiver la gestion des transaction sur le channel local aussi
$this->subChannels[] = $this->channel;
}
if ($channels) {
foreach ($channels as $channel) {
if ($channel instanceof Capacitor) $channel = $channel->getChannel();
if ($channel instanceof CapacitorChannel) {
$this->subChannels[] = $channel;
} else {
throw ValueException::invalid_type($channel, CapacitorChannel::class);
}
}
}
return $this;
}
function inTransaction(): bool {
return $this->db()->inTransaction();
}
function beginTransaction(?callable $func=null, bool $commit=true): void {
$db = $this->db();
if ($this->subChannels !== null) {
# on gère des subchannels: ne débuter la transaction que si ce n'est déjà fait
if ($this->subManageTransactions === null) {
foreach ($this->subChannels as $channel) {
$name = $channel->getName();
$this->subManageTransactions ??= [];
if (!array_key_exists($name, $this->subManageTransactions)) {
$this->subManageTransactions[$name] = $channel->isManageTransactions();
}
$channel->setManageTransactions(false);
}
if (!$db->inTransaction()) $db->beginTransaction();
}
} elseif (!$db->inTransaction()) {
$db->beginTransaction();
}
if ($func !== null) {
$commited = false;
try {
func::call($func, $this);
if ($commit) {
$this->commit();
$commited = true;
}
} finally {
if ($commit && !$commited) $this->rollback();
}
}
}
protected function beforeEndTransaction(): void {
if ($this->subManageTransactions !== null) {
foreach ($this->subChannels as $channel) {
$name = $channel->getName();
$channel->setManageTransactions($this->subManageTransactions[$name]);
}
$this->subManageTransactions = null;
}
}
function commit(): void {
$this->beforeEndTransaction();
$db = $this->db();
if ($db->inTransaction()) $db->commit();
}
function rollback(): void {
$this->beforeEndTransaction();
$db = $this->db();
if ($db->inTransaction()) $db->rollback();
}
function getCreateSql(): string {
return $this->storage->_getCreateSql($this->channel);
}
function exists(): bool {
return $this->storage->_exists($this->channel);
}
function ensureExists(): void {
$this->storage->_ensureExists($this->channel);
}
function reset(bool $recreate=false): void {
$this->storage->_reset($this->channel, $recreate);
}
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
if ($this->subChannels !== null) $this->beginTransaction();
return $this->storage->_charge($this->channel, $item, $func, $args, $values);
}
function discharge(bool $reset=true): Traversable {
return $this->storage->_discharge($this->channel, $reset);
}
function count($filter=null): int {
return $this->storage->_count($this->channel, $filter);
}
function one($filter, ?array $mergeQuery=null): ?array {
return $this->storage->_one($this->channel, $filter, $mergeQuery);
}
function all($filter, ?array $mergeQuery=null): Traversable {
return $this->storage->_all($this->channel, $filter, $mergeQuery);
}
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
if ($this->subChannels !== null) $this->beginTransaction();
return $this->storage->_each($this->channel, $filter, $func, $args, $mergeQuery, $nbUpdated);
}
function delete($filter, $func=null, ?array $args=null): int {
if ($this->subChannels !== null) $this->beginTransaction();
return $this->storage->_delete($this->channel, $filter, $func, $args);
}
function close(): void {
$this->storage->close();
}
}

View File

@ -0,0 +1,407 @@
<?php
namespace nulib\db;
use nulib\cl;
use nulib\str;
use Traversable;
/**
* Class CapacitorChannel: un canal d'une instance de {@link ICapacitor}
*/
class CapacitorChannel {
const NAME = null;
const TABLE_NAME = null;
const COLUMN_DEFINITIONS = null;
const PRIMARY_KEYS = null;
const MANAGE_TRANSACTIONS = true;
const EACH_COMMIT_THRESHOLD = 100;
const USE_CACHE = false;
static function verifix_name(?string &$name, ?string &$tableName=null): void {
if ($name !== null) {
$name = strtolower($name);
if ($tableName === null) {
$tableName = str_replace("-", "_", $tableName) . "_channel";
}
} else {
$name = static::class;
if ($name === self::class) {
$name = "default";
if ($tableName === null) $tableName = "default_channel";
} else {
$name = preg_replace('/^.*\\\\/', "", $name);
$name = preg_replace('/Channel$/', "", $name);
$name = lcfirst($name);
if ($tableName === null) $tableName = str::camel2us($name);
$name = strtolower($name);
}
}
}
protected static function verifix_eachCommitThreshold(?int $eachCommitThreshold): ?int {
$eachCommitThreshold ??= static::EACH_COMMIT_THRESHOLD;
if ($eachCommitThreshold < 0) $eachCommitThreshold = null;
return $eachCommitThreshold;
}
function __construct(?string $name=null, ?int $eachCommitThreshold=null, ?bool $manageTransactions=null) {
$name ??= static::NAME;
$tableName ??= static::TABLE_NAME;
self::verifix_name($name, $tableName);
$this->name = $name;
$this->tableName = $tableName;
$this->manageTransactions = $manageTransactions ?? static::MANAGE_TRANSACTIONS;
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
$this->useCache = static::USE_CACHE;
$this->setup = false;
$this->created = false;
$columnDefinitions = cl::withn(static::COLUMN_DEFINITIONS);
$primaryKeys = cl::withn(static::PRIMARY_KEYS);
if ($primaryKeys === null && $columnDefinitions !== null) {
$index = 0;
foreach ($columnDefinitions as $col => $def) {
if ($col === $index) {
$index++;
if (preg_match('/\bprimary\s+key\s+\((.+)\)/i', $def, $ms)) {
$primaryKeys = preg_split('/\s*,\s*/', trim($ms[1]));
}
} else {
if (preg_match('/\bprimary\s+key\b/i', $def)) {
$primaryKeys[] = $col;
}
}
}
}
$this->columnDefinitions = $columnDefinitions;
$this->primaryKeys = $primaryKeys;
}
protected string $name;
function getName(): string {
return $this->name;
}
protected string $tableName;
function getTableName(): string {
return $this->tableName;
}
/**
* @var bool indiquer si les modifications de each doivent être gérées dans
* une transaction. si false, l'utilisateur doit lui même gérer la
* transaction.
*/
protected bool $manageTransactions;
function isManageTransactions(): bool {
return $this->manageTransactions;
}
function setManageTransactions(bool $manageTransactions=true): self {
$this->manageTransactions = $manageTransactions;
return $this;
}
/**
* @var ?int nombre maximum de modifications dans une transaction avant un
* commit automatique dans {@link Capacitor::each()}. Utiliser null pour
* désactiver la fonctionnalité.
*
* ce paramètre n'a d'effet que si $manageTransactions==true
*/
protected ?int $eachCommitThreshold;
function getEachCommitThreshold(): ?int {
return $this->eachCommitThreshold;
}
function setEachCommitThreshold(?int $eachCommitThreshold=null): self {
$this->eachCommitThreshold = self::verifix_eachCommitThreshold($eachCommitThreshold);
return $this;
}
/**
* @var bool faut-il passer par le cache pour les requêtes de all(), each()
* et delete()?
* ça peut être nécessaire avec MySQL/MariaDB si on utilise les requêtes non
* bufférisées, et que la fonction manipule la base de données
*/
protected bool $useCache;
function isUseCache(): bool {
return $this->useCache;
}
function setUseCache(bool $useCache=true): self {
$this->useCache = $useCache;
return $this;
}
/**
* initialiser ce channel avant sa première utilisation.
*/
protected function setup(): void {
}
protected bool $setup;
function ensureSetup() {
if (!$this->setup) {
$this->setup();
$this->setup = true;
}
}
protected bool $created;
function isCreated(): bool {
return $this->created;
}
function setCreated(bool $created=true): void {
$this->created = $created;
}
protected ?array $columnDefinitions;
/**
* retourner un ensemble de définitions pour des colonnes supplémentaires à
* insérer lors du chargement d'une valeur
*
* la clé primaire "id_" a pour définition "integer primary key autoincrement".
* elle peut être redéfinie, et dans ce cas la valeur à utiliser doit être
* retournée par {@link getItemValues()}
*
* la colonne "item__" contient la valeur sérialisée de l'élément chargé. bien
* que ce soit possible techniquement, cette colonne n'a pas à être redéfinie
*
* les colonnes dont le nom se termine par "_" sont réservées.
* les colonnes dont le nom se termine par "__" sont automatiquement sérialisées
* lors de l'insertion dans la base de données, et automatiquement désérialisées
* avant d'être retournées à l'utilisateur (sans le suffixe "__")
*/
function getColumnDefinitions(): ?array {
return $this->columnDefinitions;
}
protected ?array $primaryKeys;
function getPrimaryKeys(): ?array {
return $this->primaryKeys;
}
/**
* calculer les valeurs des colonnes supplémentaires à insérer pour le
* chargement de $item. pour une même valeur de $item, la valeur de retour
* doit toujours être la même. pour rajouter des valeurs supplémentaires qui
* dépendent de l'environnement, il faut plutôt les retournner dans
* {@link self::onCreate()} ou {@link self::onUpdate()}
*
* Cette méthode est utilisée par {@link Capacitor::charge()}. Si la clé
* primaire est incluse (il s'agit généralement de "id_"), la ligne
* correspondate est mise à jour si elle existe.
* Retourner la clé primaire par cette méthode est l'unique moyen de
* déclencher une mise à jour plutôt qu'une nouvelle création.
*
* Retourner [false] pour annuler le chargement
*/
function getItemValues($item): ?array {
return null;
}
/**
* Avant d'utiliser un id pour rechercher dans la base de donnée, corriger sa
* valeur le cas échéant.
*
* Cette fonction assume que la clé primaire n'est pas multiple. Elle n'est
* pas utilisée si une clé primaire multiple est définie.
*/
function verifixId(string &$id): void {
}
/**
* retourne true si un nouvel élément ou un élément mis à jour a été chargé.
* false si l'élément chargé est identique au précédent.
*
* cette méthode doit être utilisée dans {@link self::onUpdate()}
*/
function wasRowModified(array $values, array $pvalues): bool {
return $values["item__sum_"] !== $pvalues["item__sum_"];
}
final function serialize($item): ?string {
return $item !== null? serialize($item): null;
}
final function unserialize(?string $serial) {
return $serial !== null? unserialize($serial): null;
}
const SERIAL_DEFINITION = "mediumtext";
const SUM_DEFINITION = "varchar(40)";
final function sum(?string $serial, $value=null): ?string {
if ($serial === null) $serial = $this->serialize($value);
return $serial !== null? sha1($serial): null;
}
final function isSerialCol(string &$key): bool {
return str::del_suffix($key, "__");
}
final function getSumCols(string $key): array {
return ["${key}__", "${key}__sum_"];
}
function getSum(string $key, $value): array {
$sumCols = $this->getSumCols($key);
$serial = $this->serialize($value);
$sum = $this->sum($serial, $value);
return array_combine($sumCols, [$serial, $sum]);
}
function wasSumModified(string $key, $value, array $pvalues): bool {
$sumCol = $this->getSumCols($key)[1];
$sum = $this->sum(null, $value);
$psum = $pvalues[$sumCol] ?? $this->sum(null, $pvalues[$key] ?? null);
return $sum !== $psum;
}
function _wasSumModified(string $key, array $row, array $prow): bool {
$sumCol = $this->getSumCols($key)[1];
$sum = $row[$sumCol] ?? null;
$psum = $prow[$sumCol] ?? null;
return $sum !== $psum;
}
/**
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
* créer un nouvel élément
*
* @param mixed $item l'élément à charger
* @param array $values la ligne à créer, calculée à partir de $item et des
* valeurs retournées par {@link getItemValues()}
* @return ?array le cas échéant, un tableau non null à merger dans $values et
* utilisé pour provisionner la ligne nouvellement créée.
* Retourner [false] pour annuler le chargement (la ligne n'est pas créée)
*
* Si $item est modifié dans cette méthode, il est possible de le retourner
* avec la clé "item" pour mettre à jour la ligne correspondante.
*
* la création ou la mise à jour est uniquement décidée en fonction des
* valeurs calculées par {@link self::getItemValues()}. Bien que cette méthode
* peut techniquement retourner de nouvelles valeurs pour la clé primaire, ça
* risque de créer des doublons
*/
function onCreate($item, array $values, ?array $alwaysNull): ?array {
return null;
}
/**
* méthode appelée lors du chargement avec {@link Capacitor::charge()} pour
* mettre à jour un élément existant
*
* @param mixed $item l'élément à charger
* @param array $values la nouvelle ligne, calculée à partir de $item et
* des valeurs retournées par {@link getItemValues()}
* @param array $pvalues la précédente ligne, chargée depuis la base de
* données
* @return ?array null s'il ne faut pas mettre à jour la ligne. sinon, ce
* tableau est mergé dans $values puis utilisé pour mettre à jour la ligne
* existante
* Retourner [false] pour annuler le chargement (la ligne n'est pas mise à
* jour)
*
* - Il est possible de mettre à jour $item en le retourant avec la clé "item"
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
* modifiée. si elle est retournée, elle est ignorée
*/
function onUpdate($item, array $values, array $pvalues): ?array {
return null;
}
/**
* méthode appelée lors du parcours des éléments avec
* {@link Capacitor::each()}
*
* @param mixed $item l'élément courant
* @param ?array $values la ligne courante
* @return ?array le cas échéant, un tableau non null utilisé pour mettre à
* jour la ligne courante
*
* - Il est possible de mettre à jour $item en le retourant avec la clé "item"
* - La clé primaire (il s'agit généralement de "id_") ne peut pas être
* modifiée. si elle est retournée, elle est ignorée
*/
function onEach($item, array $values): ?array {
return null;
}
const onEach = "->".[self::class, "onEach"][1];
/**
* méthode appelée lors du parcours des éléments avec
* {@link Capacitor::delete()}
*
* @param mixed $item l'élément courant
* @param ?array $values la ligne courante
* @return bool true s'il faut supprimer la ligne, false sinon
*/
function onDelete($item, array $values): bool {
return true;
}
const onDelete = "->".[self::class, "onDelete"][1];
#############################################################################
# Méthodes déléguées pour des workflows centrés sur le channel
/**
* @var Capacitor|null instance de Capacitor par laquelle cette instance est
* utilisée
*/
protected ?Capacitor $capacitor;
function getCapacitor(): ?Capacitor {
return $this->capacitor;
}
function setCapacitor(Capacitor $capacitor): self {
$this->capacitor = $capacitor;
return $this;
}
function charge($item, $func=null, ?array $args=null, ?array &$values=null): int {
return $this->capacitor->charge($item, $func, $args, $values);
}
function discharge(bool $reset=true): Traversable {
return $this->capacitor->discharge($reset);
}
function count($filter=null): int {
return $this->capacitor->count($filter);
}
function one($filter, ?array $mergeQuery=null): ?array {
return $this->capacitor->one($filter, $mergeQuery);
}
function all($filter, ?array $mergeQuery=null): Traversable {
return $this->capacitor->all($filter, $mergeQuery);
}
function each($filter, $func=null, ?array $args=null, ?array $mergeQuery=null, ?int &$nbUpdated=null): int {
return $this->capacitor->each($filter, $func, $args, $mergeQuery, $nbUpdated);
}
function delete($filter, $func=null, ?array $args=null): int {
return $this->capacitor->delete($filter, $func, $args);
}
}

Some files were not shown because too many files have changed in this diff Show More