/// Thursday, October 27
Boo as a template compiler
Lazy Style Code Fu master (ahem) me presents Boo the template compiler.
If you read "custom preprocessor idea for Boo" then now you'll see the start of one of the projects I had in mind. Thanks to the Boo newsgroup I discovered Boo has compiler attributes that have the behaviour I want. I started working on it yesterday and code generation works.
Say you want to generate classes for tables in your databases. First retrieve the database metadata. I represent the metadata returned in a Hash named model. model contains the tables for the database as well as the name and columns for each table.
# controller.boo
import test from "test"
model = {
"tables" : [
{ "name": "user",
"columns": ["user_id", "username", "password"]
},
{ "name": "item",
"columns": ["item_id", "name", "description"]
}
]
}
f = View(model)
print f.RunTemplate()
Don't let the Hash scare you. If you plan to do any AJAX, get used to the JSON format. I much prefer it over XML. You would probably build stongly typed classes for the metadata. JSON is just a quick way to represent objects. Back on topic, staying within the MVC pattern, create a view for model.
# view.boo
import MGutz.CodeInjector from "MGutz.CodeInjector"
[InjectCode(TemplateCodeBuilder, "view.tpl")]
class View:
[property(Model)]
_model as Hash
def constructor(model as Hash):
_model = model
View doesn't do much but accept a model. The magic happens in the compiler. InjectCode is a special attribute instructing the Boo compiler, "invoke me when you compile this class." When the atribute is invoked, it instantiates TemplateCodeBuilder to process the contents of the view.tpl file. TemplateCodeBuilder converts the file into Boo code and it becomes the body for a RunTemplate() method. InjectCode then injects RunTemplate() into the class View.
Look at the view's template using the model.
# view.tpl - template for the view
<% for table as Hash in Model['tables']: %>
class <%= table['name'] %>:
<% for column in table['columns']: %>
[property(<%=column%>)]
_<%=column%> as string
<% end %>
<% end %>
It's ASP(.NET) syntax. Notice how Model is accessible here. You could access the instance variable _model just as well. Remember this template is injected as a function into View, it has access to private methods, private fields, ... like any member of the View. Also note the end keyword. This is an alternate Boo syntax when using it in markup scripts. end is a required keyword in your templates.
Compile this baby. view.boo + view.tpl merges into a single class into test.dll.
> booc -t:library -out:test.dll view.boo
Run the controller.
> booi controller.boo
class user:
[property(user_id)]
_user_id as string
[property(username)]
_username as string
[property(password)]
_password as string
class item:
[property(item_id)]
_item_id as string
[property(name)]
_name as string
[property(description)]
_description as string
Voila! Instant classes for your database. Remember, the template is compiled. Think of a template as a function in an abstract format. You now have a compiled class that executes your template without any run-time parsing, merging, etc. This is ideal not only for code generation but also for views in a web framework.
I'll finish up by saying Boo is such a wonderful language. It's what VB.NET should have been. Even down at the compiler pipeline level you just sense how solid Boo is.
If you're wondering what the generated code for View.RunTemplate() looks like.
def RunTemplate() as string:
out = System.Text.StringBuilder()
for table as Hash in Model['tables']:
out.Append("class ")
out.Append( table['name'] )
out.Append(":\r\n")
for column in table['columns']:
out.Append("\t")
out.Append("[property(")
out.Append(column)
out.Append(")]\r\n")
out.Append("\t")
out.Append("_")
out.Append(column)
out.Append(" as string\r\n\r\n")
end
end
out.Append("\r\n\r\n")
return out.ToString()
end
Comments:
<< Home
