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:
- The password is stored as a special kind of object, called a user object. User objects are not persisted, so they're safe to use as a password store.
- The parameter expansion mechanism will call this object's ToString method, which yields the actual password.
- The password object is created on demand only. So getPassword is a lazy factory function.
- Data stored via Vars.SetValue always goes into global scope, therefore it can be called from the most nested of blocks in interpreter code.
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.