Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Changes In Branch th1-query-api Excluding Merge-Ins
This is equivalent to a diff from 336e1355b9 to 82e78034da
2012-08-06
| ||
11:15 | Fix minor documentation typo. check-in: 372879b388 user: drh tags: trunk | |
2012-08-03
| ||
18:41 | merged in [336e1355b927f]. Leaf check-in: 82e78034da user: stephan tags: th1-query-api | |
2012-07-27
| ||
00:26 | Fix another faulty though harmless memset() initialization. check-in: 336e1355b9 user: drh tags: trunk | |
00:00 | Fix a faulty (but harmless) initialization in the MD5 code. check-in: ed005e302d user: drh tags: trunk | |
2012-07-22
| ||
17:59 | merged in trunk [11abffbb497d67a], added a test script to check a th1 memleak case. check-in: 13b0e0b4d4 user: stephan tags: th1-query-api | |
Changes to src/blob.c.
17 17 ** 18 18 ** A Blob is a variable-length containers for arbitrary string 19 19 ** or binary data. 20 20 */ 21 21 #include "config.h" 22 22 #include <zlib.h> 23 23 #include "blob.h" 24 - 25 24 #if INTERFACE 25 + 26 26 /* 27 27 ** A Blob can hold a string or a binary object of arbitrary size. The 28 28 ** size changes as necessary. 29 29 */ 30 30 struct Blob { 31 31 unsigned int nUsed; /* Number of bytes used in aData[] */ 32 32 unsigned int nAlloc; /* Number of bytes allocated for aData[] */ ................................................................................ 47 47 48 48 /* 49 49 ** Seek whence parameter values 50 50 */ 51 51 #define BLOB_SEEK_SET 1 52 52 #define BLOB_SEEK_CUR 2 53 53 #define BLOB_SEEK_END 3 54 - 55 54 #endif /* INTERFACE */ 56 55 57 56 /* 58 57 ** Make sure a blob is initialized 59 58 */ 60 59 #define blob_is_init(x) \ 61 60 assert((x)->xRealloc==blobReallocMalloc || (x)->xRealloc==blobReallocStatic)
Changes to src/db.c.
1096 1096 ** 1097 1097 ** Check for unfinalized statements and report errors if the reportErrors 1098 1098 ** argument is true. Ignore unfinalized statements when false. 1099 1099 */ 1100 1100 void db_close(int reportErrors){ 1101 1101 sqlite3_stmt *pStmt; 1102 1102 if( g.db==0 ) return; 1103 + if(g.interp){ 1104 + /* clean up up any query_prepare statements */ 1105 + Th_DeleteInterp(g.interp); 1106 + g.interp = 0; 1107 + } 1108 + 1103 1109 if( g.fSqlStats ){ 1104 1110 int cur, hiwtr; 1105 1111 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_USED, &cur, &hiwtr, 0); 1106 1112 fprintf(stderr, "-- LOOKASIDE_USED %10d %10d\n", cur, hiwtr); 1107 1113 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_HIT, &cur, &hiwtr, 0); 1108 1114 fprintf(stderr, "-- LOOKASIDE_HIT %10d\n", hiwtr); 1109 1115 sqlite3_db_status(g.db, SQLITE_DBSTATUS_LOOKASIDE_MISS_SIZE, &cur,&hiwtr,0);
Changes to src/main.c.
677 677 if( p==0 ) fossil_panic("out of memory"); 678 678 return p; 679 679 } 680 680 void fossil_free(void *p){ 681 681 free(p); 682 682 } 683 683 void *fossil_realloc(void *p, size_t n){ 684 - p = realloc(p, n); 685 - if( p==0 ) fossil_panic("out of memory"); 686 - return p; 684 + void * re = realloc(p, n); 685 + if( re==0 && n>0 ) fossil_panic("out of memory"); 686 + return (n > 0) ? re : 0; 687 687 } 688 688 689 689 /* 690 690 ** This function implements a cross-platform "system()" interface. 691 691 */ 692 692 int fossil_system(const char *zOrigCmd){ 693 693 int rc; ................................................................................ 785 785 786 786 787 787 /* 788 788 ** Look for a command-line option. If present, return a pointer. 789 789 ** Return NULL if missing. 790 790 ** 791 791 ** hasArg==0 means the option is a flag. It is either present or not. 792 -** hasArg==1 means the option has an argument. Return a pointer to the 793 -** argument. 792 +** hasArg==1 means the option has an argument. Return a pointer to 793 +** the argument. If hasArg is 0 and the argument is found then a 794 +** pointer to an empty string is returned (to distinguish from 795 +** NULL). If hasArg==1 and the option lies at the end of the argument 796 +** list, it is treated as if it had not been found. 797 +** 798 +** Note that this function REMOVES any found entry from the args list, 799 +** so calling this twice for the same var will cause NULL to be 800 +** returned after the first time. 801 +** 802 +** zLong may not be NULL but zShort may be. 803 +** 804 +** Options are accepted in these forms, depending on the value of 805 +** hasArg: 806 +** 807 +** hasArg=true: 808 +** -long (hasArg==0) 809 +** -long VALUE (hasArg==1) 810 +** -long=VALUE (hasArg==1) 811 +** -short (hasArg==0) 812 +** -short VALUE (hasArg==1) 813 +** -short=VALUE (hasArg==1) 794 814 */ 795 815 const char *find_option(const char *zLong, const char *zShort, int hasArg){ 796 816 int i; 797 - int nLong; 817 + int nLong, nShort; 798 818 const char *zReturn = 0; 799 819 assert( hasArg==0 || hasArg==1 ); 800 820 nLong = strlen(zLong); 821 + nShort = zShort ? strlen(zShort) : 0; 801 822 for(i=1; i<g.argc; i++){ 802 823 char *z; 803 - if (i+hasArg >= g.argc) break; 804 824 z = g.argv[i]; 805 825 if( z[0]!='-' ) continue; 806 826 z++; 807 827 if( z[0]=='-' ){ 808 828 if( z[1]==0 ){ 809 829 remove_from_argv(i, 1); 810 830 break; ................................................................................ 813 833 } 814 834 if( strncmp(z,zLong,nLong)==0 ){ 815 835 if( hasArg && z[nLong]=='=' ){ 816 836 zReturn = &z[nLong+1]; 817 837 remove_from_argv(i, 1); 818 838 break; 819 839 }else if( z[nLong]==0 ){ 820 - zReturn = g.argv[i+hasArg]; 840 + if( hasArg ){ 841 + if (i+hasArg >= g.argc) break; 842 + zReturn = g.argv[i+hasArg]; 843 + }else{ 844 + zReturn = ""; 845 + } 846 + remove_from_argv(i, 1+hasArg); 847 + break; 848 + } 849 + }else if( strncmp(z,zShort,nShort)==0 ){ 850 + if( hasArg && z[nShort]=='=' ){ 851 + zReturn = &z[nShort+1]; 852 + remove_from_argv(i, 1); 853 + break; 854 + }else if( z[nShort]==0 ){ 855 + if( hasArg ){ 856 + if (i+hasArg >= g.argc) break; 857 + zReturn = g.argv[i+hasArg]; 858 + }else{ 859 + zReturn = ""; 860 + } 821 861 remove_from_argv(i, 1+hasArg); 822 862 break; 823 863 } 824 - }else if( fossil_strcmp(z,zShort)==0 ){ 825 - zReturn = g.argv[i+hasArg]; 826 - remove_from_argv(i, 1+hasArg); 827 - break; 828 864 } 829 865 } 866 + 830 867 return zReturn; 831 868 } 832 869 833 870 /* 834 871 ** Verify that there are no unprocessed command-line options. If 835 872 ** Any remaining command-line argument begins with "-" print 836 873 ** an error message and quit.
Changes to src/report.c.
79 79 href("%R/rptsql?rn=%d", rn)); 80 80 } 81 81 blob_appendf(&ril, "</li>\n"); 82 82 } 83 83 84 84 Th_Store("report_items", blob_str(&ril)); 85 85 86 - Th_Render(zScript); 86 + Th_Render(zScript, Th_Render_Flags_DEFAULT); 87 87 88 88 blob_reset(&ril); 89 89 if( g.thTrace ) Th_Trace("END_REPORTLIST<br />\n", -1); 90 90 91 91 style_footer(); 92 92 } 93 93
Changes to src/style.c.
17 17 ** 18 18 ** This file contains code to implement the basic web page look and feel. 19 19 ** 20 20 */ 21 21 #include "config.h" 22 22 #include "style.h" 23 23 24 - 25 24 /* 26 25 ** Elements of the submenu are collected into the following 27 26 ** structure and displayed below the main menu by style_header(). 28 27 ** 29 28 ** Populate this structure with calls to style_submenu_element() 30 29 ** prior to calling style_header(). 31 30 */ ................................................................................ 192 191 Th_Store("manifest_version", MANIFEST_VERSION); 193 192 Th_Store("manifest_date", MANIFEST_DATE); 194 193 Th_Store("compiler_name", COMPILER_NAME); 195 194 if( g.zLogin ){ 196 195 Th_Store("login", g.zLogin); 197 196 } 198 197 if( g.thTrace ) Th_Trace("BEGIN_HEADER_SCRIPT<br />\n", -1); 199 - Th_Render(zHeader); 198 + Th_Render(zHeader, Th_Render_Flags_DEFAULT); 200 199 if( g.thTrace ) Th_Trace("END_HEADER<br />\n", -1); 201 200 Th_Unstore("title"); /* Avoid collisions with ticket field names */ 202 201 cgi_destination(CGI_BODY); 203 202 g.cgiOutput = 1; 204 203 headerHasBeenGenerated = 1; 205 204 sideboxUsed = 0; 206 205 ................................................................................ 279 278 280 279 /* Set the href= field on hyperlinks. Do this before the footer since 281 280 ** the footer will be generating </html> */ 282 281 style_resolve_href(); 283 282 284 283 zFooter = db_get("footer", (char*)zDefaultFooter); 285 284 if( g.thTrace ) Th_Trace("BEGIN_FOOTER<br />\n", -1); 286 - Th_Render(zFooter); 285 + Th_Render(zFooter, Th_Render_Flags_DEFAULT); 287 286 if( g.thTrace ) Th_Trace("END_FOOTER<br />\n", -1); 288 287 289 288 /* Render trace log if TH1 tracing is enabled. */ 290 289 if( g.thTrace ){ 291 290 cgi_append_content("<span class=\"thTrace\"><hr />\n", -1); 292 291 cgi_append_content(blob_str(&g.thLog), blob_size(&g.thLog)); 293 292 cgi_append_content("</span>\n", -1); ................................................................................ 958 957 } 959 958 960 959 /* Process through TH1 in order to give an opportunity to substitute 961 960 ** variables such as $baseurl. 962 961 */ 963 962 Th_Store("baseurl", g.zBaseURL); 964 963 Th_Store("home", g.zTop); 965 - Th_Render(blob_str(&css)); 964 + Th_Render(blob_str(&css), Th_Render_Flags_DEFAULT); 966 965 967 966 /* Tell CGI that the content returned by this page is considered cacheable */ 968 967 g.isConst = 1; 969 968 } 970 969 971 970 /* 972 971 ** WEBPAGE: test_env
Changes to src/th.c.
3 3 ** The implementation of the TH core. This file contains the parser, and 4 4 ** the implementation of the interface in th.h. 5 5 */ 6 6 7 7 #include "th.h" 8 8 #include <string.h> 9 9 #include <assert.h> 10 +#include <stdio.h> /* FILE class */ 10 11 11 12 typedef struct Th_Command Th_Command; 12 13 typedef struct Th_Frame Th_Frame; 13 14 typedef struct Th_Variable Th_Variable; 14 15 16 +/* 17 +** Shared instance. See th.h for the docs. 18 +*/ 19 +const Th_Vtab_OutputMethods Th_Vtab_OutputMethods_FILE = { 20 + Th_Output_f_FILE /* xWrite() */, 21 + Th_Output_dispose_FILE /* dispose() */, 22 + NULL /*pState*/, 23 + 1/*enabled*/ 24 +}; 25 + 26 +/* 27 +** Holds client-provided "garbage collected" data for 28 +** a Th_Interp instance. 29 +*/ 30 +struct Th_GcEntry { 31 + void * pData; /* arbitrary data */ 32 + void (*xDel)( Th_Interp *, void * ); /* finalizer for pData */ 33 +}; 34 +typedef struct Th_GcEntry Th_GcEntry; 35 + 15 36 /* 16 37 ** Interpreter structure. 17 38 */ 18 39 struct Th_Interp { 19 - Th_Vtab *pVtab; /* Copy of the argument passed to Th_CreateInterp() */ 40 + Th_Vtab *pVtab; /* Copy of the argument passed to Th_CreateInterp() */ 20 41 char *zResult; /* Current interpreter result (Th_Malloc()ed) */ 21 - int nResult; /* number of bytes in zResult */ 22 - Th_Hash *paCmd; /* Table of registered commands */ 23 - Th_Frame *pFrame; /* Current execution frame */ 24 - int isListMode; /* True if thSplitList() should operate in "list" mode */ 42 + int nResult; /* number of bytes in zResult */ 43 + Th_Hash *paCmd; /* Table of registered commands */ 44 + Th_Frame *pFrame; /* Current execution frame */ 45 + int isListMode; /* True if thSplitList() should operate in "list" mode */ 46 + Th_Hash * paGc; /* Holds client-provided data owned by this 47 + object. It would be more efficient to store 48 + these in a list (because we don't expect many 49 + entries), but Th_Hash has the strong advantage 50 + of being here and working. 51 + */ 25 52 }; 26 53 27 54 /* 28 55 ** Each TH command registered using Th_CreateCommand() is represented 29 56 ** by an instance of the following structure stored in the Th_Interp.paCmd 30 57 ** hash-table. 31 58 */ ................................................................................ 104 131 static int thEndOfLine(const char *, int); 105 132 106 133 static int thPushFrame(Th_Interp*, Th_Frame*); 107 134 static void thPopFrame(Th_Interp*); 108 135 109 136 static void thFreeVariable(Th_HashEntry*, void*); 110 137 static void thFreeCommand(Th_HashEntry*, void*); 138 +static void thFreeGc(Th_HashEntry*, void*); 111 139 112 140 /* 113 141 ** The following are used by both the expression and language parsers. 114 142 ** Given that the start of the input string (z, n) is a language 115 143 ** construct of the relevant type (a command enclosed in [], an escape 116 144 ** sequence etc.), these functions determine the number of bytes 117 145 ** of the input consumed by the construct. For example: ................................................................................ 286 314 Th_Command *pCommand = (Th_Command *)pEntry->pData; 287 315 if( pCommand->xDel ){ 288 316 pCommand->xDel((Th_Interp *)pContext, pCommand->pContext); 289 317 } 290 318 Th_Free((Th_Interp *)pContext, pEntry->pData); 291 319 pEntry->pData = 0; 292 320 } 321 + 322 +/* 323 +** Th_Hash visitor/destructor for Th_Interp::paGc entries. Frees 324 +** pEntry->pData but not pEntry. 325 +*/ 326 +static void thFreeGc(Th_HashEntry *pEntry, void *pContext){ 327 + Th_GcEntry *gc = (Th_GcEntry *)pEntry->pData; 328 + if(gc){ 329 + if( gc->xDel ){ 330 + gc->xDel( (Th_Interp*)pContext, gc->pData ); 331 + } 332 + Th_Free((Th_Interp *)pContext, pEntry->pData); 333 + pEntry->pData = 0; 334 + } 335 +} 336 + 293 337 294 338 /* 295 339 ** Push a new frame onto the stack. 296 340 */ 297 341 static int thPushFrame(Th_Interp *interp, Th_Frame *pFrame){ 298 342 pFrame->paVar = Th_HashNew(interp); 299 343 pFrame->pCaller = interp->pFrame; ................................................................................ 1367 1411 } 1368 1412 1369 1413 1370 1414 /* 1371 1415 ** Wrappers around the supplied malloc() and free() 1372 1416 */ 1373 1417 void *Th_Malloc(Th_Interp *pInterp, int nByte){ 1374 - void *p = pInterp->pVtab->xMalloc(nByte); 1418 + void * p = pInterp->pVtab->xRealloc(NULL, nByte); 1375 1419 if( p ){ 1376 1420 memset(p, 0, nByte); 1421 + }else{ 1422 + assert( 0 == nByte ); 1377 1423 } 1378 1424 return p; 1379 1425 } 1380 1426 void Th_Free(Th_Interp *pInterp, void *z){ 1381 1427 if( z ){ 1382 - pInterp->pVtab->xFree(z); 1428 + pInterp->pVtab->xRealloc(z, 0); 1429 + } 1430 +} 1431 +void *Th_Realloc(Th_Interp *pInterp, void *z, int nByte){ 1432 + void *p = pInterp->pVtab->xRealloc(z, nByte); 1433 + return p; 1434 +} 1435 + 1436 + 1437 +int Th_Vtab_Output( Th_Vtab *vTab, char const * zData, int nData ){ 1438 + if(!vTab->out.xWrite){ 1439 + return -1; 1440 + }else if(!vTab->out.enabled){ 1441 + return 0; 1442 + }else{ 1443 + return vTab->out.xWrite( zData, nData, vTab->out.pState ); 1444 + } 1445 +} 1446 + 1447 + 1448 +int Th_Output( Th_Interp *pInterp, char const * zData, int nData ){ 1449 + return Th_Vtab_Output( pInterp->pVtab, zData, nData ); 1450 +} 1451 + 1452 +void Th_OutputEnable( Th_Interp *pInterp, char flag ){ 1453 + pInterp->pVtab->out.enabled = flag; 1454 +} 1455 + 1456 +char Th_OutputEnabled( Th_Interp *pInterp ){ 1457 + return pInterp->pVtab->out.enabled ? 1 : 0; 1458 +} 1459 + 1460 +int Th_Output_f_FILE( char const * zData, int nData, void * pState ){ 1461 + FILE * dest = pState ? (FILE*)pState : stdout; 1462 + int rc = (int)fwrite(zData, 1, nData, dest); 1463 + fflush(dest); 1464 + return rc; 1465 +} 1466 + 1467 +void Th_Output_dispose_FILE( void * pState ){ 1468 + FILE * f = pState ? (FILE*)pState : NULL; 1469 + if(f 1470 + && (f != stdout) 1471 + && (f != stderr) 1472 + && (f != stdin)){ 1473 + fflush(f); 1474 + fclose(f); 1383 1475 } 1384 1476 } 1385 1477 1386 1478 /* 1387 1479 ** Install a new th1 command. 1388 1480 ** 1389 1481 ** If a command of the same name already exists, it is deleted automatically. ................................................................................ 1636 1728 /* 1637 1729 ** Delete an interpreter. 1638 1730 */ 1639 1731 void Th_DeleteInterp(Th_Interp *interp){ 1640 1732 assert(interp->pFrame); 1641 1733 assert(0==interp->pFrame->pCaller); 1642 1734 1735 + /* Delete any client-side gc entries first. */ 1736 + if( interp->paGc ){ 1737 + Th_HashIterate(interp, interp->paGc, thFreeGc, (void *)interp); 1738 + Th_HashDelete(interp, interp->paGc); 1739 + interp->paGc = NULL; 1740 + } 1741 + 1742 + /* Clean up the output abstraction. */ 1743 + if( interp->pVtab && interp->pVtab->out.xDispose ){ 1744 + interp->pVtab->out.xDispose( interp->pVtab->out.pState ); 1745 + } 1746 + 1643 1747 /* Delete the contents of the global frame. */ 1644 1748 thPopFrame(interp); 1645 1749 1646 1750 /* Delete any result currently stored in the interpreter. */ 1647 1751 Th_SetResult(interp, 0, 0); 1648 1752 1649 1753 /* Delete all registered commands and the command hash-table itself. */ 1650 1754 Th_HashIterate(interp, interp->paCmd, thFreeCommand, (void *)interp); 1651 1755 Th_HashDelete(interp, interp->paCmd); 1652 1756 1757 + 1653 1758 /* Delete the interpreter structure itself. */ 1654 1759 Th_Free(interp, (void *)interp); 1655 1760 } 1656 1761 1657 1762 /* 1658 1763 ** Create a new interpreter. 1659 1764 */ 1660 1765 Th_Interp * Th_CreateInterp(Th_Vtab *pVtab){ 1661 1766 Th_Interp *p; 1662 1767 1663 1768 /* Allocate and initialise the interpreter and the global frame */ 1664 - p = pVtab->xMalloc(sizeof(Th_Interp) + sizeof(Th_Frame)); 1769 + p = pVtab->xRealloc(NULL, sizeof(Th_Interp) + sizeof(Th_Frame)); 1665 1770 memset(p, 0, sizeof(Th_Interp)); 1666 1771 p->pVtab = pVtab; 1667 1772 p->paCmd = Th_HashNew(p); 1668 1773 thPushFrame(p, (Th_Frame *)&p[1]); 1669 1774 1670 1775 return p; 1671 1776 } ................................................................................ 2269 2374 ** This function is the same as the standard strlen() function, except 2270 2375 ** that it returns 0 (instead of being undefined) if the argument is 2271 2376 ** a null pointer. 2272 2377 */ 2273 2378 int th_strlen(const char *zStr){ 2274 2379 int n = 0; 2275 2380 if( zStr ){ 2276 - while( zStr[n] ) n++; 2381 + while( zStr[n] ) ++n; 2277 2382 } 2278 2383 return n; 2279 2384 } 2280 2385 2281 2386 /* Whitespace characters: 2282 2387 ** 2283 2388 ** ' ' 0x20 ................................................................................ 2333 2438 int th_isalnum(char c){ 2334 2439 return (aCharProp[(unsigned char)c] & 0x0A); 2335 2440 } 2336 2441 2337 2442 #ifndef LONGDOUBLE_TYPE 2338 2443 # define LONGDOUBLE_TYPE long double 2339 2444 #endif 2340 -typedef char u8; 2341 2445 2342 2446 2343 2447 /* 2344 2448 ** Return TRUE if z is a pure numeric string. Return FALSE if the 2345 2449 ** string contains any character which is not part of a number. If 2346 2450 ** the string is numeric and contains the '.' character, set *realnum 2347 2451 ** to TRUE (otherwise FALSE). ................................................................................ 2371 2475 if( realnum ) *realnum = 1; 2372 2476 } 2373 2477 return *z==0; 2374 2478 } 2375 2479 2376 2480 /* 2377 2481 ** The string z[] is an ascii representation of a real number. 2378 -** Convert this string to a double. 2482 +** Convert this string to a double and assigns its value to 2483 +** pResult. 2379 2484 ** 2380 2485 ** This routine assumes that z[] really is a valid number. If it 2381 2486 ** is not, the result is undefined. 2382 2487 ** 2383 2488 ** This routine is used instead of the library atof() function because 2384 2489 ** the library atof() might want to use "," as the decimal point instead 2385 2490 ** of "." depending on how locale is set. But that would cause problems 2386 2491 ** for SQL. So this routine always uses "." regardless of locale. 2492 +** 2493 +** Returns the number of bytes of z consumed in parsing the value. 2387 2494 */ 2388 2495 static int sqlite3AtoF(const char *z, double *pResult){ 2389 2496 int sign = 1; 2390 2497 const char *zBegin = z; 2391 2498 LONGDOUBLE_TYPE v1 = 0.0; 2392 2499 while( th_isspace(*(u8*)z) ) z++; 2393 2500 if( *z=='-' ){ ................................................................................ 2436 2543 } 2437 2544 } 2438 2545 *pResult = sign<0 ? -v1 : v1; 2439 2546 return z - zBegin; 2440 2547 } 2441 2548 2442 2549 /* 2443 -** Try to convert the string passed as arguments (z, n) to an integer. 2444 -** If successful, store the result in *piOut and return TH_OK. 2445 -** 2446 -** If the string cannot be converted to an integer, return TH_ERROR. 2447 -** If the interp argument is not NULL, leave an error message in the 2448 -** interpreter result too. 2550 +** Attempts to convert (zArg,nArg) to an integer. On success *piOut is 2551 +** assigned to its value and TH_OK is returned, else piOut is not 2552 +** modified and TH_ERROR is returned. Conversion errors are considered 2553 +** non-fatal here, so interp's error state is not set. 2449 2554 */ 2450 -int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){ 2555 +int Th_TryInt(Th_Interp *interp, const char *z, int n, int *piOut){ 2451 2556 int i = 0; 2452 2557 int iOut = 0; 2453 2558 2454 2559 if( n<0 ){ 2455 2560 n = th_strlen(z); 2456 2561 } 2457 2562 2458 2563 if( n>0 && (z[0]=='-' || z[0]=='+') ){ 2459 2564 i = 1; 2460 2565 } 2461 2566 for(; i<n; i++){ 2462 2567 if( !th_isdigit(z[i]) ){ 2463 - Th_ErrorMessage(interp, "expected integer, got: \"", z, n); 2464 2568 return TH_ERROR; 2465 2569 } 2466 2570 iOut = iOut * 10 + (z[i] - 48); 2467 2571 } 2468 2572 2469 2573 if( n>0 && z[0]=='-' ){ 2470 2574 iOut *= -1; 2471 2575 } 2472 2576 2473 2577 *piOut = iOut; 2474 2578 return TH_OK; 2475 2579 } 2580 + 2581 +/* 2582 +** Try to convert the string passed as arguments (z, n) to an integer. 2583 +** If successful, store the result in *piOut and return TH_OK. 2584 +** 2585 +** If the string cannot be converted to an integer, return TH_ERROR. 2586 +** If the interp argument is not NULL, leave an error message in the 2587 +** interpreter result too. 2588 +*/ 2589 +int Th_ToInt(Th_Interp *interp, const char *z, int n, int *piOut){ 2590 + const int rc = Th_TryInt(interp, z, n, piOut); 2591 + if( TH_OK != rc ){ 2592 + Th_ErrorMessage(interp, "expected integer, got: \"", z, n); 2593 + } 2594 + return rc; 2595 +} 2596 + 2597 + 2598 +/* 2599 +** Functionally/semantically identical to Th_TryInt() but works on 2600 +** doubles. 2601 +*/ 2602 +int Th_TryDouble( 2603 + Th_Interp *interp, 2604 + const char *z, 2605 + int n, 2606 + double *pfOut 2607 +){ 2608 + if( !sqlite3IsNumber((const char *)z, 0) ){ 2609 + return TH_ERROR; 2610 + }else{ 2611 + sqlite3AtoF((const char *)z, pfOut); 2612 + return TH_OK; 2613 + } 2614 +} 2476 2615 2477 2616 /* 2478 2617 ** Try to convert the string passed as arguments (z, n) to a double. 2479 2618 ** If successful, store the result in *pfOut and return TH_OK. 2480 2619 ** 2481 2620 ** If the string cannot be converted to a double, return TH_ERROR. 2482 2621 ** If the interp argument is not NULL, leave an error message in the ................................................................................ 2484 2623 */ 2485 2624 int Th_ToDouble( 2486 2625 Th_Interp *interp, 2487 2626 const char *z, 2488 2627 int n, 2489 2628 double *pfOut 2490 2629 ){ 2491 - if( !sqlite3IsNumber((const char *)z, 0) ){ 2492 - Th_ErrorMessage(interp, "expected number, got: \"", z, n); 2493 - return TH_ERROR; 2630 + const int rc = Th_TryDouble(interp, z, n, pfOut); 2631 + if( TH_OK != rc ){ 2632 + Th_ErrorMessage(interp, "expected number, got: \"", z, n); 2494 2633 } 2495 - 2496 - sqlite3AtoF((const char *)z, pfOut); 2497 - return TH_OK; 2634 + return rc; 2498 2635 } 2499 2636 2500 2637 /* 2501 2638 ** Set the result of the interpreter to the th1 representation of 2502 2639 ** the integer iVal and return TH_OK. 2503 2640 */ 2504 2641 int Th_SetResultInt(Th_Interp *interp, int iVal){ ................................................................................ 2603 2740 *z++ = zExp[i]; 2604 2741 } 2605 2742 } 2606 2743 2607 2744 *z = '\0'; 2608 2745 return Th_SetResult(interp, zBuf, -1); 2609 2746 } 2747 + 2748 + 2749 +int Th_SetData( Th_Interp * interp, char const * key, 2750 + void * pData, 2751 + void (*finalizer)( Th_Interp *, void * ) ){ 2752 + Th_HashEntry * pEnt; 2753 + Th_GcEntry * pGc; 2754 + if(NULL == interp->paGc){ 2755 + interp->paGc = Th_HashNew(interp); 2756 + assert(NULL != interp->paGc); 2757 + if(!interp->paGc){ 2758 + return TH_ERROR; 2759 + } 2760 + } 2761 + pEnt = Th_HashFind(interp, interp->paGc, key, th_strlen(key), 1); 2762 + if( pEnt->pData ){ 2763 + thFreeGc( pEnt, interp ); 2764 + } 2765 + assert( NULL == pEnt->pData ); 2766 + pEnt->pData = pGc = (Th_GcEntry*)Th_Malloc(interp, sizeof(Th_GcEntry)); 2767 + pGc->pData = pData; 2768 + pGc->xDel = finalizer; 2769 + return 0; 2770 + 2771 +} 2772 +void * Th_GetData( Th_Interp * interp, char const * key ){ 2773 + Th_HashEntry * e = interp->paGc 2774 + ? Th_HashFind(interp, interp->paGc, key, th_strlen(key), 0) 2775 + : NULL; 2776 + return e ? ((Th_GcEntry*)e->pData)->pData : NULL; 2777 +} 2778 + 2779 +int Th_RegisterCommands( Th_Interp * interp, 2780 + Th_Command_Reg const * aCommand ){ 2781 + int i; 2782 + int rc = TH_OK; 2783 + for(i=0; (TH_OK==rc) && aCommand[i].zName; ++i){ 2784 + if ( !aCommand[i].zName ) break; 2785 + else if( !aCommand[i].xProc ) continue; 2786 + else{ 2787 + rc = Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, 2788 + aCommand[i].pContext, 0); 2789 + } 2790 + } 2791 + return rc; 2792 +} 2793 + 2794 + 2795 + 2796 +#ifdef TH_ENABLE_OB 2797 +/* Reminder: the ob code "really" belongs in th_lang.c or th_main.c, 2798 + but it needs access to Th_Interp::pVtab in order to swap out 2799 + Th_Vtab_OutputMethods parts for purposes of stacking layers of 2800 + buffers. We could add access to it via the public interface, 2801 + but that didn't seem appropriate. 2802 +*/ 2803 + 2804 +/* 2805 +** Th_Output_f() impl which redirects output to a Th_Ob_Manager. 2806 +** Requires that pState be a (Th_Ob_Man*). 2807 +*/ 2808 +static int Th_Output_f_ob( char const * zData, int len, void * pState ); 2809 + 2810 +/* 2811 +** Th_Output::dispose() impl which requires pState to be-a Th_Ob_Manager. 2812 +*/ 2813 +static void Th_Output_dispose_ob( void * pState ); 2814 + 2815 +/* Empty-initialized Th_Ob_Manager instance. */ 2816 +#define Th_Ob_Man_empty_m { \ 2817 + NULL/*aBuf*/, \ 2818 + 0/*nBuf*/, \ 2819 + -1/*cursor*/, \ 2820 + NULL/*interp*/, \ 2821 + NULL/*aOutput*/ \ 2822 +} 2823 + 2824 +/* Empty-initialized Th_Vtab_OutputMethods instance. */ 2825 +#define Th_Vtab_OutputMethods_empty_m { \ 2826 + NULL /* write() */, \ 2827 + NULL /* dispose() */, \ 2828 + NULL /*pState*/,\ 2829 + 1/*enabled*/\ 2830 +} 2831 +/* Vtab_OutputMethods instance initialized for OB support. */ 2832 +#define Th_Vtab_OutputMethods_ob_m { \ 2833 + Th_Output_f_ob /*write()*/, \ 2834 + Th_Output_dispose_ob /* dispose() */, \ 2835 + NULL /*pState*/,\ 2836 + 1/*enabled*/\ 2837 +} 2838 +/* Empty-initialized Th_Vtab_Man instance. */ 2839 +static const Th_Ob_Manager Th_Ob_Man_empty = Th_Ob_Man_empty_m; 2840 +/* Empty-initialized Th_Vtab_OutputMethods instance. */ 2841 +static Th_Vtab_OutputMethods Th_Vtab_OutputMethods_empty = Th_Vtab_OutputMethods_empty_m; 2842 +/* Th_Vtab_OutputMethods instance initialized for OB support. */ 2843 +static Th_Vtab_OutputMethods Th_Vtab_OutputMethods_ob = Th_Vtab_OutputMethods_ob_m; 2844 + 2845 +/* 2846 +** Internal key for Th_Set/GetData(), for storing a Th_Ob_Manager instance. 2847 +*/ 2848 +#define Th_Ob_Man_KEY "Th_Ob_Manager" 2849 + 2850 +Th_Ob_Manager * Th_Ob_GetManager(Th_Interp *interp){ 2851 + return (Th_Ob_Manager*) Th_GetData(interp, Th_Ob_Man_KEY ); 2852 +} 2853 + 2854 +Blob * Th_Ob_GetCurrentBuffer( Th_Ob_Manager * pMan ){ 2855 + return pMan->nBuf>0 ? pMan->aBuf[pMan->cursor] : 0; 2856 +} 2857 + 2858 + 2859 +/* 2860 +** Th_Output_f() impl which expects pState to be (Th_Ob_Manager*). 2861 +** (zData,len) are appended to pState's current output buffer. 2862 +*/ 2863 +int Th_Output_f_ob( char const * zData, int len, void * pState ){ 2864 + Th_Ob_Manager * pMan = (Th_Ob_Manager*)pState; 2865 + Blob * b = Th_Ob_GetCurrentBuffer( pMan ); 2866 + assert( NULL != pMan ); 2867 + assert( b ); 2868 + blob_append( b, zData, len ); 2869 + return len; 2870 +} 2871 + 2872 +void Th_Output_dispose_ob( void * pState ){ 2873 + /* possible todo: move the cleanup logic from 2874 + Th_Ob_Pop() to here? */ 2875 +#if 0 2876 + Th_Ob_Manager * pMan = (Th_Ob_Manager*)pState; 2877 + Blob * b = Th_Ob_GetCurrentBuffer( pMan ); 2878 + assert( NULL != pMan ); 2879 + assert( b ); 2880 +#endif 2881 +} 2882 + 2883 + 2884 + 2885 +int Th_Ob_Push( Th_Ob_Manager * pMan, 2886 + Th_Vtab_OutputMethods const * pWriter, 2887 + Blob ** pOut ){ 2888 + Blob * pBlob; 2889 + int x, i; 2890 + if( NULL == pWriter ){ 2891 + pWriter = &Th_Vtab_OutputMethods_ob; 2892 + } 2893 + assert( NULL != pMan->interp ); 2894 + pBlob = (Blob *)Th_Malloc(pMan->interp, sizeof(Blob)); 2895 + *pBlob = empty_blob; 2896 + 2897 + if( pMan->cursor >= pMan->nBuf-2 ){ 2898 + /* expand if needed */ 2899 + void * re; 2900 + x = pMan->nBuf + 5; 2901 + if( pMan->cursor >= x ) { 2902 + assert( 0 && "This really should not happen." ); 2903 + x = pMan->cursor + 5; 2904 + } 2905 + re = Th_Realloc( pMan->interp, pMan->aBuf, x * sizeof(Blob*) ); 2906 + if(NULL==re){ 2907 + goto error; 2908 + } 2909 + pMan->aBuf = (Blob **)re; 2910 + re = Th_Realloc( pMan->interp, pMan->aOutput, x * sizeof(Th_Vtab_OutputMethods) ); 2911 + if(NULL==re){ 2912 + goto error; 2913 + } 2914 + pMan->aOutput = (Th_Vtab_OutputMethods*)re; 2915 + for( i = pMan->nBuf; i < x; ++i ){ 2916 + pMan->aOutput[i] = Th_Vtab_OutputMethods_empty; 2917 + pMan->aBuf[i] = NULL; 2918 + } 2919 + pMan->nBuf = x; 2920 + } 2921 + assert( pMan->nBuf > pMan->cursor ); 2922 + assert( pMan->cursor >= -1 ); 2923 + ++pMan->cursor; 2924 + pMan->aBuf[pMan->cursor] = pBlob; 2925 + pMan->aOutput[pMan->cursor] = pMan->interp->pVtab->out; 2926 + pMan->interp->pVtab->out = *pWriter; 2927 + pMan->interp->pVtab->out.pState = pMan; 2928 + if( pOut ){ 2929 + *pOut = pBlob; 2930 + } 2931 + return TH_OK; 2932 + error: 2933 + if( pBlob ){ 2934 + Th_Free( pMan->interp, pBlob ); 2935 + } 2936 + return TH_ERROR; 2937 +} 2938 + 2939 +Blob * Th_Ob_Pop( Th_Ob_Manager * pMan ){ 2940 + if( pMan->cursor < 0 ){ 2941 + return NULL; 2942 + }else{ 2943 + Blob * rc; 2944 + Th_Vtab_OutputMethods * theOut; 2945 + assert( pMan->nBuf > pMan->cursor ); 2946 + rc = pMan->aBuf[pMan->cursor]; 2947 + pMan->aBuf[pMan->cursor] = NULL; 2948 + theOut = &pMan->aOutput[pMan->cursor]; 2949 +#if 0 2950 + /* We need something like this (but not this!) if we extend the 2951 + support to use other (non-Blob) proxies. We will likely need 2952 + another callback function or two for that case, e.g. xStart() 2953 + and xEnd(), which would be called when they are pushed/popped 2954 + to/from the stack. 2955 + */ 2956 + if( theOut->xDispose ){ 2957 + theOut->xDispose( theOut->pState ); 2958 + } 2959 +#endif 2960 + pMan->interp->pVtab->out = *theOut; 2961 + pMan->aOutput[pMan->cursor] = Th_Vtab_OutputMethods_empty; 2962 + if(-1 == --pMan->cursor){ 2963 + Th_Interp * interp = pMan->interp; 2964 + Th_Free( pMan->interp, pMan->aBuf ); 2965 + Th_Free( pMan->interp, pMan->aOutput ); 2966 + *pMan = Th_Ob_Man_empty; 2967 + pMan->interp = interp; 2968 + assert(-1 == pMan->cursor); 2969 + } 2970 + return rc; 2971 + } 2972 +} 2973 + 2974 +int Th_Ob_PopAndFree( Th_Ob_Manager * pMan ){ 2975 + Blob * b = Th_Ob_Pop( pMan ); 2976 + if(!b) return 1; 2977 + else { 2978 + blob_reset(b); 2979 + Th_Free( pMan->interp, b ); 2980 + return 0; 2981 + } 2982 +} 2983 + 2984 + 2985 +void Th_ob_cleanup( Th_Ob_Manager * man ){ 2986 + while( 0 == Th_Ob_PopAndFree(man) ){} 2987 +} 2988 + 2989 + 2990 +/* 2991 +** TH command: 2992 +** 2993 +** ob clean 2994 +** 2995 +** Erases any currently buffered contents but does not modify 2996 +** the buffering level. 2997 +*/ 2998 +static int ob_clean_command( Th_Interp *interp, void *ctx, 2999 + int argc, const char **argv, int *argl 3000 +){ 3001 + const char doRc = ctx ? 1 : 0; 3002 + Th_Ob_Manager * pMan = ctx ? (Th_Ob_Manager *)ctx : Th_Ob_GetManager(interp); 3003 + Blob * b; 3004 + assert( pMan && (interp == pMan->interp) ); 3005 + b = pMan ? Th_Ob_GetCurrentBuffer(pMan) : NULL; 3006 + if(!b){ 3007 + Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 ); 3008 + return TH_ERROR; 3009 + }else{ 3010 + blob_reset(b); 3011 + if( doRc ) { 3012 + Th_SetResultInt( interp, 0 ); 3013 + } 3014 + return TH_OK; 3015 + } 3016 +} 3017 + 3018 +/* 3019 +** TH command: 3020 +** 3021 +** ob end 3022 +** 3023 +** Erases any currently buffered contents and pops the current buffer 3024 +** from the stack. 3025 +*/ 3026 +static int ob_end_command( Th_Interp *interp, void *ctx, 3027 + int argc, const char **argv, int *argl ){ 3028 + const char doRc = ctx ? 1 : 0; 3029 + Th_Ob_Manager * pMan = ctx ? (Th_Ob_Manager *)ctx : Th_Ob_GetManager(interp); 3030 + Blob * b; 3031 + assert( pMan && (interp == pMan->interp) ); 3032 + b = Th_Ob_Pop(pMan); 3033 + if(!b){ 3034 + Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 ); 3035 + return TH_ERROR; 3036 + }else{ 3037 + blob_reset(b); 3038 + Th_Free( interp, b ); 3039 + if(doRc){ 3040 + Th_SetResultInt( interp, 0 ); 3041 + } 3042 + return TH_OK; 3043 + } 3044 +} 3045 + 3046 +/* 3047 +** TH command: 3048 +** 3049 +** ob flush ?pop|end? 3050 +** 3051 +** Briefly reverts the output layer to the next-lower 3052 +** level, flushes the current buffer to that output layer, 3053 +** and clears out the current buffer. Does not change the 3054 +** buffering level unless "end" is specified, in which case 3055 +** it behaves as if "ob end" had been called (after flushing 3056 +** the buffer). 3057 +*/ 3058 +static int ob_flush_command( Th_Interp *interp, void *ctx, 3059 + int argc, const char **argv, int *argl ){ 3060 + Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx; 3061 + Blob * b = NULL; 3062 + Th_Vtab * oldVtab; 3063 + int rc = TH_OK; 3064 + assert( pMan && (interp == pMan->interp) ); 3065 + b = Th_Ob_GetCurrentBuffer(pMan); 3066 + if( NULL == b ){ 3067 + Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 ); 3068 + return TH_ERROR; 3069 + } 3070 + oldVtab = interp->pVtab; 3071 + interp->pVtab->out = pMan->aOutput[pMan->cursor]; 3072 + Th_Output( interp, blob_str(b), b->nUsed ); 3073 + interp->pVtab = oldVtab; 3074 + blob_reset(b); 3075 + 3076 + if(!rc && argc>2){ 3077 + int argPos = 2; 3078 + char const * sub = argv[argPos]; 3079 + int subL = argl[argPos]; 3080 + /* "flush end" */ 3081 + if(th_strlen(sub)==3 && 3082 + ((0==memcmp("end", sub, subL) 3083 + || (0==memcmp("pop", sub, subL))))){ 3084 + rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1); 3085 + } 3086 + } 3087 + Th_SetResultInt( interp, 0 ); 3088 + return rc; 3089 +} 3090 + 3091 +/* 3092 +** TH command: 3093 +** 3094 +** ob get ?clean|end|pop? 3095 +** 3096 +** Fetches the contents of the current buffer level. If either 3097 +** 'clean' or 'end' are specified then the effect is as if "ob clean" 3098 +** or "ob end", respectively, are called after fetching the 3099 +** value. Calling "ob get end" is functionality equivalent to "ob get" 3100 +** followed by "ob end". 3101 +*/ 3102 +static int ob_get_command( Th_Interp *interp, void *ctx, 3103 + int argc, const char **argv, int *argl){ 3104 + Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx; 3105 + Blob * b = NULL; 3106 + assert( pMan && (interp == pMan->interp) ); 3107 + b = Th_Ob_GetCurrentBuffer(pMan); 3108 + if( NULL == b ){ 3109 + Th_ErrorMessage( interp, "Not currently buffering.", NULL, 0 ); 3110 + return TH_ERROR; 3111 + }else{ 3112 + int argPos = 2; 3113 + char const * sub; 3114 + int subL; 3115 + int rc = TH_OK; 3116 + Th_SetResult( interp, blob_str(b), b->nUsed ); 3117 + if(argc>argPos){ 3118 + sub = argv[argPos]; 3119 + subL = argl[argPos]; 3120 + /* "ob get clean" */ 3121 + if(!rc && th_strlen(sub)==5 && 0==memcmp("clean", sub, subL)){ 3122 + rc |= ob_clean_command(interp, NULL, argc-1, argv+1, argl+1); 3123 + }/* "ob get end" */ 3124 + else if(!rc && th_strlen(sub)==3 && 3125 + ((0==memcmp("end", sub, subL)) 3126 + || (0==memcmp("pop", sub, subL)))){ 3127 + rc |= ob_end_command(interp, NULL, argc-1, argv+1, argl+1); 3128 + } 3129 + } 3130 + return rc; 3131 + } 3132 +} 3133 + 3134 +/* 3135 +** TH command: 3136 +** 3137 +** ob level 3138 +** 3139 +** Returns the buffering level, where 0 means no buffering is 3140 +** active, 1 means 1 level is active, etc. 3141 +*/ 3142 +static int ob_level_command( Th_Interp *interp, void *ctx, 3143 + int argc, const char **argv, int *argl 3144 +){ 3145 + Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx; 3146 + Th_SetResultInt( interp, 1 + pMan->cursor ); 3147 + return TH_OK; 3148 +} 3149 + 3150 +/* 3151 +** TH command: 3152 +** 3153 +** ob start|push 3154 +** 3155 +** Pushes a new level of buffering onto the buffer stack. 3156 +** Returns the new buffering level (1-based). 3157 +** 3158 +** TODO: take an optional final argument naming the output handler. 3159 +** e.g. "stdout" or "cgi" or "default" 3160 +** 3161 +*/ 3162 +static int ob_start_command( Th_Interp *interp, void *ctx, 3163 + int argc, const char **argv, int *argl 3164 +){ 3165 + Th_Ob_Manager * pMan = (Th_Ob_Manager *)ctx; 3166 + Blob * b = NULL; 3167 + int rc; 3168 + Th_Vtab_OutputMethods const * pWriter = &Th_Vtab_OutputMethods_ob; 3169 + assert( pMan && (interp == pMan->interp) ); 3170 + rc = Th_Ob_Push(pMan, NULL, &b); 3171 + if( TH_OK != rc ){ 3172 + assert( NULL == b ); 3173 + return rc; 3174 + } 3175 + assert( NULL != b ); 3176 + Th_SetResultInt( interp, 1 + pMan->cursor ); 3177 + return TH_OK; 3178 +} 3179 + 3180 +static void finalizerObMan( Th_Interp * interp, void * p ){ 3181 + Th_Ob_Manager * man = (Th_Ob_Manager*)p; 3182 + if(man){ 3183 + assert( interp == man->interp ); 3184 + Th_ob_cleanup( man ); 3185 + Th_Free( interp, p ); 3186 + } 3187 +} 3188 + 3189 +/* 3190 +** TH command: 3191 +** 3192 +** ob clean|(end|pop)|flush|get|level|(start|push) 3193 +** 3194 +** Runs the given subcommand. Some subcommands have other subcommands 3195 +** (see their docs for details). 3196 +** 3197 +*/ 3198 +static int ob_cmd( 3199 + Th_Interp *interp, 3200 + void *ignored, 3201 + int argc, 3202 + const char **argv, 3203 + int *argl 3204 +){ 3205 + Th_Ob_Manager * pMan = Th_Ob_GetManager(interp); 3206 + Th_SubCommand aSub[] = { 3207 + { "clean", ob_clean_command }, 3208 + { "end", ob_end_command }, 3209 + { "flush", ob_flush_command }, 3210 + { "get", ob_get_command }, 3211 + { "level", ob_level_command }, 3212 + { "pop", ob_end_command }, 3213 + { "push", ob_start_command }, 3214 + { "start", ob_start_command }, 3215 + /* TODO: enable/disable commands which call Th_OutputEnable(). */ 3216 + { 0, 0 } 3217 + }; 3218 + assert(NULL != pMan && pMan->interp==interp); 3219 + return Th_CallSubCommand(interp, pMan, argc, argv, argl, aSub); 3220 +} 3221 + 3222 +int th_register_ob(Th_Interp * interp){ 3223 + int rc; 3224 + static Th_Command_Reg aCommand[] = { 3225 + {"ob", ob_cmd, 0}, 3226 + {0,0,0} 3227 + }; 3228 + rc = Th_RegisterCommands( interp, aCommand ); 3229 + if(NULL == Th_Ob_GetManager(interp)){ 3230 + Th_Ob_Manager * pMan; 3231 + pMan = Th_Malloc(interp, sizeof(Th_Ob_Manager)); 3232 + if(!pMan){ 3233 + rc = TH_ERROR; 3234 + }else{ 3235 + *pMan = Th_Ob_Man_empty; 3236 + pMan->interp = interp; 3237 + assert( -1 == pMan->cursor ); 3238 + Th_SetData( interp, Th_Ob_Man_KEY, pMan, finalizerObMan ); 3239 + assert( NULL != Th_Ob_GetManager(interp) ); 3240 + } 3241 + } 3242 + return rc; 3243 +} 3244 + 3245 +#undef Th_Ob_Man_empty_m 3246 +#undef Th_Vtab_OutputMethods_empty_m 3247 +#undef Th_Vtab_OutputMethods_ob_m 3248 +#undef Th_Ob_Man_KEY 3249 +#endif 3250 +/* end TH_ENABLE_OB */
Changes to src/th.h.
1 +#include "config.h" 2 + 3 +/* 4 +** TH_ENABLE_QUERY, if defined, enables the "query" family of functions. 5 +** They provide SELECT-only access to the repository db. 6 +*/ 7 +#define TH_ENABLE_QUERY 8 + 9 +/* 10 +** TH_ENABLE_OB, if defined, enables the "ob" family of functions. 11 +** They are functionally similar to PHP's ob_start(), ob_end(), etc. 12 +** family of functions, providing output capturing/buffering. 13 +*/ 14 +#define TH_ENABLE_OB 15 + 16 +/* 17 +** TH_ENABLE_ARGV, if defined, enables the "argv" family of functions. 18 +** They provide access to CLI arguments as well as GET/POST arguments. 19 +** They do not provide access to POST data submitted in JSON mode. 20 +*/ 21 +#define TH_ENABLE_ARGV 22 + 23 +#ifdef TH_ENABLE_OB 24 +#ifndef INTERFACE 25 +#include "blob.h" /* maintenance reminder: also pulls in fossil_realloc() and friends */ 26 +#endif 27 +#endif 1 28 2 29 /* This header file defines the external interface to the custom Scripting 3 30 ** Language (TH) interpreter. TH is very similar to TCL but is not an 4 31 ** exact clone. 5 32 */ 6 33 34 +/* 35 +** Th_Output_f() specifies a generic output routine for use by 36 +** Th_Vtab_OutputMethods and friends. Its first argument is the data to 37 +** write, the second is the number of bytes to write, and the 3rd is 38 +** an implementation-specific state pointer (may be NULL, depending on 39 +** the implementation). The return value is the number of bytes output 40 +** (which may differ from len due to encoding and whatnot). On error 41 +** a negative value must be returned. 42 +*/ 43 +typedef int (*Th_Output_f)( char const * zData, int len, void * pState ); 44 + 45 +/* 46 +** This structure defines the output state associated with a 47 +** Th_Vtab. It is intended that a given Vtab be able to swap out 48 +** output back-ends during its lifetime, e.g. to form a stack of 49 +** buffers. 50 +*/ 51 +struct Th_Vtab_OutputMethods { 52 + Th_Output_f xWrite; /* output handler */ 53 + void (*xDispose)( void * pState ); /* Called when the framework is done with 54 + this output handler,passed this object's 55 + pState pointer.. */ 56 + void * pState; /* final argument for xWrite() and xDispose()*/ 57 + char enabled; /* if 0, Th_Output() does nothing. */ 58 +}; 59 +typedef struct Th_Vtab_OutputMethods Th_Vtab_OutputMethods; 60 + 61 +/* 62 +** Shared Th_Vtab_OutputMethods instance used for copy-initialization. This 63 +** implementation uses Th_Output_f_FILE as its write() impl and 64 +** Th_Output_dispose_FILE() for cleanup. If its pState member is NULL 65 +** it outputs to stdout, else pState must be a (FILE*) which it will 66 +** output to. 67 +*/ 68 +extern const Th_Vtab_OutputMethods Th_Vtab_OutputMethods_FILE; 69 + 7 70 /* 8 71 ** Before creating an interpreter, the application must allocate and 9 72 ** populate an instance of the following structure. It must remain valid 10 73 ** for the lifetime of the interpreter. 11 74 */ 12 75 struct Th_Vtab { 13 - void *(*xMalloc)(unsigned int); 14 - void (*xFree)(void *); 76 + void *(*xRealloc)(void *, unsigned int); /** 77 + Re/deallocation routine. Must behave like 78 + realloc(3), with the minor extension that 79 + realloc(anything,positiveValue) _must_ return 80 + NULL on allocation error. The Standard's wording 81 + allows realloc() to return "some value suitable for 82 + passing to free()" on error, but because client code 83 + has no way of knowing if any non-NULL value is an error 84 + value, no sane realloc() implementation would/should 85 + return anything _but_ NULL on allocation error. 86 + */ 87 + Th_Vtab_OutputMethods out; /** Output handler. TH functions which generate 88 + output should send it here (via Th_Output()). 89 + */ 15 90 }; 16 91 typedef struct Th_Vtab Th_Vtab; 92 + 17 93 18 94 /* 19 95 ** Opaque handle for interpeter. 20 96 */ 21 97 typedef struct Th_Interp Th_Interp; 98 + 22 99 23 100 /* 24 -** Create and delete interpreters. 101 +** Creates a new interpreter instance using the given v-table. pVtab 102 +** must outlive the returned object, and pVtab->out.xDispose() will be 103 +** called when the interpreter is cleaned up. The optional "ob" API 104 +** swaps out Vtab::out instances, so pVtab->out might not be active 105 +** for the entire lifetime of the interpreter. 106 +** 107 +** Potential TODO: we "should probably" add a dispose() method to the 108 +** Th_Vtab interface. 25 109 */ 26 110 Th_Interp * Th_CreateInterp(Th_Vtab *pVtab); 27 -void Th_DeleteInterp(Th_Interp *); 111 + 112 +/* 113 +** Frees up all resources associated with interp then frees interp. 114 +*/ 115 +void Th_DeleteInterp(Th_Interp *interp); 28 116 29 117 /* 30 118 ** Evaluate an TH program in the stack frame identified by parameter 31 119 ** iFrame, according to the following rules: 32 120 ** 33 121 ** * If iFrame is 0, this means the current frame. 34 122 ** ................................................................................ 52 140 ** begins with "::", the lookup is in the top level (global) frame. 53 141 */ 54 142 int Th_GetVar(Th_Interp *, const char *, int); 55 143 int Th_SetVar(Th_Interp *, const char *, int, const char *, int); 56 144 int Th_LinkVar(Th_Interp *, const char *, int, int, const char *, int); 57 145 int Th_UnsetVar(Th_Interp *, const char *, int); 58 146 59 -typedef int (*Th_CommandProc)(Th_Interp *, void *, int, const char **, int *); 147 +/* 148 +** Typedef for Th interpreter callbacks, i.e. script-bound native C 149 +** functions. 150 +** 151 +** The interp argument is the interpreter running the function. pState 152 +** is arbitrary state which is passed to Th_CreateCommand(). arg 153 +** contains the number of arguments (argument #0 is the command's 154 +** name, in the same way that main()'s argv[0] is the binary's 155 +** name). argv is the list of arguments. argl is an array argc items 156 +** long which contains the length of each argument in the 157 +** list. e.g. argv[0] is argl[0] bytes long. 158 +*/ 159 +typedef int (*Th_CommandProc)(Th_Interp * interp, void * pState, int argc, const char ** argv, int * argl); 60 160 61 161 /* 62 -** Register new commands. 162 +** Registers a new command with interp. zName must be a NUL-terminated 163 +** name for the function. xProc is the native implementation of the 164 +** function. pContext is arbitrary data to pass as xProc()'s 2nd 165 +** argument. xDel is an optional finalizer which should be called when 166 +** interpreter is finalized. If xDel is not NULL then it is passed 167 +** (interp,pContext) when interp is finalized. 168 +** 169 +** Return TH_OK on success. 63 170 */ 64 171 int Th_CreateCommand( 65 172 Th_Interp *interp, 66 173 const char *zName, 67 - /* int (*xProc)(Th_Interp *, void *, int, const char **, int *), */ 68 174 Th_CommandProc xProc, 69 175 void *pContext, 70 176 void (*xDel)(Th_Interp *, void *) 71 177 ); 72 178 73 179 /* 74 180 ** Delete or rename commands. ................................................................................ 118 224 119 225 /* 120 226 ** Access the memory management functions associated with the specified 121 227 ** interpreter. 122 228 */ 123 229 void *Th_Malloc(Th_Interp *, int); 124 230 void Th_Free(Th_Interp *, void *); 125 - 231 +void *Th_Realloc(Th_Interp *, void *, int); 126 232 /* 127 233 ** Functions for handling TH lists. 128 234 */ 129 235 int Th_ListAppend(Th_Interp *, char **, int *, const char *, int); 130 236 int Th_SplitList(Th_Interp *, const char *, int, char ***, int **, int *); 131 237 132 238 int Th_StringAppend(Th_Interp *, char **, int *, const char *, int); ................................................................................ 134 240 /* 135 241 ** Functions for handling numbers and pointers. 136 242 */ 137 243 int Th_ToInt(Th_Interp *, const char *, int, int *); 138 244 int Th_ToDouble(Th_Interp *, const char *, int, double *); 139 245 int Th_SetResultInt(Th_Interp *, int); 140 246 int Th_SetResultDouble(Th_Interp *, double); 247 +int Th_TryInt(Th_Interp *, const char * zArg, int nArg, int * piOut); 248 +int Th_TryDouble(Th_Interp *, const char * zArg, int nArg, double * pfOut); 141 249 142 250 /* 143 251 ** Drop in replacements for the corresponding standard library functions. 144 252 */ 145 253 int th_strlen(const char *); 146 254 int th_isdigit(char); 147 255 int th_isspace(char); ................................................................................ 149 257 int th_isspecial(char); 150 258 char *th_strdup(Th_Interp *interp, const char *z, int n); 151 259 152 260 /* 153 261 ** Interfaces to register the language extensions. 154 262 */ 155 263 int th_register_language(Th_Interp *interp); /* th_lang.c */ 156 -int th_register_sqlite(Th_Interp *interp); /* th_sqlite.c */ 157 264 int th_register_vfs(Th_Interp *interp); /* th_vfs.c */ 158 265 int th_register_testvfs(Th_Interp *interp); /* th_testvfs.c */ 266 + 267 +/* 268 +** Registers the TCL extensions. Only available if FOSSIL_ENABLE_TCL 269 +** is enabled at compile-time. 270 +*/ 159 271 int th_register_tcl(Th_Interp *interp, void *pContext); /* th_tcl.c */ 272 + 273 +#ifdef TH_ENABLE_ARGV 274 +/* 275 +** Registers the "argv" API. See www/th1_argv.wiki. 276 +*/ 277 +int th_register_argv(Th_Interp *interp); /* th_main.c */ 278 +#endif 279 + 280 +#ifdef TH_ENABLE_QUERY 281 +/* 282 +** Registers the "query" API. See www/th1_query.wiki. 283 +*/ 284 +int th_register_query(Th_Interp *interp); /* th_main.c */ 285 +#endif 286 +#ifdef TH_ENABLE_OB 287 +/* 288 +** Registers the "ob" API. See www/th1_ob.wiki. 289 +*/ 290 +int th_register_ob(Th_Interp * interp); /* th.c */ 291 +#endif 160 292 161 293 /* 162 294 ** General purpose hash table from th_lang.c. 163 295 */ 164 296 typedef struct Th_Hash Th_Hash; 165 297 typedef struct Th_HashEntry Th_HashEntry; 166 298 struct Th_HashEntry { ................................................................................ 173 305 void Th_HashDelete(Th_Interp *, Th_Hash *); 174 306 void Th_HashIterate(Th_Interp*,Th_Hash*,void (*x)(Th_HashEntry*, void*),void*); 175 307 Th_HashEntry *Th_HashFind(Th_Interp*, Th_Hash*, const char*, int, int); 176 308 177 309 /* 178 310 ** Useful functions from th_lang.c. 179 311 */ 312 + 313 +/* 314 +** Generic "wrong number of arguments" helper which sets the error 315 +** state of interp to the given message plus a generic prefix. 316 +*/ 180 317 int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg); 181 318 319 +/* 320 +** Works like Th_WrongNumArgs() but expects (zCmdName,zCmdLen) to be 321 +** the current command's (name,length), i.e. (argv[0],argl[0]). 322 +*/ 323 +int Th_WrongNumArgs2(Th_Interp *interp, const char *zCmdName, 324 + int zCmdLen, const char *zMsg); 325 + 182 326 typedef struct Th_SubCommand {char *zName; Th_CommandProc xProc;} Th_SubCommand; 183 327 int Th_CallSubCommand(Th_Interp*,void*,int,const char**,int*,Th_SubCommand*); 328 + 329 +/* 330 +** Works similarly to Th_CallSubCommand() but adjusts argc/argv/argl 331 +** by 1 before passing on the call to the subcommand. This allows them 332 +** to function the same whether they are called as top-level commands 333 +** or as sub-sub-commands. 334 +*/ 335 +int Th_CallSubCommand2(Th_Interp *interp, void *ctx, int argc, const char **argv, int *argl, Th_SubCommand *aSub); 336 + 337 +/* 338 +** Sends the given data through vTab->out.f() if vTab->out.enabled is 339 +** true, otherwise this is a no-op. Returns 0 or higher on success, * 340 +** a negative value if vTab->out.f is NULL. 341 +*/ 342 +int Th_Vtab_Output( Th_Vtab *vTab, char const * zData, int len ); 343 + 344 +/* 345 +** Sends the given output through pInterp's vtab's output 346 +** implementation. See Th_Vtab_OutputMethods() for the argument and 347 +** return value semantics. 348 +*/ 349 +int Th_Output( Th_Interp *pInterp, char const * zData, int len ); 350 + 351 +/* 352 +** Enables or disables output of the current Vtab API, depending on 353 +** whether flag is true (non-0) or false (0). Note that when output 354 +** buffering/stacking is enabled (e.g. via the "ob" API) this modifies 355 +** only the current output mechanism, and not any further down the 356 +** stack. 357 +*/ 358 +void Th_OutputEnable( Th_Interp *pInterp, char flag ); 359 + 360 +/* 361 +** Returns true if output is enabled for the current output mechanism 362 +** of pInterp, else false. See Th_OutputEnable(). 363 +*/ 364 +char Th_OutputEnabled( Th_Interp *pInterp ); 365 + 366 + 367 + 368 +/* 369 +** A Th_Output_f() implementation which sends its output to either 370 +** pState (which must be NULL or a (FILE*)) or stdout (if pState is 371 +** NULL). 372 +*/ 373 +int Th_Output_f_FILE( char const * zData, int len, void * pState ); 374 + 375 +/* 376 +** A Th_Vtab_OutputMethods::xDispose() impl for FILE handles. If pState is not 377 +** one of the standard streams (stdin, stdout, stderr) then it is 378 +** fclose()d by this call. 379 +*/ 380 +void Th_Output_dispose_FILE( void * pState ); 381 + 382 +/* 383 +** A helper type for holding lists of function registration information. 384 +** For use with Th_RegisterCommands(). 385 +*/ 386 +struct Th_Command_Reg { 387 + const char *zName; /* Function name. */ 388 + Th_CommandProc xProc; /* Callback function */ 389 + void *pContext; /* Arbitrary data for the callback. */ 390 +}; 391 +typedef struct Th_Command_Reg Th_Command_Reg; 392 + 393 +/* 394 +** Th_Render_Flags_XXX are flags for Th_Render(). 395 +*/ 396 +/* makeheaders cannot do enums: enum Th_Render_Flags {...};*/ 397 +/* 398 +** Default flags ("compatibility mode"). 399 +*/ 400 +#define Th_Render_Flags_DEFAULT 0 401 +/* 402 +** If set, Th_Render() will not process $var and $<var> 403 +** variable references outside of TH1 blocks. 404 +*/ 405 +#define Th_Render_Flags_NO_DOLLAR_DEREF (1 << 1) 406 + 407 +/* 408 +** Runs the given th1 program through Fossil's th1 interpreter. Flags 409 +** may contain a bitmask made up of any of the Th_Render_Flags_XXX 410 +** values. 411 +*/ 412 +int Th_Render(const char *zTh1Program, int Th_Render_Flags); 413 + 414 +/* 415 +** Adds a piece of memory to the given interpreter, such that: 416 +** 417 +** a) it will be cleaned up when the interpreter is destroyed, by 418 +** calling finalizer(interp, pData). The finalizer may be NULL. 419 +** Cleanup happens in an unspecified/unpredictable order. 420 +** 421 +** b) it can be fetched via Th_GetData(). 422 +** 423 +** If a given key is added more than once then any previous 424 +** entry is cleaned up before adding it. 425 +** 426 +** Returns 0 on success, non-0 on allocation error. 427 +*/ 428 +int Th_SetData( Th_Interp * interp, char const * key, 429 + void * pData, 430 + void (*finalizer)( Th_Interp *, void * ) ); 431 + 432 +/* 433 +** Fetches data added via Th_SetData(), or NULL if no data 434 +** has been associated with the given key. 435 +*/ 436 +void * Th_GetData( Th_Interp * interp, char const * key ); 437 + 438 + 439 +/* 440 +** Registers a list of commands with the interpreter. pList must be a non-NULL 441 +** pointer to an array of Th_Command_Reg objects, the last one of which MUST 442 +** have a NULL zName field (that is the end-of-list marker). 443 +** Returns TH_OK on success, "something else" on error. 444 +*/ 445 +int Th_RegisterCommands( Th_Interp * interp, Th_Command_Reg const * pList ); 446 + 447 +#ifdef TH_ENABLE_OB 448 +/* 449 +** Output buffer stack manager for TH. Used/managed by the Th_ob_xxx() 450 +** functions. This class manages Th_Interp::pVtab->out for a specific 451 +** interpreter, swapping it in and out in order to redirect output 452 +** generated via Th_Output() to internal buffers. The buffers can be 453 +** pushed and popped from the stack, allowing clients to selectively 454 +** capture output for a given block of TH1 code. 455 +*/ 456 +struct Th_Ob_Manager { 457 + Blob ** aBuf; /* Stack of Blobs */ 458 + int nBuf; /* Number of blobs */ 459 + int cursor; /* Current level (-1=not active) */ 460 + Th_Interp * interp; /* The associated interpreter */ 461 + Th_Vtab_OutputMethods * aOutput 462 + /* Stack of output routines corresponding 463 + to the current buffering level. 464 + Has nBuf entries. 465 + */; 466 +}; 467 + 468 +/* 469 +** Manager of a stack of Th_Vtab_Output objects for output buffering. 470 +** It gets its name ("ob") from the similarly-named PHP functionality. 471 +** 472 +** See Th_Ob_GetManager(). 473 +** 474 +** Potential TODO: remove the Blob from the interface and replace it 475 +** with a Th_Output_f (or similar) which clients can pass in to have 476 +** the data transfered from Th_Ob_Manager to them. We would also need to 477 +** add APIs for clearing the buffer. 478 +*/ 479 +typedef struct Th_Ob_Manager Th_Ob_Manager; 480 + 481 +/* 482 +** Returns the ob manager for the given interpreter. The manager gets 483 +** installed by the th_register_ob(). In Fossil ob support is 484 +** installed automatically if it is available at built time. 485 +*/ 486 +Th_Ob_Manager * Th_Ob_GetManager(Th_Interp *ignored); 487 + 488 +/* 489 +** Returns the top-most Blob in pMan's stack, or NULL if buffering is 490 +** not active or if the current buffering level does not refer to a 491 +** blob. (Note: the latter will never currently be the case, but may 492 +** be if the API is expanded to offer other output direction options, 493 +** e.g. (ob start file /tmp/foo.out).) 494 +*/ 495 +Blob * Th_Ob_GetCurrentBuffer( Th_Ob_Manager * pMan ); 496 + 497 +/* 498 +** Pushes a new blob onto pMan's stack. On success returns TH_OK and 499 +** assigns *pOut (if pOut is not NULL) to the new blob (which is owned 500 +** by pMan). On error pOut is not modified and non-0 is returned. The 501 +** new blob can be cleaned up via Th_Ob_Pop() or Th_Ob_PopAndFree() 502 +** (please read both to understand the difference!). 503 +*/ 504 +int Th_Ob_Push( Th_Ob_Manager * pMan, Th_Vtab_OutputMethods const * pWriter, Blob ** pOut ); 505 + 506 +/* 507 +** Pops the top-most output buffer off the stack and returns 508 +** it. Returns NULL if there is no current buffer. When the last 509 +** buffer is popped, pMan's internals are cleaned up (but pMan is not 510 +** freed). 511 +** 512 +** The caller owns the returned object and must eventually clean it up 513 +** by first passing it to blob_reset() and then Th_Free() it. 514 +** 515 +** See also: Th_Ob_PopAndFree(). 516 +*/ 517 +Blob * Th_Ob_Pop( Th_Ob_Manager * pMan ); 518 + 519 +/* 520 +** Convenience form of Th_Ob_Pop() which pops and frees the 521 +** top-most buffer. Returns 0 on success, non-0 if there is no 522 +** stack to pop. Thus is can be used in a loop like: 523 +** 524 +** while( !Th_Ob_PopAndFree(theManager) ) {} 525 +*/ 526 +int Th_Ob_PopAndFree( Th_Ob_Manager * pMan ); 527 + 528 +#endif 529 +/* end TH_ENABLE_OB */
Changes to src/th_lang.c.
14 14 #include <string.h> 15 15 #include <assert.h> 16 16 17 17 int Th_WrongNumArgs(Th_Interp *interp, const char *zMsg){ 18 18 Th_ErrorMessage(interp, "wrong # args: should be \"", zMsg, -1); 19 19 return TH_ERROR; 20 20 } 21 + 22 +int Th_WrongNumArgs2(Th_Interp *interp, const char *zCmdName, 23 + int zCmdLen, const char *zMsg){ 24 + char * zBuf = 0; 25 + int nBuf = 0; 26 + Th_StringAppend(interp, &zBuf, &nBuf, zCmdName, zCmdLen); 27 + Th_StringAppend(interp, &zBuf, &nBuf, ": wrong # args: expecting: ", -1); 28 + Th_StringAppend(interp, &zBuf, &nBuf, zMsg, -1); 29 + Th_StringAppend(interp, &zBuf, &nBuf, "", 1); 30 + Th_ErrorMessage(interp, zBuf, NULL, 0); 31 + Th_Free(interp, zBuf); 32 + return TH_ERROR; 33 +} 21 34 22 35 /* 23 36 ** Syntax: 24 37 ** 25 38 ** catch script ?varname? 26 39 */ 27 40 static int catch_command( ................................................................................ 456 469 argl[2]; /* Space for copies of parameter names and default values */ 457 470 p = (ProcDefn *)Th_Malloc(interp, nByte); 458 471 459 472 /* If the last parameter in the parameter list is "args", then set the 460 473 ** ProcDefn.hasArgs flag. The "args" parameter does not require an 461 474 ** entry in the ProcDefn.azParam[] or ProcDefn.azDefault[] arrays. 462 475 */ 463 - if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){ 464 - p->hasArgs = 1; 465 - nParam--; 476 + if(nParam>0){ 477 + if( anParam[nParam-1]==4 && 0==memcmp(azParam[nParam-1], "args", 4) ){ 478 + p->hasArgs = 1; 479 + nParam--; 480 + } 466 481 } 467 482 468 483 p->nParam = nParam; 469 484 p->azParam = (char **)&p[1]; 470 485 p->anParam = (int *)&p->azParam[nParam]; 471 486 p->azDefault = (char **)&p->anParam[nParam]; 472 487 p->anDefault = (int *)&p->azDefault[nParam]; ................................................................................ 868 883 return aSub[i].xProc(interp, ctx, argc, argv, argl); 869 884 } 870 885 } 871 886 872 887 Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]); 873 888 return TH_ERROR; 874 889 } 890 + 891 +int Th_CallSubCommand2( 892 + Th_Interp *interp, 893 + void *ctx, 894 + int argc, 895 + const char **argv, 896 + int *argl, 897 + Th_SubCommand *aSub 898 +){ 899 + int i; 900 + for(i=0; aSub[i].zName; i++){ 901 + char const *zName = aSub[i].zName; 902 + if( th_strlen(zName)==argl[1] && 0==memcmp(zName, argv[1], argl[1]) ){ 903 + return aSub[i].xProc(interp, ctx, argc-1, argv+1, argl+1); 904 + } 905 + } 906 + Th_ErrorMessage(interp, "Expected sub-command, got:", argv[1], argl[1]); 907 + return TH_ERROR; 908 +} 909 + 875 910 876 911 /* 877 912 ** TH Syntax: 878 913 ** 879 914 ** string compare STR1 STR2 880 915 ** string first NEEDLE HAYSTACK ?STARTINDEX? 881 916 ** string is CLASS STRING ................................................................................ 1026 1061 } 1027 1062 1028 1063 /* 1029 1064 ** Register the built-in th1 language commands with interpreter interp. 1030 1065 ** Usually this is called soon after interpreter creation. 1031 1066 */ 1032 1067 int th_register_language(Th_Interp *interp){ 1068 + int rc; 1033 1069 /* Array of built-in commands. */ 1034 - struct _Command { 1035 - const char *zName; 1036 - Th_CommandProc xProc; 1037 - void *pContext; 1038 - } aCommand[] = { 1070 + struct Th_Command_Reg aCommand[] = { 1039 1071 {"catch", catch_command, 0}, 1040 1072 {"expr", expr_command, 0}, 1041 1073 {"for", for_command, 0}, 1042 1074 {"if", if_command, 0}, 1043 1075 {"info", info_command, 0}, 1044 1076 {"lindex", lindex_command, 0}, 1045 1077 {"list", list_command, 0}, ................................................................................ 1057 1089 {"return", return_command, 0}, 1058 1090 {"break", simple_command, (void *)TH_BREAK}, 1059 1091 {"continue", simple_command, (void *)TH_CONTINUE}, 1060 1092 {"error", simple_command, (void *)TH_ERROR}, 1061 1093 1062 1094 {0, 0, 0} 1063 1095 }; 1064 - int i; 1065 - 1066 - /* Add the language commands. */ 1067 - for(i=0; i<(sizeof(aCommand)/sizeof(aCommand[0])); i++){ 1068 - void *ctx; 1069 - if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; 1070 - ctx = aCommand[i].pContext; 1071 - Th_CreateCommand(interp, aCommand[i].zName, aCommand[i].xProc, ctx, 0); 1072 - } 1073 - 1074 - return TH_OK; 1096 + rc = Th_RegisterCommands(interp, aCommand); 1097 + return rc; 1075 1098 }
Changes to src/th_main.c.
17 17 ** 18 18 ** This file contains an interface between the TH scripting language 19 19 ** (an independent project) and fossil. 20 20 */ 21 21 #include "config.h" 22 22 #include "th_main.h" 23 23 24 +#ifdef TH_ENABLE_QUERY 25 +#ifndef INTERFACE 26 +#include "sqlite3.h" 27 +#endif 28 +#endif 29 + 24 30 /* 25 31 ** Global variable counting the number of outstanding calls to malloc() 26 32 ** made by the th1 implementation. This is used to catch memory leaks 27 33 ** in the interpreter. Obviously, it also means th1 is not threadsafe. 28 34 */ 29 35 static int nOutstandingMalloc = 0; 30 36 ................................................................................ 38 44 } 39 45 return p; 40 46 } 41 47 static void xFree(void *p){ 42 48 if( p ){ 43 49 nOutstandingMalloc--; 44 50 } 45 - free(p); 51 + fossil_free(p); 46 52 } 47 -static Th_Vtab vtab = { xMalloc, xFree }; 53 + 54 +/* 55 +** Default Th_Vtab::xRealloc() implementation. 56 +*/ 57 +static void *xRealloc(void * p, unsigned int n){ 58 + assert(n>=0 && "Invalid memory (re/de)allocation size."); 59 + if(0 == n){ 60 + xFree(p); 61 + return NULL; 62 + }else if(NULL == p){ 63 + return xMalloc(n); 64 + }else{ 65 + return fossil_realloc(p, n) 66 + /* In theory nOutstandingMalloc doesn't need to be updated here 67 + unless xRealloc() is sorely misused. 68 + */; 69 + } 70 +} 48 71 49 72 /* 50 73 ** Generate a TH1 trace message if debugging is enabled. 51 74 */ 52 75 void Th_Trace(const char *zFormat, ...){ 53 76 va_list ap; 54 77 va_start(ap, zFormat); ................................................................................ 55 78 blob_vappendf(&g.thLog, zFormat, ap); 56 79 va_end(ap); 57 80 } 58 81 59 82 60 83 /* 61 84 ** True if output is enabled. False if disabled. 85 +** 86 +** We "could" replace this with Th_OutputEnable() and friends, but 87 +** there is a functional difference: this particular flag prohibits 88 +** some extra escaping which would happen (but be discared, unused) if 89 +** relied solely on that API. Also, because that API only works on the 90 +** current Vtab_Output handler, relying soly on that handling would 91 +** introduce incompatible behaviour with the historical enable_output 92 +** command. 62 93 */ 63 94 static int enableOutput = 1; 64 95 65 96 /* 66 97 ** TH command: enable_output BOOLEAN 67 98 ** 68 99 ** Enable or disable the puts and hputs commands. ................................................................................ 71 102 Th_Interp *interp, 72 103 void *p, 73 104 int argc, 74 105 const char **argv, 75 106 int *argl 76 107 ){ 77 108 if( argc!=2 ){ 78 - return Th_WrongNumArgs(interp, "enable_output BOOLEAN"); 109 + return Th_WrongNumArgs2(interp, 110 + argv[0], argl[0], 111 + "BOOLEAN"); 112 + }else{ 113 + int rc = Th_ToInt(interp, argv[1], argl[1], &enableOutput); 114 + return rc; 79 115 } 80 - return Th_ToInt(interp, argv[1], argl[1], &enableOutput); 81 116 } 117 + 118 +/* 119 +** Th_Output_f() impl which sends all output to cgi_append_content(). 120 +*/ 121 +static int Th_Output_f_cgi_content( char const * zData, int nData, void * pState ){ 122 + cgi_append_content(zData, nData); 123 + return nData; 124 +} 125 + 82 126 83 127 /* 84 128 ** Send text to the appropriate output: Either to the console 85 129 ** or to the CGI reply buffer. 86 130 */ 87 -static void sendText(const char *z, int n, int encode){ 131 +static void sendText(Th_Interp *pInterp, const char *z, int n, int encode){ 132 + if(NULL == pInterp){ 133 + pInterp = g.interp; 134 + } 135 + assert( NULL != pInterp ); 88 136 if( enableOutput && n ){ 89 137 if( n<0 ) n = strlen(z); 90 138 if( encode ){ 91 139 z = htmlize(z, n); 92 140 n = strlen(z); 93 141 } 94 - if( g.cgiOutput ){ 95 - cgi_append_content(z, n); 96 - }else{ 97 - fwrite(z, 1, n, stdout); 98 - fflush(stdout); 99 - } 100 - if( encode ) free((char*)z); 142 + Th_Output( pInterp, z, n ); 143 + if( encode ) fossil_free((char*)z); 101 144 } 102 145 } 103 146 147 +/* 148 +** Internal state for the putsCmd() function, allowing it to be used 149 +** as the basis for multiple implementations with slightly different 150 +** behaviours based on the context. An instance of this type must be 151 +** set as the Context parameter for any putsCmd()-based script command 152 +** binding. 153 +*/ 154 +struct PutsCmdData { 155 + char escapeHtml; /* If true, htmlize all output. */ 156 + char const * sep; /* Optional NUL-terminated separator to output 157 + between arguments. May be NULL. */ 158 + char const * eol; /* Optional NUL-terminated end-of-line separator, 159 + output after the final argument. May be NULL. */ 160 +}; 161 +typedef struct PutsCmdData PutsCmdData; 162 + 104 163 /* 105 164 ** TH command: puts STRING 106 165 ** TH command: html STRING 107 166 ** 108 -** Output STRING as HTML (html) or unchanged (puts). 167 +** Output STRING as HTML (html) or unchanged (puts). 168 +** 169 +** pConvert MUST be a (PutsCmdData [const]*). It is not modified by 170 +** this function. 109 171 */ 110 172 static int putsCmd( 111 173 Th_Interp *interp, 112 174 void *pConvert, 113 175 int argc, 114 176 const char **argv, 115 177 int *argl 116 178 ){ 117 - if( argc!=2 ){ 118 - return Th_WrongNumArgs(interp, "puts STRING"); 179 + PutsCmdData const * fmt = (PutsCmdData const *)pConvert; 180 + const int sepLen = fmt->sep ? strlen(fmt->sep) : 0; 181 + int i; 182 + if( argc<2 ){ 183 + return Th_WrongNumArgs2(interp, 184 + argv[0], argl[0], 185 + "STRING ...STRING_N"); 119 186 } 120 - sendText((char*)argv[1], argl[1], pConvert!=0); 187 + for( i = 1; i < argc; ++i ){ 188 + if(sepLen && (i>1)){ 189 + sendText(interp, fmt->sep, sepLen, 0); 190 + } 191 + sendText(interp, (char const*)argv[i], argl[i], fmt->escapeHtml); 192 + } 193 + if(fmt->eol){ 194 + sendText(interp, fmt->eol, strlen(fmt->eol), 0); 195 + } 121 196 return TH_OK; 122 197 } 123 198 124 199 /* 125 200 ** TH command: wiki STRING 126 201 ** 127 202 ** Render the input string as wiki. ................................................................................ 130 205 Th_Interp *interp, 131 206 void *p, 132 207 int argc, 133 208 const char **argv, 134 209 int *argl 135 210 ){ 136 211 if( argc!=2 ){ 137 - return Th_WrongNumArgs(interp, "wiki STRING"); 212 + return Th_WrongNumArgs2(interp, 213 + argv[0], argl[0], 214 + "STRING"); 138 215 } 139 216 if( enableOutput ){ 140 217 Blob src; 141 218 blob_init(&src, (char*)argv[1], argl[1]); 142 219 wiki_convert(&src, 0, WIKI_INLINE); 143 220 blob_reset(&src); 144 221 } ................................................................................ 156 233 void *p, 157 234 int argc, 158 235 const char **argv, 159 236 int *argl 160 237 ){ 161 238 char *zOut; 162 239 if( argc!=2 ){ 163 - return Th_WrongNumArgs(interp, "htmlize STRING"); 240 + return Th_WrongNumArgs2(interp, 241 + argv[0], argl[0], 242 + "STRING"); 164 243 } 165 244 zOut = htmlize((char*)argv[1], argl[1]); 166 245 Th_SetResult(interp, zOut, -1); 167 246 free(zOut); 168 247 return TH_OK; 169 248 } 170 249 250 +#if 0 251 +/* This is not yet needed, but something like it may become useful for 252 + custom page/command support, for rendering snippets/templates. */ 253 +/* 254 +** TH command: render STRING 255 +** 256 +** Render the input string as TH1. 257 +*/ 258 +static int renderCmd( 259 + Th_Interp *interp, 260 + void *p, 261 + int argc, 262 + const char **argv, 263 + int *argl 264 +){ 265 + if( argc<2 ){ 266 + return Th_WrongNumArgs2(interp, 267 + argv[0], argl[0], 268 + "STRING ?STRING...?"); 269 + }else{ 270 + Th_Ob_Manager * man = Th_Ob_GetManager(interp); 271 + Blob * b = NULL; 272 + Blob buf = empty_blob; 273 + int rc, i; 274 + /*FIXME: assert(NULL != man && man->interp==interp);*/ 275 + man->interp = interp; 276 + /* Combine all inputs into one buffer so that we can use that to 277 + embed TH1 tags across argument boundaries. 278 + 279 + FIX:E optimize away buf for the 1-arg case. 280 + */ 281 + for( i = 1; TH_OK==rc && i < argc; ++i ){ 282 + char const * str = argv[i]; 283 + blob_append( &buf, str, argl[i] ); 284 + /*rc = Th_Render( str, Th_Render_Flags_NO_DOLLAR_DEREF );*/ 285 + } 286 + rc = Th_Ob_Push( man, &b ); 287 + if(rc){ 288 + blob_reset( &buf ); 289 + return rc; 290 + } 291 + rc = Th_Render( buf.aData, Th_Render_Flags_DEFAULT ); 292 + blob_reset(&buf); 293 + b = Th_Ob_Pop( man ); 294 + if(TH_OK==rc){ 295 + Th_SetResult( interp, b->aData, b->nUsed ); 296 + } 297 + blob_reset( b ); 298 + Th_Free( interp, b ); 299 + return rc; 300 + } 301 +}/* renderCmd() */ 302 +#endif 303 + 171 304 /* 172 305 ** TH command: date 173 306 ** 174 307 ** Return a string which is the current time and date. If the 175 308 ** -local option is used, the date appears using localtime instead 176 309 ** of UTC. 177 310 */ ................................................................................ 203 336 void *p, 204 337 int argc, 205 338 const char **argv, 206 339 int *argl 207 340 ){ 208 341 int rc; 209 342 if( argc!=2 ){ 210 - return Th_WrongNumArgs(interp, "hascap STRING"); 343 + return Th_WrongNumArgs2(interp, 344 + argv[0], argl[0], 345 + "STRING"); 211 346 } 212 347 rc = login_has_capability((char*)argv[1],argl[1]); 213 348 if( g.thTrace ){ 214 349 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); 215 350 } 216 351 Th_SetResultInt(interp, rc); 217 352 return TH_OK; ................................................................................ 234 369 int argc, 235 370 const char **argv, 236 371 int *argl 237 372 ){ 238 373 int rc = 0; 239 374 char const * zArg; 240 375 if( argc!=2 ){ 241 - return Th_WrongNumArgs(interp, "hasfeature STRING"); 376 + return Th_WrongNumArgs2(interp, 377 + argv[0], argl[0], 378 + "STRING"); 242 379 } 243 380 zArg = (char const*)argv[1]; 244 381 if(NULL==zArg){ 245 382 /* placeholder for following ifdefs... */ 246 383 } 247 384 #if defined(FOSSIL_ENABLE_JSON) 248 385 else if( 0 == fossil_strnicmp( zArg, "json", 4 ) ){ ................................................................................ 278 415 int argc, 279 416 const char **argv, 280 417 int *argl 281 418 ){ 282 419 int rc = 0; 283 420 int i; 284 421 if( argc!=2 ){ 285 - return Th_WrongNumArgs(interp, "anycap STRING"); 422 + return Th_WrongNumArgs2(interp, 423 + argv[0], argl[0], 424 + "STRING"); 286 425 } 287 426 for(i=0; rc==0 && i<argl[1]; i++){ 288 427 rc = login_has_capability((char*)&argv[1][i],1); 289 428 } 290 429 if( g.thTrace ){ 291 430 Th_Trace("[hascap %#h] => %d<br />\n", argl[1], argv[1], rc); 292 431 } ................................................................................ 308 447 Th_Interp *interp, 309 448 void *p, 310 449 int argc, 311 450 const char **argv, 312 451 int *argl 313 452 ){ 314 453 if( argc!=4 ){ 315 - return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES"); 454 + return Th_WrongNumArgs2(interp, 455 + argv[0], argl[0], 456 + "NAME TEXT-LIST NUMLINES"); 316 457 } 317 458 if( enableOutput ){ 318 459 int height; 319 460 Blob name; 320 461 int nValue; 321 462 const char *zValue; 322 463 char *z, *zH; ................................................................................ 327 468 328 469 if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR; 329 470 Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem); 330 471 blob_init(&name, (char*)argv[1], argl[1]); 331 472 zValue = Th_Fetch(blob_str(&name), &nValue); 332 473 z = mprintf("<select name=\"%z\" size=\"%d\">", 333 474 htmlize(blob_buffer(&name), blob_size(&name)), height); 334 - sendText(z, -1, 0); 475 + sendText(interp, z, -1, 0); 335 476 free(z); 336 477 blob_reset(&name); 337 478 for(i=0; i<nElem; i++){ 338 479 zH = htmlize((char*)azElem[i], aszElem[i]); 339 480 if( zValue && aszElem[i]==nValue 340 481 && memcmp(zValue, azElem[i], nValue)==0 ){ 341 482 z = mprintf("<option value=\"%s\" selected=\"selected\">%s</option>", 342 483 zH, zH); 343 484 }else{ 344 485 z = mprintf("<option value=\"%s\">%s</option>", zH, zH); 345 486 } 346 487 free(zH); 347 - sendText(z, -1, 0); 488 + sendText(interp, z, -1, 0); 348 489 free(z); 349 490 } 350 - sendText("</select>", -1, 0); 491 + sendText(interp, "</select>", -1, 0); 351 492 Th_Free(interp, azElem); 352 493 } 353 494 return TH_OK; 354 495 } 355 496 356 497 /* 357 498 ** TH1 command: linecount STRING MAX MIN ................................................................................ 366 507 const char **argv, 367 508 int *argl 368 509 ){ 369 510 const char *z; 370 511 int size, n, i; 371 512 int iMin, iMax; 372 513 if( argc!=4 ){ 373 - return Th_WrongNumArgs(interp, "linecount STRING MAX MIN"); 514 + return Th_WrongNumArgs2(interp, 515 + argv[0], argl[0], 516 + "STRING MAX MIN"); 374 517 } 375 518 if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR; 376 519 if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR; 377 520 z = argv[1]; 378 521 size = argl[1]; 379 522 for(n=1, i=0; i<size; i++){ 380 523 if( z[i]=='\n' ){ ................................................................................ 401 544 int argc, 402 545 const char **argv, 403 546 int *argl 404 547 ){ 405 548 int openRepository; 406 549 407 550 if( argc!=1 && argc!=2 ){ 408 - return Th_WrongNumArgs(interp, "repository ?BOOLEAN?"); 551 + return Th_WrongNumArgs2(interp, 552 + argv[0], argl[0], 553 + "?BOOLEAN?"); 409 554 } 410 555 if( argc==2 ){ 411 556 if( Th_ToInt(interp, argv[1], argl[1], &openRepository) ){ 412 557 return TH_ERROR; 413 558 } 414 559 if( openRepository ) db_find_and_open_repository(OPEN_OK_NOT_FOUND, 0); 415 560 } 416 561 Th_SetResult(interp, g.zRepositoryName, -1); 417 562 return TH_OK; 418 563 } 419 564 565 + 566 +#ifdef TH_ENABLE_ARGV 567 +/* 568 +** TH command: 569 +** 570 +** argv len 571 +** 572 +** Returns the number of command-line arguments. 573 +*/ 574 +static int argvArgcCmd( 575 + Th_Interp *interp, 576 + void *p, 577 + int argc, 578 + const char **argv, 579 + int *argl 580 +){ 581 + Th_SetResultInt( interp, g.argc ); 582 + return TH_OK; 583 +} 584 + 585 + 586 + 587 +/* 588 +** TH command: 589 +** 590 +** argv at Index 591 +** 592 +** Returns the raw argument at the given index, throwing if 593 +** out of bounds. 594 +*/ 595 +static int argvGetAtCmd( 596 + Th_Interp *interp, 597 + void *p, 598 + int argc, 599 + const char **argv, 600 + int *argl 601 +){ 602 + char const * zVal; 603 + int pos = 0; 604 + if( argc != 2 ){ 605 + return Th_WrongNumArgs2(interp, 606 + argv[0], argl[0], 607 + "Index"); 608 + } 609 + if( TH_OK != Th_ToInt(interp, argv[1], argl[1], &pos) ){ 610 + return TH_ERROR; 611 + } 612 + if( pos < 0 || pos >= g.argc ){ 613 + Th_ErrorMessage(interp, "Argument out of range:", argv[1], argl[1]); 614 + return TH_ERROR; 615 + } 616 + if( 0 == pos ){/*special case*/ 617 + zVal = fossil_nameofexe(); 618 + }else{ 619 + zVal = (pos>0 && pos<g.argc) ? g.argv[pos] : 0; 620 + } 621 + Th_SetResult( interp, zVal, zVal ? strlen(zVal) : 0 ); 622 + return TH_OK; 623 +} 624 + 625 + 626 +/* 627 +** TH command: 628 +** 629 +** argv getstr longName ??shortName? ?defaultValue?? 630 +** 631 +** Functions more or less like Fossil's find_option(). 632 +** If the given argument is found then its value is returned, 633 +** else defaultValue is returned. If that is not set 634 +** and the option is not found, an error is thrown. 635 +** If defaultValue is provided, shortName must also be provided 636 +** but it may be empty. For example: 637 +** 638 +** set foo [argv getstr foo "" "hi, world"] 639 +** 640 +** ACHTUNG: find_option() removes any entries it finds from 641 +** g.argv, such that future calls to find_option() will not 642 +** find the same option. 643 +*/ 644 +static int argvFindOptionStringCmd( 645 + Th_Interp *interp, 646 + void *p, 647 + int argc, 648 + const char **argv, 649 + int *argl 650 +){ 651 + enum { BufLen = 100 }; 652 + char zLong[BufLen] = {0}; 653 + char zShort[BufLen] = {0}; 654 + char aBuf[BufLen] = {0}; 655 + int hasArg; 656 + char const * zVal = NULL; 657 + char const * zDefault = NULL; 658 + int check; 659 + if( 1 < argc ){ 660 + assert( argl[1] < BufLen ); 661 + check = snprintf( zLong, BufLen, "%s", argv[1] ); 662 + assert( check <= BufLen ); 663 + } 664 + if( (2 < argc) && (0 < argl[2]) ){ 665 + assert( argl[2] < BufLen ); 666 + check = snprintf( zShort, BufLen, "%s", argv[2] ); 667 + assert( check <= BufLen ); 668 + } 669 + if( 3 < argc){ 670 + zDefault = argv[3]; 671 + } 672 + 673 + if(0 == zLong[0]){ 674 + return Th_WrongNumArgs2(interp, 675 + argv[0], argl[0], 676 + "longName ?shortName? ?defaultVal?"); 677 + } 678 + if(g.cgiOutput){ 679 + zVal = cgi_parameter( zLong, NULL ); 680 + if( !zVal && zShort[0] ){ 681 + zVal = cgi_parameter( zShort, NULL ); 682 + } 683 + }else{ 684 + zVal = find_option( zLong, zShort[0] ? zShort : NULL, 1 ); 685 + } 686 + if(!zVal){ 687 + zVal = zDefault; 688 + if(!zVal){ 689 + Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1); 690 + return TH_ERROR; 691 + } 692 + } 693 + Th_SetResult( interp, zVal, zVal ? strlen(zVal) : 0 ); 694 + return TH_OK; 695 +} 696 + 697 +/* 698 +** TH command: 699 +** 700 +** argv getbool longName ??shortName? ?defaultValue?? 701 +** 702 +** Works just like argv getstr but treats any empty value or one 703 +** starting with the digit '0' as a boolean false. 704 +** 705 +** Returns the result as an integer 0 (false) or 1 (true). 706 +*/ 707 +static int argvFindOptionBoolCmd( 708 + Th_Interp *interp, 709 + void *p, 710 + int argc, 711 + const char **argv, 712 + int *argl 713 +){ 714 + /* FIXME: refactor to re-use the code from getstr */ 715 + enum { BufLen = 100 }; 716 + char zLong[BufLen] = {0}; 717 + char zShort[BufLen] = {0}; 718 + char aBuf[BufLen] = {0}; 719 + int hasArg; 720 + char const * zVal = NULL; 721 + char const * zDefault = NULL; 722 + int val; 723 + int rc; 724 + int check; 725 + if( 1 < argc ){ 726 + assert( argl[1] < BufLen ); 727 + check = snprintf( zLong, BufLen, "%s", argv[1] ); 728 + assert( check <= BufLen ); 729 + } 730 + if( (2 < argc) && (0 < argl[2]) ){ 731 + assert( argl[2] < BufLen ); 732 + check = snprintf( zShort, BufLen, "%s", argv[2] ); 733 + assert( check <= BufLen ); 734 + } 735 + if( 3 < argc){ 736 + zDefault = argv[3]; 737 + } 738 + 739 + if(0 == zLong[0]){ 740 + return Th_WrongNumArgs2(interp, 741 + argv[0], argl[0], 742 + "longName ?shortName? ?defaultVal?"); 743 + } 744 + if(g.cgiOutput){ 745 + zVal = cgi_parameter( zLong, NULL ); 746 + if( !zVal && zShort[0] ){ 747 + zVal = cgi_parameter( zShort, NULL ); 748 + } 749 + }else{ 750 + zVal = find_option( zLong, zShort[0] ? zShort : NULL, 0 ); 751 + } 752 + if(zVal && !*zVal){ 753 + zVal = "1"; 754 + } 755 + if(!zVal){ 756 + zVal = zDefault; 757 + if(!zVal){ 758 + Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1); 759 + return TH_ERROR; 760 + } 761 + } 762 + if( !*zVal ){ 763 + zVal = "0"; 764 + } 765 + zVal = (zVal && *zVal && (*zVal!='0')) ? zVal : 0; 766 + Th_SetResultInt( interp, zVal ? 1 : 0 ); 767 + return TH_OK; 768 +} 769 + 770 +/* 771 +** TH command: 772 +** 773 +** argv getint longName ?shortName? ?defaultValue? 774 +** 775 +** Works like argv getstr but returns the value as an integer 776 +** (throwing an error if the argument cannot be converted). 777 +*/ 778 +static int argvFindOptionIntCmd( 779 + Th_Interp *interp, 780 + void *p, 781 + int argc, 782 + const char **argv, 783 + int *argl 784 +){ 785 + /* FIXME: refactor to re-use the code from getstr */ 786 + enum { BufLen = 100 }; 787 + char zLong[BufLen] = {0}; 788 + char zShort[BufLen] = {0}; 789 + char aBuf[BufLen] = {0}; 790 + int hasArg; 791 + char const * zVal = NULL; 792 + char const * zDefault = NULL; 793 + int val = 0; 794 + int check; 795 + if( 1 < argc ){ 796 + assert( argl[1] < BufLen ); 797 + check = snprintf( zLong, BufLen, "%s", argv[1] ); 798 + assert( check <= BufLen ); 799 + } 800 + if( (2 < argc) && (0 < argl[2]) ){ 801 + assert( argl[2] < BufLen ); 802 + check = snprintf( zShort, BufLen, "%s", argv[2] ); 803 + assert( check <= BufLen ); 804 + } 805 + if( 3 < argc){ 806 + zDefault = argv[3]; 807 + } 808 + 809 + if(0 == zLong[0]){ 810 + return Th_WrongNumArgs2(interp, 811 + argv[0], argl[0], 812 + "longName ?shortName? ?defaultVal?"); 813 + } 814 + if(g.cgiOutput){ 815 + zVal = cgi_parameter( zLong, NULL ); 816 + if( !zVal && zShort[0] ){ 817 + zVal = cgi_parameter( zShort, NULL ); 818 + } 819 + }else{ 820 + zVal = find_option( zLong, zShort[0] ? zShort : NULL, 1 ); 821 + } 822 + if(!zVal){ 823 + zVal = zDefault; 824 + if(!zVal){ 825 + Th_ErrorMessage(interp, "Option not found and no default provided:", zLong, -1); 826 + return TH_ERROR; 827 + } 828 + } 829 + Th_ToInt(interp, zVal, strlen(zVal), &val); 830 + Th_SetResultInt( interp, val ); 831 + return TH_OK; 832 +} 833 + 834 +/* 835 +** TH command: 836 +** 837 +** argv subcommand 838 +** 839 +** This is the top-level dispatching function. 840 +*/ 841 +static int argvTopLevelCmd( 842 + Th_Interp *interp, 843 + void *ctx, 844 + int argc, 845 + const char **argv, 846 + int *argl 847 +){ 848 + static Th_SubCommand aSub[] = { 849 + {"len", argvArgcCmd}, 850 + {"at", argvGetAtCmd}, 851 + {"getstr", argvFindOptionStringCmd}, 852 + {"string", argvFindOptionStringCmd}, 853 + {"getbool", argvFindOptionBoolCmd}, 854 + {"bool", argvFindOptionBoolCmd}, 855 + {"getint", argvFindOptionIntCmd}, 856 + {"int", argvFindOptionIntCmd}, 857 + {0, 0} 858 + }; 859 + Th_CallSubCommand2( interp, ctx, argc, argv, argl, aSub ); 860 +} 861 + 862 +int th_register_argv(Th_Interp *interp){ 863 + static Th_Command_Reg aCommand[] = { 864 + {"argv", argvTopLevelCmd, 0 }, 865 + {0, 0, 0} 866 + }; 867 + Th_RegisterCommands( interp, aCommand ); 868 +} 869 + 870 +#endif 871 +/* end TH_ENABLE_ARGV */ 872 + 873 +#ifdef TH_ENABLE_QUERY 874 + 875 +/* 876 +** Adds the given prepared statement to the interpreter. Returns the 877 +** statement's opaque identifier (a positive value). Ownerships of 878 +** pStmt is transfered to interp and it must be cleaned up by the 879 +** client by calling Th_query_FinalizeStmt(), passing it the value returned 880 +** by this function. 881 +** 882 +** If interp is destroyed before all statements are finalized, 883 +** it will finalize them but may emit a warning message. 884 +*/ 885 +static int Th_query_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt); 886 + 887 + 888 +/* 889 +** Internal state for the "query" API. 890 +*/ 891 +struct Th_Query { 892 + sqlite3_stmt ** aStmt; /* Array of statement handles. */ 893 + int nStmt; /* number of entries in aStmt. */ 894 + int colCmdIndex; /* column index argument. Set by some top-level dispatchers 895 + for their subcommands. 896 + */ 897 +}; 898 +/* 899 +** Internal key for use with Th_Data_Add(). 900 +*/ 901 +#define Th_Query_KEY "Th_Query" 902 +typedef struct Th_Query Th_Query; 903 + 904 +/* 905 +** Returns the Th_Query object associated with the given interpreter, 906 +** or 0 if there is not one. 907 +*/ 908 +static Th_Query * Th_query_manager( Th_Interp * interp ){ 909 + void * p = Th_GetData( interp, Th_Query_KEY ); 910 + return p ? (Th_Query*)p : NULL; 911 +} 912 + 913 +static int Th_query_AddStmt(Th_Interp *interp, sqlite3_stmt * pStmt){ 914 + Th_Query * sq = Th_query_manager(interp); 915 + int i, x; 916 + sqlite3_stmt * s; 917 + sqlite3_stmt ** list = sq->aStmt; 918 + for( i = 0; i < sq->nStmt; ++i ){ 919 + s = list[i]; 920 + if(NULL==s){ 921 + list[i] = pStmt; 922 + return i+1; 923 + } 924 + } 925 + x = (sq->nStmt + 1) * 2; 926 + list = (sqlite3_stmt**)fossil_realloc( list, sizeof(sqlite3_stmt*)*x ); 927 + for( i = sq->nStmt; i < x; ++i ){ 928 + list[i] = NULL; 929 + } 930 + list[sq->nStmt] = pStmt; 931 + x = sq->nStmt; 932 + sq->nStmt = i; 933 + sq->aStmt = list; 934 + return x + 1; 935 +} 936 + 937 + 938 +/* 939 +** Expects stmtId to be a statement identifier returned by 940 +** Th_query_AddStmt(). On success, finalizes the statement and returns 0. 941 +** On error (statement not found) non-0 is returned. After this 942 +** call, some subsequent call to Th_query_AddStmt() may return the 943 +** same statement ID. 944 +*/ 945 +static int Th_query_FinalizeStmt(Th_Interp *interp, int stmtId){ 946 + Th_Query * sq = Th_query_manager(interp); 947 + sqlite3_stmt * st; 948 + int rc = 0; 949 + assert( stmtId>0 && stmtId<=sq->nStmt ); 950 + st = sq->aStmt[stmtId-1]; 951 + if(NULL != st){ 952 + sq->aStmt[stmtId-1] = NULL; 953 + sqlite3_finalize(st); 954 + return 0; 955 + }else{ 956 + return 1; 957 + } 958 +} 959 + 960 +/* 961 +** Works like Th_query_FinalizeStmt() but takes a statement pointer, which 962 +** must have been Th_query_AddStmt()'d to the given interpreter. 963 +*/ 964 +static int Th_query_FinalizeStmt2(Th_Interp *interp, sqlite3_stmt * pSt){ 965 + Th_Query * sq = Th_query_manager(interp); 966 + int i = 0; 967 + sqlite3_stmt * st = NULL; 968 + int rc = 0; 969 + for( ; i < sq->nStmt; ++i ){ 970 + st = sq->aStmt[i]; 971 + if(st == pSt) break; 972 + } 973 + if( st == pSt ){ 974 + assert( i>=0 && i<sq->nStmt ); 975 + sq->aStmt[i] = NULL; 976 + sqlite3_finalize(st); 977 + return 0; 978 + }else{ 979 + return 1; 980 + } 981 +} 982 + 983 + 984 +/* 985 +** Fetches the statement with the given ID, as returned by 986 +** Th_query_AddStmt(). Returns NULL if stmtId does not refer (or no longer 987 +** refers) to a statement added via Th_query_AddStmt(). 988 +*/ 989 +static sqlite3_stmt * Th_query_GetStmt(Th_Interp *interp, int stmtId){ 990 + Th_Query * sq = Th_query_manager(interp); 991 + return (!sq || (stmtId<1) || (stmtId > sq->nStmt)) 992 + ? NULL 993 + : sq->aStmt[stmtId-1]; 994 +} 995 + 996 + 997 +/* 998 +** Th_GCEntry finalizer which requires that p be a (Th_Query*). 999 +*/ 1000 +static void finalizerSqlite( Th_Interp * interp, void * p ){ 1001 + Th_Query * sq = (Th_Query *)p; 1002 + int i; 1003 + sqlite3_stmt * st = NULL; 1004 + if(!sq) { 1005 + fossil_warning("Got a finalizer call for a NULL Th_Query."); 1006 + return; 1007 + } 1008 + for( i = 0; i < sq->nStmt; ++i ){ 1009 + st = sq->aStmt[i]; 1010 + if(NULL != st){ 1011 + fossil_warning("Auto-finalizing unfinalized " 1012 + "statement id #%d: %s", 1013 + i+1, sqlite3_sql(st)); 1014 + Th_query_FinalizeStmt( interp, i+1 ); 1015 + } 1016 + } 1017 + Th_Free(interp, sq->aStmt); 1018 + Th_Free(interp, sq); 1019 +} 1020 + 1021 + 1022 +/* 1023 +** TH command: 1024 +** 1025 +** query prepare SQL 1026 +** 1027 +** Returns an opaque statement identifier. 1028 +*/ 1029 +static int queryPrepareCmd( 1030 + Th_Interp *interp, 1031 + void *p, 1032 + int argc, 1033 + const char **argv, 1034 + int *argl 1035 +){ 1036 + char const * zSql; 1037 + sqlite3_stmt * pStmt = NULL; 1038 + int rc; 1039 + char const * errMsg = NULL; 1040 + if( argc!=2 ){ 1041 + return Th_WrongNumArgs2(interp, 1042 + argv[0], argl[0], 1043 + "STRING"); 1044 + } 1045 + zSql = argv[1]; 1046 + rc = sqlite3_prepare( g.db, zSql, strlen(zSql), &pStmt, NULL ); 1047 + if(SQLITE_OK==rc){ 1048 + if(sqlite3_column_count( pStmt ) < 1){ 1049 + errMsg = "Only SELECT-like queries are supported."; 1050 + rc = SQLITE_ERROR; 1051 + sqlite3_finalize( pStmt ); 1052 + pStmt = NULL; 1053 + } 1054 + }else{ 1055 + errMsg = sqlite3_errmsg( g.db ); 1056 + } 1057 + if(SQLITE_OK!=rc){ 1058 + assert(NULL != errMsg); 1059 + assert(NULL == pStmt); 1060 + Th_ErrorMessage(interp, "error preparing SQL:", errMsg, -1); 1061 + return TH_ERROR; 1062 + } 1063 + rc = Th_query_AddStmt( interp, pStmt ); 1064 + assert( rc >= 0 && "AddStmt failed."); 1065 + Th_SetResultInt( interp, rc ); 1066 + return TH_OK; 1067 +} 1068 + 1069 +/* 1070 +** Tries to convert arg, which must be argLen bytes long, to a 1071 +** statement handle id and, in turn, to a sqlite3_stmt. On success 1072 +** (the argument references a prepared statement) it returns the 1073 +** handle and stmtId (if not NULL) is assigned to the integer value of 1074 +** arg. On error NULL is returned and stmtId might be modified (if not 1075 +** NULL). If stmtId is unmodified after an error then it is not a 1076 +** number, else it is a number but does not reference an opened 1077 +** statement. 1078 +*/ 1079 +static sqlite3_stmt * queryStmtHandle(Th_Interp *interp, char const * arg, int argLen, int * stmtId ){ 1080 + int rc = 0; 1081 + sqlite3_stmt * pStmt = NULL; 1082 + if( 0 == Th_ToInt( interp, arg, argLen, &rc ) ){ 1083 + if(stmtId){ 1084 + *stmtId = rc; 1085 + } 1086 + pStmt = Th_query_GetStmt( interp, rc ); 1087 + if(NULL==pStmt){ 1088 + Th_ErrorMessage(interp, "no such statement handle:", arg, -1); 1089 + } 1090 + } 1091 + return pStmt; 1092 + 1093 +} 1094 + 1095 +/* 1096 +** TH command: 1097 +** 1098 +** query finalize stmtId 1099 +** query stmtId finalize 1100 +** 1101 +** sqlite3_finalize()s the given statement. 1102 +*/ 1103 +static int queryFinalizeCmd( 1104 + Th_Interp *interp, 1105 + void *p, 1106 + int argc, 1107 + const char **argv, 1108 + int *argl 1109 +){ 1110 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1111 + int requireArgc = pStmt ? 1 : 2; 1112 + char * zSql; 1113 + int stId = 0; 1114 + char const * arg; 1115 + int rc; 1116 + if( argc!=requireArgc ){ 1117 + return Th_WrongNumArgs2(interp, 1118 + argv[0], argl[0], 1119 + "StmtHandle"); 1120 + } 1121 + if(!pStmt){ 1122 + arg = argv[1]; 1123 + pStmt = queryStmtHandle(interp, arg, argl[1], &stId); 1124 + if(!pStmt){ 1125 + Th_ErrorMessage(interp, "Not a valid statement handle argument.", NULL, 0); 1126 + return TH_ERROR; 1127 + } 1128 + } 1129 + assert( NULL != pStmt ); 1130 + rc = Th_query_FinalizeStmt2( interp, pStmt ); 1131 + Th_SetResultInt( interp, rc ); 1132 + return TH_OK; 1133 +} 1134 + 1135 +/* 1136 +** Reports the current sqlite3_errmsg() via TH and returns TH_ERROR. 1137 +*/ 1138 +static int queryReportDbErr( Th_Interp * interp ){ 1139 + char const * msg = sqlite3_errmsg( g.db ); 1140 + Th_ErrorMessage(interp, "db error:", msg, -1); 1141 + return TH_ERROR; 1142 +} 1143 + 1144 +/* 1145 +** Internal helper for fetching statement handle and index parameters. 1146 +** The first 4 args should be the args passed to the TH1 callback. 1147 +** pStmt must be a pointer to a NULL pointer. pIndex may be NULL or 1148 +** a pointer to store the statement index argument in. If pIndex is 1149 +** NULL then argc is asserted to be at least 2, else it must be at 1150 +** least 3. 1151 +** 1152 +** On success it returns 0, sets *pStmt to the referenced statement 1153 +** handle, and pIndex (if not NULL) to the integer value of argv[2] 1154 +** argument. On error it reports the error via TH, returns non-0, and 1155 +** modifies neither pStmt nor pIndex. 1156 +*/ 1157 +static int queryStmtIndexArgs( 1158 + Th_Interp * interp, 1159 + int argc, 1160 + char const ** argv, 1161 + int *argl, 1162 + sqlite3_stmt ** pStmt, 1163 + int * pIndex ){ 1164 + int index = 0; 1165 + sqlite3_stmt * stmt; 1166 + if( !pIndex ){ 1167 + if(argc<2){ 1168 + return Th_WrongNumArgs2(interp, 1169 + argv[0], argl[0], 1170 + "StmtHandle"); 1171 + } 1172 + }else{ 1173 + if( argc<3 ){ 1174 + return Th_WrongNumArgs2(interp, 1175 + argv[0], argl[0], 1176 + "StmtHandle Index"); 1177 + } 1178 + if( 0 != Th_ToInt( interp, argv[2], argl[2], &index ) ){ 1179 + return TH_ERROR; 1180 + } 1181 + } 1182 + stmt = *pStmt ? *pStmt : queryStmtHandle(interp, argv[1], argl[1], NULL); 1183 + if( NULL == stmt ){ 1184 + return TH_ERROR; 1185 + }else{ 1186 + *pStmt = stmt; 1187 + if( pIndex ){ 1188 + *pIndex = index; 1189 + } 1190 + return 0; 1191 + } 1192 +} 1193 + 1194 +/* 1195 +** TH command: 1196 +** 1197 +** query step stmtId 1198 +** query stmtId step 1199 +** 1200 +** Steps the given statement handle. Returns 0 at the end of the set, 1201 +** a positive value if it fetches a row, and throws on error. 1202 +*/ 1203 +static int queryStepCmd( 1204 + Th_Interp *interp, 1205 + void *p, 1206 + int argc, 1207 + const char **argv, 1208 + int *argl 1209 +){ 1210 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1211 + int requireArgc = pStmt ? 1 : 2; 1212 + int rc = 0; 1213 + if( argc!=requireArgc ){ 1214 + return Th_WrongNumArgs2(interp, 1215 + argv[0], argl[0], 1216 + "StmtHandle"); 1217 + } 1218 + if(!pStmt && 0 != queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, NULL)){ 1219 + return TH_ERROR; 1220 + } 1221 + assert(NULL != pStmt); 1222 + rc = sqlite3_step( pStmt ); 1223 + switch(rc){ 1224 + case SQLITE_ROW: 1225 + rc = 1; 1226 + break; 1227 + case SQLITE_DONE: 1228 + rc = 0; 1229 + break; 1230 + default: 1231 + return queryReportDbErr( interp ); 1232 + } 1233 + Th_SetResultInt( interp, rc ); 1234 + return TH_OK; 1235 +} 1236 + 1237 +/* 1238 +** TH command: 1239 +** 1240 +** query StmtId reset 1241 +** query reset StmtId 1242 +** 1243 +** Equivalent to sqlite3_reset(). 1244 +*/ 1245 +static int queryResetCmd( 1246 + Th_Interp *interp, 1247 + void *p, 1248 + int argc, 1249 + const char **argv, 1250 + int *argl 1251 +){ 1252 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1253 + int const rc = sqlite3_reset(pStmt); 1254 + if(rc){ 1255 + Th_ErrorMessage(interp, "Reset of statement failed.", NULL, 0); 1256 + return TH_ERROR; 1257 + }else{ 1258 + return TH_OK; 1259 + } 1260 +} 1261 + 1262 + 1263 +/* 1264 +** TH command: 1265 +** 1266 +** query col string stmtId Index 1267 +** query stmtId col string Index 1268 +** query stmtId col Index string 1269 +** 1270 +** Returns the result column value at the given 0-based index. 1271 +*/ 1272 +static int queryColStringCmd( 1273 + Th_Interp *interp, 1274 + void *p, 1275 + int argc, 1276 + const char **argv, 1277 + int *argl 1278 +){ 1279 + Th_Query * sq = Th_query_manager(interp); 1280 + int index = sq->colCmdIndex; 1281 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1282 + int requireArgc = pStmt ? 2 : 3; 1283 + char const * val; 1284 + int valLen; 1285 + if( index >= 0 ) --requireArgc; 1286 + if( argc!=requireArgc ){ 1287 + return Th_WrongNumArgs2(interp, 1288 + argv[0], argl[0], 1289 + "StmtHandle Index"); 1290 + } 1291 + if(!pStmt){ 1292 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1293 + }else if(index<0){ 1294 + Th_ToInt(interp, argv[1], argl[1], &index); 1295 + } 1296 + if(index < 0){ 1297 + return TH_ERROR; 1298 + } 1299 + val = sqlite3_column_text( pStmt, index ); 1300 + valLen = val ? sqlite3_column_bytes( pStmt, index ) : 0; 1301 + Th_SetResult( interp, val, valLen ); 1302 + return TH_OK; 1303 +} 1304 + 1305 +/* 1306 +** TH command: 1307 +** 1308 +** query col int stmtId Index 1309 +** query stmtId col int Index 1310 +** query stmtId col Index int 1311 +** 1312 +** Returns the result column value at the given 0-based index. 1313 +*/ 1314 +static int queryColIntCmd( 1315 + Th_Interp *interp, 1316 + void *p, 1317 + int argc, 1318 + const char **argv, 1319 + int *argl 1320 +){ 1321 + Th_Query * sq = Th_query_manager(interp); 1322 + int index = sq->colCmdIndex; 1323 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1324 + int requireArgc = pStmt ? 2 : 3; 1325 + int rc = 0; 1326 + if( index >= 0 ) --requireArgc; 1327 + if( argc!=requireArgc ){ 1328 + return Th_WrongNumArgs2(interp, 1329 + argv[0], argl[0], 1330 + "StmtHandle Index"); 1331 + } 1332 + if(!pStmt){ 1333 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1334 + }else if(index<0){ 1335 + Th_ToInt(interp, argv[1], argl[1], &index); 1336 + } 1337 + if(index < 0){ 1338 + return TH_ERROR; 1339 + } 1340 + Th_SetResultInt( interp, sqlite3_column_int( pStmt, index ) ); 1341 + return TH_OK; 1342 +} 1343 + 1344 +/* 1345 +** TH command: 1346 +** 1347 +** query col double stmtId Index 1348 +** query stmtId col double Index 1349 +** query stmtId col Index double 1350 +** 1351 +** Returns the result column value at the given 0-based index. 1352 +*/ 1353 +static int queryColDoubleCmd( 1354 + Th_Interp *interp, 1355 + void *p, 1356 + int argc, 1357 + const char **argv, 1358 + int *argl 1359 +){ 1360 + Th_Query * sq = Th_query_manager(interp); 1361 + int index = sq->colCmdIndex; 1362 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1363 + int requireArgc = pStmt ? 2 : 3; 1364 + double rc = 0; 1365 + if( index >= 0 ) --requireArgc; 1366 + if( argc!=requireArgc ){ 1367 + return Th_WrongNumArgs2(interp, 1368 + argv[0], argl[0], 1369 + "StmtHandle Index"); 1370 + } 1371 + if(!pStmt){ 1372 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1373 + }else if(index<0){ 1374 + Th_ToInt(interp, argv[1], argl[1], &index); 1375 + } 1376 + if(index < 0){ 1377 + return TH_ERROR; 1378 + } 1379 + Th_SetResultDouble( interp, sqlite3_column_double( pStmt, index ) ); 1380 + return TH_OK; 1381 +} 1382 + 1383 +/* 1384 +** TH command: 1385 +** 1386 +** query col isnull stmtId Index 1387 +** query stmtId col isnull Index 1388 +** query stmtId col Index isnull 1389 +** 1390 +** Returns non-0 if the given 0-based result column index contains 1391 +** an SQL NULL value, else returns 0. 1392 +*/ 1393 +static int queryColIsNullCmd( 1394 + Th_Interp *interp, 1395 + void *p, 1396 + int argc, 1397 + const char **argv, 1398 + int *argl 1399 +){ 1400 + Th_Query * sq = Th_query_manager(interp); 1401 + int index = sq->colCmdIndex; 1402 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1403 + int requireArgc = pStmt ? 2 : 3; 1404 + if( index >= 0 ) --requireArgc; 1405 + double rc = 0; 1406 + if( argc!=requireArgc ){ 1407 + return Th_WrongNumArgs2(interp, 1408 + argv[0], argl[0], 1409 + "StmtHandle Index"); 1410 + } 1411 + if(!pStmt){ 1412 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1413 + }else if(index<0){ 1414 + Th_ToInt(interp, argv[1], argl[1], &index); 1415 + } 1416 + if(index < 0){ 1417 + return TH_ERROR; 1418 + } 1419 + Th_SetResultInt( interp, 1420 + SQLITE_NULL==sqlite3_column_type( pStmt, index ) 1421 + ? 1 : 0); 1422 + return TH_OK; 1423 +} 1424 + 1425 +/* 1426 +** TH command: 1427 +** 1428 +** query col type stmtId Index 1429 +** query stmtId col type Index 1430 +** query stmtId col Index type 1431 +** 1432 +** Returns the sqlite type identifier for the given 0-based result 1433 +** column index. The values are available in TH as $SQLITE_NULL, 1434 +** $SQLITE_INTEGER, etc. 1435 +*/ 1436 +static int queryColTypeCmd( 1437 + Th_Interp *interp, 1438 + void *p, 1439 + int argc, 1440 + const char **argv, 1441 + int *argl 1442 +){ 1443 + Th_Query * sq = Th_query_manager(interp); 1444 + int index = sq->colCmdIndex; 1445 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1446 + int requireArgc = pStmt ? 2 : 3; 1447 + if( index >= 0 ) --requireArgc; 1448 + double rc = 0; 1449 + if( argc!=requireArgc ){ 1450 + return Th_WrongNumArgs2(interp, 1451 + argv[0], argl[0], 1452 + "StmtHandle Index"); 1453 + } 1454 + if(!pStmt){ 1455 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1456 + }else if(index<0){ 1457 + Th_ToInt( interp, argv[1], argl[1], &index ); 1458 + } 1459 + if(index < 0){ 1460 + return TH_ERROR; 1461 + } 1462 + Th_SetResultInt( interp, sqlite3_column_type( pStmt, index ) ); 1463 + return TH_OK; 1464 +} 1465 + 1466 +/* 1467 +** TH command: 1468 +** 1469 +** query col count stmtId 1470 +** query stmtId col count 1471 +** 1472 +** Returns the number of result columns in the query. 1473 +*/ 1474 +static int queryColCountCmd( 1475 + Th_Interp *interp, 1476 + void *p, 1477 + int argc, 1478 + const char **argv, 1479 + int *argl 1480 +){ 1481 + int rc; 1482 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1483 + int requireArgc = pStmt ? 1 : 2; 1484 + if( argc!=requireArgc ){ 1485 + return Th_WrongNumArgs2(interp, 1486 + argv[0], argl[0], 1487 + "StmtHandle"); 1488 + } 1489 + if(!pStmt){ 1490 + pStmt = queryStmtHandle(interp, argv[1], argl[1], NULL); 1491 + if( NULL == pStmt ){ 1492 + return TH_ERROR; 1493 + } 1494 + } 1495 + rc = sqlite3_column_count( pStmt ); 1496 + Th_SetResultInt( interp, rc ); 1497 + return TH_OK; 1498 +} 1499 + 1500 +/* 1501 +** TH command: 1502 +** 1503 +** query col name stmtId Index 1504 +** query stmtId col name Index 1505 +** query stmtId col Index name 1506 +** 1507 +** Returns the result column name at the given 0-based index. 1508 +*/ 1509 +static int queryColNameCmd( 1510 + Th_Interp *interp, 1511 + void *p, 1512 + int argc, 1513 + const char **argv, 1514 + int *argl 1515 +){ 1516 + Th_Query * sq = Th_query_manager(interp); 1517 + int index = sq->colCmdIndex; 1518 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1519 + int requireArgc = pStmt ? 2 : 3; 1520 + char const * val; 1521 + int rc = 0; 1522 + if( index >= 0 ) --requireArgc; 1523 + if( argc!=requireArgc ){ 1524 + return Th_WrongNumArgs2(interp, 1525 + argv[0], argl[0], 1526 + "StmtHandle Index"); 1527 + } 1528 + if(!pStmt){ 1529 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1530 + }else if(index<0){ 1531 + Th_ToInt( interp, argv[1], argl[1], &index ); 1532 + } 1533 + if(index < 0){ 1534 + return TH_ERROR; 1535 + } 1536 + assert(NULL!=pStmt); 1537 + val = sqlite3_column_name( pStmt, index ); 1538 + if(NULL==val){ 1539 + Th_ErrorMessage(interp, "Column index out of bounds(?):", argv[2], -1); 1540 + return TH_ERROR; 1541 + }else{ 1542 + Th_SetResult( interp, val, strlen( val ) ); 1543 + return TH_OK; 1544 + } 1545 +} 1546 + 1547 +/* 1548 +** TH command: 1549 +** 1550 +** query col time stmtId Index format 1551 +** query stmtId col name Index format 1552 +** query stmtId col Index name format 1553 +** 1554 +** Returns the result column name at the given 0-based index. 1555 +*/ 1556 +static int queryColTimeCmd( 1557 + Th_Interp *interp, 1558 + void *ctx, 1559 + int argc, 1560 + const char **argv, 1561 + int *argl 1562 +){ 1563 + Th_Query * sq = Th_query_manager(interp); 1564 + int index = sq->colCmdIndex; 1565 + sqlite3_stmt * pStmt = (sqlite3_stmt*)ctx; 1566 + int minArgs = pStmt ? 3 : 4; 1567 + int argPos; 1568 + char const * val; 1569 + char * fval; 1570 + int i, rc = 0; 1571 + char const * fmt; 1572 + Blob sql = empty_blob; 1573 + if( index >= 0 ) --minArgs; 1574 + if( argc<minArgs ){ 1575 + return Th_WrongNumArgs2(interp, 1576 + argv[0], argl[0], 1577 + "StmtHandle Index Format"); 1578 + } 1579 + if(!pStmt){ 1580 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1581 + argPos = 3; 1582 + }else if(index<0){ 1583 + Th_ToInt( interp, argv[1], argl[1], &index ); 1584 + argPos = 2; 1585 + }else{ 1586 + argPos = 1; 1587 + } 1588 + if(index < 0){ 1589 + return TH_ERROR; 1590 + } 1591 + val = sqlite3_column_text( pStmt, index ); 1592 + fmt = argv[argPos++]; 1593 + assert(NULL!=pStmt); 1594 + blob_appendf(&sql,"SELECT strftime(%Q,%Q", 1595 + fmt, val); 1596 + if(argc>argPos){ 1597 + for(i = argPos; i < argc; ++i ){ 1598 + blob_appendf(&sql, ",%Q", argv[i]); 1599 + } 1600 + } 1601 + blob_append(&sql, ")", 1); 1602 + fval = db_text(NULL,"%s", sql.aData); 1603 + 1604 + blob_reset(&sql); 1605 + Th_SetResult( interp, fval, fval ? strlen(fval) : 0 ); 1606 + fossil_free(fval); 1607 + return 0; 1608 +} 1609 + 1610 +/* 1611 +** TH command: 1612 +** 1613 +** query strftime TimeVal ?Modifiers...? 1614 +** 1615 +** Acts as a proxy to sqlite3's strftime() SQL function. 1616 +*/ 1617 +static int queryStrftimeCmd( 1618 + Th_Interp *interp, 1619 + void *ctx, 1620 + int argc, 1621 + const char **argv, 1622 + int *argl 1623 +){ 1624 + char const * val; 1625 + char * fval; 1626 + int i, rc = 0; 1627 + int index = -1; 1628 + char const * fmt; 1629 + Blob sql = empty_blob; 1630 + if( argc<3 ){ 1631 + return Th_WrongNumArgs2(interp, 1632 + argv[0], argl[0], 1633 + "Format Value ?Modifiers...?"); 1634 + } 1635 + fmt = argv[1]; 1636 + val = argv[2]; 1637 + blob_appendf(&sql,"SELECT strftime(%Q,%Q", 1638 + fmt, val); 1639 + if(argc>3){ 1640 + for(i = 3; i < argc; ++i ){ 1641 + blob_appendf(&sql, ",%Q", argv[i]); 1642 + } 1643 + } 1644 + blob_append(&sql, ")", 1); 1645 + fval = db_text(NULL,"%s", sql.aData); 1646 + blob_reset(&sql); 1647 + Th_SetResult( interp, fval, fval ? strlen(fval) : 0 ); 1648 + fossil_free(fval); 1649 + return 0; 1650 +} 1651 + 1652 + 1653 +/* 1654 +** TH command: 1655 +** 1656 +** query bind null stmtId Index 1657 +** query stmtId bind null Index 1658 +** 1659 +** Binds a value to the given 1-based parameter index. 1660 +*/ 1661 +static int queryBindNullCmd( 1662 + Th_Interp *interp, 1663 + void *p, 1664 + int argc, 1665 + const char **argv, 1666 + int *argl 1667 +){ 1668 + Th_Query * sq = Th_query_manager(interp); 1669 + int index = sq->colCmdIndex; 1670 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1671 + int requireArgc = pStmt ? 2 : 3; 1672 + if( index > 0 ) --requireArgc; 1673 + int rc; 1674 + if( argc!=requireArgc ){ 1675 + return Th_WrongNumArgs2(interp, 1676 + argv[0], argl[0], 1677 + "StmtHandle Index"); 1678 + } 1679 + if(!pStmt){ 1680 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1681 + }else if(index<1){ 1682 + Th_ToInt( interp, argv[1], argl[1], &index ); 1683 + } 1684 + if(index < 1){ 1685 + return TH_ERROR; 1686 + } 1687 + rc = sqlite3_bind_null( pStmt, index ); 1688 + if(rc){ 1689 + return queryReportDbErr( interp ); 1690 + } 1691 + Th_SetResultInt( interp, 0 ); 1692 + return TH_OK; 1693 +} 1694 + 1695 + 1696 +/* 1697 +** TH command: 1698 +** 1699 +** query bind string stmtId Index Value 1700 +** query stmtId bind string Index Value 1701 +** 1702 +** Binds a value to the given 1-based parameter index. 1703 +*/ 1704 +static int queryBindStringCmd( 1705 + Th_Interp *interp, 1706 + void *p, 1707 + int argc, 1708 + const char **argv, 1709 + int *argl 1710 +){ 1711 + Th_Query * sq = Th_query_manager(interp); 1712 + int index = sq->colCmdIndex; 1713 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1714 + int requireArgc = pStmt ? 3 : 4; 1715 + int rc; 1716 + int argPos; 1717 + if( index > 0 ) --requireArgc; 1718 + if( argc!=requireArgc ){ 1719 + return Th_WrongNumArgs2(interp, 1720 + argv[0], argl[0], 1721 + "StmtHandle Index Value"); 1722 + } 1723 + if(!pStmt){ 1724 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1725 + argPos = 3; 1726 + }else if(index<1){ 1727 + Th_ToInt( interp, argv[1], argl[1], &index ); 1728 + argPos = 2; 1729 + }else{ 1730 + argPos = 1; 1731 + } 1732 + if(index < 1){ 1733 + return TH_ERROR; 1734 + } 1735 + rc = sqlite3_bind_text( pStmt, index, argv[argPos], argl[argPos], SQLITE_TRANSIENT ); 1736 + if(rc){ 1737 + return queryReportDbErr( interp ); 1738 + } 1739 + Th_SetResultInt( interp, 0 ); 1740 + return TH_OK; 1741 +} 1742 + 1743 +/* 1744 +** TH command: 1745 +** 1746 +** query bind int stmtId Index Value 1747 +** query stmtId bind int Index Value 1748 +** 1749 +** Binds a value to the given 1-based parameter index. 1750 +*/ 1751 +static int queryBindIntCmd( 1752 + Th_Interp *interp, 1753 + void *p, 1754 + int argc, 1755 + const char **argv, 1756 + int *argl 1757 +){ 1758 + Th_Query * sq = Th_query_manager(interp); 1759 + int index = sq->colCmdIndex; 1760 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1761 + int requireArgc = pStmt ? 3 : 4; 1762 + int rc; 1763 + int argPos; 1764 + int val; 1765 + if( index > 0 ) --requireArgc; 1766 + if( argc!=requireArgc ){ 1767 + return Th_WrongNumArgs2(interp, 1768 + argv[0], argl[0], 1769 + "StmtHandle Index Value"); 1770 + } 1771 + if(!pStmt){ 1772 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1773 + argPos = 3; 1774 + }else if(index<1){ 1775 + Th_ToInt( interp, argv[1], argl[1], &index ); 1776 + argPos = 2; 1777 + }else{ 1778 + argPos = 1; 1779 + } 1780 + if(index < 1){ 1781 + return TH_ERROR; 1782 + } 1783 + if( 0 != Th_ToInt( interp, argv[argPos], argl[argPos], &val ) ){ 1784 + return TH_ERROR; 1785 + } 1786 + 1787 + rc = sqlite3_bind_int( pStmt, index, val ); 1788 + if(rc){ 1789 + return queryReportDbErr( interp ); 1790 + } 1791 + Th_SetResultInt( interp, 0 ); 1792 + return TH_OK; 1793 +} 1794 + 1795 +/* 1796 +** TH command: 1797 +** 1798 +** query bind double stmtId Index Value 1799 +** query stmtId bind double Index Value 1800 +** 1801 +** Binds a value to the given 1-based parameter index. 1802 +*/ 1803 +static int queryBindDoubleCmd( 1804 + Th_Interp *interp, 1805 + void *p, 1806 + int argc, 1807 + const char **argv, 1808 + int *argl 1809 +){ 1810 + Th_Query * sq = Th_query_manager(interp); 1811 + int index = sq->colCmdIndex; 1812 + sqlite3_stmt * pStmt = (sqlite3_stmt*)p; 1813 + int requireArgc = pStmt ? 3 : 4; 1814 + int rc; 1815 + int argPos; 1816 + double val; 1817 + if( index > 0 ) --requireArgc; 1818 + if( argc!=requireArgc ){ 1819 + return Th_WrongNumArgs2(interp, 1820 + argv[0], argl[0], 1821 + "StmtHandle Index Value"); 1822 + } 1823 + if(!pStmt){ 1824 + queryStmtIndexArgs(interp, argc, argv, argl, &pStmt, &index); 1825 + argPos = 3; 1826 + }else if(index<1){ 1827 + Th_ToInt( interp, argv[1], argl[1], &index ); 1828 + argPos = 2; 1829 + }else{ 1830 + argPos = 1; 1831 + } 1832 + if(index < 1){ 1833 + return TH_ERROR; 1834 + } 1835 + if( 0 != Th_ToDouble( interp, argv[argPos], argl[argPos], &val ) ){ 1836 + return TH_ERROR; 1837 + } 1838 + 1839 + rc = sqlite3_bind_double( pStmt, index, val ); 1840 + if(rc){ 1841 + return queryReportDbErr( interp ); 1842 + } 1843 + Th_SetResultInt( interp, 0 ); 1844 + return TH_OK; 1845 +} 1846 + 1847 +/* 1848 +** TH command: 1849 +** 1850 +** bind subcommand StmtId... 1851 +** bind StmtId subcommand... 1852 +** 1853 +** This is the top-level dispatcher for the "bind" family of commands. 1854 +*/ 1855 +static int queryBindTopLevelCmd( 1856 + Th_Interp *interp, 1857 + void *ctx, 1858 + int argc, 1859 + const char **argv, 1860 + int *argl 1861 +){ 1862 + int colIndex = -1; 1863 + static Th_SubCommand aSub[] = { 1864 + {"int", queryBindIntCmd}, 1865 + {"double", queryBindDoubleCmd}, 1866 + {"null", queryBindNullCmd}, 1867 + {"string", queryBindStringCmd}, 1868 + {0, 0} 1869 + }; 1870 + Th_Query * sq = Th_query_manager(interp); 1871 + assert(NULL != sq); 1872 + if( 1 == argc ){ 1873 + Th_WrongNumArgs2( interp, argv[0], argl[0], 1874 + "subcommand: int|double|null|string"); 1875 + return TH_ERROR; 1876 + }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &colIndex) ){ 1877 + if(colIndex <0){ 1878 + Th_ErrorMessage( interp, "Invalid column index.", NULL, 0); 1879 + return TH_ERROR; 1880 + } 1881 + ++argv; 1882 + ++argl; 1883 + --argc; 1884 + } 1885 + sq->colCmdIndex = colIndex; 1886 + Th_CallSubCommand2( interp, ctx, argc, argv, argl, aSub ); 1887 + 1888 +} 1889 + 1890 +/* 1891 +** TH command: 1892 +** 1893 +** query col subcommand ... 1894 +** query StmtId col subcommand ... 1895 +** 1896 +** This is the top-level dispatcher for the col subcommands. 1897 +*/ 1898 +static int queryColTopLevelCmd( 1899 + Th_Interp *interp, 1900 + void *ctx, 1901 + int argc, 1902 + const char **argv, 1903 + int *argl 1904 +){ 1905 + int colIndex = -1; 1906 + static Th_SubCommand aSub[] = { 1907 + {"count", queryColCountCmd}, 1908 + {"is_null", queryColIsNullCmd}, 1909 + {"isnull", queryColIsNullCmd}, 1910 + {"name", queryColNameCmd}, 1911 + {"double", queryColDoubleCmd}, 1912 + {"int", queryColIntCmd}, 1913 + {"string", queryColStringCmd}, 1914 + {"time", queryColTimeCmd}, 1915 + {"type", queryColTypeCmd}, 1916 + {0, 0} 1917 + }; 1918 + static Th_SubCommand aSubWithIndex[] = { 1919 + /* 1920 + This subset is coded to accept the column index 1921 + either before the subcommand name or after it. 1922 + If called like (bind StmtId subcommand) then 1923 + only these commands will be checked. 1924 + */ 1925 + {"is_null", queryColIsNullCmd}, 1926 + {"isnull", queryColIsNullCmd}, 1927 + {"name", queryColNameCmd}, 1928 + {"double", queryColDoubleCmd}, 1929 + {"int", queryColIntCmd}, 1930 + {"string", queryColStringCmd}, 1931 + {"time", queryColTimeCmd}, 1932 + {"type", queryColTypeCmd}, 1933 + {0, 0} 1934 + }; 1935 + Th_Query * sq = Th_query_manager(interp); 1936 + assert(NULL != sq); 1937 + if( 1 == argc ){ 1938 + Th_WrongNumArgs2( interp, argv[0], argl[0], 1939 + "subcommand: " 1940 + "count|is_null|isnull|name|" 1941 + "double|int|string|time|type"); 1942 + return TH_ERROR; 1943 + }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &colIndex) ){ 1944 + if(colIndex <0){ 1945 + Th_ErrorMessage( interp, "Invalid column index.", NULL, 0); 1946 + return TH_ERROR; 1947 + } 1948 + ++argv; 1949 + ++argl; 1950 + --argc; 1951 + } 1952 + sq->colCmdIndex = colIndex; 1953 + Th_CallSubCommand2( interp, ctx, argc, argv, argl, 1954 + (colIndex<0) ? aSub : aSubWithIndex ); 1955 +} 1956 + 1957 + 1958 +/* 1959 +** TH command: 1960 +** 1961 +** query subcommand ... 1962 +** query StmtId subcommand ... 1963 +** 1964 +** This is the top-level dispatcher for the query subcommand. 1965 +*/ 1966 +static int queryTopLevelCmd( 1967 + Th_Interp *interp, 1968 + void *ctx, 1969 + int argc, 1970 + const char **argv, 1971 + int *argl 1972 +){ 1973 + int stmtId = 0; 1974 + sqlite3_stmt * pStmt = NULL; 1975 + static Th_SubCommand aSubAll[] = { 1976 + {"bind", queryBindTopLevelCmd}, 1977 + {"col", queryColTopLevelCmd}, 1978 + {"finalize", queryFinalizeCmd}, 1979 + {"prepare", queryPrepareCmd}, 1980 + {"reset", queryResetCmd}, 1981 + {"step", queryStepCmd}, 1982 + {"strftime", queryStrftimeCmd}, 1983 + {0, 0} 1984 + }; 1985 + static Th_SubCommand aSubWithStmt[] = { 1986 + /* This subset is coded to deal with being supplied a statement 1987 + via pStmt or via one of their args. When called like (query 1988 + StmtId ...) only these subcommands will be checked.*/ 1989 + {"bind", queryBindTopLevelCmd}, 1990 + {"col", queryColTopLevelCmd}, 1991 + {"step", queryStepCmd}, 1992 + {"finalize", queryFinalizeCmd}, 1993 + {"reset", queryResetCmd}, 1994 + {0, 0} 1995 + }; 1996 + 1997 + 1998 + assert( NULL != Th_query_manager(interp) ); 1999 + if( 1 == argc ){ 2000 + Th_WrongNumArgs2( interp, argv[0], argl[0], 2001 + "subcommand: bind|col|finalize|prepare|reset|step|strftime"); 2002 + return TH_ERROR; 2003 + }else if( 0 == Th_TryInt(interp,argv[1], argl[1], &stmtId) ){ 2004 + ++argv; 2005 + ++argl; 2006 + --argc; 2007 + pStmt = Th_query_GetStmt( interp, stmtId ); 2008 + } 2009 + 2010 + Th_CallSubCommand2( interp, pStmt, argc, argv, argl, 2011 + pStmt ? aSubWithStmt : aSubAll ); 2012 +} 2013 + 2014 +/* 2015 +** Registers the "query" API with the given interpreter. Returns TH_OK 2016 +** on success, TH_ERROR on error. 2017 +*/ 2018 +int th_register_query(Th_Interp *interp){ 2019 + enum { BufLen = 100 }; 2020 + char buf[BufLen]; 2021 + int i, l; 2022 +#define SET(K) l = snprintf(buf, BufLen, "%d", K); \ 2023 + Th_SetVar( interp, #K, strlen(#K), buf, l ); 2024 + SET(SQLITE_BLOB); 2025 + SET(SQLITE_FLOAT); 2026 + SET(SQLITE_INTEGER); 2027 + SET(SQLITE_NULL); 2028 + SET(SQLITE_TEXT); 2029 +#if 0 2030 + /* so far we don't need these in script code */ 2031 + SET(SQLITE_ERROR); 2032 + SET(SQLITE_DONE); 2033 + SET(SQLITE_OK); 2034 + SET(SQLITE_ROW); 2035 +#endif 2036 +#undef SET 2037 + int rc = TH_OK; 2038 + static Th_Command_Reg aCommand[] = { 2039 + {"query", queryTopLevelCmd, 0}, 2040 + {0, 0, 0} 2041 + }; 2042 + rc = Th_RegisterCommands( interp, aCommand ); 2043 + if(TH_OK==rc){ 2044 + Th_Query * sq = Th_Malloc(interp, sizeof(Th_Query)); 2045 + if(!sq){ 2046 + rc = TH_ERROR; 2047 + }else{ 2048 + assert( NULL == sq->aStmt ); 2049 + assert( 0 == sq->nStmt ); 2050 + Th_SetData( interp, Th_Query_KEY, sq, finalizerSqlite ); 2051 + assert( sq == Th_query_manager(interp) ); 2052 + } 2053 + } 2054 + return rc; 2055 +} 2056 + 2057 +#endif 2058 +/* end TH_ENABLE_QUERY */ 2059 + 420 2060 /* 421 2061 ** Make sure the interpreter has been initialized. Initialize it if 422 2062 ** it has not been already. 423 2063 ** 424 2064 ** The interpreter is stored in the g.interp global variable. 425 2065 */ 426 2066 void Th_FossilInit(void){ 427 - static struct _Command { 428 - const char *zName; 429 - Th_CommandProc xProc; 430 - void *pContext; 431 - } aCommand[] = { 2067 + /* The fossil-internal Th_Vtab instance. */ 2068 + static Th_Vtab vtab = { xRealloc, {/*out*/ 2069 + NULL /*write()*/, 2070 + NULL/*dispose()*/, 2071 + NULL/*pState*/, 2072 + 1/*enabled*/ 2073 + } 2074 + }; 2075 + 2076 + static PutsCmdData puts_Html = {0, 0, 0}; 2077 + static PutsCmdData puts_Normal = {1, 0, 0}; 2078 + static Th_Command_Reg aCommand[] = { 432 2079 {"anycap", anycapCmd, 0}, 433 2080 {"combobox", comboboxCmd, 0}, 2081 + {"date", dateCmd, 0}, 434 2082 {"enable_output", enableOutputCmd, 0}, 435 - {"linecount", linecntCmd, 0}, 436 2083 {"hascap", hascapCmd, 0}, 437 2084 {"hasfeature", hasfeatureCmd, 0}, 2085 + {"html", putsCmd, &puts_Html}, 438 2086 {"htmlize", htmlizeCmd, 0}, 439 - {"date", dateCmd, 0}, 440 - {"html", putsCmd, 0}, 441 - {"puts", putsCmd, (void*)1}, 442 - {"wiki", wikiCmd, 0}, 2087 + {"linecount", linecntCmd, 0}, 2088 + {"puts", putsCmd, &puts_Normal}, 2089 +#if 0 2090 + {"render", renderCmd, 0}, 2091 +#endif 443 2092 {"repository", repositoryCmd, 0}, 2093 + {"wiki", wikiCmd, 0}, 2094 + 444 2095 {0, 0, 0} 445 2096 }; 446 2097 if( g.interp==0 ){ 447 2098 int i; 2099 + if(g.cgiOutput){ 2100 + vtab.out.xWrite = Th_Output_f_cgi_content; 2101 + }else{ 2102 + vtab.out = Th_Vtab_OutputMethods_FILE; 2103 + vtab.out.pState = stdout; 2104 + } 2105 + vtab.out.enabled = enableOutput; 448 2106 g.interp = Th_CreateInterp(&vtab); 449 2107 th_register_language(g.interp); /* Basic scripting commands. */ 450 2108 #ifdef FOSSIL_ENABLE_TCL 451 2109 if( getenv("TH1_ENABLE_TCL")!=0 || db_get_boolean("tcl", 0) ){ 452 2110 th_register_tcl(g.interp, &g.tcl); /* Tcl integration commands. */ 453 2111 } 454 2112 #endif 455 - for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){ 456 - if ( !aCommand[i].zName || !aCommand[i].xProc ) continue; 457 - Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc, 458 - aCommand[i].pContext, 0); 459 - } 2113 +#ifdef TH_ENABLE_OB 2114 + th_register_ob(g.interp); 2115 +#endif 2116 +#ifdef TH_ENABLE_QUERY 2117 + th_register_query(g.interp); 2118 +#endif 2119 +#ifdef TH_ENABLE_ARGV 2120 + th_register_argv(g.interp); 2121 +#endif 2122 + Th_RegisterCommands( g.interp, aCommand ); 2123 + Th_Eval( g.interp, 0, "proc incr {name {step 1}} {\n" 2124 + "upvar $name x\n" 2125 + "set x [expr $x+$step]\n" 2126 + "}", -1 ); 460 2127 } 461 2128 } 462 2129 463 2130 /* 464 2131 ** Store a string value in a variable in the interpreter. 465 2132 */ 466 2133 void Th_Store(const char *zName, const char *zValue){ ................................................................................ 551 2218 i += 2; 552 2219 } 553 2220 return i; 554 2221 } 555 2222 556 2223 /* 557 2224 ** The z[] input contains text mixed with TH1 scripts. 558 -** The TH1 scripts are contained within <th1>...</th1>. 559 -** TH1 variables are $aaa or $<aaa>. The first form of 560 -** variable is literal. The second is run through htmlize 561 -** before being inserted. 2225 +** The TH1 scripts are contained within <th1>...</th1>. 2226 +** 2227 +** If flags does NOT contain the Th_Render_Flags_NO_DOLLAR_DEREF bit 2228 +** then TH1 variables are $aaa or $<aaa>. The first form of variable 2229 +** is literal. The second is run through htmlize before being 2230 +** inserted. 562 2231 ** 563 2232 ** This routine processes the template and writes the results 564 -** on either stdout or into CGI. 2233 +** via Th_Output(). 565 2234 */ 566 -int Th_Render(const char *z){ 2235 +int Th_Render(const char *z, int flags){ 567 2236 int i = 0; 568 2237 int n; 569 2238 int rc = TH_OK; 570 - char *zResult; 2239 + char const *zResult; 2240 + char doDollar = !(flags & Th_Render_Flags_NO_DOLLAR_DEREF); 571 2241 Th_FossilInit(); 572 2242 while( z[i] ){ 573 - if( z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){ 2243 + if( doDollar && z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){ 574 2244 const char *zVar; 575 2245 int nVar; 576 2246 int encode = 1; 577 - sendText(z, i, 0); 2247 + sendText(g.interp, z, i, 0); 578 2248 if( z[i+1]=='<' ){ 579 2249 /* Variables of the form $<aaa> are html escaped */ 580 2250 zVar = &z[i+2]; 581 2251 nVar = n-2; 582 2252 }else{ 583 2253 /* Variables of the form $aaa are output raw */ 584 2254 zVar = &z[i+1]; 585 2255 nVar = n; 586 2256 encode = 0; 587 2257 } 588 - rc = Th_GetVar(g.interp, (char*)zVar, nVar); 2258 + rc = Th_GetVar(g.interp, zVar, nVar); 589 2259 z += i+1+n; 590 2260 i = 0; 591 - zResult = (char*)Th_GetResult(g.interp, &n); 592 - sendText((char*)zResult, n, encode); 2261 + zResult = Th_GetResult(g.interp, &n); 2262 + sendText(g.interp, zResult, n, encode); 593 2263 }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){ 594 - sendText(z, i, 0); 2264 + sendText(g.interp, z, i, 0); 595 2265 z += i+5; 596 2266 for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){} 597 2267 rc = Th_Eval(g.interp, 0, (const char*)z, i); 598 2268 if( rc!=TH_OK ) break; 599 2269 z += i; 600 2270 if( z[0] ){ z += 6; } 601 2271 i = 0; 602 2272 }else{ 603 2273 i++; 604 2274 } 605 2275 } 606 2276 if( rc==TH_ERROR ){ 607 - sendText("<hr><p class=\"thmainError\">ERROR: ", -1, 0); 608 - zResult = (char*)Th_GetResult(g.interp, &n); 609 - sendText((char*)zResult, n, 1); 610 - sendText("</p>", -1, 0); 2277 + sendText(g.interp, "<hr><p class=\"thmainError\">ERROR: ", -1, 0); 2278 + zResult = Th_GetResult(g.interp, &n); 2279 + sendText(g.interp, zResult, n, 1); 2280 + sendText(g.interp, "</p>", -1, 0); 611 2281 }else{ 612 - sendText(z, i, 0); 2282 + sendText(g.interp, z, i, 0); 613 2283 } 614 2284 return rc; 615 2285 } 616 2286 617 2287 /* 618 2288 ** COMMAND: test-th-render 2289 +** COMMAND: th1 2290 +** 2291 +** Processes a file provided on the command line as a TH1-capable 2292 +** script/page. Output is sent to stdout or the CGI output buffer, as 2293 +** appropriate. The input file is assumed to be text/wiki/HTML content 2294 +** which may contain TH1 tag blocks and variables in the form $var or 2295 +** $<var>. Each block is executed in the same TH1 interpreter 2296 +** instance. 2297 +** 2298 +** ACHTUNG: not all of the $variables which are set in CGI mode 2299 +** are available via this (CLI) command. 2300 +** 619 2301 */ 620 2302 void test_th_render(void){ 621 2303 Blob in; 622 2304 if( g.argc<3 ){ 623 2305 usage("FILE"); 2306 + assert(0 && "usage() does not return"); 624 2307 } 625 - db_open_config(0); /* Needed for global "tcl" setting. */ 626 2308 blob_zero(&in); 2309 + db_open_config(0); /* Needed for global "tcl" setting. */ 2310 +#ifdef TH_ENABLE_QUERY 2311 + db_find_and_open_repository(OPEN_ANY_SCHEMA,0) 2312 + /* required for th1 query API. */; 2313 +#endif 627 2314 blob_read_from_file(&in, g.argv[2]); 628 - Th_Render(blob_str(&in)); 2315 + Th_Render(blob_str(&in), Th_Render_Flags_DEFAULT); 629 2316 }
Changes to src/tkt.c.
326 326 } 327 327 style_header("View Ticket"); 328 328 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW<br />\n", -1); 329 329 ticket_init(); 330 330 initializeVariablesFromDb(); 331 331 zScript = ticket_viewpage_code(); 332 332 if( g.thTrace ) Th_Trace("BEGIN_TKTVIEW_SCRIPT<br />\n", -1); 333 - Th_Render(zScript); 333 + Th_Render(zScript, Th_Render_Flags_DEFAULT ); 334 334 if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); 335 335 336 336 zFullName = db_text(0, 337 337 "SELECT tkt_uuid FROM ticket" 338 338 " WHERE tkt_uuid GLOB '%q*'", zUuid); 339 339 if( zFullName ){ 340 340 int cnt = 0; ................................................................................ 538 538 @ </p> 539 539 zScript = ticket_newpage_code(); 540 540 Th_Store("login", g.zLogin); 541 541 Th_Store("date", db_text(0, "SELECT datetime('now')")); 542 542 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, 543 543 (void*)&zNewUuid, 0); 544 544 if( g.thTrace ) Th_Trace("BEGIN_TKTNEW_SCRIPT<br />\n", -1); 545 - if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zNewUuid ){ 545 + if( Th_Render(zScript, Th_Render_Flags_DEFAULT)==TH_RETURN && !g.thTrace && zNewUuid ){ 546 546 cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zNewUuid)); 547 547 return; 548 548 } 549 549 @ </form> 550 550 if( g.thTrace ) Th_Trace("END_TKTVIEW<br />\n", -1); 551 551 style_footer(); 552 552 } ................................................................................ 605 605 @ </p> 606 606 zScript = ticket_editpage_code(); 607 607 Th_Store("login", g.zLogin); 608 608 Th_Store("date", db_text(0, "SELECT datetime('now')")); 609 609 Th_CreateCommand(g.interp, "append_field", appendRemarkCmd, 0, 0); 610 610 Th_CreateCommand(g.interp, "submit_ticket", submitTicketCmd, (void*)&zName,0); 611 611 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT_SCRIPT<br />\n", -1); 612 - if( Th_Render(zScript)==TH_RETURN && !g.thTrace && zName ){ 612 + if( Th_Render(zScript, Th_Render_Flags_DEFAULT)==TH_RETURN && !g.thTrace && zName ){ 613 613 cgi_redirect(mprintf("%s/tktview/%s", g.zTop, zName)); 614 614 return; 615 615 } 616 616 @ </form> 617 617 if( g.thTrace ) Th_Trace("BEGIN_TKTEDIT<br />\n", -1); 618 618 style_footer(); 619 619 }
Added test/th1-ob-1.th1.
1 +<th1> 2 +set i 0 3 +set max 6 4 +for {} {$i < $max} {incr i} { 5 + ob start 6 + puts "this is level " [ob level] 7 + set buf($i) [ob get] 8 +} 9 + 10 +for {set i [expr $max-1]} {$i >= 0} {incr i -1} { 11 + ob pop 12 +} 13 +for {set i [expr $max-1]} {$i >= 0} {incr i -1} { 14 + puts buf($i) = $buf($i) "\n" 15 +} 16 +puts "buffering level = " [ob level] \n 17 +</th1>
Added test/th1-query-api-1.th1.
1 +This is not a formal test suite, but a tinkering ground. 2 +Run it through "fossil test-th-render THIS_FILE". 3 +<th1> 4 +set stmt [query prepare {SELECT login, cap FROM user}] 5 +set colCount [query $stmt col count] 6 +puts "query column count: ${colCount}\n" 7 +puts "stmt id=${stmt}\n" 8 + 9 +proc noop {} {} 10 +proc incr {name {step 1}} { 11 + upvar $name x 12 + set x [expr $x+$step] 13 +} 14 + 15 + 16 +set sep " " 17 +set i 0 18 +set colNames(0) 0 19 +for {set i 0} {$i < $colCount} {incr i} { 20 + set colNames($i) [query $stmt col name $i] 21 + puts "colNames($i)=" $colNames($i) "\n" 22 +} 23 + 24 +for {set row 0} {[query $stmt step]} {incr row} { 25 + for {set i 0} {$i < $colCount} {incr i} { 26 + if {$i > 0} { 27 + puts $sep 28 + } else { 29 + puts "#$row: $sep" 30 + } 31 + puts $colNames($i) = [query $stmt col string $i] 32 + } 33 + puts "\n" 34 +} 35 +unset row 36 + 37 +query $stmt finalize 38 +#query finalize $stmt 39 + 40 + 41 +proc query_step_each {{stmt} {callback}} { 42 + set colNames(0) 0 43 + set colCount [query $stmt col count] 44 + for {set i 0} {$i < $colCount} {incr i} { 45 + set colNames($i) [query $stmt col name $i] 46 + } 47 + upvar cb $callback 48 + for {set row 0} {[query $stmt step]} {incr row} { 49 + #puts "Calling callback: $stmt $colCount colNames\n" 50 + $callback $stmt $colCount 51 + } 52 +} 53 + 54 +set sql {SELECT uid, login FROM user WHERE uid!=?} 55 +#set sql {SELECT uid, login FROM user WHERE login=?} 56 +#set sql {SELECT tagid, value, null FROM tagxref WHERE value IS ? LIMIT 3} 57 +set stmt [query prepare $sql] 58 +puts "stmt ID=" $stmt "\n" 59 +#query bind int $stmt 1 3 60 +#query $stmt bind 1 int 3 61 +query $stmt bind 1 string 3 62 +#query $stmt bind string 1 3 63 +#set stmt [query prepare $sql] 64 +#query $stmt bind 1 string 1 65 +#set stmt [query prepare $sql] 66 +#query $stmt bind null 1 67 +puts "USER LIST:\n" 68 +catch { 69 + proc my_each {stmt colCount} { 70 + upvar 2 sep sep 71 + puts [query $stmt col int 0] " (type=" [query $stmt col type 0] ")" $sep 72 + puts [query $stmt col double 0] $sep 73 + puts [query $stmt col string 1] " (type=" [query $stmt col type 1] ")" $sep 74 + puts "isnull 0 ?= " [query $stmt col is_null 0] $sep 75 + puts "isnull 2 ?= " [query col is_null $stmt 2] 76 +# for {set i 0} {$i < $colCount} {incr i} { 77 +# if {$i > 0} { puts $sep } 78 +# } 79 + puts "\n" 80 +# error "hi!" 81 + } 82 + query_step_each $stmt my_each 83 +# query reset $stmt 84 +# query $stmt reset 85 +# query_step_each $stmt { 86 +# proc each {stmt cc} { puts hi "\n" } 87 +# } 88 + return 0 89 +} rc 90 +query finalize $stmt 91 +if { 0 != $rc } { 92 + puts "ERROR: $rc\n" 93 +} 94 + 95 +set consts [list SQLITE_BLOB SQLITE_FLOAT SQLITE_INTEGER SQLITE_NULL SQLITE_TEXT] 96 +#set consts $SQLITE_CONSTANTS 97 +puts consts = $consts "\n" 98 +for {set i 0} {$i < [llength $consts]} {incr i} { 99 + set x [lindex $consts $i] 100 + puts \$$x = [expr \$$x] "\n" 101 +} 102 + 103 +set ARGC [argv len] 104 +puts ARGC = $ARGC "\n" 105 +for {set i 0} {$i < $ARGC} {incr i} { 106 + puts "argv at $i = " [argv at $i] \n 107 +} 108 + 109 +set magicDefault hi 110 +set optA [argv string AA a $magicDefault] 111 +puts "argv string AA = " $optA \n 112 + 113 +set optA [argv bool BB b 0] 114 +puts "argv bool BB = " $optA \n 115 + 116 +set exception 0 117 +catch { 118 + argv int noSuchOptionAndNoDefault 119 +} exception 120 +puts exception = $exception "\n" 121 + 122 +proc multiStmt {} { 123 + set max 5 124 + set i 0 125 + set s(0) 0 126 + for {set i 0} {$i < $max} {incr i} { 127 + set s($i) [query prepare "SELECT $i"] 128 + puts "s($i) = $s($i)\n" 129 + } 130 + for {set i 0} {$i < $max} {incr i} { 131 + query $s($i) step 132 + } 133 + for {set i 0} {$i < $max} {incr i} { 134 + puts "closing stmt $s($i)\n" 135 + query $s($i) finalize 136 + } 137 + 138 + puts "Preparing again\n" 139 + 140 + for {set i 0} {$i < $max} {incr i} { 141 + set s($i) [query prepare "SELECT $i"] 142 + puts "s($i) = $s($i)\n" 143 + } 144 + for {set i 0} {$i < $max} {incr i} { 145 + query $s($i) step 146 + } 147 + puts "Closing again\n" 148 + 149 + for {set i 0} {$i < $max} {incr i} { 150 + puts "closing stmt $s($i)\n" 151 + query $s($i) finalize 152 + } 153 +} 154 +multiStmt 155 + 156 +puts "If you got this far, you win!\n" 157 +</th1>
Added test/th1-query-api-2.th1.
1 +<th1> 2 +catch { 3 + set stmt [query prepare { 4 + SELECT login, cap, cexpire, mtime, NULL FROM user 5 + WHERE uid<? AND cexpire IS NOT NULL 6 + AND mtime IS NOT NULL 7 + }] 8 + puts "stmt ID=$stmt\n" 9 +# query bind int $stmt 1 2 10 +# query $stmt bind int 1 2 11 + query $stmt bind 1 int 5 12 +# segfault: query bind 1 int $stmt 2 13 + set sep "\n" 14 + for {} {[query $stmt step]} {} { 15 + puts [query $stmt col string 0] $sep 16 + puts [query $stmt col time 2 {%Y%m%d @ %H:%M:%S}] $sep 17 + puts [query $stmt col time 2 {%Y%m%d @ %H:%M:%S} {+10 years}] $sep 18 + puts [query $stmt col 2 time {%Y%m%d @ %H:%M:%S} {+10 years}] $sep 19 +# puts [query col time $stmt 2 %s] $sep 20 + puts [query col time $stmt 3 %s unixepoch] $sep 21 +# puts [query strftime %s [query col string $stmt 3] unixepoch] 22 + puts [query strftime %s [query col string $stmt 3] unixepoch] $sep 23 + puts [query strftime {%Y%m%d @ %H:%M:%S} [query col string $stmt 2] {+10 years}] $sep 24 + puts "\n" 25 + puts "old isnull: " [query col isnull $stmt 4] "\n" 26 + 27 + puts "new old isnull: " [query col 4 isnull $stmt] "\n" 28 + puts "new isnull: " [query $stmt col isnull 4] "\n" 29 + puts "new new isnull: " [query $stmt col 4 isnull] "\n" 30 + 31 + puts "old col type: " [query col type $stmt 1] "\n" 32 + puts "new col type: " [query $stmt col type 1] "\n" 33 + puts "new new col type: " [query $stmt col 1 type] "\n" 34 + 35 + puts "old col name: " [query col name $stmt 1] "\n" 36 + puts "new col name: " [query $stmt col name 1] "\n" 37 + puts "new new col name: " [query $stmt col 1 name] "\n" 38 + 39 + puts "old col double: " [query col double $stmt 2] "\n" 40 + puts "new col double: " [query $stmt col double 2] "\n" 41 + puts "new new col double: " [query $stmt col 2 double] "\n" 42 + 43 + puts "old col int: " [query col int $stmt 2] "\n" 44 + puts "new col int: " [query $stmt col int 2] "\n" 45 + puts "new new col int: " [query $stmt col 2 int] "\n" 46 + 47 + puts "old col string: " [query col string $stmt 2] "\n" 48 + puts "new col string: " [query $stmt col string 2] "\n" 49 + puts "new new col string: " [query $stmt col 2 string] "\n" 50 + 51 + puts "\n" 52 + } 53 + 54 + puts "alt-form col count: " [query $stmt col count] "\n" 55 + query finalize $stmt 56 + return 0 57 +} rc 58 +if {0 != $rc} { 59 + puts "ERROR: $rc\n" 60 +} 61 +puts "Done!\n" 62 + 63 + 64 +ob start 65 +puts buffered 66 +set x [ob get pop] 67 +puts x=$x 68 + 69 + 70 +</th1>
Added test/th1-variadic-proc.th1.
1 +<th1> 2 +proc vfunc {args} { 3 + set argc [llength $args] 4 + puts "argc=$argc Check this for a memleak when args length > 0\n" 5 +} 6 +vfunc 7 +vfunc 1 8 +vfunc 1 2 9 +vfunc 1 2 3 10 +</th1>
Added www/th1_argv.wiki.
1 +<h1>TH1 "argv" API</h1> 2 + 3 +The "argv" API provides features for accessing command-line arguments 4 +and GET/POST values. They (unfortunately) do not provide access to 5 +POST data submitted in JSON mode (which fossil internally doesn't really 6 +know about). 7 + 8 +Example usage: 9 + 10 +<nowiki><pre> 11 +<th1> 12 +set argc [argv len] 13 +set appName [argv at 0] 14 +# Fetch --foo|-f argument: 15 +set foo [argv getstr foo f "default value"] 16 +<th1> 17 +</pre></nowiki> 18 + 19 +(Note that fossil does not actually care if an argument starts 20 +with 1 or 2 dashes. The convention of using 1 for "short-form" 21 +flags and 2 for "long-form" is purely historical.) 22 + 23 +The various subcommands are described below... 24 + 25 +<h2>len</h2> 26 + 27 +Returns the number of arguments. 28 + 29 +<nowiki><pre> 30 +set argc [argv len] 31 +</pre></nowiki> 32 + 33 + 34 +<h2>at</h2> 35 + 36 +Fetches the argument at the given index (0-based). 37 + 38 +<nowiki><pre> 39 +set arg [argv at 3] 40 +</pre></nowiki> 41 + 42 +The fossil binary's name is stored in argument #0. 43 + 44 +<h2>getstr|string</h2> 45 + 46 +Searches for a CLI/GET/POST parameter. In CLI this function has some 47 +non-intuitive behaviour inherited from fossil's internals: once a 48 +flag/parameter is fetched, it is removed from the internal arguments 49 +list, meaning that this function will never see it a second time. 50 + 51 +<nowiki><pre> 52 +set something [argv string "something" "S" "default"] 53 +</pre></nowiki> 54 + 55 +If no default value is provided, an error is triggered if the value is 56 +not found. 57 + 58 +If you do not want to search for a short-form flag, set it to an empty 59 +string. 60 + 61 +NOTE: flag checking does not work in CGI mode when using <em>upper-case</em> 62 +flags (fossil treats upper-case names as environment variables). 63 + 64 +<h2>getbool|bool</h2> 65 + 66 +Works almost like <tt>getstr</tt> but searches for boolean flags. CLI boolean flags 67 +have no explicit value, and are "true" if the are set at all. 68 + 69 +<nowiki><pre> 70 +set doSomething [argv bool "do-something" "D" 0] 71 +</pre></nowiki> 72 + 73 +<h2>getint|int</h2> 74 + 75 +Works almost like <tt>getstr</tt> but searches for integer flags. 76 + 77 + 78 +<nowiki><pre> 79 +set limit [argv int "limit" "L" 10] 80 +</pre></nowiki>
Added www/th1_ob.wiki.
1 +<h1>TH1 "ob" (Output Buffering) API</h1> 2 + 3 +The "ob" API mimics the 4 +[http://php.net/manual/en/function.ob-start.php|PHP output buffering] fairly closely, 5 +and provides these features: 6 + 7 + * Redirect output from <tt>puts</tt> and friends to a buffer. 8 + * Fetch the buffer as a string or discard it (as you wish). 9 + * Supports nesting buffering arbitrarily deep, but each level which gets opened must also be closed by the scripter. 10 + 11 +Example usage: 12 + 13 +<nowiki><pre> 14 +<th1> 15 +puts "this is unbuffered" 16 +ob push # or: ob start (same thing) 17 +# all output until the next ob start|end gets collected 18 +# in a buffer... 19 +</th1> 20 + 21 +this is buffered 22 + 23 +<th1> 24 +puts "current buffer level = " [ob level] "\n" 25 +puts "this part is also collected in the buffer." 26 + 27 +# Collect the buffer's contents: 28 +set buf [ob get pop] 29 +# That is equivalent to: 30 +# set buf [ob get] 31 +# ob pop 32 + 33 +puts "\nThis is now unbuffered, but we buffered: $buf\n" 34 +</th1> 35 +</pre></nowiki> 36 + 37 +The functions are summarized below... 38 + 39 +<h2>ob push|start</h2> 40 + 41 +<tt>push</tt> and <tt>start</tt> are aliases ("start" comes from the PHP API, but 42 +"push" is probably more natural to those working with th1). 43 + 44 +<tt>ob start</tt> pushes a level of buffering onto the buffer stack, such that 45 +future calls which generate output through the th1-internal mechanism will have it 46 +transparently redirected to the current buffer. 47 + 48 +It is important that every call to <tt>ob start</tt> be followed up (eventually) 49 +by either <tt>ob end</tt> or <tt>ob get end</tt>. 50 + 51 +<h2>ob pop|end</h2> 52 + 53 +<tt>pop</tt> and <tt>end</tt> are aliases ("end" comes from the PHP API, but 54 +"pop" is probably more natural to those working with th1). 55 + 56 +This discards any current buffered contents and reverts the output state to 57 +the one it had before the previous <tt>ob start</tt>. i.e. that might be another 58 +buffering level or it might be the th1-normal output mechanism. 59 + 60 +The global resources associated with buffering are cleaned up when the 61 +last buffering level is left (and re-created as needed when a new 62 +level is started). 63 + 64 +<h2>ob clean</h2> 65 + 66 +This discards the current contents of the current buffer level but 67 +does not change the buffer stack level. 68 + 69 +<h2>ob get</h2> 70 + 71 +This fetches the current contents as a string. It optionally accepts 72 +either <tt>end</tt> (or its alias <tt>pop</tt>) or <tt>clean</tt>, in which cases it behaves like 73 +either <tt>ob end|pop</tt> or <tt>ob clean</tt>, respectively, in addition 74 +to returning the buffer contents. i.e. <tt>ob get clean</tt> will 75 +fetch the contents and clean up the buffer, but does not change the 76 +buffering level, whereas <tt>ob get end|pop</tt> pops the buffer off the 77 +stack after fetching its contents. 78 + 79 +<h2>ob level</h2> 80 + 81 +Returns the current buffering level (0 if not buffering). 82 + 83 +<h2>ob flush</h2> 84 + 85 +It is not expected that this will be useful all that often, but for 86 +the cases where it is, here's how it works: this behaves as if we 87 +fetched the buffer state (<tt>ob get</tt>), reverted TH1 to its 88 +previous output mechanism, push the buffer state to TH1, revert TH1 89 +<em>back</em> to the current buffering state, and then clear the 90 +current buffer contents (like <tt>ob clean</tt>). This does not change 91 +the buffering level, though it temporarily behaves as if it does. 92 + 93 +In other words, this function pushes the current buffer contents to the 94 +next-lower output mechanism (which may be another ob buffering level, 95 +fossil's internal CGI output buffer, or it might be be 96 +<tt>fwrite(stdout)</tt>).
Added www/th1_query.wiki.
1 +<h1>TH1 "query" API</h1> 2 + 3 +The "query" API provides limited access to the fossil database. 4 +It restricts usage to queries which return result columns (i.e. 5 +<tt>SELECT</tt> and friends). 6 +Example usage: 7 + 8 +<nowiki><pre> 9 +<th1> 10 +catch { 11 + set stmt [query prepare "SELECT login, cap FROM user"] 12 + puts "stmt ID=$stmt\n" 13 + for {} {[query $stmt step]} {} { 14 + puts [query $stmt col string 0] " " [query $stmt col string 1] \n 15 + } 16 + query $stmt finalize 17 + return 0 18 +} rc 19 +if {0 != $rc} { 20 + puts "ERROR: $rc\n" 21 +} 22 +<th1> 23 +</pre></nowiki> 24 + 25 +The various subcommands are summarized in the following subsections, and here 26 +are some notes regarding calling conventions: 27 + 28 +The (bind, col, step, finalize) functions accept their statement ID argument 29 +either right after the "query" command or right after the final subcommand. 30 +The following examples demonstrate this: 31 + 32 +<nowiki><pre> 33 +query $stmt step 34 +query step $stmt 35 + 36 +query $stmt finalize 37 +query finalize $stmt 38 + 39 +query col string $stmt 1 40 +query $stmt col string 1 41 + 42 +query bind string $stmt 1 "foo" 43 +query $stmt bind string 1 "foo" 44 +</pre></nowiki> 45 + 46 +The "prefered" form is: 47 + 48 +<nowiki><pre> 49 +query StmtId command ... 50 +</pre></nowiki> 51 + 52 +(Why, then, are both forms accepted? Because the "preferred" form only 53 +evolved only after using the first form in script code.) 54 + 55 + 56 +<h2>prepare</h2> 57 + 58 +This subcommand prepares a query for execution. It returns a statement handle 59 +ID which must be passed to any other functions using the API. 60 + 61 +All prepared statements must be <tt>finalize</tt>d when they have outlived 62 +their usefulness. 63 + 64 +<nowiki><pre> 65 +set stmt [query prepare {SELECT ...}] 66 +... 67 +query $stmt finalize 68 +</pre></nowiki> 69 + 70 + 71 +<h2>finalize</h2> 72 + 73 +Releases all resources associated with the statement. Note that future 74 +calls to <tt>prepare</tt> might re-use the same statement statement 75 +ID. 76 + 77 +<nowiki><pre> 78 +set stmt [query prepare "SELECT ..."] 79 +... 80 +query $stmt finalize 81 +</pre></nowiki> 82 + 83 + 84 +<h2>step</h2> 85 + 86 +This subcommand steps the result set by one row. It returns 0 87 +at the end of the set, a positive value if a new row is available, 88 +and throws for any other condition. 89 + 90 +<nowiki><pre> 91 +for {} {[query $stmt step]} {} { 92 + puts [query $stmt col string 0] "\n" 93 +} 94 +</pre></nowiki> 95 + 96 + 97 +<h2>reset</h2> 98 + 99 +Resets a query so that it can be executed again. This is only needed when 100 +binding parameters in a loop - call it at the end of each loop iteration. 101 + 102 +<nowiki><pre> 103 +query $stmt reset 104 +query reset $stmt 105 +</pre></nowiki> 106 + 107 + 108 +<h2>bind xxx</h2> 109 + 110 +The <tt>bind xxx</tt> family of subcommands attach values to queries 111 +before stepping through them. The subcommands include: 112 + 113 + * <tt>bind StmtId int Index Value</tt> 114 + * <tt>bind StmtId double Index Value</tt> 115 + * <tt>bind StmtId null Index</tt> 116 + * <tt>bind StmtId string Index Value</tt> 117 + 118 +Note that all of those optionally accept the statement handle directly after 119 +the "query" command (before the "col" subcommand). e.g. 120 +<tt>query bind null $stmt 1</tt> and 121 +<tt>query $stmt bind null 1</tt> are equivalent. They also accept the column index 122 +either before or after the type name, e.g. 123 +<tt>query $stmt bind 1 string ...</tt> and <tt>query $stmt bind string 1 ...</tt> are equivalent. 124 + 125 + 126 +Achtung: the bind API uses 1-based indexes, just like SQL does. 127 + 128 +<nowiki><pre> 129 +set stmt [query prepare "SELECT ... WHERE user=?"] 130 +query $stmt bind int 1 drh 131 +if {[query $stmt step]} { 132 + puts [query $stmt col string 0] "\n" 133 +} 134 +query $stmt finalize 135 +</pre></nowiki> 136 + 137 + 138 +<h2>col xxx</h2> 139 + 140 +The <tt>col xxx</tt> familys of subcommands are for fetching values and metadata from result rows. 141 + 142 + * <tt>col StmtId count</tt> Returns the number of result columns in the statement. 143 + * <tt>col StmtId isnull Index</tt> Returns non-0 if the given column contains an SQL NULL value. 144 + * <tt>col StmtId (double|int|string) Index</tt> Fetches a column's value as either a number or string. 145 + * <tt>col StmtId time Index Format Modifiers</tt> Formats a time value. See below. 146 + * <tt>col StmtId type Index</tt> Returns the given column's type as a value from the <tt>SQLITE_TYPENAME</tt> family of constants. 147 + 148 +Note that all of those optionally accept the statement handle directly after 149 +the "query" command (before the "col" subcommand). e.g. 150 +<tt>query $stmt col count</tt> and 151 +<tt>query col count $stmt</tt> are equivalent. They also accept the column index 152 +either before or after the type name, e.g. 153 +<tt>query $stmt col 1 string</tt> and <tt>query $stmt col string 1</tt> are equivalent. 154 + 155 +Achtung: the col API uses 0-based indexes, just like SQL does. 156 + 157 +<h3>col time</h3> 158 + 159 +This function is a proxy for sqlite3's 160 +<tt>[http://www.sqlite.org/lang_datefunc.html|strftime()]</tt> function. It is used like this: 161 + 162 + 163 +<nowiki><pre> 164 +query $stmt col time $index {%Y%m%d @ %H:%M:%S} 165 +</pre></nowiki> 166 + 167 +Any remaining arguments are treated as "modifiers" and passed as-is to strfmtime. For example: 168 + 169 +<nowiki><pre> 170 +query $stmt col time $index {%Y%m%d @ %H:%M:%S} {+5 years} 171 +query $stmt col time $index %s unixepoch 172 +</pre></nowiki> 173 + 174 + 175 + 176 + 177 +<h2>strftime</h2> 178 + 179 +This works like <tt>col time</tt> (described below) but takes its 180 +value from an arbitrary source specified by the 3rd argument. 181 + 182 +<nowiki><pre> 183 +query strftime %s 1319211587 unixepoch 184 +query strftime {%Y%m%d @ %H:%M:%S} [query $stmt col string 2] {+10 years}] 185 +</pre></nowiki> 186 + 187 +<h2>Global Variables</h2> 188 + 189 +This API installs the following global variables, all of which correspond to 190 +<tt>SQLITE_xxx</tt> constant values: 191 + 192 + * <tt>SQLITE_BLOB</tt> 193 + * <tt>SQLITE_FLOAT</tt> 194 + * <tt>SQLITE_INTEGER</tt> 195 + * <tt>SQLITE_NULL</tt> 196 + * <tt>SQLITE_TEXT</tt> 197 + 198 +These values are used only by the <tt>col type</tt> function. They can be 199 +accessed from script code via <tt>$::SQLITE_xxx</tt>.