-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
409 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
Armadillo | ||
========= | ||
|
||
A small library for [Django-like template inheritance](https://docs.djangoproject.com/en/dev/topics/templates/#template-inheritance) | ||
adapted for ERB. | ||
|
||
Usage | ||
----- | ||
|
||
To render an Armadillo template you need to call the `Armadillo.render` method. | ||
|
||
This method accepts any of the following options: | ||
* `:scope` - Any object you want to bound to the template scope. | ||
* `:base_path` - The path of the directory for which the templates are going to | ||
be searched on. | ||
|
||
Note: A `.erb` extension is assumed for every file and should not be part of | ||
the filename given as the template filename. | ||
|
||
|
||
```ruby | ||
Armadillo.render("myview.html", { :items => [1, 2, 3] }, { | ||
:base_path => File.join(Dir.pwd, "views"), | ||
:scope => self | ||
}) | ||
``` | ||
|
||
```erb | ||
<!-- views/myview.html.erb --> | ||
<% extends("base.html") %> | ||
<% vlock(:title) do %> | ||
<%= current_user.name %> | ||
<% end %> | ||
<% vlock(:body) do %> | ||
<ul> | ||
<% items.each do |item| %> | ||
<li><%= item %></li> | ||
<% end %> | ||
</ul> | ||
<% end %> | ||
<!-- views/base.html.erb --> | ||
<!DOCTYPE> | ||
<html> | ||
<title><% vlock(:title) %> - MyApp</title> | ||
<body> | ||
<% vlock(:body) %> | ||
</body> | ||
</html> | ||
``` | ||
|
||
### Usage example using Cuba | ||
|
||
```ruby | ||
module View | ||
def render_view(template_name, locals = {}) | ||
content = Armadillo.render(template_name, locals, { | ||
:base_path => File.join(APP_PATH, "views"), | ||
:scope => self, | ||
:escape_html => true | ||
}) | ||
res.write(content) | ||
halt(res.finish) | ||
end | ||
end | ||
|
||
on get, root do | ||
render_view("main/index.html", { | ||
:items => [1, 2, 3] | ||
}) | ||
end | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
require "rake/testtask" | ||
|
||
desc "Run all tests" | ||
Rake::TestTask.new(:test) do |t| | ||
t.pattern = "./spec/**/*_spec.rb" | ||
t.verbose = false | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
Gem::Specification.new do |s| | ||
s.name = "armadillo" | ||
s.version = "0.0.1" | ||
s.summary = "Template inheritance with ERB templates" | ||
s.description = "A small library for Django-like template inheritance adapted for ERB" | ||
s.authors = ["Sebastian Borrazas"] | ||
s.email = ["seba.borrazas@gmail.com"] | ||
s.homepage = "http://github.com/sborrazas/armadillo" | ||
s.license = "MIT" | ||
|
||
s.files = Dir[ | ||
"LICENSE", | ||
"README.md", | ||
"Rakefile", | ||
"lib/**/*.rb", | ||
"*.gemspec", | ||
"spec/*.*" | ||
] | ||
|
||
s.require_paths = ["lib"] | ||
|
||
s.add_dependency "tilt" | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,171 @@ | ||
require "tilt" | ||
require "delegate" | ||
|
||
module Armadillo | ||
|
||
VERSION = "0.0.1" | ||
|
||
DEFAULT_OPTIONS = { | ||
:default_encoding => Encoding.default_external, | ||
:outvar => "@_output" | ||
} | ||
|
||
# @api private | ||
class TemplateContext < SimpleDelegator | ||
|
||
# Extend the specified template in which the inner view blocks will be | ||
# rendered. | ||
# | ||
# @note | ||
# This is a template instruction. | ||
# | ||
# @param template_path [String] | ||
# @param locals [Hash] | ||
def extends(template_path, locals = {}) | ||
@extends_data = [template_path, locals] | ||
end | ||
|
||
# Determine the contents or specify the place in which the view block will | ||
# be rendered. | ||
# | ||
# @note | ||
# This is a template instruction. | ||
# | ||
# @param block_name [Symbol] | ||
# @param block [Block] | ||
def vlock(block_name, &block) | ||
raise "Invalid vlock usage" unless current_frame | ||
|
||
if extends? | ||
raise "No block given" unless block_given? | ||
|
||
current_frame[:vlocks][block_name] = block | ||
elsif (frame = get_frame(block_name, current_frame)) | ||
temporary_frame(frame[:parent_frame]) do | ||
frame[:vlocks][block_name].call | ||
end | ||
elsif block_given? | ||
block.call | ||
end | ||
end | ||
|
||
# Create a new frame with previous frame as parent. | ||
def create_frame | ||
@current_frame = { | ||
:vlocks => {}, | ||
:parent_frame => current_frame | ||
} | ||
end | ||
|
||
# Determine if the current template should extend from a new template. | ||
# | ||
# @return [Boolean] | ||
def extends? | ||
!! @extends_data | ||
end | ||
|
||
# Return and delete the extract data. | ||
# | ||
# @return [Array<(String, Hash)>] | ||
# The extended template name and the locals. | ||
def extract_extends_data | ||
@extends_data.tap { @extends_data = nil } | ||
end | ||
|
||
private | ||
|
||
# Get the current frame. Each frame contains the blocks specified using | ||
# #vlock and its parent frame. | ||
# | ||
# @return [Hash] | ||
def current_frame | ||
@current_frame | ||
end | ||
|
||
# Create a temporary current frame for the block to be executed. | ||
# | ||
# @param frame [Hash] | ||
# @param block [Block] | ||
def temporary_frame(frame, &block) | ||
old = current_frame | ||
@current_frame = frame | ||
block.call | ||
@current_frame = old | ||
end | ||
|
||
# Get the block from the frames stack by its name. | ||
# | ||
# @param block_name [Symbol] | ||
def get_frame(block_name, frame) | ||
if frame[:vlocks].has_key?(block_name) | ||
frame | ||
elsif frame[:parent_frame] | ||
get_frame(block_name, frame[:parent_frame]) | ||
end | ||
end | ||
end | ||
|
||
# Render the erb template. | ||
# | ||
# @param template_path [String] | ||
# @param locals [Hash] | ||
# @option options [Object] :scope (Object.new) | ||
# Any object you want to bound to the template scope. | ||
# @option options [String, nil] :base_path (nil) | ||
# The path of the directory for which the templates are going to be | ||
# searched on. | ||
# | ||
# @note | ||
# options also accepts any options offered by the Erubis templating system. | ||
# | ||
# @return [String] | ||
# @api public | ||
def self.render(template_path, locals = {}, options = {}) | ||
scope = options.fetch(:scope) { Object.new } | ||
context = TemplateContext.new(scope) | ||
_render(template_path, locals, context, options) | ||
end | ||
|
||
# Render the erb template with the given context. | ||
# | ||
# @param template_path [String] | ||
# @param context [Armadillo::TemplateContext] | ||
# @param locals [Hash] | ||
# @option options [String] :base_path (nil) | ||
# | ||
# @note | ||
# options also accepts any options offered by the Erubis templating system. | ||
# | ||
# @api private | ||
def self._render(template_path, locals, context, options) | ||
context.create_frame | ||
template_path = "#{template_path}.erb" | ||
if (base_path = options.fetch(:base_path, nil)) | ||
template_path = File.join(base_path, template_path) | ||
end | ||
template = _templates_cache.fetch(template_path) do | ||
Tilt.new(template_path, 1, DEFAULT_OPTIONS.merge(options)) | ||
end | ||
|
||
content = template.render(context, locals) | ||
|
||
if context.extends? | ||
template_path, locals = context.extract_extends_data | ||
content = _render(template_path, locals, context, options) | ||
end | ||
|
||
content | ||
end | ||
private_class_method :_render | ||
|
||
# Get Tilt templates cache. | ||
# | ||
# @return [Tilt::Cache] | ||
# | ||
# @api private | ||
def self._templates_cache | ||
Thread.current[:tilt_cache] ||= Tilt::Cache.new | ||
end | ||
private_class_method :_templates_cache | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
require "minitest/spec" | ||
require "minitest/autorun" | ||
require "armadillo" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
require_relative "spec_helper" | ||
|
||
describe Armadillo do | ||
|
||
TEMPLATES_PATH = File.join(File.dirname(__FILE__), "templates") | ||
|
||
def assert_lines_match(content, lines) | ||
content_lines = content.split("\n") | ||
lines.each do |line| | ||
assert_includes(content_lines, line) | ||
end | ||
end | ||
|
||
describe ".render" do | ||
it "renders a regular erb template" do | ||
locals = { :items => ["a", "b", "c"] } | ||
content = Armadillo.render("basic.text", locals, { | ||
:base_path => TEMPLATES_PATH | ||
}) | ||
assert_lines_match(content, ["Basic", "a", "b", "c"]) | ||
end | ||
|
||
it "renders a one-step inheritance template" do | ||
content = Armadillo.render("one_step_1.text", {}, { | ||
:base_path => TEMPLATES_PATH | ||
}) | ||
assert_lines_match(content, ["Base", "Title", "Subtitle"]) | ||
end | ||
|
||
it "allows parent templates to access locals from #extends" do | ||
locals = { :items => ["a", "b", "c"] } | ||
|
||
content = Armadillo.render("parent_locals_2.text", locals, { | ||
:base_path => TEMPLATES_PATH | ||
}) | ||
assert_lines_match(content, ["Base", locals[:items].first]) | ||
end | ||
|
||
it "renders a two-step inheritance template" do | ||
content = Armadillo.render("two_step_2.text", {}, { | ||
:base_path => TEMPLATES_PATH | ||
}) | ||
assert_lines_match(content, ["Base", "Title", "Subtitle"]) | ||
end | ||
|
||
describe "when reusing child vlocks" do | ||
it "renders them according to the inheritance" do | ||
content = Armadillo.render("nested_two_step_2.text", {}, { | ||
:base_path => TEMPLATES_PATH | ||
}) | ||
assert_lines_match(content, ["Base", "Title - Subtitle"]) | ||
end | ||
end | ||
|
||
describe "when sending a scope object" do | ||
it "access the object methods as locals" do | ||
obj = Object.new | ||
def obj.some_text | ||
"text!" | ||
end | ||
|
||
content = Armadillo.render("scope_object.text", {}, { | ||
:base_path => TEMPLATES_PATH, | ||
:scope => obj | ||
}) | ||
assert_lines_match(content, ["Base", obj.some_text]) | ||
end | ||
end | ||
|
||
describe "when sending :escape_html option" do | ||
it "sanitizes the HTML by default" do | ||
content = Armadillo.render("sanitized.html", {}, { | ||
:base_path => TEMPLATES_PATH, | ||
:escape_html => true | ||
}) | ||
assert_lines_match(content, ["Sanitized &", "Not sanitized &"]) | ||
end | ||
end | ||
end | ||
|
||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Base | ||
<% vlock(:title) %> | ||
<% vlock(:subtitle) %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Basic | ||
<% items.each do |item| %> | ||
<%= item %> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<% extends("base.text") %> | ||
<% vlock(:title) do %> | ||
Title - <% vlock(:title) %> | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
<% extends("nested_two_step_1.text") %> | ||
<% vlock(:title) do %> | ||
Subtitle | ||
<% end %> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<% extends("base.text") %> | ||
<% vlock(:title) do %> | ||
Title | ||
<% end %> | ||
<% vlock(:subtitle) do %> | ||
Subtitle | ||
<% end %> |
Oops, something went wrong.