Cheri is a framework for creating builder applications; that is,
applications that build or represent hierarchical, tree-like, structures, using a
declarative syntax. Cheri includes a number of builders based on the framework, as well as
a builder–builder for easily creating
new builders or extending existing builders. Cheri also comes with a demo application,
Cheri::JRuby::Explorer (CJX),
that is built using two of the supplied builders (Cheri::Swing
and Cheri::Html).
Cheri may be installed as a gem (gem install cheri),
or downloaded as a zip file from the
Cheri project page. The Cheri::Swing
and CJX components require JRuby version 1.0.0RC3 or
later; the other components run on C (MRI) Ruby as well.
Background
Cheri grew out of the JRBuilder
project, a Groovy-SwingBuilder-style builder for JRuby.
JRBuilder worked well enough, but was difficult to extend, or integrate with other builder
components. Cheri is designed to allow multiple builder components to be used together. It
also provides a means to extend builders without subclassing them, so multiple extensions
may be used concurrently. (The Cheri::Swing and Cheri::Html
example on the right demonstrates two builders being used seamlessly.)
Cheri::Swing is the direct descendant of JRBuilder, though it retains little of the original
code. (There is also a Cheri::AWT component, but it exists mostly to support Cheri::Swing.)
Cheri::Html originated as a sort of proof-of-concept trial of the generality of Cheri's
architecture. In fact, it took only about 30 minutes to write a simple Cheri::Html, but the
exercise lead to a number of improvements to the Cheri model (and lead me to embark on a more
complete Cheri::Html). Cheri::Xml came about because, well, the code was pretty much there in
Cheri::Html, so it would have been silly not to include it.
Though it's fairly easy to create new builders using the Cheri framework, I felt an even
simpler mechanism would be useful. The Cheri builder-builder provides a means to create
relatively simple builders without delving into the workings of the Cheri framework. It also
provides a means to extend existing builders. (The extending
Cheri::Swing example on the right demonstrates one way of extending Cheri::Swing with
additional classes.)
Common features
Cheri builders are mixin modules; to use one, you include it in a class. The builder's functionality
is available to instances of that class, and any subclasses (unless the including class is
Object — inclusion in Object / at the top level is supported, but discouraged; inheritance is
disabled in that case).
Cheri builders implement method_missing in the classes in which
they're included; in normal operation, that is how Cheri receives the symbols that are
mapped to class (or method) names for objects to be constructed. (As you will see, however,
it is possible to use Cheri builders without relying on
method_missing.) If no included builder can resolve a symbol received
by method_missing, Cheri calls super to pass
the call along, in case other code inherited by the class relies on it; likewise, if your
class must implement method_missing, it can still work smoothly with Cheri
builders as long as it passes along methods it doesn't consume:
class MyClass
include Cheri::Xml
#...
def method_missing(sym,*args)
if my_sym?(sym)
#...
else
super
end
end
end
All Cheri builders also implement a cheri method, which plays two
roles, depending on how it's called. When called with a block, the cheri
method enables Cheri builder syntax within its scope for all included builders. When called
without a block, it returns a CheriProxy object that can act as a receiver for builder methods,
for any included builder. (For this reason, it's often referred to in Cheri code as a proxy
method, though that isn't completely accurate.)
include Cheri::Swing
#...
@f = frame 'My Frame' #=> NoMethodError
#=> (no builder in scope)
@f = cheri.frame 'My Frame' #=> JFrame instance
cheri {
@f = frame 'My Frame' #=> JFrame instance
}
Each built-in Cheri builder also supplies its own proxy method (in addition to the
cheri method): swing for Cheri::Swing
(and also awt, since Cheri::Swing includes the little-renowned
Cheri::AWT), html for Cheri::Html, and
xml for Cheri::Xml. These methods play the same dual scoping/proxy
roles as the cheri method, but apply only to their respective
builders. (Each also provides additional functionality; see the sections on individual builders
for details.) The builder-specific proxy methods also serve to disambiguate overloaded builder
method names. Consider:
include Cheri::AWT #=> uncommon, but legal
include Cheri::Swing
include Cheri::Html
# HTML frame? Swing frame? AWT frame?
@f = cheri.frame
# (answer: whichever was loaded last)
@af = awt.frame #=> java.awt.Frame
@sf = swing.frame #=> javax.swing.JFrame
@hf = html.frame #=> HTML frame
#=> (Cheri::Html::EmptyElem)
The proxy methods can also be used in any case where a conflict exists with a
defined method with the same name (since method_missing
only works if the method is, um, missing). A common conflict arises with the HTML
p element; I normally undef p
when using Cheri::Html, but could just use html.p instead.
In fact, as noted earlier, it's possible to avoid method_missing
altogether, if you have reason to do so (and are willing to forgo the aesthetic pleasure of
unencumbered builder syntax):
$stdout << html{
html.body{
html.table{
html.tr{html.td 'Hi Mom'}
}}}
Stay tuned…
This is a beta release of Cheri — more features are in the works, and
more documentation is on the way, so be sure to check back from time-to-time. Feel free
to post bugs or questions on the forums or
mailing list at the
Cheri project/download page.
Bill Dortch
22 June 2007
