mullet 0.0.0

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.
data/lib/mullet.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'mullet/default_model'
2
+ require 'mullet/default_nested_model'
3
+ require 'mullet/model'
4
+ require 'mullet/template_error'
5
+ require 'mullet/version'
6
+
7
+ require 'mullet/html/template'
8
+ require 'mullet/html/template_loader'
@@ -0,0 +1,33 @@
1
+ module Mullet
2
+
3
+ # Collection of renderers which will be rendered in the order they were added.
4
+ module Container
5
+ def initialize()
6
+ super if defined?(super)
7
+ @children = []
8
+ end
9
+
10
+ def add_child(child)
11
+ @children << child
12
+ end
13
+
14
+ def delete_child(child)
15
+ @children.delete(child);
16
+ end
17
+
18
+ def clear_children()
19
+ @children.clear()
20
+ end
21
+
22
+ # Renders children in order they were added.
23
+ #
24
+ # @param [RenderContext] renderContext
25
+ # render context
26
+ def render_children(renderContext)
27
+ @children.each do |child|
28
+ child.render(renderContext)
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,67 @@
1
+ require 'mullet/model'
2
+
3
+ module Mullet
4
+
5
+ # Default model implementation which resolves variable names to values by
6
+ # reading from a data object. Given a variable name _key_, the following
7
+ # mechanisms are tried in this order:
8
+ #
9
+ # * If the variable name is `this`, then return the object.
10
+ # * If the object is a `Hash`, then use _key_ as the key to retrieve the
11
+ # value from the hash.
12
+ # * If the object has a method named _key_ taking no parameters, then use
13
+ # the value returned from calling the method.
14
+ # * If the object has an instance variable named @_key_, then use the
15
+ # variable value.
16
+ #
17
+ # If the value is a Proc, then use the value returned from calling it.
18
+ class DefaultModel
19
+ include Model
20
+
21
+ def initialize(data)
22
+ @data = data
23
+ end
24
+
25
+ def fetch_impl(key)
26
+ if key == :this
27
+ return @data
28
+ end
29
+
30
+ # Is the variable name a key in a Hash?
31
+ if @data.respond_to?(:fetch)
32
+ # Call the block if the key is not found.
33
+ return @data.fetch(key) {|k| @data.fetch(k.to_s(), NOT_FOUND) }
34
+ end
35
+
36
+ # Does the variable name match a method name in the object?
37
+ if @data.respond_to?(key)
38
+ method = @data.method(key)
39
+ if method.arity == 0
40
+ return method.call()
41
+ end
42
+ end
43
+
44
+ # Does the variable name match an instance variable name in the object?
45
+ variable = :"@#{key}"
46
+ if @data.instance_variable_defined?(variable)
47
+ return @data.instance_variable_get(variable)
48
+ end
49
+
50
+ return NOT_FOUND
51
+ end
52
+
53
+ # Resolves variable name to value.
54
+ #
55
+ # @param [Symbol] key
56
+ # variable name
57
+ # @return variable value
58
+ def fetch(key)
59
+ value = fetch_impl(key)
60
+ if value.is_a?(Proc)
61
+ value = value.call()
62
+ end
63
+ return value
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,49 @@
1
+ require 'mullet/default_model'
2
+
3
+ module Mullet
4
+
5
+ # Composite model that combines models in nested scopes. Tries each model in
6
+ # sequence until a value is successfully resolved.
7
+ class DefaultNestedModel
8
+ include Model
9
+
10
+ # Constructor
11
+ #
12
+ # @param dataObjects
13
+ # scopes in outer to inner order
14
+ def initialize(*dataObjects)
15
+ @scopes = []
16
+ dataObjects.each {|data| push_scope(data) }
17
+ end
18
+
19
+ # Resolves variable name to value.
20
+ #
21
+ # @param [Symbol] key
22
+ # variable name
23
+ # @return variable value
24
+ def fetch(key)
25
+ @scopes.reverse_each do |scope|
26
+ value = scope.fetch(key)
27
+ if value != Model::NOT_FOUND
28
+ return value
29
+ end
30
+ end
31
+
32
+ return Model::NOT_FOUND
33
+ end
34
+
35
+ # Adds a nested scope to search in subsequent lookups.
36
+ #
37
+ # @param data
38
+ # data object
39
+ def push_scope(data)
40
+ @scopes.push(data.is_a?(Model) ? data : DefaultModel.new(data))
41
+ end
42
+
43
+ # Removes innermost nested scope.
44
+ def pop_scope()
45
+ @scopes.pop()
46
+ end
47
+ end
48
+
49
+ end
@@ -0,0 +1,32 @@
1
+ module Mullet
2
+
3
+ # Operation to set attribute value.
4
+ module AttributeCommand
5
+
6
+ # Constructor
7
+ #
8
+ # @param [Symbol] attribute_name
9
+ # name of attribute this command sets
10
+ def initialize(attribute_name)
11
+ @attribute_name = attribute_name
12
+ end
13
+
14
+ # Sets attribute in the _attributecommand.
15
+ #
16
+ # @param [RenderContext] render_context
17
+ # render context
18
+ # @param [Attributes] attributes
19
+ # attributes to update
20
+ def execute(render_context, attributes)
21
+ value = get_value(render_context)
22
+ if value == Model::NOT_FOUND || value == nil
23
+ # Value not found. Do not render the attribute.
24
+ attributes.delete(@attribute_name)
25
+ else
26
+ escaped_value = render_context.escape_xml(value)
27
+ attributes.store(@attribute_name, escaped_value)
28
+ end
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,29 @@
1
+ module Mullet
2
+
3
+ # Recognized template commands
4
+ module Command
5
+ PREFIX = 'mullet:'
6
+ NAMESPACE_URI = 'http://pukkaone.github.com/mullet/1'
7
+
8
+ ACTION = 'action'
9
+ ALT = 'alt'
10
+ ALT_MESSAGE = 'alt-message'
11
+ ATTR = 'attr'
12
+ ATTR_MESSAGE = 'attr-message'
13
+ ESCAPE_XML = 'escape-xml'
14
+ FOR = 'for'
15
+ HREF = 'href'
16
+ IF = 'if'
17
+ INCLUDE = 'include'
18
+ REMOVE = 'remove'
19
+ SRC = 'src'
20
+ TEXT = 'text'
21
+ TEXT_MESSAGE = 'text-message'
22
+ TITLE = 'title'
23
+ TITLE_MESSAGE = 'title-message'
24
+ UNLESS = 'unless'
25
+ VALUE = 'value'
26
+ VALUE_MESSAGE = 'value-message'
27
+ end
28
+
29
+ end
@@ -0,0 +1,142 @@
1
+ require 'nokogiri'
2
+ require 'mullet/html/command'
3
+ require 'set'
4
+
5
+ module Mullet; module HTML
6
+
7
+ # Handles SAX events to build a template.
8
+ class TemplateBuilder < Nokogiri::XML::SAX::Document
9
+ include Command
10
+
11
+ XMLNS_ATTRIBUTE_PREFIX = "xmlns:"
12
+ COMMANDS = [
13
+ ATTRIBUTE,
14
+ ATTRIBUTE_MESSAGE,
15
+ CONTENT,
16
+ ESCAPE_XML,
17
+ FOR,
18
+ IF,
19
+ INCLUDE,
20
+ TEXT,
21
+ TEXT_MESSAGE,
22
+ UNLESS].to_set
23
+ START_CDATA = "<![CDATA["
24
+ END_CDATA = "]]>"
25
+
26
+ @loader = nil
27
+
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
+ # Constructor
39
+ #
40
+ # @param [TemplateLoader] loader
41
+ # template loader to use to load included template files
42
+ def initialize(loader)
43
+ @loader = loader
44
+ end
45
+
46
+ # Adds renderer to current container.
47
+ #
48
+ # @param [#render] renderer
49
+ # renderer to add
50
+ def add_child(renderer)
51
+ @containers.last.add_child(renderer)
52
+ end
53
+
54
+ # Deletes renderer from current container.
55
+ #
56
+ # @param [#render] renderer
57
+ # renderer to delete
58
+ def delete_child(renderer)
59
+ @containers.last.delete_child(renderer)
60
+ end
61
+
62
+ # Partitions the attributes into ordinary and command attributes.
63
+ #
64
+ # @param [Array] attributes
65
+ # input attributes
66
+ # @param [Hash] ns
67
+ # hash of namespace prefix to uri mappings
68
+ # @param [#store] ordinaryAttributes
69
+ # hash will receive name to value mappings for ordinary attributes
70
+ # @param [#store] commandAttributes
71
+ # hash will receive name to value mappings for command attributes
72
+ # @return [Boolean] true if any command attribute found
73
+ def find_commands(attributes, ns, ordinaryAttributes, commandAttributes)
74
+ foundCommand = false
75
+ 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}'")
80
+ end
81
+ commandAttributes.store(commandName, attr.value)
82
+ foundCommand = true
83
+ else
84
+ attributeName = [attr.prefix, attr.localname].compact.join(':')
85
+ ordinaryAttributes.store(attributeName, attr.value)
86
+ end
87
+ end
88
+
89
+ ns.each do |prefix, uri|
90
+ if uri != NAMESPACE_URI
91
+ attributeName = ['xmlns', prefix].compact.join(':')
92
+ ordinaryAttributes.store(attributeName, uri)
93
+ end
94
+ end
95
+
96
+ return foundCommand
97
+ end
98
+
99
+ def render_start_tag(name, attributes, prefix, uri, namespaceDecls)
100
+ tag = "<"
101
+ if prefix
102
+ tag << prefix << ":"
103
+ end
104
+ tag << name
105
+
106
+ attributes.each do |attribute|
107
+ tag << " "
108
+ if attribute.prefix
109
+ tag << attribute.prefix << ":"
110
+ end
111
+ tag = attribute.localname << '="' << attribute.value << '"'
112
+ end
113
+
114
+ namespaceDecls.each do |namespaceDecl|
115
+ uri = namespaceDecl[1]
116
+ if uri != TEMPLATE_NAMESPACE_URI
117
+ tag << " xmlns:" << namespaceDecl[0] << '="' << uri << '"'
118
+ end
119
+ end
120
+
121
+ tag << ">"
122
+ return tag
123
+ end
124
+
125
+ 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)
132
+ else
133
+ puts "attributes #{templateAttributes}"
134
+ end
135
+ end
136
+
137
+ def end_document
138
+ puts "the document has ended"
139
+ end
140
+ end
141
+
142
+ end; end
@@ -0,0 +1,55 @@
1
+ require 'nokogiri'
2
+
3
+ module Mullet
4
+
5
+ class TemplateDocument < Nokogiri::XML::SAX::Document
6
+ TEMPLATE_NAMESPACE_URI = "http://pukkaone.github.com/mullet/1"
7
+
8
+ def render_start_tag(name, attributes, prefix, uri, namespaceDecls)
9
+ tag = "<"
10
+ if prefix
11
+ tag << prefix << ":"
12
+ end
13
+ tag << name
14
+
15
+ attributes.each do |attribute|
16
+ tag << " "
17
+ if attribute.prefix
18
+ tag << attribute.prefix << ":"
19
+ end
20
+ tag = attribute.localname << '="' << attribute.value << '"'
21
+ end
22
+
23
+ namespaceDecls.each do |namespaceDecl|
24
+ uri = namespaceDecl[1]
25
+ if uri != TEMPLATE_NAMESPACE_URI
26
+ tag << " xmlns:" << namespaceDecl[0] << '="' << uri << '"'
27
+ end
28
+ end
29
+
30
+ tag << ">"
31
+ return tag
32
+ end
33
+
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
38
+ end
39
+ if templateAttributes.empty?
40
+ puts render_start_tag(name, attributes, prefix, uri, ns)
41
+ else
42
+ puts "attributes #{templateAttributes}"
43
+ end
44
+ end
45
+
46
+ def end_document
47
+ puts "the document has ended"
48
+ end
49
+ end
50
+
51
+ parser = Nokogiri::HTML::SAX::Parser.new(TemplateDocument.new)
52
+ f = File.open("login.html")
53
+ parser.parse(f)
54
+ f.close
55
+ end
@@ -0,0 +1,14 @@
1
+ module Mullet
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.
8
+ module Model
9
+
10
+ # special value indicating variable name was not found
11
+ NOT_FOUND = Object.new()
12
+ end
13
+
14
+ end
@@ -0,0 +1,84 @@
1
+ module Mullet
2
+
3
+ # Holds the rendering context to reduce the number of parameters passed to
4
+ # render methods.
5
+ class RenderContext
6
+ attr_accessor :escapeXmlEnabled
7
+
8
+ # Constructor.
9
+ #
10
+ # @param [Model] model
11
+ # provides data to render
12
+ # @param [Proc] missingValueStrategy
13
+ # executed on attempt to render a variable that was not found
14
+ # @param [Proc] nilValueStrategy
15
+ # executed on attempt to render nil value
16
+ # @param [#<<] output
17
+ # where rendered output will be written
18
+ def initialize(model, missingValueStrategy, nilValueStrategy, output)
19
+ @model = model
20
+ @missingValueStrategy = missingValueStrategy
21
+ @nilValueStrategy = nilValueStrategy
22
+ @output = output
23
+ @escapeXmlEnabled = true
24
+ end
25
+
26
+ # Escapes characters that could be interpreted as XML markup if enabled.
27
+ #
28
+ # @param [String] input
29
+ # input string
30
+ # @return escaped string, or the input string if escaping is disabled.
31
+ def escape_xml(key)
32
+ return @escapeXmlEnabled ? CGI.escape_html(input) : input
33
+ end
34
+
35
+ # Resolves variable name to value.
36
+ #
37
+ # @param [Symbol] key
38
+ # variable name
39
+ # @return value
40
+ def fetch(key)
41
+ return @model.fetch(key)
42
+ end
43
+
44
+ # Adds a nested scope to search in subsequent lookups.
45
+ #
46
+ # @param data
47
+ # data object
48
+ def push_scope(data)
49
+ @model.push_scope(data)
50
+ end
51
+
52
+ # Removes innermost nested scope.
53
+ def pop_scope()
54
+ @model.pop_scope()
55
+ end
56
+
57
+ # Gets model value that is intended for display in the rendered output.
58
+ # Applies configured strategies for handling missing and nil values.
59
+ #
60
+ # @param [Symbol] key
61
+ # variable name
62
+ def get_display_value(key)
63
+ value = @model.fetch(key)
64
+ if value == Model::NOT_FOUND
65
+ value = @missingValueStrategy.call(key)
66
+ end
67
+ if value == nil
68
+ value = @nilValueStrategy.call(key)
69
+ end
70
+ return value
71
+ end
72
+
73
+ # Writes rendered output.
74
+ #
75
+ # @param [String] str
76
+ # string to write
77
+ # @return this object
78
+ def <<(str)
79
+ @output << str
80
+ return self
81
+ end
82
+ end
83
+
84
+ end
@@ -0,0 +1,6 @@
1
+ module Mullet
2
+
3
+ class TemplateError < StandardError
4
+ end
5
+
6
+ end
@@ -0,0 +1,3 @@
1
+ module Mullet
2
+ VERSION = '0.0.0'
3
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mullet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Chin Huang
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-11-02 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: &2152289140 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.5.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *2152289140
25
+ - !ruby/object:Gem::Dependency
26
+ name: yard
27
+ requirement: &2152288740 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *2152288740
36
+ description: ! "It's like mustache but in HTML syntax.\n\n * Extremely simple variable
37
+ syntax is incapable of expressing logic in the\n templates.\n * Templates are
38
+ clean HTML. Your HTML authoring tool and browser will\n correctly display the
39
+ templates while you prototype your user interface.\n"
40
+ email: pukkaone@gmail.com
41
+ executables: []
42
+ extensions: []
43
+ extra_rdoc_files: []
44
+ files:
45
+ - lib/mullet/container.rb
46
+ - lib/mullet/default_model.rb
47
+ - lib/mullet/default_nested_model.rb
48
+ - lib/mullet/html/attribute_command.rb
49
+ - lib/mullet/html/command.rb
50
+ - lib/mullet/html/template_builder.rb
51
+ - lib/mullet/html/template_loader.rb
52
+ - lib/mullet/model.rb
53
+ - lib/mullet/render_context.rb
54
+ - lib/mullet/template_error.rb
55
+ - lib/mullet/version.rb
56
+ - lib/mullet.rb
57
+ homepage: http://pukkaone.github.com/mullet/
58
+ licenses: []
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ none: false
65
+ requirements:
66
+ - - ! '>='
67
+ - !ruby/object:Gem::Version
68
+ version: '1.9'
69
+ required_rubygems_version: !ruby/object:Gem::Requirement
70
+ none: false
71
+ requirements:
72
+ - - ! '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project: nowarning
77
+ rubygems_version: 1.8.11
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Logic-less HTML template engine
81
+ test_files: []