/src/ssfossil/fossil/src/th_main.c
C | 530 lines | 371 code | 25 blank | 134 comment | 73 complexity | 0341a29b557d1b97a801f335ecdea086 MD5 | raw file
- /*
- ** Copyright (c) 2008 D. Richard Hipp
- **
- ** This program is free software; you can redistribute it and/or
- ** modify it under the terms of the Simplified BSD License (also
- ** known as the "2-Clause License" or "FreeBSD License".)
- ** This program is distributed in the hope that it will be useful,
- ** but without any warranty; without even the implied warranty of
- ** merchantability or fitness for a particular purpose.
- **
- ** Author contact information:
- ** drh@hwaci.com
- ** http://www.hwaci.com/drh/
- **
- *******************************************************************************
- **
- ** This file contains an interface between the TH scripting language
- ** (an independent project) and fossil.
- */
- #include "config.h"
- #include "th_main.h"
- /*
- ** Global variable counting the number of outstanding calls to malloc()
- ** made by the th1 implementation. This is used to catch memory leaks
- ** in the interpreter. Obviously, it also means th1 is not threadsafe.
- */
- static int nOutstandingMalloc = 0;
- /*
- ** Implementations of malloc() and free() to pass to the interpreter.
- */
- static void *xMalloc(unsigned int n){
- void *p = malloc(n);
- if( p ){
- nOutstandingMalloc++;
- }
- return p;
- }
- static void xFree(void *p){
- if( p ){
- nOutstandingMalloc--;
- }
- free(p);
- }
- static Th_Vtab vtab = { xMalloc, xFree };
- /*
- ** Generate a TH1 trace message if debugging is enabled.
- */
- void Th_Trace(const char *zFormat, ...){
- va_list ap;
- va_start(ap, zFormat);
- blob_vappendf(&g.thLog, zFormat, ap);
- va_end(ap);
- }
- /*
- ** True if output is enabled. False if disabled.
- */
- static int enableOutput = 1;
- /*
- ** TH command: enable_output BOOLEAN
- **
- ** Enable or disable the puts and hputs commands.
- */
- static int enableOutputCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "enable_output BOOLEAN");
- }
- return Th_ToInt(interp, argv[1], argl[1], &enableOutput);
- }
- /*
- ** Send text to the appropriate output: Either to the console
- ** or to the CGI reply buffer.
- */
- static void sendText(const char *z, int n, int encode){
- if( enableOutput && n ){
- if( n<0 ) n = strlen(z);
- if( encode ){
- z = htmlize(z, n);
- n = strlen(z);
- }
- if( g.cgiOutput ){
- cgi_append_content(z, n);
- }else{
- fwrite(z, 1, n, stdout);
- }
- if( encode ) free((char*)z);
- }
- }
- /*
- ** TH command: puts STRING
- ** TH command: html STRING
- **
- ** Output STRING as HTML (html) or unchanged (puts).
- */
- static int putsCmd(
- Th_Interp *interp,
- void *pConvert,
- int argc,
- const char **argv,
- int *argl
- ){
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "puts STRING");
- }
- sendText((char*)argv[1], argl[1], pConvert!=0);
- return TH_OK;
- }
- /*
- ** TH command: wiki STRING
- **
- ** Render the input string as wiki.
- */
- static int wikiCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "wiki STRING");
- }
- if( enableOutput ){
- Blob src;
- blob_init(&src, (char*)argv[1], argl[1]);
- wiki_convert(&src, 0, WIKI_INLINE);
- blob_reset(&src);
- }
- return TH_OK;
- }
- /*
- ** TH command: htmlize STRING
- **
- ** Escape all characters of STRING which have special meaning in HTML.
- ** Return a new string result.
- */
- static int htmlizeCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- char *zOut;
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "htmlize STRING");
- }
- zOut = htmlize((char*)argv[1], argl[1]);
- Th_SetResult(interp, zOut, -1);
- free(zOut);
- return TH_OK;
- }
- /*
- ** TH command: date
- **
- ** Return a string which is the current time and date.
- */
- static int dateCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- char *zOut = db_text("??", "SELECT datetime('now')");
- Th_SetResult(interp, zOut, -1);
- free(zOut);
- return TH_OK;
- }
- /*
- ** TH command: hascap STRING
- **
- ** Return true if the user has all of the capabilities listed in STRING.
- */
- static int hascapCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- int rc;
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "hascap STRING");
- }
- rc = login_has_capability((char*)argv[1],argl[1]);
- if( g.thTrace ){
- Th_Trace("[hascap %.*h] => %d<br />\n", argl[1], argv[1], rc);
- }
- Th_SetResultInt(interp, rc);
- return TH_OK;
- }
- /*
- ** TH command: anycap STRING
- **
- ** Return true if the user has any one of the capabilities listed in STRING.
- */
- static int anycapCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- int rc = 0;
- int i;
- if( argc!=2 ){
- return Th_WrongNumArgs(interp, "anycap STRING");
- }
- for(i=0; rc==0 && i<argl[1]; i++){
- rc = login_has_capability((char*)&argv[1][i],1);
- }
- if( g.thTrace ){
- Th_Trace("[hascap %.*h] => %d<br />\n", argl[1], argv[1], rc);
- }
- Th_SetResultInt(interp, rc);
- return TH_OK;
- }
- /*
- ** TH1 command: combobox NAME TEXT-LIST NUMLINES
- **
- ** Generate an HTML combobox. NAME is both the name of the
- ** CGI parameter and the name of a variable that contains the
- ** currently selected value. TEXT-LIST is a list of possible
- ** values for the combobox. NUMLINES is 1 for a true combobox.
- ** If NUMLINES is greater than one then the display is a listbox
- ** with the number of lines given.
- */
- static int comboboxCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- if( argc!=4 ){
- return Th_WrongNumArgs(interp, "combobox NAME TEXT-LIST NUMLINES");
- }
- if( enableOutput ){
- int height;
- Blob name;
- int nValue;
- const char *zValue;
- char *z, *zH;
- int nElem;
- int *aszElem;
- char **azElem;
- int i;
- if( Th_ToInt(interp, argv[3], argl[3], &height) ) return TH_ERROR;
- Th_SplitList(interp, argv[2], argl[2], &azElem, &aszElem, &nElem);
- blob_init(&name, (char*)argv[1], argl[1]);
- zValue = Th_Fetch(blob_str(&name), &nValue);
- z = mprintf("<select name=\"%z\" size=\"%d\">",
- htmlize(blob_buffer(&name), blob_size(&name)), height);
- sendText(z, -1, 0);
- free(z);
- blob_reset(&name);
- for(i=0; i<nElem; i++){
- zH = htmlize((char*)azElem[i], aszElem[i]);
- if( zValue && aszElem[i]==nValue
- && memcmp(zValue, azElem[i], nValue)==0 ){
- z = mprintf("<option value=\"%s\" selected=\"selected\">%s</option>",
- zH, zH);
- }else{
- z = mprintf("<option value=\"%s\">%s</option>", zH, zH);
- }
- free(zH);
- sendText(z, -1, 0);
- free(z);
- }
- sendText("</select>", -1, 0);
- Th_Free(interp, azElem);
- }
- return TH_OK;
- }
- /*
- ** TH1 command: linecount STRING MAX MIN
- **
- ** Return one more than the number of \n characters in STRING. But
- ** never return less than MIN or more than MAX.
- */
- static int linecntCmd(
- Th_Interp *interp,
- void *p,
- int argc,
- const char **argv,
- int *argl
- ){
- const char *z;
- int size, n, i;
- int iMin, iMax;
- if( argc!=4 ){
- return Th_WrongNumArgs(interp, "linecount STRING MAX MIN");
- }
- if( Th_ToInt(interp, argv[2], argl[2], &iMax) ) return TH_ERROR;
- if( Th_ToInt(interp, argv[3], argl[3], &iMin) ) return TH_ERROR;
- z = argv[1];
- size = argl[1];
- for(n=1, i=0; i<size; i++){
- if( z[i]=='\n' ){
- n++;
- if( n>=iMax ) break;
- }
- }
- if( n<iMin ) n = iMin;
- if( n>iMax ) n = iMax;
- Th_SetResultInt(interp, n);
- return TH_OK;
- }
- /*
- ** Make sure the interpreter has been initialized. Initialize it if
- ** it has not been already.
- **
- ** The interpreter is stored in the g.interp global variable.
- */
- void Th_FossilInit(void){
- static struct _Command {
- const char *zName;
- Th_CommandProc xProc;
- void *pContext;
- } aCommand[] = {
- {"anycap", anycapCmd, 0},
- {"combobox", comboboxCmd, 0},
- {"enable_output", enableOutputCmd, 0},
- {"linecount", linecntCmd, 0},
- {"hascap", hascapCmd, 0},
- {"htmlize", htmlizeCmd, 0},
- {"date", dateCmd, 0},
- {"html", putsCmd, 0},
- {"puts", putsCmd, (void*)1},
- {"wiki", wikiCmd, 0},
- };
- if( g.interp==0 ){
- int i;
- g.interp = Th_CreateInterp(&vtab);
- th_register_language(g.interp); /* Basic scripting commands. */
- for(i=0; i<sizeof(aCommand)/sizeof(aCommand[0]); i++){
- Th_CreateCommand(g.interp, aCommand[i].zName, aCommand[i].xProc,
- aCommand[i].pContext, 0);
- }
- }
- }
- /*
- ** Store a string value in a variable in the interpreter.
- */
- void Th_Store(const char *zName, const char *zValue){
- Th_FossilInit();
- if( zValue ){
- if( g.thTrace ){
- Th_Trace("set %h {%h}<br />\n", zName, zValue);
- }
- Th_SetVar(g.interp, zName, -1, zValue, strlen(zValue));
- }
- }
- /*
- ** Unset a variable.
- */
- void Th_Unstore(const char *zName){
- if( g.interp ){
- Th_UnsetVar(g.interp, (char*)zName, -1);
- }
- }
- /*
- ** Retrieve a string value from the interpreter. If no such
- ** variable exists, return NULL.
- */
- char *Th_Fetch(const char *zName, int *pSize){
- int rc;
- Th_FossilInit();
- rc = Th_GetVar(g.interp, (char*)zName, -1);
- if( rc==TH_OK ){
- return (char*)Th_GetResult(g.interp, pSize);
- }else{
- return 0;
- }
- }
- /*
- ** Return true if the string begins with the TH1 begin-script
- ** tag: <th1>.
- */
- static int isBeginScriptTag(const char *z){
- return z[0]=='<'
- && (z[1]=='t' || z[1]=='T')
- && (z[2]=='h' || z[2]=='H')
- && z[3]=='1'
- && z[4]=='>';
- }
- /*
- ** Return true if the string begins with the TH1 end-script
- ** tag: </th1>.
- */
- static int isEndScriptTag(const char *z){
- return z[0]=='<'
- && z[1]=='/'
- && (z[2]=='t' || z[2]=='T')
- && (z[3]=='h' || z[3]=='H')
- && z[4]=='1'
- && z[5]=='>';
- }
- /*
- ** If string z[0...] contains a valid variable name, return
- ** the number of characters in that name. Otherwise, return 0.
- */
- static int validVarName(const char *z){
- int i = 0;
- int inBracket = 0;
- if( z[0]=='<' ){
- inBracket = 1;
- z++;
- }
- if( z[0]==':' && z[1]==':' && isalpha(z[2]) ){
- z += 3;
- i += 3;
- }else if( isalpha(z[0]) ){
- z ++;
- i += 1;
- }else{
- return 0;
- }
- while( isalnum(z[0]) || z[0]=='_' ){
- z++;
- i++;
- }
- if( inBracket ){
- if( z[0]!='>' ) return 0;
- i += 2;
- }
- return i;
- }
- /*
- ** The z[] input contains text mixed with TH1 scripts.
- ** The TH1 scripts are contained within <th1>...</th1>.
- ** TH1 variables are $aaa or $<aaa>. The first form of
- ** variable is literal. The second is run through htmlize
- ** before being inserted.
- **
- ** This routine processes the template and writes the results
- ** on either stdout or into CGI.
- */
- int Th_Render(const char *z){
- int i = 0;
- int n;
- int rc = TH_OK;
- char *zResult;
- Th_FossilInit();
- while( z[i] ){
- if( z[i]=='$' && (n = validVarName(&z[i+1]))>0 ){
- const char *zVar;
- int nVar;
- sendText(z, i, 0);
- if( z[i+1]=='<' ){
- /* Variables of the form $<aaa> */
- zVar = &z[i+2];
- nVar = n-2;
- }else{
- /* Variables of the form $aaa */
- zVar = &z[i+1];
- nVar = n;
- }
- rc = Th_GetVar(g.interp, (char*)zVar, nVar);
- z += i+1+n;
- i = 0;
- zResult = (char*)Th_GetResult(g.interp, &n);
- sendText((char*)zResult, n, n>nVar);
- }else if( z[i]=='<' && isBeginScriptTag(&z[i]) ){
- sendText(z, i, 0);
- z += i+5;
- for(i=0; z[i] && (z[i]!='<' || !isEndScriptTag(&z[i])); i++){}
- rc = Th_Eval(g.interp, 0, (const char*)z, i);
- if( rc!=TH_OK ) break;
- z += i;
- if( z[0] ){ z += 6; }
- i = 0;
- }else{
- i++;
- }
- }
- if( rc==TH_ERROR ){
- sendText("<hr><p><font color=\"red\"><b>ERROR: ", -1, 0);
- zResult = (char*)Th_GetResult(g.interp, &n);
- sendText((char*)zResult, n, 1);
- sendText("</b></font></p>", -1, 0);
- }else{
- sendText(z, i, 0);
- }
- return rc;
- }
- /*
- ** COMMAND: test-th-render
- */
- void test_th_render(void){
- Blob in;
- if( g.argc<3 ){
- usage("FILE");
- }
- blob_zero(&in);
- blob_read_from_file(&in, g.argv[2]);
- Th_Render(blob_str(&in));
- }