Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Simple partial implementation #2

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions haml-contrib.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ Gem::Specification.new do |spec|

spec.add_dependency "haml", ">= 3.2.0.alpha.13"
spec.add_development_dependency "minitest"
spec.add_development_dependency "nokogiri"

end
53 changes: 53 additions & 0 deletions lib/haml/layouts.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
require 'haml'
require 'haml/options_ext'

module Haml

Options.add_option :layout_base_dir, Dir.getwd
Options.add_option :layout, 'layout.haml'

class Engine

# TODO: work out the best place to put this documentation.

# A simple layouts implementation.
#
# Renders the Haml template as normal, then renders the file specified
# by +options.layout+ (defaults to +layout.haml+) and inserts the original
# rendered file where +yield+ is called in the layout.
#
# Can also handle +content_for :sym+ blocks which will be inserted in the
# layout when +yield :sym+ is called, similar to Rails.
#
# To specify a different directory to look for layout files use the
# +layout_base_dir+ option, the default is the current working directory.
def render_with_layout(scope = Object.new, locals = {}, &block)
return render_without_layout(scope, locals, &block) unless options[:layout]

regions = {}
scope.instance_variable_set '@layout_regions', regions

layout_file = File.expand_path(options.layout, options.layout_base_dir)
options.layout = nil

unnamed = render_without_layout(scope, locals)

Haml::Engine.new(File.read(layout_file), options.to_hash).render_without_layout(scope, locals) do |*region|
region[0] ? regions[region[0]] : unnamed
end
end
alias :render_without_layout :render
alias :render :render_with_layout
alias :to_html :render_with_layout

end

module Helpers

def content_for(region, &blk)
@layout_regions[region] = capture_haml(&blk)
end

end

end
37 changes: 37 additions & 0 deletions lib/haml/options_ext.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
require 'haml/options'

module Haml

# @private
class Options

def to_hash
self.class.defaults.keys.inject({}) do |hash, key|
hash[key] = send(key)
hash
end
end

def self.add_option(name, default, for_buffer=false)
@defaults[name] = default
attr_accessor name
@buffer_option_keys << name if for_buffer
end

end

class Engine

def render_with_options(scope = Object.new, locals = {}, &block)
# We sometimes need to be able to get the original options when making later
# calls to render (e.g. when rendering partials) so we "hide" them in the scope
# object. (We can't just use the buffer options as we may need _all_ the options.)
scope.instance_variable_set '@_original_options', options unless scope.instance_variable_get '@_original_options'
render_without_options(scope, locals, &block)
end
alias :render_without_options :render
alias :render :render_with_options
alias :to_html :render_with_options
end

end
52 changes: 52 additions & 0 deletions lib/haml/partials.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
require 'haml'
require 'haml/options_ext'

module Haml

Options.add_option :partial_base_dir, Dir.getwd, true

module Helpers

# A simple implementation of partials.
#
# Renders the named Haml file and returns the generated HTML.
#
# The directory to search for partials in can be set with the
# +:partial_base_dir+ option, which defaults to the current working directory.
#
# If the named file is not found, the suffixes +.haml+ and +.html.haml+ are
# added and looked for, and then the same names but with the prefix +_+. The
# first file found will be used.
#
# +self+ is used as the context, so instance variables can be used in the
# partial and will have the same value as the parent template.
#
# This method makes no effort to cache the generated Ruby code, it simply
# uses +Haml::Engine.new().render+ each time. This implementation is intended
# for static site generators or one off document generation, and not as a
# base for a web framework.
#
# @param partial [#to_s] the filename of the partial to render
# @param locals [Hash] a hash of local variables to use in the partial
# @return [String] the HTML generated from the partial
# @raise [StandardError] if the partial file cannot be found

def render(partial, locals = {})
Haml::Engine.new(File.read(find_file_for_partial(partial)), @_original_options.to_hash).render(self, locals)
end

private
def find_file_for_partial(file)
['', '_'].each do |prefix|
['', '.haml', '.html.haml'].each do |suffix|
candidate = File.expand_path "#{prefix}#{file}#{suffix}", haml_buffer.options[:partial_base_dir]
return candidate if File.exist? candidate
end
end

raise "Partial #{file} not found"
end

end

end
5 changes: 5 additions & 0 deletions test/layouts/basic.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
-content_for :title do
Hello

%p
Here’s some content.
1 change: 1 addition & 0 deletions test/layouts/basic_content.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%p.content
2 changes: 2 additions & 0 deletions test/layouts/basic_layout.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
%div.layout
=yield
4 changes: 4 additions & 0 deletions test/layouts/content_for_content.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
- content_for :region do
%span.region Region

%p.content Content
4 changes: 4 additions & 0 deletions test/layouts/content_for_layout.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.content_for
=yield :region

=yield
6 changes: 6 additions & 0 deletions test/layouts/layout.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
!!!
%html
%head
%title= yield :title
%body
= yield
2 changes: 2 additions & 0 deletions test/layouts/layout_uses_same_options_content.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.content
Content
2 changes: 2 additions & 0 deletions test/layouts/layout_uses_same_options_layout.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.layout
=yield
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.content
=render 'basic_layout_with_partial_partial'
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.layout
=yield
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.partial
1 change: 1 addition & 0 deletions test/layouts_and_partials/layout_has_partial_content.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.content Content
6 changes: 6 additions & 0 deletions test/layouts_and_partials/layout_has_partial_layout.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.layout_partial_container
=render 'layout_has_partial_partial'

.layout
=yield

1 change: 1 addition & 0 deletions test/layouts_and_partials/layout_has_partial_partial.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.partial Partial
27 changes: 27 additions & 0 deletions test/layouts_and_partials_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
require "test_helper"
require "haml/layouts"
Haml::Options.defaults[:layout] = nil # turn off layouts so they don't affect other tests
require "haml/partials"

class LayoutsPartialsTest < Minitest::Unit::TestCase

def render(file, options = {})
base_dir = File.expand_path('../layouts_and_partials', __FILE__)
super File.read(File.expand_path(file, base_dir)), options.merge(:layout_base_dir => base_dir, :partial_base_dir => base_dir)
end

def test_basic_partial_and_layout
html = render('basic_layout_with_partial_content.haml', :layout => 'basic_layout_with_partial_layout.haml')

assert_css '.layout > .content > .partial', html
refute_css '.layout > .content > .layout > .partial', html # partial should not have the layout
end

def test_layout_can_have_partial
html = render('layout_has_partial_content.haml', :layout => 'layout_has_partial_layout.haml')

assert_css '.layout_partial_container > .partial', html
assert_css '.layout > .content', html
end

end
38 changes: 38 additions & 0 deletions test/layouts_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
require "test_helper"
require "haml/layouts"
Haml::Options.defaults[:layout] = nil # turn off layouts so they don't affect other tests

class LayoutTest < Minitest::Unit::TestCase

def render(file, options = {})
base_dir = File.expand_path('../layouts', __FILE__)
super File.read(File.expand_path(file, base_dir)), options.merge(:layout_base_dir => base_dir)
end

def test_basic_layout
html = render('basic_content.haml', :layout => 'basic_layout.haml')

assert_match /class='content'/, html # content is rendered
assert_match /class='layout'/, html # layout is rendered
end

def test_content_for
html = render('content_for_content.haml', :layout => 'content_for_layout.haml')

assert_css('div.content_for > span.region', html)
assert_css('p.content', html)
end

def test_layout_uses_same_options
html = render('layout_uses_same_options_content.haml',
:layout => 'layout_uses_same_options_layout.haml',
:attr_wrapper => '"', :remove_whitespace => true)

assert_match /class="layout"/, html
assert_match /class="content"/, html

assert_match /class="layout"><div/, html
assert_match /class="content">Content/, html
end

end
1 change: 1 addition & 0 deletions test/partials/basic.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= render 'basic_partial'
1 change: 1 addition & 0 deletions test/partials/basic_partial.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%p
2 changes: 2 additions & 0 deletions test/partials/instance_vars.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
- @my_instance_var = 42
= render 'instance_vars_partial'
1 change: 1 addition & 0 deletions test/partials/instance_vars_partial.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= @my_instance_var
1 change: 1 addition & 0 deletions test/partials/locals.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= render 'locals_partial', :my_local => 42
1 change: 1 addition & 0 deletions test/partials/locals_partial.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= my_local
1 change: 1 addition & 0 deletions test/partials/options.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
= render 'options_partial'
2 changes: 2 additions & 0 deletions test/partials/options_partial.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
%p.foo
Text
29 changes: 29 additions & 0 deletions test/partials_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
require "test_helper"
require "haml/partials"

class PartialsTest < Minitest::Unit::TestCase

def render(file, options = {})
base_dir = File.expand_path('../partials', __FILE__)
super File.read(File.expand_path(file, base_dir)), options.merge(:partial_base_dir => base_dir)
end

def test_basic_partial
assert_equal "<p></p>\n", render('basic.haml')
end

def test_locals
assert_equal "42\n", render('locals.haml')
end

def test_instance_vars
assert_equal "42\n", render('instance_vars.haml')
end

def test_options
html = render('options.haml', :attr_wrapper => '"', :remove_whitespace => true)

assert_match /"foo"/, html # :attr_wrapper is in options_for_buffer
assert_match />Text</, html # :remove_whitespace isn't in options_for_buffer
end
end
16 changes: 15 additions & 1 deletion test/test_helper.rb
Original file line number Diff line number Diff line change
@@ -1,11 +1,25 @@
require "rubygems"
require "bundler/setup"
require "haml"
require "minitest/autorun"
require "nokogiri"

MiniTest::Unit::TestCase.send :include, Module.new {
def render(text, options = {}, &block)
scope = options.delete(:scope) || Object.new
locals = options.delete(:locals) || {}
Haml::Engine.new(text, options).to_html(scope, locals, &block)
end
}

def assert_css(css, html, msg=nil)
msg = message(msg) {"Expected #{mu_pp html} to match css selector #{mu_pp css}"}
fragment = Nokogiri::HTML.fragment(html)
assert fragment.at_css(css), msg
end

def refute_css(css, html, msg=nil)
msg = message(msg) {"Expected #{mu_pp html} to not match css selector #{mu_pp css}"}
fragment = Nokogiri::HTML.fragment(html)
refute fragment.at_css(css), msg
end
}