= Undies
A pure-Ruby DSL for streaming templated HTML, XML, or plain text. Named for its gratuitous use of the underscore.
== Installation
$ gem install undies
== DSL
Undies uses an underscore-based DSL for rendering content. It can render plain text, html-escaped text, xml, and html.
=== Plain text
Escaped (xml / html) text:
_ "this will be escaped & streamed"
# => "this will be escaped & streamed"
Raw (un-escaped) text:
__ "this will not be escaped"
# => "this will not be escaped"
=== XML
Empty node:
_thing # => ""
Node with content:
_thing {
_ "Some Content"
_ "More Content "
} # => "Some ContentMore Content"
Node with attributes:
_thing(:one => 1, 'a' => "Aye")
# => ""
Nested nodes:
_thing {
_Something {
_ "Some Content"
}
} # => "Some Content"
Namespaces / Doctype stuff:
__ ""
_thing('xmlns:ss="urn:some-cool-namespace"') {
send("_ss:Value", {'ss:some_attr' => "some attr value"}) {
_ "Some Content"
}
} # => "Some Content"
Comments:
__ ""
# => ""
=== HTML
Same stuff applies:
__ ""
_div(:style => "border-top: 1px") {
_h1 { _ "Hello World" }
_p { _ "blah blah" }
} # => ""
id attribute helper:
_h1.header!
# => ""
_h1.header!.title!
# => ""
class attributes helper:
_h1.header.awesome
# => ""
use in combo:
_h1.header!.awesome
# => "
== Streamed Output
Undies works by streaming content defined by using its DSL to a given output stream. Any call to Template#_, Template#__, or Tempate#_ will stream its output (or you can write helpers you mix in that use these base api methods). This has a number of advantages:
* content is written out immediately
* maintain a relatively low memory profile while rendering
* can process large templates with linear performance impact
However, because content is streamed then forgotten as it is being rendered, Undies templates cannot be self-referrential. No one element may refer to other previously rendered elements.
== Rendering
To render using Undies, create a Template instance, providing the template source, data, and output information.
source = Undies::Source.new("/path/to/sourcefile")
data = { :two_plus_two => 4 }
output = Undies::Output.new(@some_io_stream)
Undies::Template.new(source, {}, output)
=== Source
You specify Undies source using the Undies::Source object. You can create source either form a block or a file. Source content (either block or file) will be evaluated in context of the template.
=== Data
Undies renders source content in isolated scope (the context of the template). This means that content has access to only the data it is given or the Undies API itself. When you define a template for rendering, you provide not only the template source, but any data that source should be rendered with. Data is given in the form of a Hash. The string form of the hash keys are exposed as local methods that return their corresponding values.
=== Output
As said before, Undies streams to a given output stream. You specify a Template's output by creating an Undies::Output object. These objects take an io stream and a hash of options:
* :pp (pretty-print) : set to a Fixnum to tab-space indent pretty print the output.
=== Examples
# file source, no local data, no pretty printing
source = Undies::Source.new("/path/to/source")
Undies::Template.new(source, {}, Undies::Output.new(@io))
# proc source, simple local data, no pretty printing
source = Undies::Source.new(Proc.new do
_div {
_ content.to_s
}
end)
Undies::Template.new(source, {:content => "Some Content!!" }, Undies::Output.new(@io))
# pretty printing (4 space tab indentation)
source = Undies::Source.new("/path/to/source")
Undies::Template.new(source, {}, Undies::Output.new(@io, :pp => 4))
=== Builder approach
The above examples use the "source rendering" approach. This works great when you know your source content before render time and create a source object from it (ie rendering a view template). However, in some cases, you may not know the source until render time and/or want to use a more declarative style to specify render output. Undies content can be specified programmatically using the "builder rendering" approach.
To render using this approach, create a Template instance passing it data and output info as above. However, don't pass in any source info, only pass in any local data if you like, and save off the created template:
# choosing not to use any local data in this example
template = Undies::Template.new(Undies::Output.new(@io))
Now just interact with the Undies API directly.
# notice that it becomes less important to bind any local data to the Template using this approach
something = "Some Thing!"
template._div.something! {
template._ something.to_s
}
*Note:* there is one extra caveat to be aware of using this approach. You need to be sure and flush the template when content processing is complete. Just pass the template to the Undies::Template#flush method:
# ensures all content is streamed to the template's output stream
# this is necessary when not using the source approach above
Undies::Template.flush(template)
=== Manual approach
There is another method you can use to render output: the Manual approach. Like the Builder approach, this method is ideal when you don't know the source until render time. The key difference is that blocks are not used to imply nesting relationships. Using this approach, you manually 'push' and 'pop' to move up and down nesting relationship contexts. So a push on an element would move the template context to the element pushed. A pop would move back to the current context's parent element. As you would expect, pop'ing on the root of a template has no effect on the context and pushing a non-element node has no effect on the context.
To render using this approach, create a Template as you would with the Builder approach. Interact with the Undies API directly. Use the Template#push and Template#pop class methods to change the template scope.
# this is the equivalent to the Builder approach example above
template = Undies::Template.new(Undies::Output.new(@io))
something = "Some Thing!"
current = template._div.something!
template.__push(current)
template._ something.to_s
template.__pop
# alternate method for flushing a template
template.__flush
*Note:* as with the Builder approach, you must flush the template when content processing is complete.
== License
Copyright (c) 2011-Present Kelly Redding
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.