File: context.rb

package info (click to toggle)
ruby-mustache 1.1.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, bullseye, sid, trixie
  • size: 480 kB
  • sloc: ruby: 2,267; makefile: 2
file content (173 lines) | stat: -rw-r--r-- 5,261 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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
51
52
53
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
require 'mustache/context_miss'

class Mustache

  # A Context represents the context which a Mustache template is
  # executed within. All Mustache tags reference keys in the Context.
  #
  class Context

    # Initializes a Mustache::Context.
    #
    # @param [Mustache] mustache A Mustache instance.
    #
    def initialize(mustache)
      @stack = [mustache]
      @partial_template_cache = {}
    end

    # A {{>partial}} tag translates into a call to the context's
    # `partial` method, which would be this sucker right here.
    #
    # If the Mustache view handling the rendering (e.g. the view
    # representing your profile page or some other template) responds
    # to `partial`, we call it and render the result.
    #
    def partial(name, indentation = '')
      # Look for the first Mustache in the stack.
      mustache = mustache_in_stack

      # Indent the partial template by the given indentation.
      part = mustache.partial(name).to_s.gsub(/^/, indentation)

      # Get a template object for the partial and render the result.
      template_for_partial(part).render(self)
    end

    def template_for_partial(partial)
      @partial_template_cache[partial] ||= Template.new(partial)
    end

    # Find the first Mustache in the stack.
    #
    # If we're being rendered inside a Mustache object as a context,
    # we'll use that one.
    #
    # @return [Mustache] First Mustache in the stack.
    #
    def mustache_in_stack
      @mustache_in_stack ||= @stack.find { |frame| frame.is_a?(Mustache) }
    end

    # Allows customization of how Mustache escapes things.
    #
    # @param [Object] value Value to escape.
    #
    # @return [String] Escaped string.
    #
    def escape(value)
      mustache_in_stack.escape(value)
    end

    # Adds a new object to the context's internal stack.
    #
    # @param [Object] new_obj Object to be added to the internal stack.
    #
    # @return [Context] Returns the Context.
    #
    def push(new_obj)
      @stack.unshift(new_obj)
      @mustache_in_stack = nil
      self
    end

    # Removes the most recently added object from the context's
    # internal stack.
    #
    # @return [Context] Returns the Context.
    #
    def pop
      @stack.shift
      @mustache_in_stack = nil
      self
    end

    # Can be used to add a value to the context in a hash-like way.
    #
    # context[:name] = "Chris"
    def []=(name, value)
      push(name => value)
    end

    # Alias for `fetch`.
    def [](name)
      fetch(name, nil)
    end

    # Do we know about a particular key? In other words, will calling
    # `context[key]` give us a result that was set. Basically.
    def has_key?(key)
      fetch(key, false)
    rescue ContextMiss
      false
    end

    # Similar to Hash#fetch, finds a value by `name` in the context's
    # stack. You may specify the default return value by passing a
    # second parameter.
    #
    # If no second parameter is passed (or raise_on_context_miss is
    # set to true), will raise a ContextMiss exception on miss.
    def fetch(name, default = :__raise)
      @stack.each do |frame|
        # Prevent infinite recursion.
        next if frame == self

        value = find(frame, name, :__missing)
        return value if :__missing != value
      end

      if default == :__raise || mustache_in_stack.raise_on_context_miss?
        raise ContextMiss.new("Can't find #{name} in #{@stack.inspect}")
      else
        default
      end
    end

    # Finds a key in an object, using whatever method is most
    # appropriate. If the object is a hash, does a simple hash lookup.
    # If it's an object that responds to the key as a method call,
    # invokes that method. You get the idea.
    #
    # @param [Object] obj The object to perform the lookup on.
    # @param [String,Symbol] key The key whose value you want
    # @param [Object] default An optional default value, to return if the key is not found.
    #
    # @return [Object] The value of key in object if it is found, and default otherwise.
    #
    def find(obj, key, default = nil)
      return find_in_hash(obj.to_hash, key, default) if obj.respond_to?(:to_hash)

      unless obj.respond_to?(key)
        # no match for the key, but it may include a hyphen, so try again replacing hyphens with underscores.
        key = key.to_s.tr('-', '_')
        return default unless obj.respond_to?(key)
      end

      meth = obj.method(key) rescue proc { obj.send(key) }
      meth.arity == 1 ? meth.to_proc : meth.call
    end

    def current
      @stack.first
    end


    private

    # Fetches a hash key if it exists, or returns the given default.
    def find_in_hash(obj, key, default)
      return obj[key]      if obj.has_key?(key)
      return obj[key.to_s] if obj.has_key?(key.to_s)
      return obj[key]      if obj.respond_to?(:default_proc) && obj.default_proc && obj[key]

      # If default is :__missing then we are from #fetch which is hunting through the stack
      # If default is nil then we are reducing dot notation
      if :__missing != default && mustache_in_stack.raise_on_context_miss?
        raise ContextMiss.new("Can't find #{key} in #{obj}")
      else
        obj.fetch(key, default)
      end
    end
  end
end