thymeleaf 0.1.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.
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: []