[Journal - Scripting Session]

Scripting Session

Saturday, July 23, 2005

Today, I'm just playing with new .NET Console capabilities. I'm running WebEdit.NET, and my aim is to access an ODBC database, perform a query, and create an HTML table from it.

First, let's make tracing easier:

tr(obj)
{
    WebEditApp.Trace(obj);
}

Next, a function that shovels data to a new document:

toNewDoc(obj)
{
    if(WebEditApp.DocumentManager.OpenNewDocument()){
        Vars.TextAll = Conv.ObjectToString(obj);
    }
    tr(null);
}

Exercise: what is the trace call for?

To access an ODBC database, I have installed the DataDocs AddIn. I've defined a data source called "BloodBank". The handy ReadStorage method makes WebEdit's extensible document storage mechanism easy to use from code. So, wrapping it up, an "sql" command of the "data:" protocol is issued:

readData(sSql)
{
    sCmd = string.Concat("data:sql? ", sSql, " @BloodBank");
    WebEditApp.DocumentManager.ReadStorage(sCmd);
}

Next, we need to cross one of those domain boundaries - data to markup:

tableLines(asLines)
{
    sb = new StringBuilder();
    sb.Append("<table>");
    sb.Append(Environment.NewLine);
    foreach(sLine in asLines){
        if(Flow.IsString(sLine)){
            sb.Append("<tr>");
            aStr = sLine.Split(Chars.Tab);
            foreach(sCol in aStr){
                sb.Append("<td>");
                sb.Append(sCol);
                sb.Append("</td>");
            }
            sb.Append("</tr>");
        }
        // keep cosmetic blank lines
        sb.Append(Environment.NewLine);
    }
    sb.Append("</table>");
    sb.Append(Environment.NewLine);
    sb.ToString();
}

That done, we can call run the query:

toNewDoc(tableLines(Parse.SplitToLines(readData("select * from Donor"))))

Note that WebEdit optionally persists user functions, so it's enough to execute "code:Vars.Eval(Vars.TextAll)" just once.

Passwords

Now suppose our database requires a password. With ADO.NET, we use a connection string, but we don't want to persist the password within it. The good news is that the DataDocs AddIn support parameters in the connection string:

Password="$getPassword()%"

WebEdit's parameter dialog does not mask out passwords. And since WebEdit persists code interpreter string variables as well (optionally), it's not a good idea to use a string variable, either. Therefore, a user function call.

Update (Sunday, August 14, 2005): from now on, the parameter dialog does mask out parameters starting with a Plus sign (ig., $+Password%); see the static Gregor.AppCore.CParameterTemplate.MaskPrefix property).

Here's getPassword:

getPassword()
{
    ret = null;
    sName = "BloodBankPassword";
    if(Vars.ExistsValue(sName)){
        ret = Vars.GetValue(sName);
    }else{
        frm = new Gregor.UICore.Dialogs.FInput("Password", "Enter Password:", string.Empty);
        frm.PasswordChar = '*';
        frm.ShowDialog();
        if(Flow.IsString(frm.UserText)){
            ToString(){
                this.GetProperty("value");
            }
            ret = UserObject.Create(ToString);
            ret.AddProperty("value", frm.UserText);
            Vars.SetValue(sName, ret);
        }
    }
    ret;
}

A few observations:

I haven't talked about user objects so far - these are basically instances of a special class defined in the Gregor.NetConsole assembly. User objects reference user functions that act as methods. These user functions are typically nested in a factory function:

createPoint(x, y)
{
    ToString(){
        string.Concat('{', this.GetProperty("x"), ',', this.GetProperty("y"),'}');
    }

    ret = UserObject.Create(ToString);

    ret.AddProperty("x", x);
    ret.AddProperty("y", y);

    ret;
}

User objects also support a few standard interfaces, such as IDisposable, provided the corresponding methods are present. Virtual methods defined by System.Object may also be overridden. There's even a little inheritance (all methods are virtual) and flexible property support. More on user objects later.