tclConfig.c

Go to the documentation of this file.
00001 /*
00002  * tclConfig.c --
00003  *
00004  *      This file provides the facilities which allow Tcl and other packages
00005  *      to embed configuration information into their binary libraries.
00006  *
00007  * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net>
00008  *
00009  * See the file "license.terms" for information on usage and redistribution of
00010  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
00011  *
00012  * RCS: @(#) $Id: tclConfig.c,v 1.19 2007/12/13 15:23:16 dgp Exp $
00013  */
00014 
00015 #include "tclInt.h"
00016 
00017 /*
00018  * Internal structure to hold embedded configuration information.
00019  *
00020  * Our structure is a two-level dictionary associated with the 'interp'. The
00021  * first level is keyed with the package name and maps to the dictionary for
00022  * that package. The package dictionary is keyed with metadata keys and maps
00023  * to the metadata value for that key. This is package specific. The metadata
00024  * values are in UTF-8, converted from the external representation given to us
00025  * by the caller.
00026  */
00027 
00028 #define ASSOC_KEY       "tclPackageAboutDict"
00029 
00030 /*
00031  * A ClientData struct for the QueryConfig command.  Store the two bits
00032  * of data we need; the package name for which we store a config dict,
00033  * and the (Tcl_Interp *) in which it is stored.
00034  */
00035 
00036 typedef struct QCCD {
00037     Tcl_Obj *pkg;
00038     Tcl_Interp *interp;
00039 } QCCD;
00040 
00041 /*
00042  * Static functions in this file:
00043  */
00044 
00045 static int              QueryConfigObjCmd(ClientData clientData,
00046                             Tcl_Interp *interp, int objc,
00047                             struct Tcl_Obj *CONST *objv);
00048 static void             QueryConfigDelete(ClientData clientData);
00049 static Tcl_Obj *        GetConfigDict(Tcl_Interp *interp);
00050 static void             ConfigDictDeleteProc(ClientData clientData,
00051                             Tcl_Interp *interp);
00052 
00053 /*
00054  *----------------------------------------------------------------------
00055  *
00056  * Tcl_RegisterConfig --
00057  *
00058  *      See TIP#59 for details on what this function does.
00059  *
00060  * Results:
00061  *      None.
00062  *
00063  * Side effects:
00064  *      Creates namespace and cfg query command in it as per TIP #59.
00065  *
00066  *----------------------------------------------------------------------
00067  */
00068 
00069 void
00070 Tcl_RegisterConfig(
00071     Tcl_Interp *interp,         /* Interpreter the configuration command is
00072                                  * registered in. */
00073     CONST char *pkgName,        /* Name of the package registering the
00074                                  * embedded configuration. ASCII, thus in
00075                                  * UTF-8 too. */
00076     Tcl_Config *configuration,  /* Embedded configuration. */
00077     CONST char *valEncoding)    /* Name of the encoding used to store the
00078                                  * configuration values, ASCII, thus UTF-8. */
00079 {
00080     Tcl_Obj *pDB, *pkgDict;
00081     Tcl_DString cmdName;
00082     Tcl_Config *cfg;
00083     Tcl_Encoding venc = Tcl_GetEncoding(NULL, valEncoding);
00084     QCCD *cdPtr = (QCCD *)ckalloc(sizeof(QCCD));
00085 
00086     cdPtr->interp = interp;
00087     cdPtr->pkg = Tcl_NewStringObj(pkgName, -1);
00088 
00089     /*
00090      * Phase I: Adding the provided information to the internal database of
00091      * package meta data. Only if we have an ok encoding.
00092      *
00093      * Phase II: Create a command for querying this database, specific to the
00094      * package registerting its configuration. This is the approved interface
00095      * in TIP 59. In the future a more general interface should be done, as
00096      * followup to TIP 59. Simply because our database is now general across
00097      * packages, and not a structure tied to one package.
00098      *
00099      * Note, the created command will have a reference through its clientdata.
00100      */
00101 
00102     Tcl_IncrRefCount(cdPtr->pkg);
00103 
00104     /*
00105      * For venc == NULL aka bogus encoding we skip the step setting up the
00106      * dictionaries visible at Tcl level. I.e. they are not filled
00107      */
00108 
00109     if (venc != NULL) {
00110         /*
00111          * Retrieve package specific configuration...
00112          */
00113 
00114         pDB = GetConfigDict(interp);
00115 
00116         if (Tcl_DictObjGet(interp, pDB, cdPtr->pkg, &pkgDict) != TCL_OK
00117             || (pkgDict == NULL)) {
00118             pkgDict = Tcl_NewDictObj();
00119         } else if (Tcl_IsShared(pkgDict)) {
00120             pkgDict = Tcl_DuplicateObj(pkgDict);
00121         }
00122 
00123         /*
00124          * Extend the package configuration...
00125          */
00126 
00127         for (cfg=configuration ; cfg->key!=NULL && cfg->key[0]!='\0' ; cfg++) {
00128             Tcl_DString conv;
00129             CONST char *convValue =
00130                 Tcl_ExternalToUtfDString(venc, cfg->value, -1, &conv);
00131 
00132             /*
00133              * We know that the keys are in ASCII/UTF-8, so for them is no
00134              * conversion required.
00135              */
00136 
00137             Tcl_DictObjPut(interp, pkgDict, Tcl_NewStringObj(cfg->key, -1),
00138                            Tcl_NewStringObj(convValue, -1));
00139             Tcl_DStringFree(&conv);
00140         }
00141 
00142         /*
00143          * We're now done with the encoding, so drop it.
00144          */
00145 
00146         Tcl_FreeEncoding(venc);
00147 
00148         /*
00149          * Write the changes back into the overall database.
00150          */
00151 
00152         Tcl_DictObjPut(interp, pDB, cdPtr->pkg, pkgDict);
00153     }
00154 
00155     /*
00156      * Now create the interface command for retrieval of the package
00157      * information.
00158      */
00159 
00160     Tcl_DStringInit(&cmdName);
00161     Tcl_DStringAppend(&cmdName, "::", -1);
00162     Tcl_DStringAppend(&cmdName, pkgName, -1);
00163 
00164     /*
00165      * The incomplete command name is the name of the namespace to place it
00166      * in.
00167      */
00168 
00169     if (Tcl_FindNamespace(interp, Tcl_DStringValue(&cmdName), NULL,
00170             TCL_GLOBAL_ONLY) == NULL) {
00171         if (Tcl_CreateNamespace(interp, Tcl_DStringValue(&cmdName),
00172                 NULL, NULL) == NULL) {
00173             Tcl_Panic("%s.\n%s: %s",
00174                     Tcl_GetStringResult(interp), "Tcl_RegisterConfig",
00175                     "Unable to create namespace for package configuration.");
00176         }
00177     }
00178 
00179     Tcl_DStringAppend(&cmdName, "::pkgconfig", -1);
00180 
00181     if (Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdName),
00182             QueryConfigObjCmd, (ClientData) cdPtr, QueryConfigDelete) == NULL) {
00183         Tcl_Panic("%s: %s", "Tcl_RegisterConfig",
00184                 "Unable to create query command for package configuration");
00185     }
00186 
00187     Tcl_DStringFree(&cmdName);
00188 }
00189 
00190 /*
00191  *----------------------------------------------------------------------
00192  *
00193  * QueryConfigObjCmd --
00194  *
00195  *      Implementation of "::<package>::pkgconfig", the command to query
00196  *      configuration information embedded into a binary library.
00197  *
00198  * Results:
00199  *      A standard tcl result.
00200  *
00201  * Side effects:
00202  *      See the manual for what this command does.
00203  *
00204  *----------------------------------------------------------------------
00205  */
00206 
00207 static int
00208 QueryConfigObjCmd(
00209     ClientData clientData,
00210     Tcl_Interp *interp,
00211     int objc,
00212     struct Tcl_Obj *CONST *objv)
00213 {
00214     QCCD *cdPtr = (QCCD *) clientData;
00215     Tcl_Obj *pkgName = cdPtr->pkg;
00216     Tcl_Obj *pDB, *pkgDict, *val, *listPtr;
00217     int n, index;
00218     static CONST char *subcmdStrings[] = {
00219         "get", "list", NULL
00220     };
00221     enum subcmds {
00222         CFG_GET, CFG_LIST
00223     };
00224 
00225     if ((objc < 2) || (objc > 3)) {
00226         Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?argument?");
00227         return TCL_ERROR;
00228     }
00229     if (Tcl_GetIndexFromObj(interp, objv[1], subcmdStrings, "subcommand", 0,
00230             &index) != TCL_OK) {
00231         return TCL_ERROR;
00232     }
00233 
00234     pDB = GetConfigDict(interp);
00235     if (Tcl_DictObjGet(interp, pDB, pkgName, &pkgDict) != TCL_OK
00236             || pkgDict == NULL) {
00237         /*
00238          * Maybe a Tcl_Panic is better, because the package data has to be
00239          * present.
00240          */
00241 
00242         Tcl_SetResult(interp, "package not known", TCL_STATIC);
00243         return TCL_ERROR;
00244     }
00245 
00246     switch ((enum subcmds) index) {
00247     case CFG_GET:
00248         if (objc != 3) {
00249             Tcl_WrongNumArgs(interp, 2, objv, "key");
00250             return TCL_ERROR;
00251         }
00252 
00253         if (Tcl_DictObjGet(interp, pkgDict, objv [2], &val) != TCL_OK
00254                 || val == NULL) {
00255             Tcl_SetResult(interp, "key not known", TCL_STATIC);
00256             return TCL_ERROR;
00257         }
00258 
00259         Tcl_SetObjResult(interp, val);
00260         return TCL_OK;
00261 
00262     case CFG_LIST:
00263         if (objc != 2) {
00264             Tcl_WrongNumArgs(interp, 2, objv, NULL);
00265             return TCL_ERROR;
00266         }
00267 
00268         Tcl_DictObjSize(interp, pkgDict, &n);
00269         listPtr = Tcl_NewListObj(n, NULL);
00270 
00271         if (!listPtr) {
00272             Tcl_SetResult(interp, "insufficient memory to create list",
00273                     TCL_STATIC);
00274             return TCL_ERROR;
00275         }
00276 
00277         if (n) {
00278             List *listRepPtr = (List *)
00279                     listPtr->internalRep.twoPtrValue.ptr1;
00280             Tcl_DictSearch s;
00281             Tcl_Obj *key, **vals;
00282             int done, i = 0;
00283 
00284             listRepPtr->elemCount = n;
00285             vals = &listRepPtr->elements;
00286 
00287             for (Tcl_DictObjFirst(interp, pkgDict, &s, &key, NULL, &done);
00288                     !done; Tcl_DictObjNext(&s, &key, NULL, &done)) {
00289                 vals[i++] = key;
00290                 Tcl_IncrRefCount(key);
00291             }
00292         }
00293 
00294         Tcl_SetObjResult(interp, listPtr);
00295         return TCL_OK;
00296 
00297     default:
00298         Tcl_Panic("QueryConfigObjCmd: Unknown subcommand to 'pkgconfig'. This can't happen");
00299         break;
00300     }
00301     return TCL_ERROR;
00302 }
00303 
00304 /*
00305  *-------------------------------------------------------------------------
00306  *
00307  * QueryConfigDelete --
00308  *
00309  *      Command delete function. Cleans up after the configuration query
00310  *      command when it is deleted by the user or during finalization.
00311  *
00312  * Results:
00313  *      None.
00314  *
00315  * Side effects:
00316  *      Deallocates all non-transient memory allocated by Tcl_RegisterConfig.
00317  *
00318  *-------------------------------------------------------------------------
00319  */
00320 
00321 static void
00322 QueryConfigDelete(
00323     ClientData clientData)
00324 {
00325     QCCD *cdPtr = (QCCD *) clientData;
00326     Tcl_Obj *pkgName = cdPtr->pkg;
00327     Tcl_Obj *pDB = GetConfigDict(cdPtr->interp);
00328     Tcl_DictObjRemove(NULL, pDB, pkgName);
00329     Tcl_DecrRefCount(pkgName);
00330     ckfree((char *)cdPtr);
00331 }
00332 
00333 /*
00334  *-------------------------------------------------------------------------
00335  *
00336  * GetConfigDict --
00337  *
00338  *      Retrieve the package metadata database from the interpreter.
00339  *      Initializes it, if not present yet.
00340  *
00341  * Results:
00342  *      A Tcl_Obj reference
00343  *
00344  * Side effects:
00345  *      May allocate a Tcl_Obj.
00346  *
00347  *-------------------------------------------------------------------------
00348  */
00349 
00350 static Tcl_Obj *
00351 GetConfigDict(
00352     Tcl_Interp *interp)
00353 {
00354     Tcl_Obj *pDB = Tcl_GetAssocData(interp, ASSOC_KEY, NULL);
00355 
00356     if (pDB == NULL) {
00357         pDB = Tcl_NewDictObj();
00358         Tcl_IncrRefCount(pDB);
00359         Tcl_SetAssocData(interp, ASSOC_KEY, ConfigDictDeleteProc, pDB);
00360     }
00361 
00362     return pDB;
00363 }
00364 
00365 /*
00366  *----------------------------------------------------------------------
00367  *
00368  * ConfigDictDeleteProc --
00369  *
00370  *      This function is associated with the "Package About dict" assoc data
00371  *      for an interpreter; it is invoked when the interpreter is deleted in
00372  *      order to free the information assoicated with any pending error
00373  *      reports.
00374  *
00375  * Results:
00376  *      None.
00377  *
00378  * Side effects:
00379  *      The package metadata database is freed.
00380  *
00381  *----------------------------------------------------------------------
00382  */
00383 
00384 static void
00385 ConfigDictDeleteProc(
00386     ClientData clientData,      /* Pointer to Tcl_Obj. */
00387     Tcl_Interp *interp)         /* Interpreter being deleted. */
00388 {
00389     Tcl_Obj *pDB = (Tcl_Obj *) clientData;
00390 
00391     Tcl_DecrRefCount(pDB);
00392 }
00393 
00394 /*
00395  * Local Variables:
00396  * mode: c
00397  * c-basic-offset: 4
00398  * fill-column: 78
00399  * End:
00400  */



Generated on Wed Mar 12 12:18:14 2008 by  doxygen 1.5.1