mullet 0.0.0 → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. data/lib/mullet/container.rb +8 -4
  2. data/lib/mullet/default_model.rb +9 -9
  3. data/lib/mullet/default_nested_model.rb +7 -6
  4. data/lib/mullet/html/attribute_command.rb +8 -4
  5. data/lib/mullet/html/attributes.rb +22 -0
  6. data/lib/mullet/html/command.rb +4 -3
  7. data/lib/mullet/html/command_element_renderer.rb +19 -0
  8. data/lib/mullet/html/element.rb +41 -0
  9. data/lib/mullet/html/element_renderer.rb +261 -0
  10. data/lib/mullet/html/filtered_element_handler.rb +87 -0
  11. data/lib/mullet/html/for_element_renderer.rb +47 -0
  12. data/lib/mullet/html/if_element_renderer.rb +46 -0
  13. data/lib/mullet/html/layout.rb +48 -0
  14. data/lib/mullet/html/message.rb +55 -0
  15. data/lib/mullet/html/message_attribute_command.rb +30 -0
  16. data/lib/mullet/html/model_attribute_command.rb +30 -0
  17. data/lib/mullet/html/page_builder.rb +152 -0
  18. data/lib/mullet/html/parser/attribute.rb +8 -0
  19. data/lib/mullet/html/parser/constants.rb +1061 -0
  20. data/lib/mullet/html/parser/default_handler.rb +27 -0
  21. data/lib/mullet/html/parser/input_stream.rb +711 -0
  22. data/lib/mullet/html/parser/open_element.rb +77 -0
  23. data/lib/mullet/html/parser/simple_parser.rb +128 -0
  24. data/lib/mullet/html/parser/tokenizer.rb +1085 -0
  25. data/lib/mullet/html/remove_mode.rb +30 -0
  26. data/lib/mullet/html/static_text_renderer.rb +20 -0
  27. data/lib/mullet/html/template.rb +44 -0
  28. data/lib/mullet/html/template_builder.rb +208 -63
  29. data/lib/mullet/html/template_loader.rb +77 -39
  30. data/lib/mullet/html/template_parser.rb +48 -0
  31. data/lib/mullet/html/unless_element_renderer.rb +24 -0
  32. data/lib/mullet/model.rb +2 -5
  33. data/lib/mullet/render_context.rb +24 -18
  34. data/lib/mullet/tilt.rb +37 -0
  35. data/lib/mullet/version.rb +2 -1
  36. data/lib/mullet.rb +1 -0
  37. metadata +58 -11
@@ -0,0 +1,30 @@
1
+ require 'mullet/template_error'
2
+
3
+ module Mullet; module HTML
4
+
5
+ # Specifies what to remove.
6
+ class RemoveMode
7
+ # remove tag, and preserve children of element
8
+ TAG = RemoveMode.new()
9
+
10
+ # preserve tag, and remove children of element
11
+ CONTENT = RemoveMode.new()
12
+
13
+ # remove tag and children of element
14
+ ELEMENT = RemoveMode.new()
15
+
16
+ def self.value_of(argument)
17
+ string = argument.downcase()
18
+ if string == 'element'
19
+ return ELEMENT
20
+ elsif string == 'tag'
21
+ return TAG
22
+ elsif string == 'content'
23
+ return CONTENT
24
+ else
25
+ raise TemplateError.new("invalid remove argument '%{argument}'")
26
+ end
27
+ end
28
+ end
29
+
30
+ end; end
@@ -0,0 +1,20 @@
1
+ module Mullet; module HTML
2
+
3
+ # Renders static markup and text. May end with an unclosed start tag under
4
+ # the assumption a subsequent static text fragment closes the tag.
5
+ class StaticTextRenderer
6
+
7
+ # Constructor
8
+ #
9
+ # @param [String] text
10
+ # rendered tags and text
11
+ def initialize(text)
12
+ @text = text
13
+ end
14
+
15
+ def render(render_context)
16
+ render_context << @text
17
+ end
18
+ end
19
+
20
+ end; end
@@ -0,0 +1,44 @@
1
+ require 'mullet/container'
2
+ require 'mullet/render_context'
3
+
4
+ module Mullet; module HTML
5
+
6
+ # Template containing static text and dynamically generated content.
7
+ class Template
8
+ include Container
9
+
10
+ RETURN_EMPTY_STRING = Proc.new { '' }
11
+
12
+ def initialize()
13
+ super
14
+ @on_missing = RETURN_EMPTY_STRING
15
+ @on_nil = RETURN_EMPTY_STRING
16
+ end
17
+
18
+ def on_missing(strategy)
19
+ @on_missing = strategy
20
+ return self
21
+ end
22
+
23
+ def on_nil(strategy)
24
+ @on_nil = strategy
25
+ return self
26
+ end
27
+
28
+ def render(render_context)
29
+ render_children(render_context)
30
+ end
31
+
32
+ # Renders the template.
33
+ #
34
+ # @param [Object] data
35
+ # provides data to render
36
+ # @param [#<<] output
37
+ # where to write rendered output
38
+ def execute(data, output)
39
+ render_context = RenderContext.new(data, @on_missing, @on_nil, output)
40
+ render(render_context)
41
+ end
42
+ end
43
+
44
+ end; end
@@ -1,46 +1,65 @@
1
- require 'nokogiri'
1
+ require 'mullet/html/attributes'
2
2
  require 'mullet/html/command'
3
+ require 'mullet/html/element'
4
+ require 'mullet/html/element_renderer'
5
+ require 'mullet/html/for_element_renderer'
6
+ require 'mullet/html/if_element_renderer'
7
+ require 'mullet/html/parser/default_handler'
8
+ require 'mullet/html/parser/simple_parser'
9
+ require 'mullet/html/remove_mode'
10
+ require 'mullet/html/static_text_renderer'
11
+ require 'mullet/html/template'
12
+ require 'mullet/html/unless_element_renderer'
13
+ require 'mullet/template_error'
3
14
  require 'set'
4
15
 
5
16
  module Mullet; module HTML
6
17
 
7
18
  # Handles SAX events to build a template.
8
- class TemplateBuilder < Nokogiri::XML::SAX::Document
19
+ class TemplateBuilder < Parser::DefaultHandler
9
20
  include Command
10
21
 
11
- XMLNS_ATTRIBUTE_PREFIX = "xmlns:"
12
- COMMANDS = [
13
- ATTRIBUTE,
14
- ATTRIBUTE_MESSAGE,
15
- CONTENT,
22
+ COMMANDS = Set[
23
+ ACTION,
24
+ ALT,
25
+ ALT_MESSAGE,
26
+ ATTR,
27
+ ATTR_MESSAGE,
16
28
  ESCAPE_XML,
17
29
  FOR,
30
+ HREF,
18
31
  IF,
19
32
  INCLUDE,
33
+ REMOVE,
34
+ SRC,
20
35
  TEXT,
21
36
  TEXT_MESSAGE,
22
- UNLESS].to_set
23
- START_CDATA = "<![CDATA["
24
- END_CDATA = "]]>"
37
+ TITLE,
38
+ TITLE_MESSAGE,
39
+ UNLESS,
40
+ VALUE,
41
+ VALUE_MESSAGE]
42
+ START_CDATA = '<![CDATA['
43
+ END_CDATA = ']]>'
25
44
 
26
- @loader = nil
45
+ attr_reader :template
27
46
 
28
- # This is a stack of elements where this handler has seen the start tag and
29
- # not yet seen the end tag.
30
- @openElements = []
31
-
32
- # stack of current containers to add renderers to
33
- @containers = []
34
-
35
- @staticText = ""
36
- @template = nil
37
-
38
47
  # Constructor
39
48
  #
40
49
  # @param [TemplateLoader] loader
41
50
  # template loader to use to load included template files
42
51
  def initialize(loader)
43
52
  @loader = loader
53
+
54
+ # Stack of elements where this handler has seen the start tag and not yet
55
+ # seen the end tag.
56
+ @open_elements = []
57
+
58
+ # stack of current containers to add renderers to
59
+ @containers = []
60
+
61
+ @static_text = ''
62
+ @template = nil
44
63
  end
45
64
 
46
65
  # Adds renderer to current container.
@@ -48,7 +67,7 @@ module Mullet; module HTML
48
67
  # @param [#render] renderer
49
68
  # renderer to add
50
69
  def add_child(renderer)
51
- @containers.last.add_child(renderer)
70
+ @containers.last().add_child(renderer)
52
71
  end
53
72
 
54
73
  # Deletes renderer from current container.
@@ -56,7 +75,7 @@ module Mullet; module HTML
56
75
  # @param [#render] renderer
57
76
  # renderer to delete
58
77
  def delete_child(renderer)
59
- @containers.last.delete_child(renderer)
78
+ @containers.last().delete_child(renderer)
60
79
  end
61
80
 
62
81
  # Partitions the attributes into ordinary and command attributes.
@@ -65,77 +84,203 @@ module Mullet; module HTML
65
84
  # input attributes
66
85
  # @param [Hash] ns
67
86
  # hash of namespace prefix to uri mappings
68
- # @param [#store] ordinaryAttributes
87
+ # @param [#store] ordinary_attributes
69
88
  # hash will receive name to value mappings for ordinary attributes
70
- # @param [#store] commandAttributes
89
+ # @param [#store] command_attributes
71
90
  # hash will receive name to value mappings for command attributes
72
91
  # @return [Boolean] true if any command attribute found
73
- def find_commands(attributes, ns, ordinaryAttributes, commandAttributes)
74
- foundCommand = false
92
+ def find_commands(attributes, ns, ordinary_attributes, command_attributes)
93
+ found_command = false
75
94
  attributes.each do |attr|
76
- if attr.uri == NAMESPACE_URI
77
- commandName = attr.localname
78
- if !COMMANDS.contains(commandName)
79
- raise TemplateException("invalid command '#{commandName}'")
95
+ if attr.localname.start_with?(DATA_PREFIX)
96
+ command_name = attr.localname.slice(DATA_PREFIX.length()..-1)
97
+ if COMMANDS.include?(command_name)
98
+ command_attributes.store(command_name, attr.value)
99
+ found_command = true
100
+ end
101
+ elsif attr.uri == NAMESPACE_URI || attr.prefix == NAMESPACE_PREFIX
102
+ command_name = attr.localname
103
+ if !COMMANDS.include?(command_name)
104
+ raise TemplateError("invalid command '#{command_name}'")
80
105
  end
81
- commandAttributes.store(commandName, attr.value)
82
- foundCommand = true
106
+ command_attributes.store(command_name, attr.value)
107
+ found_command = true
83
108
  else
84
- attributeName = [attr.prefix, attr.localname].compact.join(':')
85
- ordinaryAttributes.store(attributeName, attr.value)
109
+ attribute_name = [attr.prefix, attr.localname].compact().join(':')
110
+ ordinary_attributes.store(attribute_name, attr.value)
86
111
  end
87
112
  end
88
113
 
89
114
  ns.each do |prefix, uri|
90
115
  if uri != NAMESPACE_URI
91
- attributeName = ['xmlns', prefix].compact.join(':')
92
- ordinaryAttributes.store(attributeName, uri)
116
+ attribute_name = ['xmlns', prefix].compact().join(':')
117
+ ordinary_attributes.store(attribute_name, uri)
93
118
  end
94
119
  end
95
120
 
96
- return foundCommand
121
+ return found_command
97
122
  end
98
123
 
99
- def render_start_tag(name, attributes, prefix, uri, namespaceDecls)
100
- tag = "<"
101
- if prefix
102
- tag << prefix << ":"
124
+ def append_static_text(data)
125
+ @static_text << data
126
+ end
127
+
128
+ def end_static_text()
129
+ if !@static_text.empty?()
130
+ add_child(StaticTextRenderer.new(@static_text))
131
+ @static_text = ''
132
+ end
133
+ end
134
+
135
+ # Marks the most deeply nested open element as having content.
136
+ def set_has_content()
137
+ if !@open_elements.empty?()
138
+ @open_elements.last().has_content = true
139
+ end
140
+ end
141
+
142
+ def start_document()
143
+ @template = Template.new()
144
+ @containers.push(@template)
145
+ end
146
+
147
+ def end_document()
148
+ end_static_text()
149
+ end
150
+
151
+ def doctype(name, public_id, system_id)
152
+ text = "<!DOCTYPE #{name}"
153
+
154
+ if public_id
155
+ text << %( PUBLIC "#{public_id}")
156
+ end
157
+
158
+ if system_id
159
+ text << %( "#{system_id}")
160
+ end
161
+
162
+ text << '>'
163
+ append_static_text(text)
164
+ end
165
+
166
+ def create_element_renderer(element, command_attributes)
167
+ renderer = nil
168
+
169
+ variable_name = command_attributes.fetch(IF, nil)
170
+ if variable_name != nil
171
+ renderer = IfElementRenderer.new(element, variable_name)
103
172
  end
104
- tag << name
105
173
 
106
- attributes.each do |attribute|
107
- tag << " "
108
- if attribute.prefix
109
- tag << attribute.prefix << ":"
174
+ if renderer == nil
175
+ variable_name = command_attributes.fetch(UNLESS, nil)
176
+ if variable_name != nil
177
+ renderer = UnlessElementRenderer.new(element, variable_name)
110
178
  end
111
- tag = attribute.localname << '="' << attribute.value << '"'
112
179
  end
113
180
 
114
- namespaceDecls.each do |namespaceDecl|
115
- uri = namespaceDecl[1]
116
- if uri != TEMPLATE_NAMESPACE_URI
117
- tag << " xmlns:" << namespaceDecl[0] << '="' << uri << '"'
181
+ if renderer == nil
182
+ variable_name = command_attributes.fetch(FOR, nil)
183
+ if variable_name != nil
184
+ renderer = ForElementRenderer.new(element, variable_name)
118
185
  end
119
186
  end
120
187
 
121
- tag << ">"
122
- return tag
188
+ if renderer == nil
189
+ renderer = ElementRenderer.new(element)
190
+ end
191
+
192
+ renderer.configure_commands(command_attributes, @loader)
193
+ return renderer
123
194
  end
124
195
 
125
196
  def start_element_namespace(name, attributes, prefix, uri, ns)
126
- puts "start element #{name} #{attributes} #{prefix} #{uri} #{ns}"
127
- templateAttributes = attributes.select do |attribute|
128
- attribute.uri == TEMPLATE_NAMESPACE_URI
129
- end
130
- if templateAttributes.empty?
131
- puts render_start_tag(name, attributes, prefix, uri, ns)
197
+ set_has_content()
198
+
199
+ ordinary_attributes = Attributes.new()
200
+ command_attributes = Attributes.new()
201
+ found_command = find_commands(
202
+ attributes, ns, ordinary_attributes, command_attributes)
203
+
204
+ qualified_name = [prefix, name].compact().join(':')
205
+ element = Element.new(qualified_name, ordinary_attributes)
206
+ @open_elements.push(element)
207
+
208
+ if found_command
209
+ end_static_text()
210
+
211
+ element.has_command = true
212
+ renderer = create_element_renderer(element, command_attributes)
213
+ add_child(renderer)
214
+
215
+ @containers.push(renderer)
132
216
  else
133
- puts "attributes #{templateAttributes}"
217
+ append_static_text(element.render_start_tag(element.attributes))
134
218
  end
135
219
  end
136
220
 
137
- def end_document
138
- puts "the document has ended"
221
+ def configure_remove_command(element, renderer)
222
+ case renderer.remove_mode
223
+ when RemoveMode::TAG
224
+ if !renderer.has_command && !renderer.has_dynamic_content
225
+ # Discard tag, but preserve the children.
226
+ delete_child(renderer)
227
+ renderer.children.each do |child|
228
+ add_child(child)
229
+ end
230
+ end
231
+ when RemoveMode::CONTENT
232
+ if !renderer.has_command
233
+ # Discard children. Statically render the tag.
234
+ delete_child(renderer)
235
+
236
+ append_static_text(element.render_start_tag(element.attributes))
237
+ append_static_text(element.render_end_tag())
238
+ end
239
+ when RemoveMode::ELEMENT
240
+ # Discard element and all its content.
241
+ delete_child(renderer)
242
+ end
243
+ end
244
+
245
+ def end_element_namespace(name, prefix, uri)
246
+ element = @open_elements.pop()
247
+ if element.has_command
248
+ end_static_text()
249
+
250
+ renderer = @containers.pop()
251
+ if renderer.has_dynamic_content
252
+ # Discard children because the content will be replaced at render
253
+ # time.
254
+ renderer.clear_children()
255
+ end
256
+
257
+ configure_remove_command(element, renderer)
258
+ elsif uri != Parser::SimpleParser::IMPLICIT_END_TAG_NS_URI
259
+ append_static_text(element.render_end_tag())
260
+ end
261
+ end
262
+
263
+ def characters(data)
264
+ set_has_content()
265
+ append_static_text(data)
266
+ end
267
+
268
+ def cdata_block(data)
269
+ append_static_text(START_CDATA)
270
+ characters(data)
271
+ append_static_text(END_CDATA)
272
+ end
273
+
274
+ def comment(data)
275
+ append_static_text('<!--')
276
+ characters(data)
277
+ append_static_text('-->')
278
+ end
279
+
280
+ def processing_instruction(data)
281
+ append_static_text('<?')
282
+ characters(data)
283
+ append_static_text('?>')
139
284
  end
140
285
  end
141
286
 
@@ -1,55 +1,93 @@
1
- require 'nokogiri'
1
+ require 'mullet/html/template'
2
+ require 'mullet/html/template_parser'
2
3
 
3
- module Mullet
4
+ module Mullet; module HTML
4
5
 
5
- class TemplateDocument < Nokogiri::XML::SAX::Document
6
- TEMPLATE_NAMESPACE_URI = "http://pukkaone.github.com/mullet/1"
6
+ # Loads templates from files, and caches them for fast retrieval of already
7
+ # loaded templates.
8
+ #
9
+ # By default, templates render an empty string when a variable is not found
10
+ # or its value is null. Call the `on_missing` and `on_nil` methods to
11
+ # configure how templates loaded by this loader should handle missing and nil
12
+ # values respectively.
13
+ class TemplateLoader
14
+
15
+ attr_accessor :template_path
7
16
 
8
- def render_start_tag(name, attributes, prefix, uri, namespaceDecls)
9
- tag = "<"
10
- if prefix
11
- tag << prefix << ":"
17
+ # Constructor
18
+ #
19
+ # @param [String] template_path
20
+ # name of directory to load templates from
21
+ def initialize(template_path)
22
+ @template_path = template_path
23
+ @template_cache = Hash.new()
24
+ @parser = TemplateParser.new(self)
25
+ @on_missing = Template::RETURN_EMPTY_STRING
26
+ @on_nil = Template::RETURN_EMPTY_STRING
27
+ end
28
+
29
+ # Sets block to execute on attempt to render a variable that was not found.
30
+ #
31
+ # @param [Proc] strategy
32
+ # The value returned from block will be rendered.
33
+ # @return [TemplateLoader] this object to allow method call chaining
34
+ def on_missing(strategy)
35
+ @on_missing = strategy
36
+ return self
12
37
  end
13
- tag << name
14
38
 
15
- attributes.each do |attribute|
16
- tag << " "
17
- if attribute.prefix
18
- tag << attribute.prefix << ":"
19
- end
20
- tag = attribute.localname << '="' << attribute.value << '"'
39
+ # Sets block to execute on attempt to render a nil value.
40
+ #
41
+ # @param [Proc] strategy
42
+ # The value returned from block will be rendered.
43
+ # @return [TemplateLoader] this object to allow method call chaining
44
+ def on_nil(strategy)
45
+ @on_nil = strategy
46
+ return self
21
47
  end
22
48
 
23
- namespaceDecls.each do |namespaceDecl|
24
- uri = namespaceDecl[1]
25
- if uri != TEMPLATE_NAMESPACE_URI
26
- tag << " xmlns:" << namespaceDecl[0] << '="' << uri << '"'
49
+ # Loads named template.
50
+ #
51
+ # @param [String] uri
52
+ # file name optionally followed by `#`_id_
53
+ def load(uri)
54
+ id = nil
55
+ hash_index = uri.index('#')
56
+ if hash_index
57
+ id = uri[(hash_index + 1)..-1]
58
+ uri = uri[0...hash_index]
27
59
  end
60
+
61
+ return load_file(uri, id)
28
62
  end
29
63
 
30
- tag << ">"
31
- return tag
32
- end
64
+ private
33
65
 
34
- def start_element_namespace(name, attributes, prefix, uri, ns)
35
- puts "start element #{name} #{attributes} #{prefix} #{uri} #{ns}"
36
- templateAttributes = attributes.select do |attribute|
37
- attribute.uri == TEMPLATE_NAMESPACE_URI
66
+ def get_cache_key(file_name, id)
67
+ cache_key = File.join(@template_path, file_name)
68
+ if id != nil
69
+ cache_key << '#' << id
70
+ end
71
+ return cache_key
38
72
  end
39
- if templateAttributes.empty?
40
- puts render_start_tag(name, attributes, prefix, uri, ns)
41
- else
42
- puts "attributes #{templateAttributes}"
73
+
74
+ def parse_file(file_name, id)
75
+ template_file = File.join(@template_path, file_name)
76
+ template = @parser.parse_file(template_file, id)
77
+
78
+ template.on_missing(@on_missing).on_nil(@on_nil)
79
+ return template
43
80
  end
44
- end
45
81
 
46
- def end_document
47
- puts "the document has ended"
82
+ def load_file(file_name, id)
83
+ cache_key = get_cache_key(file_name, id)
84
+ template = @template_cache.fetch(cache_key, nil)
85
+ if template == nil
86
+ template = parse_file(file_name, id)
87
+ @template_cache.store(cache_key, template)
88
+ end
89
+ return template
90
+ end
48
91
  end
49
- end
50
92
 
51
- parser = Nokogiri::HTML::SAX::Parser.new(TemplateDocument.new)
52
- f = File.open("login.html")
53
- parser.parse(f)
54
- f.close
55
- end
93
+ end; end
@@ -0,0 +1,48 @@
1
+ require 'mullet/html/filtered_element_handler'
2
+ require 'mullet/html/parser/simple_parser'
3
+ require 'mullet/html/template_builder'
4
+
5
+ module Mullet; module HTML
6
+
7
+ class TemplateParser
8
+
9
+ def initialize(loader)
10
+ @loader = loader
11
+ end
12
+
13
+ # Parses template from string.
14
+ #
15
+ # @param [String] source
16
+ # string to parse
17
+ # @return [Template] template
18
+ def parse(source)
19
+ template_builder = TemplateBuilder.new(@loader)
20
+ parser = Parser::SimpleParser.new(template_builder)
21
+ parser.parse(source)
22
+ return template_builder.template
23
+ end
24
+
25
+ # Parses template from file.
26
+ #
27
+ # @param [String] file_name
28
+ # name of file containing template
29
+ # @param [String] id
30
+ # If `nil`, then the template is the entire file, otherwise the
31
+ # template is the content of the element having an `id` attribute
32
+ # value equal to this argument.
33
+ # @return [Template] template
34
+ def parse_file(file_name, id)
35
+ template_builder = TemplateBuilder.new(@loader)
36
+ handler = (id == nil) ?
37
+ template_builder : FilteredElementHandler.new(template_builder, id)
38
+
39
+ parser = Parser::SimpleParser.new(handler)
40
+ File.open(file_name) do |file|
41
+ parser.parse(file)
42
+ end
43
+
44
+ return template_builder.template
45
+ end
46
+ end
47
+
48
+ end; end
@@ -0,0 +1,24 @@
1
+ require 'mullet/html/if_element_renderer'
2
+
3
+ module Mullet; module HTML
4
+
5
+ # Renders an element if variable is false.
6
+ class UnlessElementRenderer < IfElementRenderer
7
+
8
+ # Constructor
9
+ #
10
+ # @param [Element] element
11
+ # element to render
12
+ # @param [String] variable_name
13
+ # name of variable containing condition
14
+ def initialize(element, variable_name)
15
+ super(element, variable_name)
16
+ @variable_name = variable_name.to_sym()
17
+ end
18
+
19
+ def should_render_element(render_context)
20
+ return !super
21
+ end
22
+ end
23
+
24
+ end; end
data/lib/mullet/model.rb CHANGED
@@ -1,10 +1,7 @@
1
1
  module Mullet
2
2
 
3
- # A model responds to the method `fetch` taking a variable name argument and
4
- # returning the variable value. If the variable name is not found, it
5
- # returns the value `NOT_FOUND` instead of raising an exception. A model
6
- # class must include this module because the implementation calls
7
- # `is_a?(Model)` to determine if an object satisfies the concept of a model.
3
+ # A model responds to the method `get_variable_value` taking a variable name
4
+ # argument and returning the variable value.
8
5
  module Model
9
6
 
10
7
  # special value indicating variable name was not found