mullet 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []