opulent 0.0.9 → 1.0.2
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.
- checksums.yaml +4 -4
- data/.gitignore +9 -0
- data/.libold/opulent.rb +96 -0
- data/.libold/opulent/context.rb +80 -0
- data/.libold/opulent/engine.rb +88 -0
- data/.libold/opulent/filter.rb +101 -0
- data/.libold/opulent/logger.rb +67 -0
- data/.libold/opulent/nodes.rb +13 -0
- data/.libold/opulent/nodes/block.rb +29 -0
- data/.libold/opulent/nodes/comment.rb +35 -0
- data/.libold/opulent/nodes/control.rb +230 -0
- data/.libold/opulent/nodes/define.rb +42 -0
- data/.libold/opulent/nodes/eval.rb +41 -0
- data/.libold/opulent/nodes/expression.rb +28 -0
- data/.libold/opulent/nodes/filter.rb +70 -0
- data/.libold/opulent/nodes/helper.rb +69 -0
- data/.libold/opulent/nodes/node.rb +101 -0
- data/.libold/opulent/nodes/root.rb +62 -0
- data/.libold/opulent/nodes/text.rb +54 -0
- data/.libold/opulent/nodes/theme.rb +36 -0
- data/.libold/opulent/parser.rb +252 -0
- data/.libold/opulent/parser/block.rb +70 -0
- data/.libold/opulent/parser/comment.rb +32 -0
- data/.libold/opulent/parser/control.rb +83 -0
- data/.libold/opulent/parser/define.rb +39 -0
- data/.libold/opulent/parser/eval.rb +33 -0
- data/.libold/opulent/parser/expression.rb +350 -0
- data/.libold/opulent/parser/filter.rb +41 -0
- data/.libold/opulent/parser/node.rb +232 -0
- data/.libold/opulent/parser/root.rb +96 -0
- data/.libold/opulent/parser/text.rb +114 -0
- data/.libold/opulent/parser/theme.rb +36 -0
- data/.libold/opulent/preprocessor.rb +102 -0
- data/.libold/opulent/runtime.rb +144 -0
- data/.libold/opulent/template.rb +43 -0
- data/.libold/opulent/tokens.rb +276 -0
- data/.libold/opulent/version.rb +5 -0
- data/.rspec +2 -0
- data/.travis.yml +4 -0
- data/Gemfile +2 -0
- data/LICENSE +21 -0
- data/README.md +102 -0
- data/Rakefile +6 -0
- data/benchmark/benchmark.rb +46 -0
- data/benchmark/cases/node/node.haml +17 -0
- data/benchmark/cases/node/node.op +14 -0
- data/benchmark/cases/node/node.slim +19 -0
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/docs/syntax.md +3 -0
- data/docs/usage.md +3 -0
- data/lib/opulent.rb +11 -64
- data/lib/opulent/compiler.rb +132 -0
- data/lib/opulent/compiler/block.rb +31 -0
- data/lib/opulent/compiler/comment.rb +22 -0
- data/lib/opulent/compiler/control.rb +152 -0
- data/lib/opulent/compiler/define.rb +88 -0
- data/lib/opulent/compiler/eval.rb +15 -0
- data/lib/opulent/compiler/filter.rb +54 -0
- data/lib/opulent/compiler/node.rb +232 -0
- data/lib/opulent/compiler/root.rb +19 -0
- data/lib/opulent/compiler/text.rb +60 -0
- data/lib/opulent/context.rb +88 -0
- data/lib/opulent/engine.rb +60 -0
- data/lib/opulent/filters.rb +222 -0
- data/lib/opulent/logger.rb +47 -0
- data/lib/opulent/parser.rb +196 -0
- data/lib/opulent/parser/block.rb +56 -0
- data/lib/opulent/parser/comment.rb +27 -0
- data/lib/opulent/parser/control.rb +112 -0
- data/lib/opulent/parser/define.rb +30 -0
- data/lib/opulent/parser/eval.rb +21 -0
- data/lib/opulent/parser/expression.rb +344 -0
- data/lib/opulent/parser/filter.rb +25 -0
- data/lib/opulent/parser/node.rb +246 -0
- data/lib/opulent/parser/root.rb +48 -0
- data/lib/opulent/parser/text.rb +127 -0
- data/lib/opulent/settings.rb +79 -0
- data/lib/opulent/template.rb +67 -0
- data/lib/opulent/tokens.rb +166 -0
- data/lib/opulent/version.rb +4 -0
- data/opulent.gemspec +36 -0
- metadata +160 -7
@@ -0,0 +1,31 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate the code for a while control structure
|
6
|
+
#
|
7
|
+
# @param node [Array] Node code generation data
|
8
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
9
|
+
# @param context [Context] Processing environment data
|
10
|
+
#
|
11
|
+
def yield_node(node, indent, context)
|
12
|
+
if @block_stack[-1].has_key? node[@value]
|
13
|
+
@block_stack[-1][node[@value]].each do |child|
|
14
|
+
root child, indent, context.parent
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generate the code for a while control structure
|
20
|
+
#
|
21
|
+
# @param node [Array] Node code generation data
|
22
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
23
|
+
# @param context [Context] Processing environment data
|
24
|
+
#
|
25
|
+
def block_node(node, indent, context)
|
26
|
+
node[@children].each do |child|
|
27
|
+
root child, indent, context
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate the code for a while control structure
|
6
|
+
#
|
7
|
+
# @param node [Array] Node code generation data
|
8
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
9
|
+
# @param context [Context] Processing environment data
|
10
|
+
#
|
11
|
+
def comment(node, indent, context)
|
12
|
+
indentation = " " * indent
|
13
|
+
|
14
|
+
value = context.evaluate "\"#{node[@value]}\""
|
15
|
+
|
16
|
+
comment_tag = "#{indentation}<!-- #{value} -->\n"
|
17
|
+
|
18
|
+
@node_stack << :comment
|
19
|
+
@code += comment_tag
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,152 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate the code for a if-elsif-else control structure
|
6
|
+
#
|
7
|
+
# @param node [Array] Node code generation data
|
8
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
9
|
+
# @param context [Context] Processing environment data
|
10
|
+
#
|
11
|
+
def if_node(node, indent, context)
|
12
|
+
# Check if we have any condition met, or an else branch
|
13
|
+
index = node[@value].index do |value|
|
14
|
+
value.empty? || context.evaluate(value)
|
15
|
+
end
|
16
|
+
|
17
|
+
# If we have a branch that meets the condition, generate code for the
|
18
|
+
# children related to that specific branch
|
19
|
+
if index
|
20
|
+
node[@children][index].each do |child|
|
21
|
+
root child, indent, context
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# Generate the code for a case-when-else control structure
|
27
|
+
#
|
28
|
+
# @param node [Array] Node code generation data
|
29
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
30
|
+
# @param context [Context] Processing environment data
|
31
|
+
#
|
32
|
+
def case_node(node, indent, context)
|
33
|
+
# Evaluate the switching condition
|
34
|
+
switch_case = context.evaluate node[@options][:condition]
|
35
|
+
|
36
|
+
# Check if we have any condition met, or an else branch
|
37
|
+
index = node[@value].index do |value|
|
38
|
+
value.empty? || switch_case == context.evaluate(value)
|
39
|
+
end
|
40
|
+
|
41
|
+
# If we have a branch that meets the condition, generate code for the
|
42
|
+
# children related to that specific branch
|
43
|
+
if index
|
44
|
+
node[@children][index].each do |child|
|
45
|
+
root child, indent, context
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Generate the code for a while control structure
|
51
|
+
#
|
52
|
+
# @param node [Array] Node code generation data
|
53
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
54
|
+
# @param context [Context] Processing environment data
|
55
|
+
#
|
56
|
+
def while_node(node, indent, context)
|
57
|
+
# While we have a branch that meets the condition, generate code for the
|
58
|
+
# children related to that specific branch
|
59
|
+
while context.evaluate node[@value]
|
60
|
+
node[@children].each do |child|
|
61
|
+
root child, indent, context
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Generate the code for a while control structure
|
67
|
+
#
|
68
|
+
# @param node [Array] Node code generation data
|
69
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
70
|
+
# @param context [Context] Processing environment data
|
71
|
+
#
|
72
|
+
def until_node(node, indent, context)
|
73
|
+
# Until we have a branch that doesn't meet the condition, generate code for the
|
74
|
+
# children related to that specific branch
|
75
|
+
until context.evaluate node[@value]
|
76
|
+
node[@children].each do |child|
|
77
|
+
root child, indent, context
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# Generate the code for a while control structure
|
83
|
+
#
|
84
|
+
# @param node [Array] Node code generation data
|
85
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
86
|
+
# @param context [Context] Processing environment data
|
87
|
+
#
|
88
|
+
def each_node(node, indent, context)
|
89
|
+
result = []
|
90
|
+
|
91
|
+
# Process named variables for each structure
|
92
|
+
variables = node[@value][0].clone
|
93
|
+
|
94
|
+
# The each structure accept missing arguments as well, therefore we need to
|
95
|
+
# substitute them with our defaults
|
96
|
+
#
|
97
|
+
# each in iterable
|
98
|
+
# each value in iterable
|
99
|
+
# each key, value in iterable
|
100
|
+
|
101
|
+
# Value argument name provided only
|
102
|
+
if variables.length == 1
|
103
|
+
variables.unshift Settings::DefaultEachKey
|
104
|
+
|
105
|
+
# Missing key and value arguments
|
106
|
+
elsif variables.empty?
|
107
|
+
variables[0] = Settings::DefaultEachKey
|
108
|
+
variables[1] = Settings::DefaultEachValue
|
109
|
+
end
|
110
|
+
|
111
|
+
# Evaluate in current context and add to results
|
112
|
+
evaluate_children = Proc.new do |key, value, context|
|
113
|
+
# Update the local variables in the each context with the values from the
|
114
|
+
# current loop iteration
|
115
|
+
locals = {
|
116
|
+
variables[0] => key,
|
117
|
+
variables[1] => value
|
118
|
+
}
|
119
|
+
context.extend_locals locals
|
120
|
+
|
121
|
+
# Add the mapped child elements
|
122
|
+
node[@children].each do |child|
|
123
|
+
root child, indent, context
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
# Create a new context based on the parent context and progressively update
|
128
|
+
# variables in the new context
|
129
|
+
each_context = Context.new Hash.new, context.binding.clone
|
130
|
+
each_context.parent = context
|
131
|
+
|
132
|
+
# Evaluate the iterable object
|
133
|
+
enumerable = each_context.evaluate(node[@value][1])
|
134
|
+
|
135
|
+
# Check if input can be iterated
|
136
|
+
error :enumerable, node[@value][1] unless enumerable.respond_to? :each
|
137
|
+
|
138
|
+
# Selectively iterate through the input and add the result using the previously
|
139
|
+
# defined proc object
|
140
|
+
case enumerable
|
141
|
+
when Hash
|
142
|
+
enumerable.each do |key, value|
|
143
|
+
evaluate_children[key, value, context]
|
144
|
+
end
|
145
|
+
else
|
146
|
+
enumerable.each_with_index do |value, key|
|
147
|
+
evaluate_children[key, value, context]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
@@ -0,0 +1,88 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate code for all nodes by calling the method with their type name
|
6
|
+
#
|
7
|
+
# @param current [Array] Current node data with options
|
8
|
+
# @param indent [Fixnum] Indentation size for current node
|
9
|
+
# @param context [Context] Context holding environment variables
|
10
|
+
#
|
11
|
+
def def_node(node, indent, context)
|
12
|
+
# Create a new definition context
|
13
|
+
definition_context = Context.new
|
14
|
+
definition_context.extend_nonlocals context.binding
|
15
|
+
definition_context.name = node[@value]
|
16
|
+
definition_context.parent = context
|
17
|
+
|
18
|
+
# Set call node
|
19
|
+
call_node = node[@options][:call]
|
20
|
+
|
21
|
+
# Get call node attributes
|
22
|
+
attributes = call_node[@options][:attributes]
|
23
|
+
|
24
|
+
# Evaluate node extension in the current context
|
25
|
+
if call_node[@options][:extension]
|
26
|
+
extension = context.evaluate call_node[@options][:extension][@value]
|
27
|
+
else
|
28
|
+
extension = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
# Evaluate and generate node attributes, then process each one to
|
32
|
+
# by generating the required attribute code
|
33
|
+
attributes = {}
|
34
|
+
call_node[@options][:attributes].each do |key, attribute|
|
35
|
+
attributes[key] = map_attribute key, attribute, context
|
36
|
+
end
|
37
|
+
|
38
|
+
# Go through each extension attribute and use the value where applicable
|
39
|
+
extend_attributes attributes, extension
|
40
|
+
|
41
|
+
# Definition call arguments
|
42
|
+
arguments = {}
|
43
|
+
|
44
|
+
# Extract values which appear as definition parameters. If we have the
|
45
|
+
# key passed as argument, get its value. Otherwise, set the default
|
46
|
+
# parameter value
|
47
|
+
node[@options][:parameters].each do |key, value|
|
48
|
+
if attributes[key]
|
49
|
+
arguments[key] = attributes.delete key
|
50
|
+
else
|
51
|
+
arguments[key] = definition_context.evaluate value[@value]
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Set the remaining attributes as a value in the arguments
|
56
|
+
arguments[:attributes] = attributes
|
57
|
+
|
58
|
+
# Add call children to the block stack, depending on whether they're
|
59
|
+
# block elements or child elements
|
60
|
+
@block_stack << { @default_yield => [] }
|
61
|
+
|
62
|
+
# If we have a direct child, add it to the default yield (children)
|
63
|
+
# block and allow same block multiple times by appending nodes
|
64
|
+
call_node[@children].each do |child|
|
65
|
+
if child[@type] == :block
|
66
|
+
@block_stack[-1][child[@value]] ||= []
|
67
|
+
@block_stack[-1][child[@value]] += child[@children]
|
68
|
+
else
|
69
|
+
@block_stack[-1][@default_yield] << child
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
# Set variable to determine available blocks
|
74
|
+
#
|
75
|
+
arguments[:blocks] = Hash[@block_stack[-1].keys.map{|key| [key, true]}]
|
76
|
+
|
77
|
+
# Create local variables from argument variables
|
78
|
+
definition_context.extend_locals arguments
|
79
|
+
|
80
|
+
# Evaluate the model using the new context
|
81
|
+
node[@children].each do |child|
|
82
|
+
root child, indent, definition_context
|
83
|
+
end
|
84
|
+
|
85
|
+
@block_stack.pop
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Evaluate the embedded ruby code using the current context
|
6
|
+
#
|
7
|
+
# @param node [Array] Node code generation data
|
8
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
9
|
+
# @param context [Context] Processing environment data
|
10
|
+
#
|
11
|
+
def evaluate(node, indent, context)
|
12
|
+
context.evaluate node[@value]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate the code for a while control structure
|
6
|
+
#
|
7
|
+
# @param node [Array] Node code generation data
|
8
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
9
|
+
# @param context [Context] Processing environment data
|
10
|
+
#
|
11
|
+
def filter(node, indent, context)
|
12
|
+
# Evaluate and generate node attributes, then process each one to
|
13
|
+
# by generating the required attribute code
|
14
|
+
attributes = {}
|
15
|
+
node[@options].each do |key, attribute|
|
16
|
+
attributes[key] = map_attribute key, attribute, context
|
17
|
+
end
|
18
|
+
|
19
|
+
# Get registered filter name
|
20
|
+
name = node[@value]
|
21
|
+
|
22
|
+
# Check if filter is registered
|
23
|
+
error :filter_registered, name unless Filters.filters.has_key? name
|
24
|
+
|
25
|
+
# Load the required filter
|
26
|
+
Filters.filters[name].load_filter
|
27
|
+
|
28
|
+
# Render output using the chosen engine
|
29
|
+
output = Filters.filters[name].render node[@children]
|
30
|
+
|
31
|
+
# Main output node which contains filter rendered value
|
32
|
+
text_node = [:plain, :text, {value: output.rstrip, escaped: false, evaluate: false}, [], nil]
|
33
|
+
|
34
|
+
# If we have a provided filter tag, wrap the text node in the wrapper
|
35
|
+
# node tag and further indent
|
36
|
+
if (wrapper_tag = Filters.filters[name].options[:tag])
|
37
|
+
# Set wrapper tag attributes as evaluable expressions
|
38
|
+
atts = {}
|
39
|
+
Filters.filters[name].options[:attributes].each do |key, value|
|
40
|
+
atts[key] = [:expression, value.inspect, {evaluate: false, escaped: false}]
|
41
|
+
end
|
42
|
+
|
43
|
+
# Create the wrapper node containing the output text node as a child
|
44
|
+
wrapper_node = [:node, wrapper_tag, {attributes: atts}, [text_node], indent]
|
45
|
+
|
46
|
+
# Begin code generation from the wrapper node
|
47
|
+
root wrapper_node, indent, context
|
48
|
+
else
|
49
|
+
# Generate code for output text node
|
50
|
+
root text_node, indent, context
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
# @Opulent
|
2
|
+
module Opulent
|
3
|
+
# @Compiler
|
4
|
+
class Compiler
|
5
|
+
# Generate the code for a standard node element, with closing tags or
|
6
|
+
# self enclosing elements
|
7
|
+
#
|
8
|
+
# @param node [Array] Node code generation data
|
9
|
+
# @param indent [Fixnum] Size of the indentation to be added
|
10
|
+
# @param context [Context] Processing environment data
|
11
|
+
#
|
12
|
+
def node(node, indent, context)
|
13
|
+
indentation = " " * indent
|
14
|
+
|
15
|
+
# Check if the current node and last node should be displayed inline
|
16
|
+
inline_current = @inline_node.include? node[@value]
|
17
|
+
inline_last = @inline_node.include? @node_stack.last
|
18
|
+
|
19
|
+
# Check if the node is a special node which can be either inline or
|
20
|
+
# block structure. Write the special node as inline if its children
|
21
|
+
# are all inline nodes
|
22
|
+
if @multi_node.include?(node[@value])
|
23
|
+
# First condition should be removed to ignore preceding node and make
|
24
|
+
# it be inline no matter what. Using the first check, we write it
|
25
|
+
# inline only if the element before it was inline
|
26
|
+
unless @sibling_stack.last > 1 && node[@children].all? do |child|
|
27
|
+
@inline_node.include?(child[@value])
|
28
|
+
end
|
29
|
+
inline_current = false
|
30
|
+
multi = true
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# If we have an inline node, we remove the trailing newline character
|
35
|
+
# and write the tag code directly. Otherwise we add the tag code with
|
36
|
+
# normal indentation
|
37
|
+
if inline_last && inline_current
|
38
|
+
remove_trailing_newline
|
39
|
+
else
|
40
|
+
@code += indentation
|
41
|
+
end
|
42
|
+
|
43
|
+
# Add the tag opening, with leading whitespace to the code buffer
|
44
|
+
tag_open = "<#{node[@value]}"
|
45
|
+
@code += " " if node[@options][:leading_whitespace]
|
46
|
+
@code += tag_open
|
47
|
+
|
48
|
+
# Evaluate node extension in the current context
|
49
|
+
if node[@options][:extension]
|
50
|
+
extension = context.evaluate node[@options][:extension][@value]
|
51
|
+
else
|
52
|
+
extension = {}
|
53
|
+
end
|
54
|
+
|
55
|
+
# Evaluate and generate node attributes, then process each one to
|
56
|
+
# by generating the required attribute code
|
57
|
+
attributes = {}
|
58
|
+
node[@options][:attributes].each do |key, attribute|
|
59
|
+
attributes[key] = map_attribute key, attribute, context
|
60
|
+
end
|
61
|
+
|
62
|
+
# Go through each extension attribute and use the value where applicable
|
63
|
+
extend_attributes attributes, extension
|
64
|
+
|
65
|
+
# Join arrays, create new attributes by hash and set the
|
66
|
+
# value otherwise
|
67
|
+
attributes.each do |key, value|
|
68
|
+
@code += attribute_code key, value
|
69
|
+
end
|
70
|
+
|
71
|
+
# Set the current node as a parent for the node elements to follow
|
72
|
+
@node_stack << (multi ? :multi : node[@value])
|
73
|
+
|
74
|
+
# Check if the current node is self enclosing. Self enclosing nodes
|
75
|
+
# do not have any child elements
|
76
|
+
if node[@options][:self_enclosing]
|
77
|
+
# If the tag is self enclosing, it cannot have any child elements.
|
78
|
+
tag_close = ">"
|
79
|
+
tag_close += "\n"
|
80
|
+
|
81
|
+
@code += tag_close
|
82
|
+
else
|
83
|
+
# Set tag ending code
|
84
|
+
tag_end = ">"
|
85
|
+
|
86
|
+
# If the node is an inline node and doesn't have any child elements,
|
87
|
+
# we close it on the same line, without adding indentation
|
88
|
+
tag_end += "\n" unless inline_current || node[@children].empty?
|
89
|
+
|
90
|
+
# Set tag closing code
|
91
|
+
tag_close = "</#{node[@value]}>"
|
92
|
+
tag_close += " " if node[@options][:trailing_whitespace]
|
93
|
+
tag_close += "\n"
|
94
|
+
|
95
|
+
# Add tag ending to the buffer
|
96
|
+
@code += tag_end
|
97
|
+
|
98
|
+
# Get number of siblings
|
99
|
+
@sibling_stack << node[@children].size
|
100
|
+
|
101
|
+
# Process each child element recursively, increasing indentation
|
102
|
+
node[@children].each do |child|
|
103
|
+
root child, indent + Settings[:indent], context
|
104
|
+
end
|
105
|
+
|
106
|
+
# Remove the current node children count from the sibling stack
|
107
|
+
@sibling_stack.pop
|
108
|
+
|
109
|
+
# Remove all child nodes of the current node from the node stack
|
110
|
+
@node_stack.pop(node[@children].size)
|
111
|
+
|
112
|
+
# If we have an inline node, we remove the trailing newline from
|
113
|
+
# our buffer, otherwise add indentation
|
114
|
+
if inline_current
|
115
|
+
remove_trailing_newline
|
116
|
+
|
117
|
+
# If the node doesn't have any child elements, we close it on the same
|
118
|
+
# line, without adding indentation
|
119
|
+
elsif node[@children].any?
|
120
|
+
@code += indentation
|
121
|
+
end
|
122
|
+
|
123
|
+
# Close the current tag
|
124
|
+
@code += tag_close
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
# Map attributes by evaluating them in the current working context
|
129
|
+
#
|
130
|
+
# @param key [Symbol] Name of the attribute being processed
|
131
|
+
# @param attribute [Array] Attribute instance data
|
132
|
+
# @param context [Context] Processing environment data
|
133
|
+
#
|
134
|
+
def map_attribute(key, attribute, context)
|
135
|
+
# Process input value depending on its type. When array or hash, iterate
|
136
|
+
# and escape each string value.
|
137
|
+
process = Proc.new do |value|
|
138
|
+
case value
|
139
|
+
when Array
|
140
|
+
value.map do |v|
|
141
|
+
v.is_a?(String) ? escape(v) : v
|
142
|
+
end
|
143
|
+
when Hash
|
144
|
+
value.each do |k,v|
|
145
|
+
value[k] = value[k].is_a?(String) ? escape(v) : v
|
146
|
+
end
|
147
|
+
when String
|
148
|
+
escape value
|
149
|
+
else
|
150
|
+
value
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# Process each attribute depending on whether it's an array of values,
|
155
|
+
# exclusive to the class attribute, or an individual attribute value
|
156
|
+
if key == :class
|
157
|
+
attribute.map do |attrib|
|
158
|
+
value = context.evaluate attrib[@value]
|
159
|
+
attrib[@options][:escaped] ? process[value] : value
|
160
|
+
end
|
161
|
+
else
|
162
|
+
value = context.evaluate attribute[@value]
|
163
|
+
attribute[@options][:escaped] ? process[value] : value
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
# Extend attributes using the extension directive where applicable.
|
168
|
+
# Concatenate arrays, merge hashes and replace otherwise
|
169
|
+
#
|
170
|
+
# @param attributes [Hash] Evaluated node attributes
|
171
|
+
# @param extension [Hash] Node extension input
|
172
|
+
#
|
173
|
+
def extend_attributes(attributes, extension)
|
174
|
+
extension.each do |key, value|
|
175
|
+
case attributes[key]
|
176
|
+
when Array
|
177
|
+
if key == :class
|
178
|
+
attributes[key] << value
|
179
|
+
attributes[key].flatten!
|
180
|
+
else
|
181
|
+
attributes[key] = value
|
182
|
+
end
|
183
|
+
when Hash
|
184
|
+
if value.is_a? Hash
|
185
|
+
attributes[key].merge! value
|
186
|
+
else
|
187
|
+
attributes[key] = value
|
188
|
+
end
|
189
|
+
else
|
190
|
+
attributes[key] = value
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Generate attribute code for the current key value pair. For string
|
196
|
+
# values, generate a key value pair. For false values, remove the
|
197
|
+
# attribute. For true values, generate a standalone attribute key
|
198
|
+
#
|
199
|
+
# @param key [Symbol] Name of the attribute being generated
|
200
|
+
# @param value [Object] Value of the attribute
|
201
|
+
#
|
202
|
+
def attribute_code(key, value)
|
203
|
+
attribute_code = ""
|
204
|
+
|
205
|
+
case value
|
206
|
+
when Array
|
207
|
+
if key == :class
|
208
|
+
attribute_value = value.join ' '
|
209
|
+
else
|
210
|
+
attribute_value = value.join '_'
|
211
|
+
end
|
212
|
+
|
213
|
+
attribute_code += " #{key}"
|
214
|
+
attribute_code += "=\"#{attribute_value}\"" unless attribute_value.empty?
|
215
|
+
when Hash
|
216
|
+
value.each do |k,v|
|
217
|
+
if v
|
218
|
+
attribute_code += " #{key}-#{k}"
|
219
|
+
attribute_code += "=\"#{v.to_s}\"" unless v == true
|
220
|
+
end
|
221
|
+
end
|
222
|
+
else
|
223
|
+
if value
|
224
|
+
attribute_code += " #{key}"
|
225
|
+
attribute_code += "=\"#{value.to_s}\"" unless value == true
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
return attribute_code
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|