importation initiale dev74

This commit is contained in:
Jephté Clain 2025-02-28 20:08:17 +04:00
commit fb01429970
317 changed files with 30781 additions and 0 deletions

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
/sbin/composer.phar -delta

12
.gitignore vendored Normal file
View File

@ -0,0 +1,12 @@
/.composer.lock.runphp
.~lock*#
.*.swp
/vendor/
/.idea/shelf/
/.idea/workspace.xml
/.idea/httpRequests/
/.idea/dataSources/
/.idea/dataSources.local.xml
/.phpunit.result.cache

12
.idea/codeception.xml generated Normal file
View File

@ -0,0 +1,12 @@
<?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>
</list>
</option>
</component>
</project>

View File

@ -0,0 +1,14 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PhpAbstractStaticMethodInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocMissingReturnTagInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpDocSignatureInspection" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
<inspection_tool class="PhpSignatureMismatchDuringInheritanceInspection" enabled="false" level="WARNING" enabled_by_default="false" />
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/nulib.iml" filepath="$PROJECT_DIR$/.idea/nulib.iml" />
</modules>
</component>
</project>

12
.idea/nulib.iml generated Normal file
View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<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" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

38
.idea/php-docker-settings.xml generated Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpDockerContainerSettings">
<list>
<map>
<entry key="125ffb9d-fd5f-4e71-8182-94191665795a">
<value>
<DockerContainerSettings>
<option name="version" value="1" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/opt/project" />
<option name="hostPath" value="$PROJECT_DIR$" />
</DockerVolumeBindingImpl>
</list>
</option>
</DockerContainerSettings>
</value>
</entry>
<entry key="c4cf2564-ed91-488c-a93d-fe2daeae80db">
<value>
<DockerContainerSettings>
<option name="version" value="1" />
<option name="volumeBindings">
<list>
<DockerVolumeBindingImpl>
<option name="containerPath" value="/opt/project" />
</DockerVolumeBindingImpl>
</list>
</option>
</DockerContainerSettings>
</value>
</entry>
</map>
</list>
</component>
</project>

15
.idea/php-test-framework.xml generated Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="PhpTestFrameworkVersionCache">
<tools_cache>
<tool tool_name="PHPUnit">
<cache>
<versions>
<info id="Local/home/jclain/wop/php/nulib/vendor/autoload.php" version="9.6.13" />
<info id="Local/vendor/autoload.php" version="9.6.21" />
</versions>
</cache>
</tool>
</tools_cache>
</component>
</project>

63
.idea/php.xml generated Normal file
View File

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="MessDetectorOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCSFixerOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PHPCodeSnifferOptionsConfiguration">
<option name="highlightLevel" value="WARNING" />
<option name="transferred" value="true" />
</component>
<component name="PhpIncludePathManager">
<include_path>
<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/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/sebastian/code-unit-reverse-lookup" />
<path value="$PROJECT_DIR$/vendor/theseer/tokenizer" />
<path value="$PROJECT_DIR$/vendor/mur/tests" />
<path value="$PROJECT_DIR$/vendor/symfony/deprecation-contracts" />
<path value="$PROJECT_DIR$/vendor/symfony/yaml" />
<path value="$PROJECT_DIR$/vendor/symfony/polyfill-ctype" />
</include_path>
</component>
<component name="PhpProjectSharedConfiguration" php_language_level="7.4" />
<component name="PhpStanOptionsConfiguration">
<option name="transferred" value="true" />
</component>
<component name="PhpUnit">
<phpunit_settings>
<PhpUnitSettings custom_loader_path="$PROJECT_DIR$/vendor/autoload.php" phpunit_phar_path="" />
</phpunit_settings>
</component>
<component name="PsalmOptionsConfiguration">
<option name="transferred" value="true" />
</component>
</project>

10
.idea/phpspec.xml generated Normal file
View File

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

10
.idea/phpunit.xml generated 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>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

11
.pman.conf Normal file
View File

@ -0,0 +1,11 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
UPSTREAM=
DEVELOP=dev74
FEATURE=wip74/
RELEASE=rel74-
MAIN=dist74
TAG_SUFFIX=p74
HOTFIX=hotf74-
DIST=
NOAUTO=

8
.runphp.conf Normal file
View File

@ -0,0 +1,8 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# Chemin vers runphp, e.g sbin/runphp
RUNPHP=
# Si RUNPHP n'est pas défini, les variables suivantes peuvent être définies
DIST=d11
#REGISTRY=pubdocker.univ-reunion.fr/dist

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 "$NULIB_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"
}

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

@ -0,0 +1,226 @@
# -*- 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_get_toplevel ""
function git_get_toplevel() {
git rev-parse --show-toplevel 2>/dev/null
}
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 || die "$(ppath "$(pwd)" ~): ce répertoire 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)" ]
}
git_cleancheckout_VERBOSE=1
git_cleancheckout_DIRTY="Vous avez des modifications locales. Enregistrez ces modifications avant de continuer"
function: git_ensure_cleancheckout ""
function git_ensure_cleancheckout() {
git_check_cleancheckout && return
[ -n "$git_cleancheckout_VERBOSE" ] &&
git status --porcelain 2>/dev/null
die "$git_cleancheckout_DIRTY" || 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

21
bash/src/pman.conf.sh Normal file
View File

@ -0,0 +1,21 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## configuration par défaut
# branche upstream
UPSTREAM=
# branches de développement
DEVELOP=develop
FEATURE=wip/
# branche de préparation de release
RELEASE=release-
# branche de release
MAIN=master
TAG_PREFIX=
TAG_SUFFIX=
# branche de hotfix
HOTFIX=hotfix-
# branche de distribution
DIST=
# désactiver les releases automatiques?
NOAUTO=

416
bash/src/pman.sh Normal file
View File

@ -0,0 +1,416 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
# configuration par défaut
# doit être identique au contenu de pman.conf
# les branches sont mergées dans cet ordre:
# upstream --> develop --> [release -->] main --> dist
# feature _/ hotfix _/
UPSTREAM=
DEVELOP=develop
FEATURE=wip/
RELEASE=release-
MAIN=master
TAG_PREFIX=
TAG_SUFFIX=
HOTFIX=hotfix-
DIST=
NOAUTO=
CONFIG_VARS=(
UPSTREAM DEVELOP FEATURE RELEASE MAIN TAG_PREFIX TAG_SUFFIX HOTFIX DIST NOAUTO
)
function _init_changelog() {
setx date=date +%d/%m/%Y-%H:%M
ac_set_tmpfile changelog
echo >"$changelog" "\
Vérifiez et complétez la liste des changements le cas échéant.
Un fichier vide annule l'opération
Ces lignes ne seront pas incluses dans le fichier destination
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
"
}
function _filter_rel() {
# enlever les commits "techniques" générés par ce script
awk '
BEGIN { tech = 0 }
tech == 0 && $0 ~ /^\+.*<pman>/ { tech = 1; next }
tech == 1 && $0 ~ /^\|/ { next }
tech == 1 && $0 ~ /^\+/ { tech = 0 }
$0 !~ /<pman>/ { print }
'
}
function _filter_changes() {
# enlever les commits "inutiles" pour générer le fichier CHANGES.md
grep -vE '^([+|] )?[0-9a-f]+ modifs\.mineures sans commentaires$' |
grep -vE '^([+|] )?[0-9a-f]+ (cosmetic|typo|bug|fix|maj projet|maj deps)\$'
}
function _format_md() {
awk '
$0 == "" || $0 ~ /^#/ { print; next }
$1 == "+" {
$1 = "*"
$2 = "`" $2 "`"
print; next
}
$1 == "|" {
$1 = " *"
$2 = "`" $2 "`"
print; next
}
{
$1 = "* `" $1 "`"
print; next
}
'
}
function _list_commits() {
local source="${1:-$SrcBranch}" dest="${2:-$DestBranch}" mergebase
setx mergebase=git merge-base "$dest" "$source"
git log --oneline --graph "$mergebase..$source" |
grep -vF '|\' | grep -vF '|/' | sed -r 's/^(\| )+\* +/| /; s/^\* +/+ /' |
_filter_rel
}
function _scripte() {
echo >>"$script"
echo "$comment$(qvals "$@")" >>"$script"
}
function _scripta() {
[ $# -gt 0 ] && _scripte einfo "$*"
cat >>"$script"
}
function _script_push_branches() {
[ ${#push_branches[*]} -gt 0 ] || return
[ -n "$Origin" ] || Origin=origin
git_have_remote "$Origin" || return
local branch cmd remote rbranch
for branch in "${push_branches[@]}"; do
if [[ "$branch" == *:* ]]; then
cmd="$(qvals git push "$Origin" "$branch")"
else
setx remote=git_get_branch_remote "$branch"
if [ "$remote" == "$Origin" ]; then
setx rbranch=git_get_branch_merge "$branch"
if [ -n "$rbranch" ]; then
# pousser vers la branche distante existante
cmd="$(qvals git push "$Origin" "$branch:${rbranch#refs/heads/}")"
else
# pas de branche distante: pousser et traquer
cmd="$(qvals git push -u "$Origin" "$branch:$branch")"
fi
elif [ -n "$remote" ]; then
# pousser vers un remote différent
cmd="$(qvals git push "$Origin" "$branch:$branch")"
else
# pas de remote: pousser et traquer
cmd="$(qvals git push -u "$Origin" "$branch:$branch")"
fi
fi
_scripta <<<"$comment$cmd$or_die"
done
}
function _script_push_tags() {
local origin tag
_scripte einfo "push tags"
for tag in "${push_tags[@]}"; do
origin="$Origin"
[ -n "$origin" ] || origin=origin
_scripta <<EOF
$comment$(qvals git push --force "$origin" tag "$tag")$or_die
EOF
done
}
################################################################################
# Config
function ensure_gitdir() {
# commencer dans le répertoire indiqué
local chdir="$1"
if [ -n "$chdir" ]; then
cd "$chdir" || die || return
fi
# se mettre à la racine du dépôt git
local gitdir
git_ensure_gitvcs
setx gitdir=git_get_toplevel
cd "$gitdir" || die || return
}
function load_branches() {
local what="${1:-all}"; shift
case "$what" in
all)
setx CurrentBranch=git_get_branch
setx -a LocalBranches=git_list_branches
setx -a RemoteBranches=git_list_rbranches "$Origin"
setx -a AllBranches=git_list_pbranches "$Origin"
;;
current)
SrcBranch="$1"
[ -n "$SrcBranch" ] || SrcBranch="$CurrentBranch"
case "$SrcBranch" in
"$UPSTREAM") SrcType=upstream; DestBranch="$DEVELOP";;
"$FEATURE"*) SrcType=feature; DestBranch="$DEVELOP";;
"$DEVELOP") SrcType=develop; DestBranch="$MAIN";;
"$RELEASE"*) SrcType=release; DestBranch="$MAIN";;
"$HOTFIX"*) SrcType=hotfix; DestBranch="$MAIN";;
"$MAIN") SrcType=main; DestBranch="$DIST";;
"$DIST") SrcType=dist; DestBranch=;;
*) DestBranch=;;
esac
local branch
UpstreamBranch=
FeatureBranches=()
DevelopBranch=
ReleaseBranch=
HotfixBranch=
MainBranch=
Dist=Branch
for branch in "${AllBranches[@]}"; do
if [ "$branch" == "$UPSTREAM" ]; then
UpstreamBranch="$branch"
elif [[ "$branch" == "$FEATURE"* ]]; then
FeatureBranch+=("$branch")
elif [ "$branch" == "$DEVELOP" ]; then
DevelopBranch="$branch"
elif [[ "$branch" == "$RELEASE"* ]]; then
ReleaseBranch="$branch"
elif [[ "$branch" == "$HOTFIX"* ]]; then
HotfixBranch="$branch"
elif [ "$branch" == "$MAIN" ]; then
MainBranch="$branch"
elif [ "$branch" == "$DIST" ]; then
DistBranch="$branch"
fi
done
;;
esac
}
function load_config() {
if [ -n "$ConfigFile" ]; then
source "$ConfigFile" || die || return
elif [ -n "$ConfigBranch" ]; then
if ! array_contains LocalBranches "$ConfigBranch"; then
die "$ConfigBranch: branche de configuration introuvable" || return
else
ac_set_tmpfile ConfigFile
git show "$ConfigBranch:.pman.conf" >"$ConfigFile" 2>/dev/null
[ -s "$ConfigFile" ] || die "$ConfigBranch: aucune configuration trouvée sur cette branche" || return
source "$ConfigFile"
fi
elif [ -f .pman.conf ]; then
ConfigFile="$(pwd)/.pman.conf"
source "$ConfigFile"
elif [ -n "${MYNAME#prel}" ]; then
ConfigFile="$NULIBDIR/bash/src/pman${MYNAME#$1}.conf.sh"
source "$ConfigFile"
else
ConfigFile="$NULIBDIR/bash/src/pman.conf.sh"
fi
}
################################################################################
# Divers
function _push_branches() {
[ ${#push_branches[*]} -gt 0 ] || return
[ -n "$Origin" ] || Origin=origin
git_have_remote "$Origin" || return
local -a cmds; local branch cmd remote rbranch
for branch in "${push_branches[@]}"; do
if [[ "$branch" == *:* ]]; then
cmds+=("$(qvals git push "$Origin" "$branch")")
else
setx remote=git_get_branch_remote "$branch"
if [ "$remote" == "$Origin" ]; then
setx rbranch=git_get_branch_merge "$branch"
if [ -n "$rbranch" ]; then
# pousser vers la branche distante existante
cmds+=("$(qvals git push "$Origin" "$branch:${rbranch#refs/heads/}")")
else
# pas de branche distante: pousser et traquer
cmds+=("$(qvals git push -u "$Origin" "$branch:$branch")")
fi
elif [ -n "$remote" ]; then
# pousser vers un remote différent
cmds+=("$(qvals git push "$Origin" "$branch:$branch")")
else
# pas de remote: pousser et traquer
cmds+=("$(qvals git push -u "$Origin" "$branch:$branch")")
fi
fi
done
[ -n "$Push" ] || enote "L'option --no-push étant utilisée, les opérations à effectuer sont simplement affichées"
for cmd in "${cmds[@]}"; do
einfo "$cmd"
if [ -n "$Push" ]; then
if ! eval "$cmd"; then
ewarn "Une erreur s'est produite, les opérations seront simplement affichées"
Push=
fi
fi
done
}
################################################################################
# Merge
function _mscript_start() {
>"$script"
_scripta <<EOF
#!/bin/bash
$(qvals source "$NULIBDIR/load.sh") || exit 1
merge=
delete=
push=
for arg in "\$@"; do
case "\$arg" in
merge) merge=1;;
delete) delete=1;;
push) push=1;;
esac
done
EOF
chmod +x "$script"
}
function _mscript_merge_branch() {
local msg
# basculer sur la branche
_scripta "switch to branch $DestBranch" <<EOF
$comment$(qvals git checkout "$DestBranch")$or_die
EOF
if [ -n "$SquashMsg" ]; then
msg="$SquashMsg"
[ -n "$TechMerge" ] && msg="<pman>$msg"
_scripta "squash merge $SrcBranch" <<EOF
$comment$(qvals git merge "$SrcBranch" --squash)$or_die
$comment$(qvals git commit -m "$msg")$or_die
EOF
else
msg="Intégration de la branche $SrcBranch"
[ -n "$TechMerge" ] && msg="<pman>$msg"
_scripta "merge $SrcBranch" <<EOF
$comment$(qvals git merge "$SrcBranch" --no-ff -m "$msg")$or_die
EOF
fi
array_addu push_branches "$DestBranch"
}
function _mscript_delete_branch() {
_scripta "delete branch $SrcBranch" <<EOF
$comment$(qvals git branch -D "$SrcBranch")$or_die
EOF
array_addu delete_branches ":$SrcBranch"
}
################################################################################
# Release
function _rscript_start() {
>"$script"
_scripta <<EOF
#!/bin/bash
$(qvals source "$NULIBDIR/load.sh") || exit 1
create=
merge=
push=
for arg in "\$@"; do
case "\$arg" in
create) create=1;;
merge) merge=1;;
push) push=1;;
esac
done
EOF
chmod +x "$script"
}
function _rscript_create_release_branch() {
local date changelog
_init_changelog
echo >>"$changelog" "\
## Release $Tag du $date
"
_list_commits | _filter_changes | _format_md >>"$changelog"
if [ -s CHANGES.md ]; then
echo >>"$changelog"
cat CHANGES.md >>"$changelog"
fi
"${EDITOR:-nano}" +7 "$changelog"
[ -s "$changelog" ] || exit_with ewarn "Création de la release annulée"
# créer la branche de release et basculer dessus
_scripta "create branch $ReleaseBranch" <<EOF
$(qvals git checkout -b "$ReleaseBranch" "$SrcBranch")$or_die
EOF
# créer le changelog
_scripta "update CHANGES.md" <<EOF
$(qvals echo "$(awk <"$changelog" '
BEGIN { p = 0 }
p == 0 && $0 == "" { p = 1; next }
p == 1 { gsub(/\$/, "\\$", $0); print }
')") >CHANGES.md
git add CHANGES.md
EOF
# mettre à jour la version
_scripta "update VERSION.txt" <<EOF
$(qvals echo "$Version") >VERSION.txt
git add VERSION.txt
EOF
# Enregistrer les changements
_scripta "commit" <<EOF
$(qvals git commit -m "<pman>Init changelog & version $Version")
EOF
}
function _rscript_merge_release_branch() {
local dest="$1" tag="$2"
# basculer sur la branche
_scripta "switch to branch $dest" <<EOF
$comment$(qvals git checkout "$dest")$or_die
EOF
# fusionner la branche de release
_scripta "merge branch $ReleaseBranch" <<EOF
$comment$(qvals git merge "$ReleaseBranch" -m "<pman>Intégration de la branche $ReleaseBranch" --no-ff)$or_die
EOF
array_addu push_branches "$dest"
# tagger la release
if [ -n "$tag" ]; then
_scripta "create tag $tag" <<EOF
$comment$(qvals git tag --force "$tag")$or_die
EOF
array_addu push_tags "$tag"
fi
}
function _rscript_delete_release_branch() {
_scripta "delete branch $ReleaseBranch" <<EOF
$comment$(qvals git branch -D "$ReleaseBranch")$or_die
EOF
}

16
bash/src/pman74.conf.sh Normal file
View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## configuration de la branche 7.4 d'un projet PHP multiversion
# il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les
# modifications de la 7.4 étant incluses dans la branche 8.2
UPSTREAM=
DEVELOP=dev74
FEATURE=wip74/
RELEASE=rel74-
MAIN=dist74
TAG_PREFIX=
TAG_SUFFIX=p74
HOTFIX=hotf74-
DIST=
NOAUTO=

16
bash/src/pman82.conf.sh Normal file
View File

@ -0,0 +1,16 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
## configuration de la branche 8.2 d'un projet PHP multiversion
# il s'agit d'un projet avec deux branches parallèles: 7.4 et 8.2, les
# modifications de la 7.4 étant incluses dans la branche 8.2
UPSTREAM=dev74
DEVELOP=dev82
FEATURE=wip82/
RELEASE=rel82-
MAIN=dist82
TAG_PREFIX=
TAG_SUFFIX=p82
HOTFIX=hotf82-
DIST=
NOAUTO=

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; NULIB_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:LOGFILE '$elogto $value_' "++enregistrer les messages dans le fichier spécifié"
-Q,--very-quiet,-q,--quiet,-v,--verbose,-D,--debug '$set_verbosity $option_' "++spécifier le niveau de verbiage"
-b,--batch,-y,--automatic,-i,--interactive '$set_interaction $option_' "++spécifier le niveau d'interaction"
)
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 "$NULIB_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 "$NULIB_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

4
bin/_merge82 Executable file
View File

@ -0,0 +1,4 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
exec "$(dirname -- "$0")/pdev" --tech-merge -Bdev82 dev74 "$@"

30
bin/_runphp_build-all 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
force=
args=(
"Construire toutes les images supportées de runphp"
#"usage"
-f,--force force=1 "Créer les images même si elles existent déjà"
)
parse_args "$@"; set -- "${args[@]}"
export RUNPHP_STANDALONE="$NULIBDIR"
export RUNPHP_PROJDIR=
export RUNPHP_REGISTRY=
export RUNPHP_DIST=
export RUNPHP_BUILD_FLAVOUR=
runphp=("$MYDIR/../runphp/runphp" --bs)
[ -z "$force" ] && runphp+=(--ue)
for RUNPHP_DIST in d12 d11; do
for RUNPHP_BUILD_FLAVOUR in +ic none; do
flavour="$RUNPHP_BUILD_FLAVOUR"
[ "$flavour" == none ] && flavour=
etitle "$RUNPHP_DIST$flavour"
"${runphp[@]}"
eend
done
done

1
bin/composer Symbolic link
View File

@ -0,0 +1 @@
runphp

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 -- "$@"

67
bin/p Executable file
View File

@ -0,0 +1,67 @@
#!/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 -c color.status=always status "$@" 2>&1)"; r=$?
if [ -n "$status" ]; then
cwd="$Cwd"
[ -n "$cwd" ] || cwd="$(pwd)"
setx cwd=ppath2 "$cwd" "$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
Cwd=
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
setx toplevel=git_get_toplevel
[ -n "$toplevel" ] && Cwd="$toplevel"
args=()
isatty || args+=(--porcelain)
git_status "${args[@]}"
fi

197
bin/pdev Executable file
View File

@ -0,0 +1,197 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git pman pman.conf
git_cleancheckout_DIRTY="\
Vous avez des modifications locales.
Enregistrez ces modifications avant de fusionner la branche"
function show_action() {
local commits
setx commits=_list_commits
if [ -n "$commits" ]; then
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
eecho "$commits"
fi
}
function ensure_branches() {
[ -n "$SrcBranch" -a -n "$DestBranch" ] ||
die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche"
array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable"
array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable"
}
function merge_action() {
enote "\
Ce script va
- fusionner la branche ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE} dans ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
- pousser les branches modifiées}"
ask_yesno "Voulez-vous continuer?" O || die
local script=".git/rel-merge.sh"
local -a push_branches delete_branches
local comment=
local or_die=" || exit 1"
_mscript_start
_scripta <<EOF
################################################################################
# merge
if [ -n "\$merge" ]; then
esection "Fusionner la branche"
EOF
_mscript_merge_branch
_scripta <<EOF
fi
EOF
_scripta <<EOF
################################################################################
# delete
if [ -n "\$delete" ]; then
esection "Supprimer la branche"
EOF
_mscript_delete_branch
_scripta <<EOF
fi
EOF
_scripta <<EOF
################################################################################
# push
if [ -n "\$push" ]; then
esection "Pousser les branches"
EOF
_script_push_branches
if [ ${#delete_branches[*]} -gt 0 ]; then
_scripta <<<"if [ -n \"\$delete\" ]; then"
push_branches=("${delete_branches[@]}")
_script_push_branches
_scripta <<<fi
fi
_scripta <<EOF
fi
EOF
[ -n "$Delete" -o "$ForbidDelete" ] && Deleted=1 || Deleted=
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
if [ -n "$_NoRunScript" ]; then
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
elif ! "$script" merge ${Delete:+delete} ${Push:+push}; then
eimportant "Veuillez consulter le script $script pour le détail des opérations qui n'ont pas pu êtres effectuées"
die
elif [ -n "$Deleted" -n "$Pushed" ]; then
[ -n "$_KeepScript" ] || rm "$script"
else
local cmd
[ -n "$Deleted" ] || cmd="$cmd
./$script delete"
[ -n "$Pushed" ] || cmd="$cmd
./$script push"
einfo "Le script $script a été lancé avec les arguments 'merge${Delete:+ delete}${Push:+ push}'
Veuillez le consulter pour le détail des autres opérations à effectuer$cmd"
fi
}
################################################################################
# Programme principal
################################################################################
chdir=
Origin=
ConfigBranch=
ConfigFile=
_KeepScript=
_NoRunScript=
action=merge
TechMerge=
SquashMsg=
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
[ -z "$PMAN_NO_DELETE" ] && Delete=1 || Delete=
args=(
"fusionner la branche source dans la branche destination correspondante"
" [source]"
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
-O:,--origin Origin= "++\
origine à partir de laquelle les branches distantes sont considérées"
-B:,--config-branch ConfigBranch= "++\
branche à partir de laquelle charger la configuration"
-c:,--config-file:CONFIG ConfigFile= "++\
fichier de configuration des branches. cette option est prioritaire sur --config-branch
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
--keep-script _KeepScript=1 "++option non documentée"
--no-run-script _NoRunScript=1 "++option non documentée"
-w,--show action=show "\
lister les modifications qui seraient fusionnées dans la branche destination"
-b,--rebase action=rebase "\
lancer git rebase -i sur la branche source. cela permet de réordonner les
commits pour nettoyer l'historique avant la fusion"
--merge action=merge "++\
fusionner la branche source dans la branche destination correspondante.
c'est l'action par défaut"
--tech-merge TechMerge=1 "++option non documentée"
-s:,--squash:COMMIT_MSG SquashMsg= "\
fusionner les modifications de la branche comme un seul commit.
cette option ne devrait pas être utilisée avec --no-delete"
-n,--no-push Push= "\
ne pas pousser les branches vers leur origine après la fusion"
--push Push=1 "++\
pousser les branches vers leur origine après la fusion.
c'est l'option par défaut"
-k,--no-delete Delete= "\
ne pas supprimer la branche après la fusion dans la destination"
--delete Delete=1 "++\
supprimer la branche après la fusion dans la destination.
c'est l'option par défaut"
)
parse_args "$@"; set -- "${args[@]}"
# charger la configuration
ensure_gitdir "$chdir"
load_branches all
load_config "$MYNAME"
load_branches current "$1"
ForbidPush=
[ -n "$Origin" ] || Origin=origin
if ! git_have_remote "$Origin" && [ -n "$Push" ]; then
ewarn "L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
ForbidPush=1
fi
[ -n "$ForbidPush" ] && Push=
# puis faire l'action que l'on nous demande
case "$action" in
show)
git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY"
ensure_branches
show_action "$@"
;;
merge)
ForbidDelete=
case "$SrcType" in
develop|release|hotfix)
die "$SrcBranch: cette branche doit être fusionnée dans $DestBranch avec prel"
;;
*)
# n'autoriser la suppression que pour feature
[ "$SrcType" == feature ] || ForbidDelete=1
;;
esac
[ -n "$ForbidDelete" ] && Delete=
git_ensure_cleancheckout
if ! array_contains LocalBranches "$SrcBranch"; then
# si la branche source n'existe pas, la créer
exec "$MYDIR/pman" "$FEATURE${SrcBranch#$FEATURE}"
else
ensure_branches
merge_action "$@"
fi
;;
*)
die "$action: action non implémentée"
;;
esac

250
bin/pman Executable file
View File

@ -0,0 +1,250 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git pman pman.conf
################################################################################
# Informations
################################################################################
SHOW_VARS=(
--Configuration
"${CONFIG_VARS[@]}"
--Paramètres
CurrentBranch
CurrentType=SrcType
)
function show_action() {
local var src
echo_setv ConfigBranch="$ConfigBranch"
echo_setv ConfigFile="$(ppath "$ConfigFile")"
for var in "${SHOW_VARS[@]}"; do
if [ "${var#--}" != "$var" ]; then
estep "${var#--}"
else
splitfsep "$var" = var src
[ -n "$src" ] || src="$var"
echo_setv "$var=${!src}"
fi
done
}
################################################################################
# Initialisation
################################################################################
function init_repo_action() {
[ ${#LocalBranches[*]} -eq 0 ] || die "Ce dépôt a déjà été initialisé"
local -a push_branches
if [ ! -f .pman.conf ]; then
ac_set_tmpfile config
cp "$ConfigFile" "$config"
"${EDITOR:-nano}" "$config"
[ -s "$config" ] || exit_with ewarn "Initialisation du dépôt annulée"
cp "$config" .pman.conf
if testdiff .pman.conf "$ConfigFile"; then
ConfigFile="$(pwd)/.pman.conf"
load_config
load_branches current "$SrcBranch"
fi
git add .pman.conf
fi
if [ ! -f ".gitignore" ]; then
echo >.gitignore "\
.~lock*#
.*.swp"
git add .gitignore
fi
einfo "Création de la branche $MAIN"
git symbolic-ref HEAD "refs/heads/$MAIN"
git commit -m "commit initial"
push_branches+=("$MAIN")
einfo "Création de la branche $DEVELOP"
git checkout -b "$DEVELOP"
push_branches+=("$DEVELOP")
_push_branches
}
function init_develop_action() {
if [ -z "$DevelopBranch" ]; then
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
enote "Vous allez créer la branche ${COULEUR_VERTE}$DEVELOP${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
local -a push_branches
einfo "Création de la branche $DEVELOP"
git checkout -b "$DEVELOP" "$MAIN" || die
push_branches+=("$DEVELOP")
_push_branches
fi
git checkout -q "$DEVELOP"
}
function init_upstream_action() {
if [ -z "$UpstreamBranch" ]; then
[ -n "$UPSTREAM" ] || die "La branche UPSTREAM n'a pas été définie"
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
enote "Vous allez créer la branche ${COULEUR_VERTE}$UPSTREAM${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
local -a push_branches; local config
# faire une copie de la configuration actuelle
ac_set_tmpfile config
cp "$ConfigFile" "$config"
einfo "Création de la branche $UPSTREAM"
git checkout --orphan "$UPSTREAM" || die
git rm -rf .
cp "$config" .pman.conf
git add .pman.conf
git commit -m "commit initial"
push_branches+=("$UPSTREAM")
einfo "Fusion dans $DEVELOP"
git checkout "$DEVELOP"
git merge \
--no-ff -m "<pman>Intégration initiale de la branche $UPSTREAM" \
-srecursive -Xours --allow-unrelated-histories \
"$UPSTREAM"
push_branches+=("$DEVELOP")
_push_branches
fi
git checkout -q "$UPSTREAM"
}
function init_dist_action() {
if [ -z "$DistBranch" ]; then
[ -n "$DIST" ] || die "La branche DIST n'a pas été définie"
[ -n "$MAIN" ] || die "La branche MAIN n'a pas été définie"
[ -n "$MainBranch" ] || die "$MAIN: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
enote "Vous allez créer la branche ${COULEUR_VERTE}$DIST${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$MAIN${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
local -a push_branches
einfo "Création de la branche $DIST"
git checkout -b "$DIST" "$MAIN" || die
push_branches+=("$DIST")
_push_branches
fi
git checkout -q "$DIST"
}
function init_feature_action() {
local branch="${1#$FEATURE}"
[ -n "$branch" ] || die "Vous devez définir la nom de la branche à créer"
branch="$FEATURE$branch"
if ! array_contains AllBranches "$branch"; then
[ -n "$DEVELOP" ] || die "La branche DEVELOP n'a pas été définie"
[ -n "$DevelopBranch" ] || die "$DEVELOP: cette branche n'existe pas (le dépôt a-t-il été initialisé?)"
enote "Vous allez créer la branche ${COULEUR_VERTE}$branch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$DEVELOP${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
local -a push_branches
einfo "Création de la branche $branch"
git checkout -b "$branch" "$DEVELOP" || die
push_branches+=("$branch")
_push_branches
fi
git checkout -q "$branch"
}
function init_action() {
local what="${1:-develop}"; shift
case "$what" in
init|repo|r) init_repo_action "$@";;
main|m) git checkout -q "$MAIN";;
develop|dev|d) init_develop_action "$@";;
upstream|up|u) init_upstream_action "$@";;
dist|x) init_dist_action "$@";;
*) init_feature_action "$what" "$@";;
esac
}
################################################################################
# Programme principal
################################################################################
chdir=
ConfigBranch=
ConfigFile=
action=init
Origin=
Push=1
args=(
"gérer un projet git"
"repo|develop|upstream|dist
INITIALISATION
Par défaut, le script agit en mode initialisation qui permet de créer et/ou
configurer certaines branches du dépôt si elles n'existent pas déjà
repo
initialiser un dépôt vide et créer les branches $MAIN et $DEVELOP
develop
créer la branche $DEVELOP
upstream
créer la branche ${UPSTREAM:-UPSTREAM} en tant que source de la branche $DEVELOP
dist
créer la branche ${DIST:-DIST} en tant que destination de la branche $MAIN
anything
créer la branche ${FEATURE}anything à partir de la branche $DEVELOP"
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
-B:,--config-branch ConfigBranch= "++\
branche à partir de laquelle charger la configuration"
-c:,--config-file:CONFIG ConfigFile= "++\
fichier de configuration des branches. cette option est prioritaire sur --config-branch
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
-w,--show-config action=show "++\
afficher la configuration chargée"
-O:,--origin Origin= "++\
origine vers laquelle pousser les branches"
-n,--no-push Push= "\
ne pas pousser les branches vers leur origine après leur création"
--push Push=1 "++\
pousser les branches vers leur origine après leur création.
c'est l'option par défaut"
)
parse_args "$@"; set -- "${args[@]}"
# charger la configuration
ensure_gitdir "$chdir"
load_branches all
load_config "$MYNAME"
load_branches current
# puis faire l'action que l'on nous demande
case "$action" in
show)
show_action "$@"
;;
init)
git_ensure_cleancheckout
init_action "$@"
;;
*)
die "$action: action non implémentée"
;;
esac

239
bin/prel Executable file
View File

@ -0,0 +1,239 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
source "$(dirname -- "$0")/../load.sh" || exit 1
require: git pman pman.conf
git_cleancheckout_DIRTY="\
Vous avez des modifications locales.
Enregistrez ces modifications avant de créer une release"
function show_action() {
local commits
setx commits=_list_commits
if [ -n "$commits" ]; then
einfo "Commits à fusionner $SrcBranch --> $DestBranch"
eecho "$commits"
fi
}
function ensure_branches() {
[ -n "$SrcBranch" -a -n "$DestBranch" ] ||
die "$SrcBranch: Aucune configuration de fusion trouvée pour cette branche"
array_contains LocalBranches "$SrcBranch" || die "$SrcBranch: branche source introuvable"
array_contains LocalBranches "$DestBranch" || die "$DestBranch: branche destination introuvable"
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
local -a tags
setx -a tags=git tag -l "${TAG_PREFIX}*${TAG_SUFFIX}"
if [ -z "$ForceCreate" ]; then
array_contains tags "$Tag" && die "$Tag: le tag correspondant à la version existe déjà"
fi
}
function create_release_action() {
if [ -n "$ReleaseBranch" ]; then
Version="${ReleaseBranch#$RELEASE}"
merge_release_action "$@"; return $?
elif [ -n "$HotfixBranch" ]; then
Version="${HotfixBranch#$HOTFIX}"
merge_hotfix_action "$@"; return $?
fi
if [ -z "$Version" -a -n "$CurrentVersion" -a -f VERSION.txt ]; then
Version="$(<VERSION.txt)"
Tag="$TAG_PREFIX$Version$TAG_SUFFIX"
fi
if [ -z "$Version" ]; then
[ -f VERSION.txt ] && einfo "La version actuelle est $(<VERSION.txt)"
die "Vous devez spécifier la version de la nouvelle release avec l'une des options -v ou -C"
fi
ReleaseBranch="${RELEASE}$Version"
if [ -n "$Merge" ]; then
enote "\
Ce script va:
- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE}
- la provisionner avec une description des changements
- la fusionner dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}${Push:+
- pousser les branches modifiées}"
else
enote "\
Ce script va:
- créer la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE} <-- ${COULEUR_BLEUE}$SrcBranch${COULEUR_NORMALE}
- la provisionner avec une description des changements
Vous devrez:
- mettre à jour les informations de release puis relancer ce script"
fi
ask_yesno "Voulez-vous continuer?" O || die
local script=".git/rel-release.sh"
local -a push_branches push_tags
local comment=
local or_die=" || exit 1"
_rscript_start
_scripta <<EOF
################################################################################
# create
if [ -n "\$create" ]; then
esection "Création de la release"
EOF
_rscript_create_release_branch
_scripta <<EOF
fi
EOF
_scripta <<EOF
################################################################################
# merge
if [ -n "\$merge" ]; then
esection "Fusionner la release"
EOF
_rscript_merge_release_branch "$DestBranch" "$Tag"
_rscript_merge_release_branch "$SrcBranch"
_rscript_delete_release_branch
_scripta <<EOF
fi
EOF
_scripta <<EOF
################################################################################
# push
if [ -n "\$push" ]; then
esection "Pousser branches et tags"
EOF
_script_push_branches
_script_push_tags
_scripta <<EOF
fi
EOF
[ -n "$Merge" ] && Merged=1 || Merged=
[ -n "$Push" -o "$ForbidPush" ] && Pushed=1 || Pushed=
if [ -n "$_NoRunScript" ]; then
einfo "Veuillez consulter le script $script pour le détail des opérations à effectuer"
elif ! "$script" create ${Merge:+merge} ${Push:+push}; then
eimportant "Veuillez consulter le script $script pour le détail des opérations qui n'ont pas pu êtres effectuées"
die
elif [ -n "$Merged" -a -n "$Pushd" ]; then
[ -n "$_KeepScript" ] || rm "$script"
else
local cmd
[ -n "$Merged" ] || cmd="$cmd
./$script merge"
[ -n "$Pushd" ] || cmd="$cmd
./$script push"
einfo "Le script $script a été lancé avec les arguments 'create${Merge:+ merge}${Push:+ push}'
Veuillez le consulter pour le détail des autres opérations à effectuer$cmd"
fi
}
function merge_release_action() {
enote "\
Vous allez:
- fusionner la branche de release ${COULEUR_VERTE}$ReleaseBranch${COULEUR_NORMALE}
dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
}
function merge_hotfix_action() {
enote "\
Vous allez intégrer la branche de hotfix ${COULEUR_JAUNE}$HotfixBranch${COULEUR_NORMALE}
dans la branche destination ${COULEUR_ROUGE}$DestBranch${COULEUR_NORMALE}"
ask_yesno "Voulez-vous continuer?" O || die
}
################################################################################
# Programme principal
################################################################################
chdir=
Origin=
ConfigBranch=
ConfigFile=
_KeepScript=
_NoRunScript=
action=release
[ -z "$PMAN_NO_MERGE" ] && Merge=1 || Merge=
[ -z "$PMAN_NO_PUSH" ] && Push=1 || Push=
Version=
CurrentVersion=
ForceCreate=
args=(
"faire une nouvelle release à partir de la branche source"
" -v VERSION [source]"
-d:,--chdir:BASEDIR chdir= "répertoire dans lequel se placer avant de lancer les opérations"
-O:,--origin Origin= "++\
origine à partir de laquelle les branches distantes sont considérées"
-B:,--config-branch ConfigBranch= "++\
branche à partir de laquelle charger la configuration"
-c:,--config-file:CONFIG ConfigFile= "++\
fichier de configuration des branches. cette option est prioritaire sur --config-branch
par défaut, utiliser le fichier .pman.conf dans le répertoire du dépôt s'il existe"
--keep-script _KeepScript=1 "++option non documentée"
--no-run-script _NoRunScript=1 "++option non documentée"
-w,--show action=show "\
lister les modifications qui seraient intégrées dans la release"
--release action=release "++\
créer la release.
c'est l'action par défaut"
-k,--no-merge Merge= "\
ne pas fusionner la branche de release après l'avoir créée. implique --no-push"
--merge Merge=1 "++\
fusionner la branche de release après l'avoir créée.
c'est l'option par défaut"
-n,--no-push Push= "\
ne pas pousser les branches vers leur origine après la création de la release"
--push Push=1 "++\
pousser les branches vers leur origine après la création de la release.
c'est l'option par défaut"
-v:,--version Version= "\
spécifier la version de la release à créer"
-C,--current-version CurrentVersion=1 "++\
si aucune version n'est spécifiée, prendre la version présente dans le fichier VERSION.txt"
-f:,--force-create ForceCreate= "\
forcer la création de la release même si le tag correspond à la version existe déjà"
)
parse_args "$@"; set -- "${args[@]}"
# charger la configuration
ensure_gitdir "$chdir"
load_branches all
load_config "$MYNAME"
load_branches current "$1"; shift
if [ -n "$Merge" -a -n "$NOAUTO" ]; then
ewarn "L'option --no-merge a été forcée puisque ce dépôt ne supporte pas les releases automatiques"
Merge=
fi
[ -z "$Merge" ] && Push=
ForbidPush=
[ -n "$Origin" ] || Origin=origin
if ! git_have_remote "$Origin" && [ -n "$Push" ]; then
ewarn "L'option --no-push a été forcée puisque ce dépôt n'a pas d'origine"
ForbidPush=1
fi
[ -n "$ForbidPush" ] && Push=
# puis faire l'action que l'on nous demande
case "$action" in
show)
git_check_cleancheckout || ewarn "$git_cleancheckout_DIRTY"
ensure_branches
show_action "$@"
;;
release)
git_ensure_cleancheckout
ensure_branches
case "$SrcType" in
release) merge_release_action "$@";;
hotfix) merge_hotfix_action "$@";;
*) create_release_action "$@";;
esac
;;
*)
die "$action: action non implémentée"
;;
esac

51
bin/runphp Executable file
View File

@ -0,0 +1,51 @@
#!/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
owd="$(pwd)"
PROJDIR=
while true; do
cwd="$(pwd)"
if [ -f .runphp.conf ]; then
PROJDIR="$cwd"
break
elif [ -f composer.json ]; then
PROJDIR="$cwd"
break
fi
if [ "$cwd" == "$HOME" -o "$cwd" == / ]; then
cd "$owd"
break
fi
cd ..
done
if [ -z "$PROJDIR" ]; then
# s'il n'y a pas de projet, --bs est l'action par défaut
[ $# -gt 0 ] || set -- --bs --ue
elif [ "$MYNAME" == composer ]; then
set -- composer "$@"
else
case "$1" in
*.php|*.phar) set -- php "$@";;
esac
fi
if [ -n "$PROJDIR" ]; then
export RUNPHP_STANDALONE=
RUNPHP=; DIST=; REGISTRY=
if [ -f "$PROJDIR/.runphp.conf" ]; then
source "$PROJDIR/.runphp.conf"
[ -n "$RUNPHP" ] && exec "$PROJDIR/$RUNPHP" "$@"
elif [ -f "$PROJDIR/sbin/runphp" ]; then
exec "$PROJDIR/sbin/runphp" "$@"
elif [ -f "$PROJDIR/runphp" ]; then
exec "$PROJDIR/runphp" "$@"
fi
fi
export RUNPHP_STANDALONE="$NULIBDIR"
export RUNPHP_PROJDIR="$PROJDIR"
export RUNPHP_REGISTRY="$REGISTRY"
export RUNPHP_DIST="$DIST"
exec "$MYDIR/../runphp/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

47
composer.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "nulib/php",
"type": "library",
"description": "fonctions et classes essentielles",
"repositories": [
{
"type": "composer",
"url": "https://repos.univ-reunion.fr/composer"
}
],
"extra": {
"branch-alias": {
"dev-dev74": "7.4.x-dev",
"dev-dev82": "8.2.x-dev"
}
},
"require": {
"symfony/yaml": "^5.0",
"ext-json": "*",
"php": "^7.4"
},
"require-dev": {
"nulib/tests": "^7.4",
"ext-posix": "*",
"ext-pcntl": "*",
"ext-curl": "*",
"ext-sqlite3": "*"
},
"autoload": {
"psr-4": {
"nulib\\": "php/src"
}
},
"autoload-dev": {
"psr-4": {
"nulib\\": "php/tests"
}
},
"scripts": {
},
"authors": [
{
"name": "Jephte Clain",
"email": "Jephte.Clain@univ-reunion.fr"
}
]
}

2021
composer.lock generated Normal file

File diff suppressed because it is too large Load Diff

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/dist
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/dist
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/dist
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/dist
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/dist
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/dist
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/dist
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,17 @@
# -*- coding: utf-8 mode: dockerfile -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
ARG REGISTRY=pubdocker.univ-reunion.fr/dist
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"]

37
lib/completion.d/pman Normal file
View File

@ -0,0 +1,37 @@
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
function __pman_pdev_branches() {
local toplevel="$(git rev-parse --show-toplevel 2>/dev/null)"
[ -n "$toplevel" ] || return 0
(
# cf pman.conf.sh
UPSTREAM=
DEVELOP=develop
FEATURE=wip/
RELEASE=release-
MAIN=master
TAG_PREFIX=
TAG_SUFFIX=
HOTFIX=hotfix-
DIST=
[ -f "$toplevel/.pman.conf" ] && source "$toplevel/.pman.conf"
# lister les branches
branches="$DEVELOP|$FEATURE.*"
[ -n "$UPSTREAM" ] && branches="$branches|$UPSTREAM"
remote=origin/
{
git for-each-ref refs/heads/ --format='%(refname:short)' 2>/dev/null
git for-each-ref "refs/remotes/$remote" --format='%(refname:short)' 2>/dev/null |
grep -F "$remote" |
cut -c $((${#remote} + 1))-
} | LANG=C sort -u | grep -E "^($branches)\$"
)
}
function __pdev_completion() {
local cur
_get_comp_words_by_ref cur
COMPREPLY=($(compgen -W "$(__pman_pdev_branches)" "$cur"))
}
complete -F __pdev_completion pdev

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

5
php/run-tests Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
# -*- coding: utf-8 mode: sh -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
MYDIR="$(dirname -- "$0")"
VENDOR="$MYDIR/../vendor"
"$VENDOR/bin/phpunit" --bootstrap "$VENDOR/autoload.php" "$@" "$MYDIR/tests"

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

@ -0,0 +1,235 @@
<?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)); }
#############################################################################
static final function sort(?array &$array, int $flags=SORT_REGULAR, bool $assoc=false): void {
if ($array === null) return;
if ($assoc) asort($array, $flags);
else sort($array, $flags);
}
static final function ksort(?array &$array, int $flags=SORT_REGULAR): void {
if ($array === null) return;
ksort($array, $flags);
}
static final function usort(?array &$array, array $keys, bool $assoc=false): void {
if ($array === null) return;
if ($assoc) uasort($array, cl::compare($keys));
else usort($array, cl::compare($keys));
}
}

View File

@ -0,0 +1,36 @@
<?php
namespace nulib;
/**
* Class AccessException: indiquer que la resource ou l'objet auquel on veut
* accéder n'est pas accessible. il s'agit donc d'une erreur de l'utilisateur
*/
class AccessException extends UserException {
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";
$message = "$dest is immutable";
return new static($prefix.$message);
}
static final function not_allowed(?string $action=null, ?string $prefix=null): self {
if ($prefix) $prefix = "$prefix: ";
if ($action === null) $action = "this operation";
$message = "$action is not allowed";
return new static($prefix.$message);
}
static final function not_accessible(?string $dest=null, ?string $prefix=null): self {
if ($prefix) $prefix = "$prefix: ";
if ($dest === null) $dest = "this resource";
$message = "$dest is not accessible";
return new static($prefix.$message);
}
}

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;
}
}

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