pluginmgr/pluginmgr.tcl

Go to the documentation of this file.
00001 /*  plugin.tcl --*/
00002 /* */
00003 /*  Generic plugin management.*/
00004 /* */
00005 /*  Copyright (c) 2005 Andreas Kupries <andreas_kupries@sourceforge.net>*/
00006 /* */
00007 /*  See the file "license.terms" for information on usage and redistribution*/
00008 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00009 /*  */
00010 /*  RCS: @(#) $Id: pluginmgr.tcl,v 1.6 2007/06/23 03:39:34 andreas_kupries Exp $*/
00011 
00012 /*  ### ### ### ######### ######### #########*/
00013 /*  Description*/
00014 
00015 /*  Each instance of the plugin manager can be configured with data*/
00016 /*  which specifies where to find plugins, and how to validate*/
00017 /*  them. With that it can then be configured to load and provide access*/
00018 /*  to a specific plugin, doing all required checks and*/
00019 /*  initialization. Users for specific plugin types simply have to*/
00020 /*  encapsulate the generic class, providing all the specifics, leaving*/
00021 /*  their users only the task of naming the requested actual plugin.*/
00022 
00023 /*  ### ### ### ######### ######### #########*/
00024 /*  Requisites*/
00025 
00026 package require Tcl 8.4
00027 package require snit
00028 
00029 /*  ### ### ### ######### ######### #########*/
00030 /*  Implementation*/
00031 
00032 snit::type ::pluginmgr {
00033 
00034     /*  ### ### ### ######### ######### #########*/
00035     /*  Public API - Options*/
00036 
00037     /*  - Pattern to match package name. Exactly one '*'. No default.*/
00038     /*  - List of commands the plugin has to provide. Empty list default.*/
00039     /*  - Callback for additional checking after the API presence has*/
00040     /*    been verified. Empty list default.*/
00041     /*  - Dictionary of commands to put into the plugin interpreter.*/
00042     /*    Key: cmds for plugin, value is cmds to invoke for them.*/
00043     /*  - Interpreter to use for the -cmds (invoked commands). Default*/
00044     /*    is current interp.*/
00045 
00046     option -pattern {}
00047     option -api     {}
00048     option -check   {}
00049     option -cmds    {}
00050     option -cmdip   {}
00051     option -up =    {}
00052 
00053     /*  ### ### ### ######### ######### #########*/
00054     /*  Public API - Methods*/
00055 
00056     ret  do (type args) {
00057     if {$plugin eq ""} {
00058         return -code error "No plugin defined"
00059     }
00060     return [$sip eval $args]
00061     }
00062 
00063     ret  interpreter () {
00064     return $sip
00065     }
00066 
00067     ret  plugin () {
00068     return $plugin
00069     }
00070 
00071     ret  load (type name) {
00072     if {$name eq $plugin} return
00073 
00074     if {$options(-pattern) eq ""} {
00075         return -code error "Translation pattern is not configured"
00076     }
00077 
00078     set save $sip
00079 
00080     $self SetupIp
00081     if {![$self LoadPlugin $name]} {
00082         set sip $save
00083         return -code error "Unable to locate plugin \"$name\""
00084     }
00085 
00086     if {![$self CheckAPI missing]} {
00087         set sip $save
00088         return -code error \
00089             "Cannot use plugin \"$name\", API incomplete: \"$missing\" missing"
00090     }
00091 
00092     set savedname $plugin
00093     set plugin    $name
00094     if {![$self CheckExternal]} {
00095         set sip    $save
00096         set plugin $savedname
00097         return -code error \
00098             "Cannot use plugin \"$name\", API bad"
00099     }
00100     $self SetupExternalCmds
00101 
00102     if {$save ne ""} {interp delete $save}
00103     return
00104     }
00105 
00106     ret  unload () {
00107     if {$sip eq ""} return
00108     interp delete $sip
00109     set sip    ""
00110     set plugin ""
00111     return
00112     }
00113 
00114     ret  list () {
00115     if {$options(-pattern) eq ""} {
00116         return -code error "Translation pattern is not configured"
00117     }
00118 
00119     set save $sip
00120     $self SetupIp
00121 
00122     set result {}
00123     set pattern [string map [list \
00124         +  \\+  ?  \\?    \
00125         \[ \\\[ \] \\\]   \
00126         (  \\(  )  \\)    \
00127         . \\. \ *  {(.*)} \
00128         ] $options(-pattern)]
00129 
00130     # @mdgen NODEP: bogus-package
00131     $sip eval {catch {package require bogus-package}}
00132     foreach p [$sip eval {package names}] {
00133         if {![regexp $pattern $p -> plugin]} continue
00134         lappend result $plugin
00135     }
00136 
00137     interp delete $sip
00138     set sip $save
00139     return $result
00140     }
00141 
00142     ret  path (type path) {
00143     set path [file join [pwd] $path]
00144     if {[lsearch -exact $paths $path] < 0} {
00145         lappend paths $path
00146     }
00147     return
00148     }
00149 
00150     ret  paths () {
00151     return $paths
00152     }
00153 
00154     ret  clone () {
00155     set o [$type create %AUTO% \
00156         -pattern $options(-pattern) \
00157         -api     $options(-api)    \
00158         -check   $options(-check) \
00159         -cmds    $options(-cmds) \
00160         -cmdip   $options(-cmdip) \
00161         -setup   $options(-setup)]
00162 
00163     $o __clone__ $paths $sip $plugin
00164 
00165     # Clone has become owner of the interp.
00166     set sip    {}
00167     set plugin {}
00168 
00169     return $o
00170     }
00171 
00172     ret  __clone__ (_type paths _, type sip _, type plugin) {
00173     set paths  $_paths
00174     set sip    $_sip
00175     set plugin $_plugin
00176     return
00177     }
00178 
00179     /*  ### ### ### ######### ######### #########*/
00180     /*  Internal - Configuration and state*/
00181 
00182     variable paths  {} ; /*  List of paths to provide the sip with.*/
00183     variable sip    {} ; /*  Safe interp used for plugin execution.*/
00184     variable plugin {} ; /*  Name of currently loaded plugin.*/
00185 
00186     /*  ### ### ### ######### ######### #########*/
00187     /*  Internal - Object construction and descruction.*/
00188 
00189     constructor {args} {
00190     $self configurelist $args
00191     return
00192     }
00193 
00194     destructor {
00195     if {$sip ne ""} {interp delete $sip}
00196     return
00197     }
00198 
00199     /*  ### ### ### ######### ######### #########*/
00200     /*  Internal - Option management*/
00201 
00202     onconfigure -pattern {newvalue} {
00203      current =  $options(-pattern)
00204     if {$newvalue eq $current} return
00205 
00206      n =  [regexp -all "\\*" $newvalue]
00207     if {$n < 1} {
00208         return -code error "Invalid pattern, * missing"
00209     } elseif {$n > 1} {
00210         return -code error "Invalid pattern, too many *'s"
00211     }
00212 
00213      options = (-pattern) $newvalue
00214     return
00215     }
00216 
00217     onconfigure -api {newvalue} {
00218      current =  $options(-api)
00219     if {$newvalue eq $current} return
00220      options = (-api) $newvalue
00221     return
00222     }
00223 
00224     onconfigure -cmds {newvalue} {
00225      current =  $options(-cmds)
00226     if {$newvalue eq $current} return
00227      options = (-cmds) $newvalue
00228     return
00229     }
00230 
00231     onconfigure -cmdip {newvalue} {
00232      current =  $options(-cmdip)
00233     if {$newvalue eq $current} return
00234      options = (-cmdip) $newvalue
00235     return
00236     }
00237 
00238 
00239     /*  ### ### ### ######### ######### #########*/
00240     /*  Internal - Helper commands*/
00241 
00242     ret  SetupIp () {
00243     set sip [::safe::interpCreate]
00244     foreach p $paths {
00245         ::safe::interpAddToAccessPath $sip $p
00246     }
00247 
00248     if {![llength $options(-setup)]} return
00249     uplevel \#0 [linsert $options(-setup) end $self $sip]
00250     return
00251     }
00252 
00253     ret  LoadPlugin (type name) {
00254     if {[file exists $name]} {
00255         # Plugin files are loaded directly.
00256 
00257         $sip invokehidden source $name
00258         return 1
00259     }
00260 
00261     # Otherwise the name is transformed into a package name
00262     # and loaded thorugh the package management.
00263 
00264     set pluginpackage [string map \
00265         [list * $name] $options(-pattern)]
00266     if {[catch {
00267         $sip eval [list package require $pluginpackage]
00268     } res]} {
00269         return 0
00270     }
00271     return 1
00272     }
00273 
00274     ret  CheckAPI (type mv) {
00275     upvar 1 $mv missing
00276     if {![llength $options(-api)]} {return 1}
00277 
00278     # Check the plugin for useability.
00279 
00280     foreach p $options(-api) {
00281         if {[llength [$sip eval [list info commands $p]]] == 1} continue
00282         interp delete $sip
00283         set missing $p
00284         return 0
00285     }
00286     return 1
00287     }
00288 
00289     ret  CheckExternal () {
00290     if {![llength $options(-check)]} {return 1}
00291     return [uplevel \#0 [linsert $options(-check) end $self]]
00292     }
00293 
00294 
00295     ret  SetupExternalCmds () {
00296     if {![llength $options(-cmds)]} return
00297 
00298     set cip $options(-cmdip)
00299     foreach {pcmd ecmd} $options(-cmds) {
00300         eval [linsert $ecmd 0 interp alias $sip $pcmd $cip]
00301         #interp alias $sip $pcmd $cip {*}$ecmd
00302     }
00303     return
00304     }
00305 
00306     /*  ### ### ### ######### ######### #########*/
00307 
00308     ret  paths (type pmgr , type args) {
00309     if {[llength $args] == 0} {
00310         return -code error "wrong#args: Expect \"[info level 0] object name...\""
00311     }
00312     foreach name $args {
00313         AddPaths $pmgr $name
00314     }
00315     return
00316     }
00317 
00318     ret  AddPaths (type pmgr , type name) {
00319     global env tcl_platform
00320 
00321     if {$tcl_platform(platform) eq "windows"} {
00322         set sep \;
00323     } else {
00324         set sep :
00325     }
00326 
00327     #puts "$pmgr += ($name) $sep"
00328 
00329     regsub -all {::+} $name \000 name
00330     set name [split $name \000]
00331 
00332     # Environment variables
00333 
00334     set prefix {}
00335     foreach part $name {
00336         lappend prefix $part
00337         set ev [string toupper [join $prefix _]]_PLUGINS
00338 
00339         #puts "+? env($ev)"
00340 
00341         if {[info exists env($ev)]} {
00342         foreach path [split $env($ev) $sep] {
00343             $pmgr path $path
00344         }
00345         }
00346     }
00347 
00348     # Windows registry
00349 
00350     if {
00351         ($tcl_platform(platform) eq "windows") &&
00352         ![catch {package require registry}]
00353     } {
00354         foreach root {
00355         HKEY_LOCAL_MACHINE
00356         HKEY_CURRENT_USER
00357         } {
00358         set prefix {}
00359         foreach part $name {
00360             lappend prefix $part
00361             set rk $root\\SOFTWARE\\[join $prefix \\]PLUGINS
00362 
00363             #puts "+? registry($rk)"
00364 
00365             if {![catch {set data [registry get $rk {}]}]} {
00366             foreach path [split $data $sep] {
00367                 $pmgr path $path
00368             }
00369             }
00370         }
00371         }
00372     }
00373 
00374     # Home directory dot path
00375 
00376     set prefix {}
00377     foreach part $name {
00378         lappend prefix $part
00379         set pd [file join ~ .[join $prefix /] plugin]
00380 
00381         #puts "+? path($pd)"
00382 
00383         if {[file exists $pd]} {
00384         $pmgr path $pd
00385         }
00386     }
00387     return
00388     }
00389 }
00390 
00391 /*  ### ### ### ######### ######### #########*/
00392 /*  Ready*/
00393 
00394 package provide pluginmgr 0.1
00395 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1