Module: Tina4::Template

Defined in:
lib/tina4/template.rb

Defined Under Namespace

Classes: ErbEngine, TwigEngine

Constant Summary collapse

TEMPLATE_DIRS =
%w[templates src/templates src/views views].freeze

Class Method Summary collapse

Class Method Details

.add_global(key, value) ⇒ Object



56
57
58
# File 'lib/tina4/template.rb', line 56

def add_global(key, value)
  globals[key.to_s] = value
end

.globalsObject



52
53
54
# File 'lib/tina4/template.rb', line 52

def globals
  @globals ||= {}
end

.render(template_path, data = {}) ⇒ Object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/tina4/template.rb', line 60

def render(template_path, data = {})
  full_path = resolve_path(template_path)
  unless full_path && File.exist?(full_path)
    raise "Template not found: #{template_path}"
  end

  content = File.read(full_path, encoding: "utf-8")
  ext = File.extname(full_path).downcase
  context = globals.merge(data.transform_keys(&:to_s))

  case ext
  when ".twig", ".html", ".tina4"
    TwigEngine.new(context, File.dirname(full_path)).render(content)
  when ".erb"
    ErbEngine.render(content, context)
  else
    TwigEngine.new(context, File.dirname(full_path)).render(content)
  end
end

.render_error(code, data = {}) ⇒ Object



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
# File 'lib/tina4/template.rb', line 80

def render_error(code, data = {})
  error_dirs = TEMPLATE_DIRS.map { |d| File.join(Dir.pwd, d, "errors") }
  error_dirs << File.join(File.dirname(__FILE__), "templates", "errors")

  context = { "code" => code }.merge(data.transform_keys(&:to_s))

  error_dirs.each do |dir|
    %w[.twig .html .erb].each do |ext|
      path = File.join(dir, "#{code}#{ext}")
      if File.exist?(path)
        # encoding: "utf-8" (feature 42, ERR-DEC-01): 403.twig ships an
        # em-dash. Without a forced encoding, File.read tags the string
        # with Encoding.default_external, which is UTF-8 on a typical
        # dev machine but US-ASCII on a bare/minimal locale (no LANG/
        # LC_ALL - common in a container) - and Ruby's regex engine then
        # raises ArgumentError: invalid byte sequence in US-ASCII the
        # moment TwigEngine tries to match against it. render_error's
        # caller used to `rescue` broadly and silently fall back to a
        # bare string, so every 403 request on such a host rendered NO
        # template at all and nobody noticed. Matches lib/tina4/frond.rb,
        # which already reads its templates this way.
        content = File.read(path, encoding: "utf-8")
        return TwigEngine.new(context, dir).render(content)
      end
    end
  end
  default_error_html(code)
end

.wants_json?(accept) ⇒ Boolean

Content negotiation for an error response (feature 42, ERR-DEC-02): does an Accept header prefer application/json over text/html?

Accept: application/json (an API client) prefers JSON; a browser Accept (text/html, */*, or no header at all) prefers HTML. A mixed Accept header - a real browser's text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 - is resolved by q-value: whichever of the two media types this method cares about is weighted higher wins; a tie or neither present defaults to HTML, the historical/back-compatible behaviour for an unspecified client. This is the ONE shared decision reused by the 403/404/500 error paths (#render_error's callers in rack_app.rb and middleware.rb), so a JSON API client sees the SAME negotiated shape everywhere (ERR-403-SPLIT) - ported with the same algorithm to Python/PHP/Node.

Returns:

  • (Boolean)


22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/tina4/template.rb', line 22

def wants_json?(accept)
  accept = accept.to_s
  return false if accept.empty?

  best_json = -1.0
  best_html = -1.0
  accept.split(",").each do |part|
    segments = part.strip.split(";")
    media = segments[0].to_s.strip.downcase
    q = 1.0
    segments[1..].each do |param|
      param = param.strip
      next unless param.start_with?("q=")

      q_str = param[2..]
      q = q_str.to_f if q_str =~ /\A-?\d+(\.\d+)?\z/
    end
    if media == "application/json"
      best_json = q if q > best_json
    elsif ["text/html", "*/*", "application/xhtml+xml"].include?(media)
      best_html = q if q > best_html
    end
  end

  return false if best_json.negative?
  return true if best_html.negative?

  best_json > best_html
end