namespace ensemble

 

La commande "namespace ensemble" permet de définir comme une procédure ordinaire un ensemble "commande sous-commande ...arguments..."

Je veux par exemple définir la commande "adresse" avec les sous-commandes "qui" et "serveur" :

 adresse qui     toto@machin.com ;# -> toto
 adresse serveur toto@machin.com ;# -> machin.com

Je définis le namespace "adresse", le déclare comme ensemble, exporte et définis les procédures "qui" et "serveur"

 namespace eval adresse {
     namespace export qui serveur
     namespace ensemble create
     proc qui     a {lindex [split $a @] 0}
     proc serveur a {lindex [split $a @] 1}
 }
 adresse nimportequoi ;# -> unknown or ambiguous subcommand "sdfsdf": must be qui, or serveur

La construction montre que l'on a créé les procédures "adresse::qui" et "adresse::serveur". D'ailleurs lorsque l'interpréteur Tcl reçoit "adresse qui ...", il s'empresse de le transformer en "adresse::qui".

On pourrait être tenté d'écrire :

 namespace eval adresse {namespace ensemble create}
 proc adresse::qui a {lindex [split $a @] 0}

Mais dans ce cas, on obtient :

 adresse::qui toto@machin.com ;# -> toto
 adresse  qui toto@machin.com ;# -> unknown subcommand "qui": namespace ::adresse does not export any commands

Il manquait l'exportation explicite :

  namespace eval adresse {namespace export qui serveur}

[FM] (le 7/02/2009) : Personnellement, j'utilise une procédure récursive qui explore tous les espaces de nom (i.e. namespace) situés dans l'arborescence pour créer l'ensemble. Avec l'option -map, l'export n'est pas indispensable.

 ################################################################################
 # Procédure assemble : synopsis :  assemble namespace ?commande?               #
 # procédure de création d'un ensemble de commande de l'espace "namespace"      #
 # Si l'argument optionnel ?commande? est précisé, l'ensemble sera regroupé sous#
 # ce nom, sinon, celui-ci s'appellera "namespace"                              #
 # Fouille toute la hiérarchies des noms puis, au retour, crée un ensemble de   #
 # commande en regroupant ensemble les commandes présente dans l'espace de      #
 # nom (à la création). Des variations sont possibles, l'une d'entre elle       #
 # serait, par exemple, d'exclure de  l'ensemble toutes les commandes préfixées #
 # par "__".                                                                    #
 # la procédure unknown n'est pas gérée (mais cela serait possible)             #
 ################################################################################

 proc assemble {NS  {commande {}}} {
    if {$commande eq {}} {set commande $NS}
    foreach ns [namespace children $NS] {
       if {([namespace children $ns] ne "") && ($ns ne $NS)} {
           assemble $ns
       } else {
          namespace eval "$ns" {
             set Map [list]
             foreach c [info commands [namespace current]::*] {
                lappend Map [namespace tail $c] $c
             }
             if {[llength $Map] > 0} {
                namespace ensemble create -map $Map
             }
          }
       }
    }
    namespace eval "$NS" [subst -noc {
       set Map [list]
       foreach c [info commands [namespace current]::*] {
          lappend Map [namespace tail \$c] \$c
       }
       if {[llength \$Map] > 0} {
          namespace ensemble create -command $commande -map \$Map
       }
    }]
 }

# Illustration : Ensemble de création de fenêtre de manière langagière

# Tcl 8.5, utilisation de namespace ensemble, apply, dict et du préfixe d'expansion {*}

 namespace eval créer {
    variable PaquetParDefaut [dict create -side top -expand 1 -fill both]
    variable Y {}
    variable LE {}
    variable LA {}

    namespace eval une {
       upvar 1 Y Y LA LA LE LE PaquetParDefaut P
       proc fenêtre {} {
          variable Y
          variable LA
          variable LE {}

          while {[catch {set Y [toplevel .toplevel[incr i]]}]} {if $i>100 break}; # sécurité
          return [set LA $Y]
       }
       namespace eval frame {
          upvar 1 Y Y LE LE LA LA P P

          proc dans {w Options {Paquet {}}} {
	     variable Y
	     variable P
	     variable LE {}; # ré-initialisation de LE (une frame est féminin)
	     variable LA

	     while {[catch {set Y [::ttk::frame $w.frame[incr i] {*}$Options]}]} {if $i>100 break}
	     pack $w.frame$i {*}[expr {$Paquet eq {} ? "$P" : "$Paquet"}]
	     return [set LA $Y]
          }
       }
       namespace eval liste {
          upvar 1 P P LA LA LE LE

          proc dans {w Options {Paquet {}}} {
	     variable P
	     variable LA
	     variable LE {} ; # ré-initialisation de LE (une liste est féminin), pas de Y (une liste n'est pas un conteneur)

	     while {[catch {set LA [listbox $w.listbox[incr i] {*}$Options]}]} {if $i>100 break}
	     pack $w.listbox$i {*}[expr {$Paquet eq {} ? "$P" : "$Paquet"}]
	     return $LA
          }
       }
    }
    namespace eval un {
       upvar 1 PaquetParDefaut P LE LE LA LA

       proc paquet {args} {variable P [dict create {*}$args]}

       namespace eval bouton {
          upvar 1 LE LE LA LA P P
          proc dans {w Options {Paquet {}}} {
             variable LE
             variable P
             variable LA {}; # ré-initialisation de LA (un bouton est masculin), pas de Y (un bouton n'est pas un conteneur)

             while {[catch {set LE [::ttk::button $w.button[incr i] {*}$Options]}]} {if $i>100 break}
             pack $w.button$i {*}[expr {$Paquet eq {} ? "$P" : "$Paquet"}]
             return $LE
          }
       }
    }
    ::assemble [namespace current]; # crée l'ensemble
 }

 # Pour la forme #
 proc y {créer le widget Options {Paquet {}}} {
    # créer dans le dernier conteneur (valeur précédente de ::créer::Y)
    if {${créer} eq "créer"} {
    ${créer} $le $widget dans ${::créer::Y} $Options $Paquet
    }
 }

 # Ensemble pour récupérer les options de placement (algorithme pack) du widget
 # précédemment tracé / mémorisé
 namespace eval paquet {
    namespace eval comme {
	namespace eval le {
	    namespace upvar {::créer} LE LE
	    proc précédent {} {variable LE; return [pack info $LE]}
	}
	namespace eval la {
	    namespace upvar {::créer} LA LA
	    proc précédente {} {variable LA; return [pack info $LA]}
	}
    }
    ::assemble [namespace current]; # Création de l'ensemble de commande
 }

 # Ensemble pour récupérer les options de configuration du widget
 # précédemment tracé / mémorisé
 namespace eval options {
    namespace eval comme {
	namespace eval le {
	    namespace upvar {::créer} LE LE
	    proc précédent {} {
		variable LE
		set lambda {{x} {
		    set L [list]
		    foreach conf [$x configure] {
			if {[llength $conf] eq 5} {
			    lassign $conf opt x y z val
			    if { $opt ni $L && $opt ne {-class}} {
				lappend L $opt $val
			    }
			}
		    }
		    return $L
		}}

		return [::apply $lambda $LE]
	    }
	}
	namespace eval la {
	    namespace upvar {::créer} LA LA
	    proc précédente {} {
		variable LA
		set lambda {{x} {
		    set L [list]
		    foreach conf [$x configure] {
			if {[llength $conf] eq 5} {
			    lassign $conf opt x y z val
			    if {$opt ni $L && $opt ne {-class}} {
				lappend L $opt $val
			    }
			}
		    }
		    return $L
		}}

		return [::apply $lambda $LA]
	    }
	}
    }
    ::assemble [namespace current]; # Création de l'ensemble de commande
 }

 # Ensemble destiné à mémoriser quelques widgets (soit pour configuration ou
 # bien pour placement)
 namespace eval mémorise {
    variable CELLE-CI {} CELLE-LA {} CELUI-CI {} CELUI-LA {} LUI {} ELLE {} ICI {} Là {}
    namespace upvar ::créer LE LE LA LA Y Y

    proc celle-ci {} {variable LA; if {$LA ne {}} { variable CELLE-CI $LA } else { puts stderr "Rien à mémoriser" }}
    proc celui-ci {} {variable LE; if {$LE ne {}} { variable CELUI-CI $LE }  else { puts stderr "Rien à mémoriser" }}
    proc celle-là {} {variable LA; if {$LA ne {}} { variable CELLE-LA $LA } else { puts stderr "Rien à mémoriser" }}
    proc celui-là {} {variable LE; if {$LE ne {}} { variable CELUI-LA $LE }  else { puts stderr "Rien à mémoriser" }}
    proc la {} {variable LA; if {$LA ne {}} { variable ELLE $LA } else { puts stderr "Rien à mémoriser" }}
    proc le {} {variable LE; if {$LE ne {}} { variable LUI $LE }  else { puts stderr "Rien à mémoriser" }}
    proc ici {} {variable Y; variable ICI $Y}
    proc là {} {variable Y; variable Là $Y}

    ::assemble [namespace current]; # Création de l'ensemble de commande
 }

 # Ensemble pour récupérer ce qui a été mis en mémoire
 namespace eval remémore-toi {
    namespace eval de {
	namespace upvar ::mémorise \
	    CELLE-CI CELLE-CI CELLE-LA CELLE-LA CELUI-CI CELUI-CI \
	    CELUI-LA CELUI-LA LUI LUI ELLE ELLE ICI ICI Là Là
	namespace upvar ::créer LA LA LE LE Y Y

	proc celle-ci {} {variable CELLE-CI; variable LA ${CELLE-CI}; return $LA}
	proc celle-là {} {variable CELLE-LA; variable LA ${CELLE-LA}; return $LA}
	proc celui-ci {} {variable CELUI-CI; variable LE ${CELUI-CI}; return $LE}
	proc celui-là {} {variable CELUI-LA; variable LE ${CELUI-LA}; return $LE}
	proc lui {} {variable LUI; variable LE $LUI; return $LE}
	proc là {} {variable Là; variable Y ${Là}; return $Y}
    }
    namespace upvar ::mémorise ELLE ELLE ICI ICI
    namespace upvar ::créer LA LA Y Y
    proc d'elle {} {variable ELLE; variable LA $ELLE; return $LA}
    proc d'ici {} {variable ICI; variable Y $ICI; return $Y}

    ::assemble [namespace current]; # Création de l'ensemble de commande
 }

# Maintenant testons la chose :

 créer une fenêtre
 mémorise ici;          # Mémorise le lieu (pour utilisation ultérieure avec y)

 y créer une frame \
    [dict create -relief groove -border 10 -padding 5]\
    [dict create -side left -expand 0 -fill both -padx {10 5} -pady 10]
 mémorise celle-ci; # Mémorise le chemin tk (pour utilisation avec options et paquet) : frame "celle-ci" définie

 y créer un bouton \
    [dict create -text Item0] \
    [dict create -side top -expand 0 -pady 1]
 mémorise le;         # (le chemin tk pour utilisation avec options et paquet) : bouton "lui" défini

 for {set i 1} {$i < 20} {incr i} {
    y créer un bouton [dict replace [options comme le précédent] -text Item$i] [paquet comme le précédent]
 }

 remémore-toi d'ici;         # Récupère la valeur ici mémorisée (toplevel) et écrit-la dans la variable ::créer::Y (utilisation avec y)
 remémore-toi de celle-ci;   # Récupère la valeur mémorisée celle-ci (frame) et écrit-la dans ::créer::LA (utilisation avec options, paquet)

 y créer une frame \
    [options comme la précédente] \
    [dict replace [paquet comme la précédente] -expand 1 -padx {5 10}];
 mémorise celle-là; mémorise là;             # frame "celle-là", située "là"

 y créer une liste \
    [dict create -listvariable [set ::L [list]; set A ::L]] \
    [dict replace [paquet comme la précédente] -side top -in [remémore-toi de celle-là]]

 remémore-toi de là; remémore-toi de celle-là; # récupérations des données de la frame "celle-là", située "là"

 y créer une frame \
    [options comme la précédente] \
    [dict replace [paquet comme la précédente] -side top -expand 0 -padx 10 -in [remémore-toi de celle-là]]
 mémorise ici; mémorise la;                  # frame "elle", située "ici"

 y créer une frame \
    [dict create -relief flat] \
    [dict replace [paquet comme la précédente] -side left -expand 1 -padx 0 -in [remémore-toi d'ici]]

 remémore-toi de lui;                      # récupération du bouton "lui"
 y créer un bouton \
    [dict replace [options comme le précédent] -text Ok] \
    [dict replace [paquet comme le précédent] -side left -in [remémore-toi d'ici] -fill x]
 y créer un bouton \
    [dict replace [options comme le précédent] -text Annuler] \
    [dict replace [paquet comme le précédent] -side left -in [remémore-toi d'ici] -fill x]

 remémore-toi d'elle;                   # récupération de la frame "elle"
 y créer une frame \
    [dict create -relief flat] \
    [dict replace [paquet comme la précédente] -side left -expand 1 -padx 0 -in [remémore-toi d'ici]]

Bon, c'est pas en script d'une très grande utilité et assez long, mais l'intéressant c'est de voir comment l'enchainement et l'utilisation des ensembles permet de s'éviter bien des tests sur les arguments des fonctions (la commande switch devient donc moins utile). Programmer ceci en Tcl 8.4 ferait risquer l'asile, je pense.

Cela économise des accolades, cloisonne les variables tout en gardant la possibilité de les relier efficacement (commandes : variable, [namespace upvar], upvar). Vous remarquerez que la seule variable créée dans l'espace de nom racine est la variable jetable i, utilisée par la boucle for.

De même cela évite les conflits de nom de procédure. Ici, par exemple, il n'y a que 6 procédures créées au niveau de l'espace de nom racine ([::]) : créer, paquet, options, y, mémorise, remémore-toi. J'aurais même pu me passer des commandes paquet et options en les branchant dans l'ensemble remémore-toi (par exemple [remémore-toi du paquet de celui-ci], ou encore [remémore-toi des options du précédent]), ce qui ferait 4 commandes au total. Le code gagne énormément en clarté et aussi en inter-opérabilité avec d'éventuelles futures extensions lors d'un développement.

Mais, attention à ce petit piège : créer un namespace ou une procédure list dans l'ensemble ferait perdre la commande list à toutes les commandes situées en aval et dans l'ensemble courant, contraignant alors à utiliser la commande ::list à la place, afin de préciser à chaque fois explicitement à quelle commande list on se réfère en donnant son chemin absolu. En effet, en matière de résolution des noms de commande, l'espace de nom courant est prioritaire sur l'espace de nom racine [::].

Cela reste bien-sûr un exercice d'école, pour le plaisir des neurones, car, ici, Tk eût été suffisant.