Module: Syro::Tilt

Includes:
Cache
Defined in:
lib/syro/tilt.rb,
lib/syro/tilt/cache.rb

Overview

Render Tilt templates in Syro routes.

Defined Under Namespace

Modules: Cache

Constant Summary collapse

DEFAULT_MIME_TYPE =
'text/plain'
DOT =
'.'
EMPTY =
''
HTTP_ACCEPT =
'HTTP_ACCEPT'
MIME_TYPE_ANY =
'*/*'
ACCEPT_CAPTURE_QUALITY =
/\Aq=([\d.]+)/.freeze
ACCEPT_SPLIT_MULTIPLES =
/\s*,\s*/.freeze
ACCEPT_SPLIT_PARTS =
/\s*;\s*/.freeze

Instance Method Summary collapse

Methods included from Cache

template_cache, template_path_cache

Instance Method Details

#content_for(key) ⇒ String

Capture content from a block for use later. Note that capturing the block is not implemented here due to the differences in varying template languages. Erubi::CaptureEndEngine and Hamlit::Block::Engine work well with this method.

Parameters:

  • key (Symbol)

    The content key name.

Returns:

  • (String)

    An empty string if content is provided, otherwise the joined contents of the provided key.



167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/syro/tilt.rb', line 167

def content_for(key)
  inbox[:tilt_content_for] ||= {}
  inbox[:tilt_content_for][key] ||= []

  if block_given?
    inbox[:tilt_content_for][key].push yield

    EMPTY # Returned to prevent the result of #push from displaying.
  else
    inbox[:tilt_content_for].delete(key).join
  end
end

#content_for?(key) ⇒ Boolean

Determine if there’s content to display.

Parameters:

  • key (Symbol)

    The key to check for captured content for.

Returns:

  • (Boolean)

    Have we captured any content for this key?



187
188
189
190
191
192
# File 'lib/syro/tilt.rb', line 187

def content_for?(key)
  inbox[:tilt_content_for] ||= {}
  inbox[:tilt_content_for][key] ||= []

  !inbox[:tilt_content_for][key].empty?
end

#layout(templ = nil) ⇒ String?

Set or get the current layout. A layout is just another template to wrap other templates in. If set, it’ll be used by #render.

Parameters:

  • path (String)

    A path to a template file.

Returns:

  • (String, nil)


131
132
133
134
# File 'lib/syro/tilt.rb', line 131

def layout(templ = nil)
  inbox[:tilt_layout] = templ if templ
  inbox[:tilt_layout]
end

#partial(path, locals = {}) ⇒ String

Generate a string by rendering the Tilt template in the context of ‘self` with the locals that were passed in.

Examples:

partial('posts/show') # => "OMG look at this page!"

Parameters:

  • path (String)

    The path to the view template you’d like to render.

  • locals (Hash) (defaults to: {})

    The local variables to pass to the template.

Options Hash (locals):

  • :from (String)

    The directory to look for templates within.

Returns:

  • (String)


117
118
119
120
121
122
# File 'lib/syro/tilt.rb', line 117

def partial(path, locals = {})
  accept = env.fetch(HTTP_ACCEPT) { EMPTY }
  from = locals.delete(:from) { templates_directory }

  template(template_path(path, from, accept)).render(self, locals) { yield if block_given? }
end

#render(path, locals = {}) ⇒ Object

Render a template to Syro’s #res object and write the appropriate MIME type based on the rendered template.

Parameters:

  • path (String)

    The path to a view file.

  • locals (Hash) (defaults to: {})

    Local variables that should be accessible to the view.

Options Hash (locals):

  • :from (String)

    The directory to look for templates within.



145
146
147
148
149
150
151
152
153
154
# File 'lib/syro/tilt.rb', line 145

def render(path, locals = {})
  accept = env.fetch(HTTP_ACCEPT) { EMPTY }
  from = locals.delete(:from) { templates_directory }

  content = partial(path, locals.dup) { yield if block_given? }
  content = partial(layout, locals.dup) { content } if layout

  res.headers[Rack::CONTENT_TYPE] = template_mime_type(template_path(path, from, accept))
  res.write content
end

#template(path) ⇒ Tilt::Template

A Tilt template for the file path passed in.

Returns:

  • (Tilt::Template)


21
22
23
# File 'lib/syro/tilt.rb', line 21

def template(path)
  ::Tilt.new(path, template_options(::Tilt.template_for(path)))
end

#template_mime_type(path) ⇒ String

Get the MIME type of a template file. The MIME type is looked up from Rack’s MIME type list.

Returns:

  • (String)


97
98
99
100
101
# File 'lib/syro/tilt.rb', line 97

def template_mime_type(path)
  File.basename(path).split(DOT).reverse.map do |ext|
    Rack::Mime::MIME_TYPES[".#{ ext.downcase }"]
  end.compact.first || DEFAULT_MIME_TYPE
end

#template_options(_templ = nil) ⇒ Hash

Options passed to Tilt.new.

Parameters:

  • templ (Class)

    The class of the template engine being used e.g. “Tilt::ErubiTemplate”.

Returns:

  • (Hash)


31
32
33
# File 'lib/syro/tilt.rb', line 31

def template_options(_templ = nil)
  {}
end

#template_path(path, from = nil, accept = nil) ⇒ String

Find a template’s file path based on a “fuzzy” name like “posts/show”. The HTTP Accept header will be checked and the first template found that matches the MIME type of the Accept header will be used, otherwise the first matching template file will be used.

Parameters:

  • path (String)

    A “fuzzy” file path like “posts/show”.

  • from (String) (defaults to: nil)

    The directory to look for templates within.

Returns:

  • (String)

    The path to the template file.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
# File 'lib/syro/tilt.rb', line 54

def template_path(path, from = nil, accept = nil)
  from ||= templates_directory
  path = File.join(from, path)

  # Taken from Rack::Request#parse_http_accept_header (which is a private
  # method).
  accepts = (accept || env.fetch(HTTP_ACCEPT) { EMPTY }).to_s.split(ACCEPT_SPLIT_MULTIPLES).map do |part|
    attribute, parameters = part.split(ACCEPT_SPLIT_PARTS, 2)
    quality =
      if parameters =~ ACCEPT_CAPTURE_QUALITY
        ACCEPT_CAPTURE_QUALITY.match(parameters)[1].to_f
      else
        1.0
      end

    [attribute, quality]
  end

  # Reject "*/*" because it will always match the first thing it is compared
  # to, regardless of wether there's a better match coming up.
  accepts.reject! { |acc, _q| acc == MIME_TYPE_ANY }

  # Find all potential templates e.g. ones with the same name but different
  # template engines or MIME types.
  potentials = Dir.glob(File.join(from, '**', '*')).select do |potential|
    potential.start_with?(path)
  end.sort

  # Select the best potential template match based on MIME type and HTTP
  # Accept header.
  potentials.find do |potential|
    content_type = template_mime_type(potential)

    accepts.any? do |acc, _quality|
      Rack::Mime.match?(content_type, acc)
    end
  end || potentials.first
end

#templates_directoryString

The default directory to look for templates within.

Returns:

  • (String)


38
39
40
# File 'lib/syro/tilt.rb', line 38

def templates_directory
  'views'
end