thymeleaf 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (36) hide show
  1. checksums.yaml +7 -0
  2. data/lib/thymeleaf/configuration.rb +45 -0
  3. data/lib/thymeleaf/context/context_holder.rb +34 -0
  4. data/lib/thymeleaf/context/context_struct.rb +50 -0
  5. data/lib/thymeleaf/dialects.rb +92 -0
  6. data/lib/thymeleaf/dialects/default/default_dialect.rb +66 -0
  7. data/lib/thymeleaf/dialects/default/parsers/dollar.rb +12 -0
  8. data/lib/thymeleaf/dialects/default/parsers/each.rb +12 -0
  9. data/lib/thymeleaf/dialects/default/parsers/eval.rb +15 -0
  10. data/lib/thymeleaf/dialects/default/parsers/fragment.rb +7 -0
  11. data/lib/thymeleaf/dialects/default/parsers/selection.rb +15 -0
  12. data/lib/thymeleaf/dialects/default/processors/block.rb +13 -0
  13. data/lib/thymeleaf/dialects/default/processors/case.rb +23 -0
  14. data/lib/thymeleaf/dialects/default/processors/default.rb +8 -0
  15. data/lib/thymeleaf/dialects/default/processors/each.rb +67 -0
  16. data/lib/thymeleaf/dialects/default/processors/fragment.rb +12 -0
  17. data/lib/thymeleaf/dialects/default/processors/if.rb +14 -0
  18. data/lib/thymeleaf/dialects/default/processors/insert.rb +65 -0
  19. data/lib/thymeleaf/dialects/default/processors/object.rb +21 -0
  20. data/lib/thymeleaf/dialects/default/processors/remove.rb +85 -0
  21. data/lib/thymeleaf/dialects/default/processors/replace.rb +33 -0
  22. data/lib/thymeleaf/dialects/default/processors/switch.rb +17 -0
  23. data/lib/thymeleaf/dialects/default/processors/text.rb +9 -0
  24. data/lib/thymeleaf/dialects/default/processors/unless.rb +13 -0
  25. data/lib/thymeleaf/dialects/default/processors/utext.rb +8 -0
  26. data/lib/thymeleaf/dialects/dialect.rb +18 -0
  27. data/lib/thymeleaf/parser.rb +12 -0
  28. data/lib/thymeleaf/parser/parse_options.rb +12 -0
  29. data/lib/thymeleaf/processor.rb +33 -0
  30. data/lib/thymeleaf/processor/context_evaluator.rb +13 -0
  31. data/lib/thymeleaf/template.rb +35 -0
  32. data/lib/thymeleaf/template/template_resolver.rb +15 -0
  33. data/lib/thymeleaf/template_engine.rb +56 -0
  34. data/lib/thymeleaf/utils/booleanize.rb +4 -0
  35. data/lib/thymeleaf/version.rb +3 -0
  36. metadata +192 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2ae44746cdafe6e28cb8f57da926e006a531e0db
4
+ data.tar.gz: 8b44f5ba0c9eb82025f21f3363fbc8ecd7f27895
5
+ SHA512:
6
+ metadata.gz: 8663e50466b26f5e3f5de3b096ba355f1ff6649e9e9a7268cb66e2336cb158af1c2e1eed7d2e7d7a1130e3d94e28984e8fd0d7b67bfd5640d54ddf9211160a0f
7
+ data.tar.gz: 981a896eddf139b5050683f7a7703ad6d2e12db6adb0ddaf8f8c5f581689390cb847201501cd7ca07935e74aa091e7e440ac7e97729d3812d53b93a5d8f88498
@@ -0,0 +1,45 @@
1
+
2
+ require_relative 'dialects'
3
+ require_relative 'dialects/default/default_dialect'
4
+ require_relative 'template/template_resolver'
5
+ require_relative 'parser/parse_options'
6
+
7
+ module Thymeleaf
8
+
9
+ class << self
10
+ attr_accessor :configuration
11
+ end
12
+
13
+ # TODO: Replace accessor with getter/setter?
14
+
15
+ def self.configure(&block)
16
+ self.configuration ||= Configuration.new
17
+ block.call configuration if block_given?
18
+ end
19
+
20
+ class Configuration
21
+
22
+ attr_accessor :dialects, :template, :parser
23
+
24
+ def initialize
25
+ self.dialects = Dialects.new
26
+ self.template = TemplateResolver.new
27
+ self.parser = ParseOptions.new
28
+ add_dialect DefaultDialect
29
+ end
30
+
31
+ def add_dialect(*args)
32
+ dialects.add_dialect(*args)
33
+ end
34
+
35
+ def clear_dialects
36
+ dialects.clear_dialects
37
+ end
38
+
39
+ def template_uri(name)
40
+ template.get_template(name)
41
+ end
42
+
43
+ end
44
+
45
+ end
@@ -0,0 +1,34 @@
1
+
2
+ require_relative 'context_struct'
3
+
4
+ class ContextHolder < Struct.new(:context, :parent_context)
5
+
6
+ def initialize(context, parent_context = nil)
7
+ if context.is_a? Hash
8
+ super(ContextStruct.new(context), parent_context)
9
+ else
10
+ super(context, parent_context)
11
+ end
12
+ end
13
+
14
+ def evaluate(expr)
15
+ instance_eval(expr.to_s)
16
+ end
17
+
18
+ def method_missing(m, *args)
19
+ if context.respond_to? m
20
+ context.send(m, *args)
21
+ elsif !parent_context.nil?
22
+ parent_context.send(m, *args)
23
+ end
24
+ end
25
+
26
+ def root
27
+ if parent_context.nil?
28
+ context
29
+ else
30
+ parent_context.root
31
+ end
32
+ end
33
+
34
+ end
@@ -0,0 +1,50 @@
1
+
2
+ require 'ostruct'
3
+
4
+ class ContextStruct < OpenStruct
5
+
6
+ def initialize(hash=nil)
7
+ @table = {}
8
+ @hash_table = {}
9
+
10
+ if hash && (hash.is_a? Hash)
11
+ hash.each do |k,v|
12
+
13
+ if v.is_a?(Array)
14
+ other = Array.new
15
+ v.each do |entry|
16
+ other.push((entry.is_a?(Hash) ? self.class.new(entry) : entry))
17
+ end
18
+ v = other
19
+ end
20
+
21
+ @table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
22
+ @hash_table[k.to_sym] = v
23
+ new_ostruct_member(k)
24
+
25
+ end
26
+ end
27
+ end
28
+
29
+ def set_private(private_var, value)
30
+ value = ContextStruct.new(value) if value.is_a? Hash
31
+ send(:"\##{private_var}=", value)
32
+ end
33
+
34
+ def get_private(private_var)
35
+ send(:"\##{private_var}")
36
+ end
37
+
38
+ def has_private(private_var)
39
+ begin
40
+ !(get_private private_var).nil?
41
+ rescue
42
+ false
43
+ end
44
+ end
45
+
46
+ def to_h
47
+ @hash_table
48
+ end
49
+
50
+ end
@@ -0,0 +1,92 @@
1
+
2
+ module Thymeleaf
3
+
4
+ class Dialects
5
+
6
+ def initialize
7
+ clear_dialects
8
+ end
9
+
10
+ def add_dialect(*args)
11
+ key, dialect_class = * expand_key_dialect(*args)
12
+
13
+ dialect = dialect_class.new
14
+
15
+ registered_dialects[key] = dialect
16
+ registered_attr_processors[key] = dialect_processors(dialect)
17
+ registered_tag_processors[key] = dialect_tag_processors(dialect)
18
+ end
19
+
20
+ def clear_dialects
21
+ self.registered_dialects = {}
22
+ self.registered_attr_processors = {}
23
+ self.registered_tag_processors = {}
24
+ end
25
+
26
+ def find_attr_processor(key)
27
+ find_processor key, dialect_attr_matchers, registered_attr_processors
28
+ end
29
+
30
+ def find_tag_processor(key)
31
+ find_processor key, dialect_tag_matchers, registered_tag_processors
32
+ end
33
+
34
+ private
35
+
36
+ attr_accessor :registered_dialects, :registered_attr_processors, :registered_tag_processors
37
+
38
+ def dialect_attr_matchers
39
+ /^data-(#{registered_dialects.keys.join("|")})-(.*)$/
40
+ end
41
+
42
+ def dialect_tag_matchers
43
+ /^(#{registered_dialects.keys.join("|")})-(.*)$/
44
+ end
45
+
46
+ def null_processor
47
+ @null_prccesor ||= NullProcessor.new
48
+ end
49
+
50
+ def expand_key_dialect(*args)
51
+ if args.length == 1
52
+ [ args[0].default_key, args[0] ]
53
+ elsif args.length == 2
54
+ args
55
+ else
56
+ raise ArgumentError
57
+ end
58
+ end
59
+
60
+ def dialect_processors(dialect)
61
+ dialect.processors.reduce({}) do |processors, (processor_key, processor)|
62
+ processors[processor_key.to_s] = processor.new
63
+ processors
64
+ end
65
+ end
66
+
67
+ def dialect_tag_processors(dialect)
68
+ dialect.tag_processors.reduce({}) do |processors, (processor_key, processor)|
69
+ processors[processor_key.to_s] = processor.new
70
+ processors
71
+ end
72
+ end
73
+
74
+ def find_processor(key, dialect_matchers, processor_list)
75
+ match = dialect_matchers.match(key)
76
+
77
+ # TODO: check performance null object vs null check
78
+ return [key, null_processor] if match.nil?
79
+
80
+ dialect_key, processor_key = *match[1..2]
81
+
82
+ dialect_processors = processor_list[dialect_key]
83
+ raise ArgumentError, "No dialect found for key #{key}" if dialect_processors.nil?
84
+
85
+ processor = dialect_processors[processor_key] || dialect_processors['default']
86
+ raise ArgumentError, "No processor found for key #{key}" if processor.nil?
87
+
88
+ [processor_key, processor]
89
+ end
90
+ end
91
+
92
+ end
@@ -0,0 +1,66 @@
1
+
2
+ require_relative '../../processor'
3
+ require_relative '../../template_engine'
4
+
5
+ require_relative '../dialect'
6
+
7
+ class DefaultDialect < Dialect
8
+
9
+ CONTEXT_SWITCH_VAR = 'switch_var'
10
+ CONTEXT_FRAGMENT_VAR = 'fragment_var'
11
+ CONTEXT_OBJECT_VAR = 'context_obj'
12
+
13
+ def self.default_key
14
+ 'th'
15
+ end
16
+
17
+ def self.context_fragment_var(var_name)
18
+ "#{CONTEXT_FRAGMENT_VAR}_#{var_name}"
19
+ end
20
+
21
+ def tag_processors
22
+ {
23
+ block: BlockProcessor
24
+ }
25
+ end
26
+
27
+ # Precedence based on order for the time being
28
+ def processors
29
+ {
30
+ insert: InsertProcessor,
31
+ replace: ReplaceProcessor,
32
+ fragment: FragmentProcessor,
33
+ each: EachProcessor,
34
+ if: IfProcessor,
35
+ unless: UnlessProcessor,
36
+ switch: SwitchProcessor,
37
+ case: CaseProcessor,
38
+ object: ObjectProcessor,
39
+ text: TextProcessor,
40
+ utext: UTextProcessor,
41
+ remove: RemoveProcessor,
42
+ default: DefaultProcessor
43
+ }
44
+ end
45
+
46
+ require_relative 'parsers/eval'
47
+ require_relative 'parsers/selection'
48
+ require_relative 'parsers/each'
49
+ require_relative 'parsers/fragment'
50
+
51
+ require_relative 'processors/default'
52
+ require_relative 'processors/object'
53
+ require_relative 'processors/text'
54
+ require_relative 'processors/utext'
55
+ require_relative 'processors/if'
56
+ require_relative 'processors/unless'
57
+ require_relative 'processors/switch'
58
+ require_relative 'processors/case'
59
+ require_relative 'processors/each'
60
+ require_relative 'processors/remove'
61
+ require_relative 'processors/insert'
62
+ require_relative 'processors/replace'
63
+ require_relative 'processors/block'
64
+ require_relative 'processors/fragment'
65
+
66
+ end
@@ -0,0 +1,12 @@
1
+
2
+ class DollarExpression
3
+ def self.parse(context, expr, mode = nil, **args)
4
+ expr.gsub(/(\${.+?})/) do |match|
5
+ conv = ContextEvaluator.new(context).evaluate(match[2..-2])
6
+ if mode.eql? :single_expression
7
+ return conv
8
+ end
9
+ conv
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,12 @@
1
+ # Matches:
2
+ # "item, stat : ${iterator}",
3
+ # "item : ${iterator}" or
4
+ # "${iterator}"
5
+ class EachExpression
6
+
7
+ def self.parse(context, expr, **args)
8
+ md = expr.match(/\s*(?:([^\n,]+?)\s*(?:,\s*([^\n,]*?))?\s*:\s*)?\${(.+?)}/)
9
+ raise ArgumentError, "Not a valid each expression" if md.nil?
10
+ md[1..3]
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+
2
+ require_relative 'selection'
3
+ require_relative 'dollar'
4
+
5
+ class EvalExpression
6
+
7
+ def self.parse(context, expr, mode = nil, **_)
8
+ text = SelectionExpression.parse(context, expr, context.get_private(DefaultDialect::CONTEXT_OBJECT_VAR))
9
+ DollarExpression.parse(context, text, mode)
10
+ end
11
+
12
+ def self.parse_single_expression(context, expr, **_)
13
+ self.parse(context, expr, :single_expression)
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class FragmentExpression
2
+ def self.parse(context, expr, **args)
3
+ md = expr.match(/\s*(?:([^\n:]+)\s*)?(?:::([^\n]+))?\s*/)
4
+ raise ArgumentError, "Not a valid include expression" if md.nil?
5
+ md[1..2]
6
+ end
7
+ end
@@ -0,0 +1,15 @@
1
+
2
+ class SelectionExpression
3
+
4
+ # Parse asterisk *{...} syntax (object selection)
5
+ def self.parse(context, expr, obj_var, **args)
6
+ expr.gsub(/(\*{.+?})/) do |match|
7
+ if obj_var.nil?
8
+ "${#{match[2..-2]}}"
9
+ else
10
+ ContextEvaluator.new(ContextHolder.new obj_var).evaluate(match[2..-2])
11
+ end
12
+ end
13
+ end
14
+
15
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class BlockProcessor
3
+
4
+ include Thymeleaf::Processor
5
+
6
+ def call(node:nil, context:nil, **_)
7
+ node.children.reverse.each do |child|
8
+ subprocess_node(context, child)
9
+ node.add_next_sibling child
10
+ end
11
+ node.unlink
12
+ end
13
+ end
@@ -0,0 +1,23 @@
1
+
2
+ class CaseProcessor
3
+ include Thymeleaf::Processor
4
+
5
+ def call(node:nil, attribute:nil, context:nil, **_)
6
+ attribute.unlink
7
+
8
+ var_cmp = EvalExpression.parse(context, attribute.value)
9
+
10
+ unless case_equals? context, var_cmp
11
+ node.children.each { |child| child.unlink }
12
+ node.unlink
13
+ end
14
+
15
+ end
16
+
17
+ def case_equals?(context, var_comparation)
18
+ (context.has_private DefaultDialect::CONTEXT_SWITCH_VAR) &&
19
+ (context.get_private DefaultDialect::CONTEXT_SWITCH_VAR).eql?(var_comparation)
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,8 @@
1
+ class DefaultProcessor
2
+ include Thymeleaf::Processor
3
+
4
+ def call(key:nil, node:nil, attribute:nil, context:nil)
5
+ node[key] = [node[key], EvalExpression.parse(context, attribute.value)].compact.join(' ')
6
+ attribute.unlink
7
+ end
8
+ end
@@ -0,0 +1,67 @@
1
+ class EachProcessor
2
+
3
+ include Thymeleaf::Processor
4
+ require_relative '../parsers/each'
5
+
6
+ def call(node:nil, attribute:nil, context:nil, **_)
7
+ variable, stat, enumerable = EachExpression.parse(context, attribute.value)
8
+
9
+ elements = evaluate_in_context(context, enumerable)
10
+
11
+ stat_var = init_stat_var(stat, elements)
12
+
13
+
14
+ attribute.unlink
15
+
16
+ elements.each do |element|
17
+ subcontext_vars = {}
18
+ subcontext_vars[variable] = element unless variable.nil?
19
+
20
+ unless stat.nil?
21
+ stat_var[:index] += 1
22
+ stat_var[:count] += 1
23
+ stat_var[:current] = element
24
+ stat_var[:even] = stat_var[:count].even?
25
+ stat_var[:odd] = stat_var[:count].odd?
26
+ stat_var[:first] = (stat_var[:index].eql? 0)
27
+ stat_var[:last] = (stat_var[:count].eql? stat_var[:size])
28
+
29
+ subcontext_vars[stat] = stat_var
30
+ end
31
+
32
+ subcontext = ContextHolder.new(subcontext_vars, context)
33
+ new_node = node.dup
34
+ subprocess_node(subcontext, new_node)
35
+ node.add_previous_sibling(new_node)
36
+ end
37
+
38
+ node.children.each {|child| child.unlink }
39
+ node.unlink
40
+
41
+ context # TODO: Remove
42
+ end
43
+
44
+ def has_subcontext?
45
+ true
46
+ end
47
+
48
+ private
49
+
50
+ def init_stat_var(stat, elements)
51
+ if stat.nil?
52
+ nil
53
+ else
54
+ {
55
+ :index => -1,
56
+ :count => 0,
57
+ :size => elements.length,
58
+ :current => nil,
59
+ :even => false,
60
+ :odd => true,
61
+ :first => true,
62
+ :last => false
63
+ }
64
+ end
65
+ end
66
+
67
+ end
@@ -0,0 +1,12 @@
1
+ class FragmentProcessor
2
+ include Thymeleaf::Processor
3
+
4
+ def call(node:nil, attribute:nil, context:nil, **_)
5
+ fragment_name = EvalExpression.parse(context, attribute.value)
6
+
7
+ context.root.set_private DefaultDialect::context_fragment_var(fragment_name), node
8
+
9
+ attribute.unlink
10
+ end
11
+
12
+ end
@@ -0,0 +1,14 @@
1
+ require_relative '../../../utils/booleanize'
2
+
3
+ class IfProcessor
4
+
5
+ include Thymeleaf::Processor
6
+
7
+ def call(node:nil, attribute:nil, context:nil, **_)
8
+ attribute.unlink
9
+ unless booleanize EvalExpression.parse(context, attribute.value)
10
+ node.children.each {|child| child.unlink }
11
+ node.unlink
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,65 @@
1
+
2
+ class InsertProcessor
3
+
4
+ include Thymeleaf::Processor
5
+
6
+ require_relative '../parsers/fragment'
7
+
8
+ def call(node:nil, attribute:nil, context:nil, **_)
9
+ attribute.unlink
10
+
11
+ template, fragment = FragmentExpression.parse(context, attribute.value)
12
+
13
+ node_subcontent = get_node_template(template, node, context)
14
+
15
+ node.children.each {|child| child.unlink }
16
+
17
+ if fragment.nil?
18
+ # Avoid infinite loop when template is "this" and fragment is nil
19
+ return nil if is_self_template? template
20
+ else
21
+ node_subcontent = get_fragment_node(fragment, context, node_subcontent)
22
+ end
23
+
24
+ unless node_subcontent.nil?
25
+ node_subcontent.dup.parent = node
26
+ end
27
+ end
28
+
29
+
30
+ private
31
+
32
+ def get_node_template(template, node, context)
33
+ if is_self_template? template
34
+ root_node node
35
+ else
36
+ subtemplate = EvalExpression.parse(context, template)
37
+
38
+ load_template subtemplate do |template_file|
39
+ Thymeleaf::Parser.new(template_file).call
40
+ end
41
+ end
42
+ end
43
+
44
+ def root_node(node)
45
+ new_node = node
46
+ until new_node.parent.nil?
47
+ new_node = new_node.parent
48
+ end
49
+ new_node
50
+ end
51
+
52
+ def is_self_template?(template)
53
+ template.nil? || (template.eql? 'this')
54
+ end
55
+
56
+ def get_fragment_node(fragment_name, context, node)
57
+ root_context = context.root
58
+
59
+ if root_context.has_private DefaultDialect::context_fragment_var(fragment_name)
60
+ root_context.get_private DefaultDialect::context_fragment_var(fragment_name)
61
+ else
62
+ node.at_css fragment_name
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,21 @@
1
+ require_relative '../../../utils/booleanize'
2
+
3
+ class ObjectProcessor
4
+
5
+ include Thymeleaf::Processor
6
+
7
+ def call(attribute:nil, context:nil, **_)
8
+ attribute.unlink
9
+
10
+ obj_var = EvalExpression.parse_single_expression(context, attribute.value)
11
+ new_context = ContextHolder.new({}, context)
12
+ new_context.set_private(DefaultDialect::CONTEXT_OBJECT_VAR, obj_var)
13
+
14
+ attribute.unlink
15
+ new_context
16
+ end
17
+
18
+ def has_subcontext?
19
+ true
20
+ end
21
+ end
@@ -0,0 +1,85 @@
1
+ require_relative '../../../utils/booleanize'
2
+
3
+ class RemoveProcessor
4
+
5
+ include Thymeleaf::Processor
6
+
7
+ REMOVE_ALL = 'all'
8
+ REMOVE_BODY = 'body'
9
+ REMOVE_TAG = 'tag'
10
+ REMOVE_ALL_BUT_FIRST = 'all-but-first'
11
+ REMOVE_NONE = 'none'
12
+
13
+ def call(node:nil, attribute:nil, context:nil, **_)
14
+ attribute.unlink
15
+
16
+ expr = EvalExpression.parse(context, attribute.value)
17
+
18
+ method = case expr
19
+ when REMOVE_ALL
20
+ :remove_all
21
+ when REMOVE_BODY
22
+ :remove_body
23
+ when REMOVE_TAG
24
+ :remove_tag
25
+ when REMOVE_ALL_BUT_FIRST
26
+ :remove_allbutfirst
27
+ when REMOVE_NONE
28
+ :remove_none
29
+ else
30
+ if booleanize expr
31
+ :remove_all
32
+ else
33
+ :remove_none
34
+ end
35
+ end
36
+
37
+ send(method, node, context)
38
+ end
39
+
40
+ private
41
+ def remove_all(node, _)
42
+ node.children.each do |child|
43
+ child.unlink
44
+ end
45
+ node.unlink
46
+ end
47
+
48
+ def remove_body(node, _)
49
+ node.children.each do |child|
50
+ child.unlink
51
+ end
52
+ end
53
+
54
+ def remove_tag(node, context)
55
+ node.children.reverse.each do |child|
56
+ subprocess_node(context, child)
57
+ node.add_next_sibling child
58
+ end
59
+ node.unlink
60
+ end
61
+
62
+ def remove_allbutfirst(node, _)
63
+ skip_first(node.children) do |child|
64
+ child.unlink
65
+ end
66
+ end
67
+
68
+ def remove_none(_, _)
69
+ end
70
+
71
+ def empty_node?(node)
72
+ node.to_s.strip.empty?
73
+ end
74
+
75
+ def skip_first(node_set)
76
+ i = 0
77
+ node_set.each do |child|
78
+ if i > 0
79
+ yield child
80
+ else
81
+ i += 1 unless empty_node? child
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,33 @@
1
+
2
+ require_relative 'insert'
3
+
4
+ class ReplaceProcessor < InsertProcessor
5
+
6
+ include Thymeleaf::Processor
7
+
8
+ def call(node:nil, attribute:nil, context:nil, **_)
9
+
10
+ attribute.unlink
11
+
12
+ template, fragment = FragmentExpression.parse(context, attribute.value)
13
+
14
+ node_subcontent = get_node_template(template, node, context)
15
+
16
+ node.children.each {|child| child.unlink }
17
+
18
+ if fragment.nil?
19
+ # Avoid infinite loop when template is "this" and fragment is nil
20
+ return nil if is_self_template? template
21
+ else
22
+ node_subcontent = get_fragment_node(fragment, context, node_subcontent)
23
+ end
24
+
25
+ unless node_subcontent.nil?
26
+ node_subcontent = node_subcontent.dup
27
+ subprocess_node(context, node_subcontent)
28
+
29
+ node.replace node_subcontent
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,17 @@
1
+ class SwitchProcessor
2
+ include Thymeleaf::Processor
3
+
4
+ def call(attribute:nil, context:nil, **_)
5
+ condition = EvalExpression.parse(context, attribute.value)
6
+ new_context = ContextHolder.new({}, context)
7
+ new_context.set_private DefaultDialect::CONTEXT_SWITCH_VAR, condition
8
+
9
+ attribute.unlink
10
+ new_context
11
+ end
12
+
13
+ def has_subcontext?
14
+ true
15
+ end
16
+
17
+ end
@@ -0,0 +1,9 @@
1
+
2
+ class TextProcessor
3
+ include Thymeleaf::Processor
4
+
5
+ def call(node:nil, attribute:nil, context:nil, **_)
6
+ node.content = EvalExpression.parse(context, attribute.value)
7
+ attribute.unlink
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ require_relative '../../../utils/booleanize'
2
+
3
+ class UnlessProcessor
4
+ include Thymeleaf::Processor
5
+
6
+ def call(node:nil, attribute:nil, context:nil, **_)
7
+ attribute.unlink
8
+ if booleanize EvalExpression.parse(context, attribute.value)
9
+ node.children.each {|child| child.unlink }
10
+ node.unlink
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,8 @@
1
+ class UTextProcessor
2
+ include Thymeleaf::Processor
3
+
4
+ def call(node:nil, attribute:nil, context:nil, **_)
5
+ node.inner_html = EvalExpression.parse(context, attribute.value)
6
+ attribute.unlink
7
+ end
8
+ end
@@ -0,0 +1,18 @@
1
+
2
+ class Dialect
3
+
4
+ def self.default_key
5
+ nil
6
+ end
7
+
8
+ # Precedence based on order
9
+ def tag_processors
10
+ {}
11
+ end
12
+
13
+ # Precedence based on order
14
+ def processors
15
+ {}
16
+ end
17
+
18
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'nokogiri'
3
+
4
+ module Thymeleaf
5
+
6
+ class Parser < Struct.new(:template_markup)
7
+ def call
8
+ Nokogiri::HTML::fragment(template_markup, Thymeleaf.configuration.parser.encoding)
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,12 @@
1
+
2
+ require 'nokogiri'
3
+
4
+ class ParseOptions
5
+
6
+ attr_accessor :encoding
7
+
8
+ def initialize
9
+ self.encoding = nil
10
+ end
11
+
12
+ end
@@ -0,0 +1,33 @@
1
+
2
+ module Thymeleaf
3
+
4
+ module Processor
5
+
6
+ require_relative 'processor/context_evaluator'
7
+
8
+ def evaluate_in_context(context, expr)
9
+ ContextEvaluator.new(context).evaluate(expr)
10
+ end
11
+
12
+ def subprocess_node(context, node)
13
+ processor = Thymeleaf::TemplateEngine.new
14
+ processor.send(:process_node, context, node)
15
+ end
16
+
17
+ def load_template(template_name)
18
+ template_uri = Thymeleaf.configuration.template_uri(template_name)
19
+
20
+ File.open template_uri do |template_file|
21
+ template_file.rewind
22
+ yield template_file.read
23
+ end
24
+ end
25
+
26
+ end
27
+
28
+ class NullProcessor
29
+ def call(**_)
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,13 @@
1
+
2
+ class ContextEvaluator
3
+ def initialize(context)
4
+ self.context = context
5
+ end
6
+
7
+ def evaluate(expr)
8
+ context.evaluate(expr)
9
+ end
10
+
11
+ private
12
+ attr_accessor :context
13
+ end
@@ -0,0 +1,35 @@
1
+
2
+ require_relative 'processor'
3
+
4
+ module Thymeleaf
5
+
6
+ require_relative 'context/context_holder'
7
+
8
+ class Template < Struct.new(:template_markup, :context)
9
+ def render
10
+ do_render template_markup
11
+ end
12
+
13
+ def render_file
14
+ template_markup_uri = Thymeleaf.configuration.template_uri(template_markup)
15
+
16
+ template_file_open template_markup_uri do |template|
17
+ do_render template
18
+ end
19
+ end
20
+
21
+ private
22
+ def do_render(template)
23
+ parsed_template = Parser.new(template).call
24
+ context_holder = ContextHolder.new(context)
25
+ TemplateEngine.new.call(parsed_template, context_holder).to_s
26
+ end
27
+
28
+ def template_file_open(template_file)
29
+ File.open template_file do |template|
30
+ template.rewind
31
+ yield template.read
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,15 @@
1
+
2
+ class TemplateResolver
3
+
4
+ attr_accessor :prefix, :suffix
5
+
6
+ def initialize
7
+ self.prefix = ''
8
+ self.suffix = ''
9
+ end
10
+
11
+ def get_template(template_name)
12
+ "#{self.prefix}#{template_name}#{self.suffix}"
13
+ end
14
+
15
+ end
@@ -0,0 +1,56 @@
1
+
2
+ module Thymeleaf
3
+
4
+ class TemplateEngine
5
+ def call(parsed_template, context_holder)
6
+ process_node(context_holder, parsed_template)
7
+ parsed_template
8
+ end
9
+
10
+ private
11
+ def process_node(context_holder, node)
12
+ attr_context = process_attributes(context_holder, node)
13
+ children_context = process_tag(attr_context, node)
14
+
15
+ # TODO: Processing all nodes. Maybe filtering by only-thymeleaf nodes can give better performance?
16
+ node.children.each {|child| process_node(children_context, child)}
17
+ end
18
+
19
+ def process_attributes(context_holder, node)
20
+ attr_context = context_holder
21
+ node.attributes.each do |attribute_key, attribute|
22
+ attr_context = process_attribute(attr_context, node, attribute_key, attribute)
23
+ end
24
+ attr_context
25
+ end
26
+
27
+ def process_attribute(context_holder, node, attribute_key, attribute)
28
+ # TODO: Find all proccessors. Apply in precedence order!
29
+ dialects = Thymeleaf.configuration.dialects
30
+ key, processor = * dialects.find_attr_processor(attribute_key)
31
+
32
+ process_element(context_holder, node, attribute, key, processor)
33
+ end
34
+
35
+ def process_tag(context_holder, node)
36
+ dialects = Thymeleaf.configuration.dialects
37
+ key, processor = * dialects.find_tag_processor(node.name)
38
+
39
+ process_element(context_holder, node, nil, key, processor)
40
+ end
41
+
42
+ def processor_has_subcontext?(processor)
43
+ processor.respond_to?(:has_subcontext?) && processor.has_subcontext?
44
+ end
45
+
46
+ def process_element(context_holder, node, attribute, key, processor)
47
+ subcontext = processor.call(key: key, node: node, attribute: attribute, context: context_holder)
48
+
49
+ if processor_has_subcontext?(processor)
50
+ subcontext
51
+ else
52
+ context_holder
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,4 @@
1
+
2
+ def booleanize(str)
3
+ !(str.strip =~ /^(false|f|no|n|0|-0|nil)$/i)
4
+ end
@@ -0,0 +1,3 @@
1
+ module Thymeleaf
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,192 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: thymeleaf
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - David Barral Precedo
8
+ - Daniel Vazquez Brañas
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2016-09-09 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: nokogiri
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: bundler
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.10'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.10'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: '10.0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: '10.0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: minitest
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: awesome_print
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ - !ruby/object:Gem::Dependency
85
+ name: benchmark-ips
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '2.6'
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '2.6'
98
+ - !ruby/object:Gem::Dependency
99
+ name: webrick
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: json
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ description: Thymeleaf template engine for Ruby
127
+ email:
128
+ - contact@trabesoluciones.com
129
+ - david.barral@trabesoluciones.com
130
+ executables: []
131
+ extensions: []
132
+ extra_rdoc_files: []
133
+ files:
134
+ - lib/thymeleaf/configuration.rb
135
+ - lib/thymeleaf/context/context_holder.rb
136
+ - lib/thymeleaf/context/context_struct.rb
137
+ - lib/thymeleaf/dialects.rb
138
+ - lib/thymeleaf/dialects/default/default_dialect.rb
139
+ - lib/thymeleaf/dialects/default/parsers/dollar.rb
140
+ - lib/thymeleaf/dialects/default/parsers/each.rb
141
+ - lib/thymeleaf/dialects/default/parsers/eval.rb
142
+ - lib/thymeleaf/dialects/default/parsers/fragment.rb
143
+ - lib/thymeleaf/dialects/default/parsers/selection.rb
144
+ - lib/thymeleaf/dialects/default/processors/block.rb
145
+ - lib/thymeleaf/dialects/default/processors/case.rb
146
+ - lib/thymeleaf/dialects/default/processors/default.rb
147
+ - lib/thymeleaf/dialects/default/processors/each.rb
148
+ - lib/thymeleaf/dialects/default/processors/fragment.rb
149
+ - lib/thymeleaf/dialects/default/processors/if.rb
150
+ - lib/thymeleaf/dialects/default/processors/insert.rb
151
+ - lib/thymeleaf/dialects/default/processors/object.rb
152
+ - lib/thymeleaf/dialects/default/processors/remove.rb
153
+ - lib/thymeleaf/dialects/default/processors/replace.rb
154
+ - lib/thymeleaf/dialects/default/processors/switch.rb
155
+ - lib/thymeleaf/dialects/default/processors/text.rb
156
+ - lib/thymeleaf/dialects/default/processors/unless.rb
157
+ - lib/thymeleaf/dialects/default/processors/utext.rb
158
+ - lib/thymeleaf/dialects/dialect.rb
159
+ - lib/thymeleaf/parser.rb
160
+ - lib/thymeleaf/parser/parse_options.rb
161
+ - lib/thymeleaf/processor.rb
162
+ - lib/thymeleaf/processor/context_evaluator.rb
163
+ - lib/thymeleaf/template.rb
164
+ - lib/thymeleaf/template/template_resolver.rb
165
+ - lib/thymeleaf/template_engine.rb
166
+ - lib/thymeleaf/utils/booleanize.rb
167
+ - lib/thymeleaf/version.rb
168
+ homepage: https://trabe.github.io/thymeleaf-rb
169
+ licenses:
170
+ - MIT
171
+ metadata: {}
172
+ post_install_message:
173
+ rdoc_options: []
174
+ require_paths:
175
+ - lib
176
+ required_ruby_version: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: 2.3.0
181
+ required_rubygems_version: !ruby/object:Gem::Requirement
182
+ requirements:
183
+ - - ">="
184
+ - !ruby/object:Gem::Version
185
+ version: '0'
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 2.4.5.1
189
+ signing_key:
190
+ specification_version: 4
191
+ summary: Thymeleaf for Ruby
192
+ test_files: []