[25] | 1 | /* |
---|
| 2 | * tclConfig.c -- |
---|
| 3 | * |
---|
| 4 | * This file provides the facilities which allow Tcl and other packages |
---|
| 5 | * to embed configuration information into their binary libraries. |
---|
| 6 | * |
---|
| 7 | * Copyright (c) 2002 Andreas Kupries <andreas_kupries@users.sourceforge.net> |
---|
| 8 | * |
---|
| 9 | * See the file "license.terms" for information on usage and redistribution of |
---|
| 10 | * this file, and for a DISCLAIMER OF ALL WARRANTIES. |
---|
| 11 | * |
---|
| 12 | * RCS: @(#) $Id: tclConfig.c,v 1.19 2007/12/13 15:23:16 dgp Exp $ |
---|
| 13 | */ |
---|
| 14 | |
---|
| 15 | #include "tclInt.h" |
---|
| 16 | |
---|
| 17 | /* |
---|
| 18 | * Internal structure to hold embedded configuration information. |
---|
| 19 | * |
---|
| 20 | * Our structure is a two-level dictionary associated with the 'interp'. The |
---|
| 21 | * first level is keyed with the package name and maps to the dictionary for |
---|
| 22 | * that package. The package dictionary is keyed with metadata keys and maps |
---|
| 23 | * to the metadata value for that key. This is package specific. The metadata |
---|
| 24 | * values are in UTF-8, converted from the external representation given to us |
---|
| 25 | * by the caller. |
---|
| 26 | */ |
---|
| 27 | |
---|
| 28 | #define ASSOC_KEY "tclPackageAboutDict" |
---|
| 29 | |
---|
| 30 | /* |
---|
| 31 | * A ClientData struct for the QueryConfig command. Store the two bits |
---|
| 32 | * of data we need; the package name for which we store a config dict, |
---|
| 33 | * and the (Tcl_Interp *) in which it is stored. |
---|
| 34 | */ |
---|
| 35 | |
---|
| 36 | typedef struct QCCD { |
---|
| 37 | Tcl_Obj *pkg; |
---|
| 38 | Tcl_Interp *interp; |
---|
| 39 | } QCCD; |
---|
| 40 | |
---|
| 41 | /* |
---|
| 42 | * Static functions in this file: |
---|
| 43 | */ |
---|
| 44 | |
---|
| 45 | static int QueryConfigObjCmd(ClientData clientData, |
---|
| 46 | Tcl_Interp *interp, int objc, |
---|
| 47 | struct Tcl_Obj *CONST *objv); |
---|
| 48 | static void QueryConfigDelete(ClientData clientData); |
---|
| 49 | static Tcl_Obj * GetConfigDict(Tcl_Interp *interp); |
---|
| 50 | static void ConfigDictDeleteProc(ClientData clientData, |
---|
| 51 | Tcl_Interp *interp); |
---|
| 52 | |
---|
| 53 | /* |
---|
| 54 | *---------------------------------------------------------------------- |
---|
| 55 | * |
---|
| 56 | * Tcl_RegisterConfig -- |
---|
| 57 | * |
---|
| 58 | * See TIP#59 for details on what this function does. |
---|
| 59 | * |
---|
| 60 | * Results: |
---|
| 61 | * None. |
---|
| 62 | * |
---|
| 63 | * Side effects: |
---|
| 64 | * Creates namespace and cfg query command in it as per TIP #59. |
---|
| 65 | * |
---|
| 66 | *---------------------------------------------------------------------- |
---|
| 67 | */ |
---|
| 68 | |
---|
| 69 | void |
---|
| 70 | Tcl_RegisterConfig( |
---|
| 71 | Tcl_Interp *interp, /* Interpreter the configuration command is |
---|
| 72 | * registered in. */ |
---|
| 73 | CONST char *pkgName, /* Name of the package registering the |
---|
| 74 | * embedded configuration. ASCII, thus in |
---|
| 75 | * UTF-8 too. */ |
---|
| 76 | Tcl_Config *configuration, /* Embedded configuration. */ |
---|
| 77 | CONST char *valEncoding) /* Name of the encoding used to store the |
---|
| 78 | * configuration values, ASCII, thus UTF-8. */ |
---|
| 79 | { |
---|
| 80 | Tcl_Obj *pDB, *pkgDict; |
---|
| 81 | Tcl_DString cmdName; |
---|
| 82 | Tcl_Config *cfg; |
---|
| 83 | Tcl_Encoding venc = Tcl_GetEncoding(NULL, valEncoding); |
---|
| 84 | QCCD *cdPtr = (QCCD *)ckalloc(sizeof(QCCD)); |
---|
| 85 | |
---|
| 86 | cdPtr->interp = interp; |
---|
| 87 | cdPtr->pkg = Tcl_NewStringObj(pkgName, -1); |
---|
| 88 | |
---|
| 89 | /* |
---|
| 90 | * Phase I: Adding the provided information to the internal database of |
---|
| 91 | * package meta data. Only if we have an ok encoding. |
---|
| 92 | * |
---|
| 93 | * Phase II: Create a command for querying this database, specific to the |
---|
| 94 | * package registerting its configuration. This is the approved interface |
---|
| 95 | * in TIP 59. In the future a more general interface should be done, as |
---|
| 96 | * followup to TIP 59. Simply because our database is now general across |
---|
| 97 | * packages, and not a structure tied to one package. |
---|
| 98 | * |
---|
| 99 | * Note, the created command will have a reference through its clientdata. |
---|
| 100 | */ |
---|
| 101 | |
---|
| 102 | Tcl_IncrRefCount(cdPtr->pkg); |
---|
| 103 | |
---|
| 104 | /* |
---|
| 105 | * For venc == NULL aka bogus encoding we skip the step setting up the |
---|
| 106 | * dictionaries visible at Tcl level. I.e. they are not filled |
---|
| 107 | */ |
---|
| 108 | |
---|
| 109 | if (venc != NULL) { |
---|
| 110 | /* |
---|
| 111 | * Retrieve package specific configuration... |
---|
| 112 | */ |
---|
| 113 | |
---|
| 114 | pDB = GetConfigDict(interp); |
---|
| 115 | |
---|
| 116 | if (Tcl_DictObjGet(interp, pDB, cdPtr->pkg, &pkgDict) != TCL_OK |
---|
| 117 | || (pkgDict == NULL)) { |
---|
| 118 | pkgDict = Tcl_NewDictObj(); |
---|
| 119 | } else if (Tcl_IsShared(pkgDict)) { |
---|
| 120 | pkgDict = Tcl_DuplicateObj(pkgDict); |
---|
| 121 | } |
---|
| 122 | |
---|
| 123 | /* |
---|
| 124 | * Extend the package configuration... |
---|
| 125 | */ |
---|
| 126 | |
---|
| 127 | for (cfg=configuration ; cfg->key!=NULL && cfg->key[0]!='\0' ; cfg++) { |
---|
| 128 | Tcl_DString conv; |
---|
| 129 | CONST char *convValue = |
---|
| 130 | Tcl_ExternalToUtfDString(venc, cfg->value, -1, &conv); |
---|
| 131 | |
---|
| 132 | /* |
---|
| 133 | * We know that the keys are in ASCII/UTF-8, so for them is no |
---|
| 134 | * conversion required. |
---|
| 135 | */ |
---|
| 136 | |
---|
| 137 | Tcl_DictObjPut(interp, pkgDict, Tcl_NewStringObj(cfg->key, -1), |
---|
| 138 | Tcl_NewStringObj(convValue, -1)); |
---|
| 139 | Tcl_DStringFree(&conv); |
---|
| 140 | } |
---|
| 141 | |
---|
| 142 | /* |
---|
| 143 | * We're now done with the encoding, so drop it. |
---|
| 144 | */ |
---|
| 145 | |
---|
| 146 | Tcl_FreeEncoding(venc); |
---|
| 147 | |
---|
| 148 | /* |
---|
| 149 | * Write the changes back into the overall database. |
---|
| 150 | */ |
---|
| 151 | |
---|
| 152 | Tcl_DictObjPut(interp, pDB, cdPtr->pkg, pkgDict); |
---|
| 153 | } |
---|
| 154 | |
---|
| 155 | /* |
---|
| 156 | * Now create the interface command for retrieval of the package |
---|
| 157 | * information. |
---|
| 158 | */ |
---|
| 159 | |
---|
| 160 | Tcl_DStringInit(&cmdName); |
---|
| 161 | Tcl_DStringAppend(&cmdName, "::", -1); |
---|
| 162 | Tcl_DStringAppend(&cmdName, pkgName, -1); |
---|
| 163 | |
---|
| 164 | /* |
---|
| 165 | * The incomplete command name is the name of the namespace to place it |
---|
| 166 | * in. |
---|
| 167 | */ |
---|
| 168 | |
---|
| 169 | if (Tcl_FindNamespace(interp, Tcl_DStringValue(&cmdName), NULL, |
---|
| 170 | TCL_GLOBAL_ONLY) == NULL) { |
---|
| 171 | if (Tcl_CreateNamespace(interp, Tcl_DStringValue(&cmdName), |
---|
| 172 | NULL, NULL) == NULL) { |
---|
| 173 | Tcl_Panic("%s.\n%s: %s", |
---|
| 174 | Tcl_GetStringResult(interp), "Tcl_RegisterConfig", |
---|
| 175 | "Unable to create namespace for package configuration."); |
---|
| 176 | } |
---|
| 177 | } |
---|
| 178 | |
---|
| 179 | Tcl_DStringAppend(&cmdName, "::pkgconfig", -1); |
---|
| 180 | |
---|
| 181 | if (Tcl_CreateObjCommand(interp, Tcl_DStringValue(&cmdName), |
---|
| 182 | QueryConfigObjCmd, (ClientData) cdPtr, QueryConfigDelete) == NULL) { |
---|
| 183 | Tcl_Panic("%s: %s", "Tcl_RegisterConfig", |
---|
| 184 | "Unable to create query command for package configuration"); |
---|
| 185 | } |
---|
| 186 | |
---|
| 187 | Tcl_DStringFree(&cmdName); |
---|
| 188 | } |
---|
| 189 | |
---|
| 190 | /* |
---|
| 191 | *---------------------------------------------------------------------- |
---|
| 192 | * |
---|
| 193 | * QueryConfigObjCmd -- |
---|
| 194 | * |
---|
| 195 | * Implementation of "::<package>::pkgconfig", the command to query |
---|
| 196 | * configuration information embedded into a binary library. |
---|
| 197 | * |
---|
| 198 | * Results: |
---|
| 199 | * A standard tcl result. |
---|
| 200 | * |
---|
| 201 | * Side effects: |
---|
| 202 | * See the manual for what this command does. |
---|
| 203 | * |
---|
| 204 | *---------------------------------------------------------------------- |
---|
| 205 | */ |
---|
| 206 | |
---|
| 207 | static int |
---|
| 208 | QueryConfigObjCmd( |
---|
| 209 | ClientData clientData, |
---|
| 210 | Tcl_Interp *interp, |
---|
| 211 | int objc, |
---|
| 212 | struct Tcl_Obj *CONST *objv) |
---|
| 213 | { |
---|
| 214 | QCCD *cdPtr = (QCCD *) clientData; |
---|
| 215 | Tcl_Obj *pkgName = cdPtr->pkg; |
---|
| 216 | Tcl_Obj *pDB, *pkgDict, *val, *listPtr; |
---|
| 217 | int n, index; |
---|
| 218 | static CONST char *subcmdStrings[] = { |
---|
| 219 | "get", "list", NULL |
---|
| 220 | }; |
---|
| 221 | enum subcmds { |
---|
| 222 | CFG_GET, CFG_LIST |
---|
| 223 | }; |
---|
| 224 | |
---|
| 225 | if ((objc < 2) || (objc > 3)) { |
---|
| 226 | Tcl_WrongNumArgs(interp, 1, objv, "subcommand ?argument?"); |
---|
| 227 | return TCL_ERROR; |
---|
| 228 | } |
---|
| 229 | if (Tcl_GetIndexFromObj(interp, objv[1], subcmdStrings, "subcommand", 0, |
---|
| 230 | &index) != TCL_OK) { |
---|
| 231 | return TCL_ERROR; |
---|
| 232 | } |
---|
| 233 | |
---|
| 234 | pDB = GetConfigDict(interp); |
---|
| 235 | if (Tcl_DictObjGet(interp, pDB, pkgName, &pkgDict) != TCL_OK |
---|
| 236 | || pkgDict == NULL) { |
---|
| 237 | /* |
---|
| 238 | * Maybe a Tcl_Panic is better, because the package data has to be |
---|
| 239 | * present. |
---|
| 240 | */ |
---|
| 241 | |
---|
| 242 | Tcl_SetResult(interp, "package not known", TCL_STATIC); |
---|
| 243 | return TCL_ERROR; |
---|
| 244 | } |
---|
| 245 | |
---|
| 246 | switch ((enum subcmds) index) { |
---|
| 247 | case CFG_GET: |
---|
| 248 | if (objc != 3) { |
---|
| 249 | Tcl_WrongNumArgs(interp, 2, objv, "key"); |
---|
| 250 | return TCL_ERROR; |
---|
| 251 | } |
---|
| 252 | |
---|
| 253 | if (Tcl_DictObjGet(interp, pkgDict, objv [2], &val) != TCL_OK |
---|
| 254 | || val == NULL) { |
---|
| 255 | Tcl_SetResult(interp, "key not known", TCL_STATIC); |
---|
| 256 | return TCL_ERROR; |
---|
| 257 | } |
---|
| 258 | |
---|
| 259 | Tcl_SetObjResult(interp, val); |
---|
| 260 | return TCL_OK; |
---|
| 261 | |
---|
| 262 | case CFG_LIST: |
---|
| 263 | if (objc != 2) { |
---|
| 264 | Tcl_WrongNumArgs(interp, 2, objv, NULL); |
---|
| 265 | return TCL_ERROR; |
---|
| 266 | } |
---|
| 267 | |
---|
| 268 | Tcl_DictObjSize(interp, pkgDict, &n); |
---|
| 269 | listPtr = Tcl_NewListObj(n, NULL); |
---|
| 270 | |
---|
| 271 | if (!listPtr) { |
---|
| 272 | Tcl_SetResult(interp, "insufficient memory to create list", |
---|
| 273 | TCL_STATIC); |
---|
| 274 | return TCL_ERROR; |
---|
| 275 | } |
---|
| 276 | |
---|
| 277 | if (n) { |
---|
| 278 | List *listRepPtr = (List *) |
---|
| 279 | listPtr->internalRep.twoPtrValue.ptr1; |
---|
| 280 | Tcl_DictSearch s; |
---|
| 281 | Tcl_Obj *key, **vals; |
---|
| 282 | int done, i = 0; |
---|
| 283 | |
---|
| 284 | listRepPtr->elemCount = n; |
---|
| 285 | vals = &listRepPtr->elements; |
---|
| 286 | |
---|
| 287 | for (Tcl_DictObjFirst(interp, pkgDict, &s, &key, NULL, &done); |
---|
| 288 | !done; Tcl_DictObjNext(&s, &key, NULL, &done)) { |
---|
| 289 | vals[i++] = key; |
---|
| 290 | Tcl_IncrRefCount(key); |
---|
| 291 | } |
---|
| 292 | } |
---|
| 293 | |
---|
| 294 | Tcl_SetObjResult(interp, listPtr); |
---|
| 295 | return TCL_OK; |
---|
| 296 | |
---|
| 297 | default: |
---|
| 298 | Tcl_Panic("QueryConfigObjCmd: Unknown subcommand to 'pkgconfig'. This can't happen"); |
---|
| 299 | break; |
---|
| 300 | } |
---|
| 301 | return TCL_ERROR; |
---|
| 302 | } |
---|
| 303 | |
---|
| 304 | /* |
---|
| 305 | *------------------------------------------------------------------------- |
---|
| 306 | * |
---|
| 307 | * QueryConfigDelete -- |
---|
| 308 | * |
---|
| 309 | * Command delete function. Cleans up after the configuration query |
---|
| 310 | * command when it is deleted by the user or during finalization. |
---|
| 311 | * |
---|
| 312 | * Results: |
---|
| 313 | * None. |
---|
| 314 | * |
---|
| 315 | * Side effects: |
---|
| 316 | * Deallocates all non-transient memory allocated by Tcl_RegisterConfig. |
---|
| 317 | * |
---|
| 318 | *------------------------------------------------------------------------- |
---|
| 319 | */ |
---|
| 320 | |
---|
| 321 | static void |
---|
| 322 | QueryConfigDelete( |
---|
| 323 | ClientData clientData) |
---|
| 324 | { |
---|
| 325 | QCCD *cdPtr = (QCCD *) clientData; |
---|
| 326 | Tcl_Obj *pkgName = cdPtr->pkg; |
---|
| 327 | Tcl_Obj *pDB = GetConfigDict(cdPtr->interp); |
---|
| 328 | Tcl_DictObjRemove(NULL, pDB, pkgName); |
---|
| 329 | Tcl_DecrRefCount(pkgName); |
---|
| 330 | ckfree((char *)cdPtr); |
---|
| 331 | } |
---|
| 332 | |
---|
| 333 | /* |
---|
| 334 | *------------------------------------------------------------------------- |
---|
| 335 | * |
---|
| 336 | * GetConfigDict -- |
---|
| 337 | * |
---|
| 338 | * Retrieve the package metadata database from the interpreter. |
---|
| 339 | * Initializes it, if not present yet. |
---|
| 340 | * |
---|
| 341 | * Results: |
---|
| 342 | * A Tcl_Obj reference |
---|
| 343 | * |
---|
| 344 | * Side effects: |
---|
| 345 | * May allocate a Tcl_Obj. |
---|
| 346 | * |
---|
| 347 | *------------------------------------------------------------------------- |
---|
| 348 | */ |
---|
| 349 | |
---|
| 350 | static Tcl_Obj * |
---|
| 351 | GetConfigDict( |
---|
| 352 | Tcl_Interp *interp) |
---|
| 353 | { |
---|
| 354 | Tcl_Obj *pDB = Tcl_GetAssocData(interp, ASSOC_KEY, NULL); |
---|
| 355 | |
---|
| 356 | if (pDB == NULL) { |
---|
| 357 | pDB = Tcl_NewDictObj(); |
---|
| 358 | Tcl_IncrRefCount(pDB); |
---|
| 359 | Tcl_SetAssocData(interp, ASSOC_KEY, ConfigDictDeleteProc, pDB); |
---|
| 360 | } |
---|
| 361 | |
---|
| 362 | return pDB; |
---|
| 363 | } |
---|
| 364 | |
---|
| 365 | /* |
---|
| 366 | *---------------------------------------------------------------------- |
---|
| 367 | * |
---|
| 368 | * ConfigDictDeleteProc -- |
---|
| 369 | * |
---|
| 370 | * This function is associated with the "Package About dict" assoc data |
---|
| 371 | * for an interpreter; it is invoked when the interpreter is deleted in |
---|
| 372 | * order to free the information assoicated with any pending error |
---|
| 373 | * reports. |
---|
| 374 | * |
---|
| 375 | * Results: |
---|
| 376 | * None. |
---|
| 377 | * |
---|
| 378 | * Side effects: |
---|
| 379 | * The package metadata database is freed. |
---|
| 380 | * |
---|
| 381 | *---------------------------------------------------------------------- |
---|
| 382 | */ |
---|
| 383 | |
---|
| 384 | static void |
---|
| 385 | ConfigDictDeleteProc( |
---|
| 386 | ClientData clientData, /* Pointer to Tcl_Obj. */ |
---|
| 387 | Tcl_Interp *interp) /* Interpreter being deleted. */ |
---|
| 388 | { |
---|
| 389 | Tcl_Obj *pDB = (Tcl_Obj *) clientData; |
---|
| 390 | |
---|
| 391 | Tcl_DecrRefCount(pDB); |
---|
| 392 | } |
---|
| 393 | |
---|
| 394 | /* |
---|
| 395 | * Local Variables: |
---|
| 396 | * mode: c |
---|
| 397 | * c-basic-offset: 4 |
---|
| 398 | * fill-column: 78 |
---|
| 399 | * End: |
---|
| 400 | */ |
---|