From 36886b5bac75cda19506ba357136200ccf2fe2d4 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 28 Nov 2020 09:23:10 -0800 Subject: Add PM_DECLARED and PM_DECLAREDNULL parameter flags. This addresses the issue that "typeset foo" creates $foo set to an empty string, which differs from typeset handling in bash and ksh. It does this by concealing the internal value rather than alter the way internal values are defaulted. This imposes the following changes: A typeset variable with no assignment triggers NO_UNSET warnings when the name is used in parameter expansion or math. The typeset -AEFHLRTZailux options are applied upon the first assignment to the variable. Explicit unset before the first assignment discards all of those properties. If any option is applied to an existing name, historic behavior is unchanged. Consequent to the foregoing, the (t) parameter expansion flag prints nothing until after the first assignment, and the (i) and (I) subscript flags also print nothing. The bash behavior of "unset foo; typeset -p foo" is NOT used. This is called out as an emulation distinction, not a change. The test cases have not been updated, so several now fail. The test harness has been updated to declare default values. --- Src/builtin.c | 1 + 1 file changed, 1 insertion(+) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index 26335a2e8..691734221 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,6 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } + pm->node.flags |= PM_DECLAREDNULL; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); -- cgit 1.4.1 From 128035c93c5bc149e9c3babd48c8c145609e29e0 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 3 Dec 2020 22:30:04 -0800 Subject: Partial fix for handling of tied arrays. As of this commit when a tied array is declared but neither the scalar nor the array has an initializer, the array is initialized to empty. The scalar struct param of a tied pair stores a direct pointer to the internal array value of the array struct param, and upon assignment modifies it without referring to the containing struct. This means that there's no opportunity to clear the PM_DECLAREDNULL bits on both structs when the scalar is assigned. Conversely, assigning to the array does use the struct for the scalar. --- Src/builtin.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index 691734221..68ebead7e 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2890,6 +2890,8 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * * Don't attempt to set it yet, it's too early * to be exported properly. + * + * This creates the array with PM_DECLAREDNULL. */ asg2.name = asg->name; asg2.flags = 0; @@ -2933,6 +2935,10 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); } else if (oldval) assignsparam(asg0.name, oldval, 0); + else /*if (asg0.value.scalar)*/ { + /* We have to undo what we did wrong with asg2 */ + apm->node.flags &= ~PM_DECLAREDNULL; + } unqueue_signals(); return 0; -- cgit 1.4.1 From fa5f59bf710099dcaa8f22729591c2580c052db9 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 4 Dec 2020 15:09:14 -0800 Subject: Additional tied-array cleanup when tied scalar had a previous value --- Src/builtin.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index 68ebead7e..fff0b641f 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2933,11 +2933,11 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) if (asg->value.array) { int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); - } else if (oldval) - assignsparam(asg0.name, oldval, 0); - else /*if (asg0.value.scalar)*/ { + } else /*if (asg0.value.scalar || oldval)*/ { /* We have to undo what we did wrong with asg2 */ apm->node.flags &= ~PM_DECLAREDNULL; + if (oldval) + assignsparam(asg0.name, oldval, 0); } unqueue_signals(); -- cgit 1.4.1 From b1bd99b67f1a2e09dff651f102249c441529d530 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Fri, 4 Dec 2020 16:14:28 -0800 Subject: Final repairs for declared state of tied arrays Fixups still required in bin_typeset, but assignments to scalar work. --- Src/builtin.c | 4 ++-- Src/params.c | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index fff0b641f..8d8ff68e0 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2837,7 +2837,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) unqueue_signals(); return 1; } else if (pm) { - if (!(pm->node.flags & PM_UNSET) + if ((!(pm->node.flags & PM_UNSET) || pm->node.flags & PM_DECLARED) && (locallevel == pm->level || !(on & PM_LOCAL))) { if (pm->node.flags & PM_TIED) { if (PM_TYPE(pm->node.flags) != PM_SCALAR) { @@ -2933,7 +2933,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) if (asg->value.array) { int flags = (asg->flags & ASG_KEY_VALUE) ? ASSPM_KEY_VALUE : 0; assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); - } else /*if (asg0.value.scalar || oldval)*/ { + } else if (asg0.value.scalar || oldval) { /* We have to undo what we did wrong with asg2 */ apm->node.flags &= ~PM_DECLAREDNULL; if (oldval) diff --git a/Src/params.c b/Src/params.c index 1a047d9e0..c09a3eccf 100644 --- a/Src/params.c +++ b/Src/params.c @@ -4125,6 +4125,11 @@ tiedarrsetfn(Param pm, char *x) if (*dptr->arrptr) freearray(*dptr->arrptr); + else if (pm->ename) { + Param altpm = (Param) paramtab->getnode(paramtab, pm->ename); + if (altpm) + altpm->node.flags &= ~PM_DECLAREDNULL; + } if (x) { char sepbuf[3]; if (imeta(dptr->joinchar)) -- cgit 1.4.1 From 2eb9289d1277bf71b1b9dea2addbddd36b61f276 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Thu, 17 Dec 2020 21:08:47 -0800 Subject: Missed files from POSXIBUILTINS commit --- Src/builtin.c | 5 +++-- Test/V10private.ztst | 12 ++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index 8d8ff68e0..b0d4eb7f7 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,8 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - pm->node.flags |= PM_DECLAREDNULL; + if (isset(POSIXBUILTINS)) + pm->node.flags |= PM_DECLAREDNULL; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); @@ -2891,7 +2892,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * Don't attempt to set it yet, it's too early * to be exported properly. * - * This creates the array with PM_DECLAREDNULL. + * This may create the array with PM_DECLAREDNULL. */ asg2.name = asg->name; asg2.flags = 0; diff --git a/Test/V10private.ztst b/Test/V10private.ztst index a3a63867b..03e8259d5 100644 --- a/Test/V10private.ztst +++ b/Test/V10private.ztst @@ -19,14 +19,14 @@ () { print $scalar_test private scalar_test - print $+scalar_test + typeset +m scalar_test unset scalar_test print $+scalar_test } print $scalar_test 0:basic scope hiding >toplevel ->1 +>local scalar_test >0 >toplevel @@ -45,14 +45,14 @@ print $+unset_test () { private unset_test - print $+unset_test + typeset +m unset_test unset_test=setme print $unset_test } print $+unset_test 0:variable defined only in scope >0 ->1 +>local unset_test >setme >0 @@ -62,13 +62,13 @@ local -Pa array_test=(in function) () { private array_test - print $+array_test + typeset +m array_test } print $array_test } print $array_test 0:nested scope with different type, correctly restored ->1 +>local array_test >in function >top level -- cgit 1.4.1 From fd89e7cefa4d3806f3f469d5ad4b496d130305a6 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 27 Mar 2021 14:04:05 -0700 Subject: Change DECLAREDNULL to DEFAULTED --- Src/builtin.c | 6 +++--- Src/params.c | 16 ++++++++-------- Src/zsh.h | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index b0d4eb7f7..f0c490119 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2492,7 +2492,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), } } if (isset(POSIXBUILTINS)) - pm->node.flags |= PM_DECLAREDNULL; + pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) zerrnam(cname, "not an identifier: %s", pname); @@ -2892,7 +2892,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) * Don't attempt to set it yet, it's too early * to be exported properly. * - * This may create the array with PM_DECLAREDNULL. + * This may create the array with PM_DEFAULTED. */ asg2.name = asg->name; asg2.flags = 0; @@ -2936,7 +2936,7 @@ bin_typeset(char *name, char **argv, LinkList assigns, Options ops, int func) assignaparam(asg->name, zlinklist2array(asg->value.array, 1), flags); } else if (asg0.value.scalar || oldval) { /* We have to undo what we did wrong with asg2 */ - apm->node.flags &= ~PM_DECLAREDNULL; + apm->node.flags &= ~PM_DEFAULTED; if (oldval) assignsparam(asg0.name, oldval, 0); } diff --git a/Src/params.c b/Src/params.c index c09a3eccf..33bbc54f6 100644 --- a/Src/params.c +++ b/Src/params.c @@ -3056,7 +3056,7 @@ assignsparam(char *s, char *val, int flags) * Don't warn about anything. */ flags &= ~ASSPM_WARN; - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; } *ss = '['; v = NULL; @@ -3082,7 +3082,7 @@ assignsparam(char *s, char *val, int flags) } if (flags & ASSPM_WARN) check_warn_pm(v->pm, "scalar", created, 1); - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; if (flags & ASSPM_AUGMENT) { if (v->start == 0 && v->end == -1) { switch (PM_TYPE(v->pm->node.flags)) { @@ -3235,7 +3235,7 @@ assignaparam(char *s, char **val, int flags) if (flags & ASSPM_WARN) check_warn_pm(v->pm, "array", created, may_warn_about_nested_vars); - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; /* * At this point, we may have array entries consisting of @@ -3448,7 +3448,7 @@ sethparam(char *s, char **val) return NULL; } check_warn_pm(v->pm, "associative array", checkcreate, 1); - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; setarrvalue(v, val); unqueue_signals(); return v->pm; @@ -3520,7 +3520,7 @@ assignnparam(char *s, mnumber val, int flags) if (flags & ASSPM_WARN) check_warn_pm(v->pm, "numeric", 0, 1); } - v->pm->node.flags &= ~PM_DECLAREDNULL; + v->pm->node.flags &= ~PM_DEFAULTED; setnumvalue(v, val); unqueue_signals(); return v->pm; @@ -4128,7 +4128,7 @@ tiedarrsetfn(Param pm, char *x) else if (pm->ename) { Param altpm = (Param) paramtab->getnode(paramtab, pm->ename); if (altpm) - altpm->node.flags &= ~PM_DECLAREDNULL; + altpm->node.flags &= ~PM_DEFAULTED; } if (x) { char sepbuf[3]; @@ -5049,7 +5049,7 @@ arrfixenv(char *s, char **t) if (isset(ALLEXPORT)) pm->node.flags |= PM_EXPORTED; - pm->node.flags &= ~PM_DECLAREDNULL; + pm->node.flags &= ~PM_DEFAULTED; /* * Do not "fix" parameters that were not exported @@ -5856,7 +5856,7 @@ printparamnode(HashNode hn, int printflags) if (p->node.flags & PM_UNSET) { if ((printflags & (PRINT_POSIX_READONLY|PRINT_POSIX_EXPORT) && p->node.flags & (PM_READONLY|PM_EXPORTED)) || - (p->node.flags & PM_DECLAREDNULL) == PM_DECLAREDNULL) { + (p->node.flags & PM_DEFAULTED) == PM_DEFAULTED) { /* * Special POSIX rules: show the parameter as readonly/exported * even though it's unset, but with no value. diff --git a/Src/zsh.h b/Src/zsh.h index 787afaafa..27be1f788 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -1932,7 +1932,7 @@ struct tieddata { #define PM_DECLARED (1<<22) /* explicitly named with typeset */ #define PM_RESTRICTED (1<<23) /* cannot be changed in restricted mode */ #define PM_UNSET (1<<24) /* has null value */ -#define PM_DECLAREDNULL (PM_DECLARED|PM_UNSET) +#define PM_DEFAULTED (PM_DECLARED|PM_UNSET) #define PM_REMOVABLE (1<<25) /* special can be removed from paramtab */ #define PM_AUTOLOAD (1<<26) /* autoloaded from module */ #define PM_NORESTORE (1<<27) /* do not restore value of local special */ -- cgit 1.4.1 From e67ccd7f1efae7696dc17f6e3e720cd994e90155 Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Sat, 10 Apr 2021 14:10:21 -0700 Subject: Add TYPESET_DOES_NOT_SET option (cf. 48469) --- Src/builtin.c | 2 +- Src/options.c | 1 + Src/zsh.h | 1 + Test/E03posix.ztst | 2 +- 4 files changed, 4 insertions(+), 2 deletions(-) (limited to 'Src/builtin.c') diff --git a/Src/builtin.c b/Src/builtin.c index f0c490119..edd4cad44 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - if (isset(POSIXBUILTINS)) + if (isset(TYPESETDOESNOTSET)) pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) diff --git a/Src/options.c b/Src/options.c index fba021e7d..766ffdfdb 100644 --- a/Src/options.c +++ b/Src/options.c @@ -257,6 +257,7 @@ static struct optname optns[] = { {{NULL, "sunkeyboardhack", 0}, SUNKEYBOARDHACK}, {{NULL, "transientrprompt", 0}, TRANSIENTRPROMPT}, {{NULL, "trapsasync", 0}, TRAPSASYNC}, +{{NULL, "typesetdoesnotset", OPT_EMULATE|OPT_BOURNE}, TYPESETDOESNOTSET}, {{NULL, "typesetsilent", OPT_EMULATE|OPT_BOURNE}, TYPESETSILENT}, {{NULL, "unset", OPT_EMULATE|OPT_BSHELL}, UNSET}, {{NULL, "verbose", 0}, VERBOSE}, diff --git a/Src/zsh.h b/Src/zsh.h index 27be1f788..12efb784f 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2536,6 +2536,7 @@ enum { SUNKEYBOARDHACK, TRANSIENTRPROMPT, TRAPSASYNC, + TYPESETDOESNOTSET, TYPESETSILENT, UNSET, VERBOSE, diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index c59ca4f6e..a2769f3aa 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -4,7 +4,7 @@ # %prep - setopt POSIX_BUILTINS + setopt POSIX_BUILTINS TYPESET_DOES_NOT_SET %test -- cgit 1.4.1 From b3613e4895f7b059558c4ef211189b516dbf903d Mon Sep 17 00:00:00 2001 From: Bart Schaefer Date: Mon, 12 Apr 2021 13:59:06 -0700 Subject: Change TYPESET_DOES_NOT_SET to TYPESET_TO_UNSET to avoid double-negative --- Doc/Zsh/builtins.yo | 6 +++++- Doc/Zsh/options.yo | 10 ++++++++++ Src/builtin.c | 2 +- Src/options.c | 2 +- Src/zsh.h | 2 +- Test/D06subscript.ztst | 5 +++++ Test/E03posix.ztst | 45 ++++++++++++++++++++++++++++++++++++++++++++- 7 files changed, 67 insertions(+), 5 deletions(-) (limited to 'Src/builtin.c') diff --git a/Doc/Zsh/builtins.yo b/Doc/Zsh/builtins.yo index a7afe42cf..61dc6986f 100644 --- a/Doc/Zsh/builtins.yo +++ b/Doc/Zsh/builtins.yo @@ -1872,7 +1872,11 @@ ifnzman(noderef(Local Parameters))\ retain their special attributes when made local. For each var(name)tt(=)var(value) assignment, the parameter -var(name) is set to var(value). +var(name) is set to var(value). If the assignment is omitted and var(name) +does em(not) refer to an existing parameter, a new parameter is intialized +to empty string, zero, or empty array (as appropriate), em(unless) the +shell option tt(TYPESET_TO_UNSET) is set. When that option is set, +the parameter attributes are recorded but the parameter remains unset. If the shell option tt(TYPESET_SILENT) is not set, for each remaining var(name) that refers to a parameter that is already set, the name and diff --git a/Doc/Zsh/options.yo b/Doc/Zsh/options.yo index b3bf11f5c..6a1f82b07 100644 --- a/Doc/Zsh/options.yo +++ b/Doc/Zsh/options.yo @@ -1926,6 +1926,16 @@ If the option is set, they will only be shown when parameters are selected with the `tt(-m)' option. The option `tt(-p)' is available whether or not the option is set. ) +pindex(TYPESET_TO_UNSET) +pindex(NO_TYPESET_TO_UNSET) +pindex(TYPESETTOUNSET) +pindex(NOTYPESETTOUNSET) +item(tt(TYPESET_TO_UNSET) )( +When declaring a new parameter with any of the `tt(typeset)' family of +related commands, the parameter remains unset unless and until a +value is explicity assigned to it, either in the `tt(typeset)' command +itself or as a later assignment statement. +) pindex(VERBOSE) pindex(NO_VERBOSE) pindex(NOVERBOSE) diff --git a/Src/builtin.c b/Src/builtin.c index edd4cad44..6d119f7a5 100644 --- a/Src/builtin.c +++ b/Src/builtin.c @@ -2491,7 +2491,7 @@ typeset_single(char *cname, char *pname, Param pm, UNUSED(int func), return NULL; } } - if (isset(TYPESETDOESNOTSET)) + if (isset(TYPESETTOUNSET)) pm->node.flags |= PM_DEFAULTED; } else { if (idigit(*pname)) diff --git a/Src/options.c b/Src/options.c index 766ffdfdb..23935ae18 100644 --- a/Src/options.c +++ b/Src/options.c @@ -257,8 +257,8 @@ static struct optname optns[] = { {{NULL, "sunkeyboardhack", 0}, SUNKEYBOARDHACK}, {{NULL, "transientrprompt", 0}, TRANSIENTRPROMPT}, {{NULL, "trapsasync", 0}, TRAPSASYNC}, -{{NULL, "typesetdoesnotset", OPT_EMULATE|OPT_BOURNE}, TYPESETDOESNOTSET}, {{NULL, "typesetsilent", OPT_EMULATE|OPT_BOURNE}, TYPESETSILENT}, +{{NULL, "typesettounset", OPT_EMULATE|OPT_BOURNE}, TYPESETTOUNSET}, {{NULL, "unset", OPT_EMULATE|OPT_BSHELL}, UNSET}, {{NULL, "verbose", 0}, VERBOSE}, {{NULL, "vi", 0}, VIMODE}, diff --git a/Src/zsh.h b/Src/zsh.h index 12efb784f..490407ad0 100644 --- a/Src/zsh.h +++ b/Src/zsh.h @@ -2536,8 +2536,8 @@ enum { SUNKEYBOARDHACK, TRANSIENTRPROMPT, TRAPSASYNC, - TYPESETDOESNOTSET, TYPESETSILENT, + TYPESETTOUNSET, UNSET, VERBOSE, VIMODE, diff --git a/Test/D06subscript.ztst b/Test/D06subscript.ztst index c1a8d79cf..4225c543c 100644 --- a/Test/D06subscript.ztst +++ b/Test/D06subscript.ztst @@ -289,3 +289,8 @@ F:Regression test for workers/42297 >14 24 >b b >b?rbaz foob?r + + i=1,3 + [[ ${a[$i]} = ${a[i]} ]] +0f:Math evaluation of commas in array subscripts +F:In math, (($i)) should be the same as ((i)). diff --git a/Test/E03posix.ztst b/Test/E03posix.ztst index a2769f3aa..fb394986d 100644 --- a/Test/E03posix.ztst +++ b/Test/E03posix.ztst @@ -4,7 +4,7 @@ # %prep - setopt POSIX_BUILTINS TYPESET_DOES_NOT_SET + setopt POSIX_BUILTINS TYPESET_TO_UNSET %test @@ -117,3 +117,46 @@ } 0:readonly with typeset -p >typeset -g -r var + +# Tests expected to fail + + echo - +0f:A single "-" for echo does not end the arguments +F:POSIX requires a solitary "-" to be a plain argument +>- + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'foreach() { true; }' +-f:"foreach" is not a reserved word + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'end() { true; } +-f:"end" is not a reserved word + + a='a:b:' ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=:; printf "<%s>\n" $a' +0f:IFS is a separator, not a delimiter +> +> + + a=$'\ra\r\rb' ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=:; printf "<%s>\n" $a' +0f:All whitespace characters are "IFS whitespace" +F:isspace('\r') is true so \r should behave like space, \t, \n +F:This may also need to apply to multibyte whitespace +> +> + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'IFS=2; printf "<%s>\n" $((11*11))' +0f:IFS applies to math results (numbers treated as strings) +><1> +><1> + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'inf=42; echo $((inf))' +0f:The math constant Inf is case-sensitive, with capital I +>42 + + ARGV0=sh $ZTST_testdir/../Src/zsh -c 'EUID=10; echo "$EUID"' +-f:EUID is not a special variable +>10 + + ARGV0=sh $ZTST_testdir/../Src/zsh -c "printf '<%10s>\n' St$'\M-C\M-)'phane" +0f:Width of %s is computed in bytes not characters +F:This is considered a bugfix in zsh +>< Stéphane> -- cgit 1.4.1