glyph 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/AUTHORS.textile +1 -1
- data/CHANGELOG.textile +119 -222
- data/LICENSE.textile +1 -1
- data/README.textile +42 -23
- data/Rakefile +1 -3
- data/VERSION +1 -1
- data/benchmark.rb +72 -0
- data/book/config.yml +4 -4
- data/book/document.glyph +90 -57
- data/book/images/document_generation.png +0 -0
- data/book/lib/macros/reference.rb +75 -22
- data/book/output/html/glyph.html +3183 -2121
- data/book/output/html/images/document_generation.png +0 -0
- data/book/output/pdf/glyph.pdf +7370 -4913
- data/book/resources/document_generation.txt +34 -0
- data/book/snippets.yml +6 -0
- data/book/text/changelog.glyph +45 -34
- data/book/text/compiling/compiling.glyph +23 -0
- data/book/text/compiling/lite_mode.glyph +23 -0
- data/book/text/compiling/programmatic_usage.glyph +77 -0
- data/book/text/extending/bookmarks_headers.glyph +21 -0
- data/book/text/extending/further_reading.glyph +13 -0
- data/book/text/extending/internals.glyph +79 -0
- data/book/text/extending/interpreting.glyph +51 -0
- data/book/text/extending/macro_def.glyph +64 -0
- data/book/text/extending/params_attrs.glyph +70 -0
- data/book/text/extending/placeholders.glyph +34 -0
- data/book/text/extending/validators.glyph +16 -0
- data/book/text/getting_started/configuration.glyph +49 -0
- data/book/text/getting_started/create_project.glyph +41 -0
- data/book/text/getting_started/structure.glyph +55 -0
- data/book/text/introduction.glyph +49 -26
- data/book/text/license.glyph +1 -1
- data/book/text/macros/macros_block.glyph +99 -0
- data/book/text/macros/macros_core.glyph +208 -0
- data/book/text/macros/macros_filters.glyph +40 -0
- data/book/text/macros/macros_inline.glyph +50 -0
- data/book/text/macros/macros_structure.glyph +100 -0
- data/book/text/ref_commands.glyph +94 -73
- data/book/text/ref_config.glyph +34 -42
- data/book/text/ref_macros.glyph +1 -373
- data/book/text/text_editing/code.glyph +51 -0
- data/book/text/text_editing/conditionals.glyph +49 -0
- data/book/text/text_editing/evaluation.glyph +13 -0
- data/book/text/text_editing/glyph_files.glyph +7 -0
- data/book/text/text_editing/images.glyph +29 -0
- data/book/text/text_editing/inclusions.glyph +44 -0
- data/book/text/text_editing/links.glyph +53 -0
- data/book/text/text_editing/macro_intro.glyph +111 -0
- data/book/text/text_editing/raw_html.glyph +112 -0
- data/book/text/text_editing/sections.glyph +63 -0
- data/book/text/text_editing/stylesheets.glyph +36 -0
- data/book/text/troubleshooting/errors_command.glyph +39 -0
- data/book/text/troubleshooting/errors_generic.glyph +29 -0
- data/book/text/troubleshooting/errors_intro.glyph +3 -0
- data/book/text/troubleshooting/errors_macro.glyph +98 -0
- data/book/text/troubleshooting/errors_parser.glyph +29 -0
- data/config.yml +77 -58
- data/document.glyph +25 -25
- data/glyph.gemspec +57 -22
- data/lib/glyph.rb +54 -13
- data/lib/glyph/commands.rb +84 -17
- data/lib/glyph/config.rb +3 -3
- data/lib/glyph/document.rb +14 -8
- data/lib/glyph/interpreter.rb +18 -58
- data/lib/glyph/macro.rb +160 -55
- data/lib/glyph/macro_validators.rb +104 -12
- data/lib/glyph/node.rb +24 -0
- data/lib/glyph/parser.rb +278 -0
- data/lib/glyph/syntax_node.rb +225 -0
- data/macros/core.rb +212 -0
- data/macros/filters.rb +66 -15
- data/macros/html/block.rb +43 -105
- data/macros/html/inline.rb +11 -12
- data/macros/html/structure.rb +123 -58
- data/macros/xml.rb +33 -0
- data/spec/files/container.textile +2 -2
- data/spec/files/document.glyph +2 -2
- data/spec/files/document_with_toc.glyph +3 -3
- data/spec/files/included.textile +1 -1
- data/spec/files/ligature.jpg +0 -0
- data/spec/files/markdown.markdown +2 -1
- data/spec/lib/commands_spec.rb +46 -3
- data/spec/lib/document_spec.rb +4 -4
- data/spec/lib/glyph_spec.rb +17 -46
- data/spec/lib/interpreter_spec.rb +6 -25
- data/spec/lib/macro_spec.rb +141 -43
- data/spec/lib/macro_validators_spec.rb +27 -5
- data/spec/lib/node_spec.rb +26 -1
- data/spec/lib/parser_spec.rb +246 -0
- data/spec/lib/syntax_node_spec.rb +111 -0
- data/spec/macros/core_spec.rb +195 -0
- data/spec/macros/filters_spec.rb +38 -4
- data/spec/macros/macros_spec.rb +20 -176
- data/spec/macros/textile_spec.rb +13 -71
- data/spec/macros/xml_spec.rb +77 -0
- data/spec/spec_helper.rb +50 -10
- data/spec/tasks/load_spec.rb +13 -2
- data/styles/default.css +18 -6
- data/styles/pagination.css +1 -19
- data/tasks/generate.rake +2 -2
- data/tasks/load.rake +27 -17
- data/tasks/project.rake +1 -1
- metadata +75 -62
- data/book/script/compile.rb +0 -8
- data/book/script/prof +0 -1
- data/book/script/prof_results.htm +0 -21079
- data/book/text/authoring.glyph +0 -548
- data/book/text/extending.glyph +0 -224
- data/book/text/getting_started.glyph +0 -158
- data/book/text/troubleshooting.glyph +0 -179
- data/lib/glyph/glyph_language.rb +0 -538
- data/lib/glyph/glyph_language.treetop +0 -27
- data/macros/common.rb +0 -160
@@ -1,48 +1,140 @@
|
|
1
1
|
module Glyph
|
2
2
|
class Macro
|
3
3
|
|
4
|
+
# @since 0.2.0
|
4
5
|
module Validators
|
5
6
|
|
6
7
|
# Validates the macro according to the specified block
|
7
8
|
# @param [String] message the message to display if the validation fails.
|
8
|
-
# @param [Hash] options a hash containing validation options
|
9
|
+
# @param [Hash] options a hash containing validation options
|
10
|
+
# @option options :level the error level (:error, :warning)
|
11
|
+
# @return [Boolean] whether the validation passed or not
|
9
12
|
# @example
|
10
|
-
# validate("Invalid macro value", :level => :error) {
|
11
|
-
# validate("Invalid macro value", :level => :warning) {
|
13
|
+
# validate("Invalid macro value", :level => :error) {value == 'valid'} # Raises an error in case of failure
|
14
|
+
# validate("Invalid macro value", :level => :warning) {value == 'valid'} # Displays a warning in case of failure
|
12
15
|
def validate(message, options={:level => :error}, &block)
|
13
|
-
|
16
|
+
result = instance_eval(&block)
|
17
|
+
unless result then
|
14
18
|
send("macro_#{options[:level]}".to_sym, message)
|
15
19
|
end
|
20
|
+
result
|
21
|
+
end
|
22
|
+
|
23
|
+
# Ensures that the macro element attributes is a valid XML element name.
|
24
|
+
# @param [Hash] options a hash containing validation options (for now the only option is :level)
|
25
|
+
# @return [Boolean] whether the validation passed or not
|
26
|
+
# @since 0.3.0
|
27
|
+
def valid_xml_element(options={:level => :error})
|
28
|
+
validate("Invalid XML element '#{@node[:element]}'", options) { @node[:element].to_s.match(/^([^[:punct:]0-9<>]|_)[^<>"']*/) }
|
29
|
+
end
|
30
|
+
|
31
|
+
# Ensures that a macro attribute name is a valid XML attribute name.
|
32
|
+
# @param [String, Symbol] name the attribute name to validate
|
33
|
+
# @param [Hash] options a hash containing validation options
|
34
|
+
# @option options :level the error level (:error, :warning)
|
35
|
+
# @return [Boolean] whether the validation passed or not
|
36
|
+
# @since 0.3.0
|
37
|
+
def valid_xml_attribute(name, options={:level => :warning})
|
38
|
+
validate("Invalid XML attribute '#{name}'", options) { name.to_s.match(/^([^[:punct:]0-9<>]|_)[^<>"']*/) }
|
16
39
|
end
|
17
40
|
|
18
41
|
# Ensures that the macro receives up to _n_ parameters.
|
19
42
|
# @param [Integer] n the maximum number of parameters allowed for the macro.
|
20
|
-
# @param [Hash] options a hash containing validation options
|
43
|
+
# @param [Hash] options a hash containing validation options
|
44
|
+
# @option options :level the error level (:error, :warning)
|
45
|
+
# @return [Boolean] whether the validation passed or not
|
21
46
|
def max_parameters(n, options={:level=>:error})
|
22
|
-
validate("Macro '#{@name}' takes up to #{n} parameter(s) (#{params.length} given)", options)
|
47
|
+
validate("Macro '#{@name}' takes up to #{n} parameter(s) (#{@node.params.length} given)", options) do
|
48
|
+
if n == 0 then
|
49
|
+
no_parameters options
|
50
|
+
else
|
51
|
+
@node.params.length <= n
|
52
|
+
end
|
53
|
+
end
|
23
54
|
end
|
24
55
|
|
25
56
|
# Ensures that the macro receives at least _n_ parameters.
|
26
57
|
# @param [Integer] n the minimum number of parameters allowed for the macro.
|
27
|
-
# @param [Hash] options a hash containing validation options
|
58
|
+
# @param [Hash] options a hash containing validation options
|
59
|
+
# @option options :level the error level (:error, :warning)
|
60
|
+
# @return [Boolean] whether the validation passed or not
|
28
61
|
def min_parameters(n, options={:level=>:error})
|
29
|
-
validate("Macro '#{@name}' takes at least #{n} parameter(s) (#{params.length} given)", options) { params.length >= n }
|
62
|
+
validate("Macro '#{@name}' takes at least #{n} parameter(s) (#{@node.params.length} given)", options) { @node.params.length >= n }
|
30
63
|
end
|
31
64
|
|
32
65
|
# Ensures that the macro receives exactly _n_ parameters.
|
33
66
|
# @param [Integer] n the number of parameters allowed for the macro.
|
34
|
-
# @param [Hash] options a hash containing validation options
|
67
|
+
# @param [Hash] options a hash containing validation options
|
68
|
+
# @option options :level the error level (:error, :warning)
|
69
|
+
# @return [Boolean] whether the validation passed or not
|
35
70
|
def exact_parameters(n, options={:level=>:error})
|
36
|
-
validate("Macro '#{@name}' takes exactly #{n} parameter(s) (#{params.length} given)", options)
|
71
|
+
validate("Macro '#{@name}' takes exactly #{n} parameter(s) (#{@node.params.length} given)", options) do
|
72
|
+
if n == 0 then
|
73
|
+
no_parameters options
|
74
|
+
else
|
75
|
+
@node.params.length == n
|
76
|
+
end
|
77
|
+
end
|
37
78
|
end
|
38
79
|
|
39
80
|
# Ensures that the macro receives no parameters.
|
40
|
-
# @param [Hash] options a hash containing validation options
|
81
|
+
# @param [Hash] options a hash containing validation options
|
82
|
+
# @option options :level the error level (:error, :warning)
|
83
|
+
# @return [Boolean] whether the validation passed or not
|
41
84
|
def no_parameters(options={:level=>:error})
|
42
|
-
validate("Macro '#{@name}' takes no parameters (#{params.length} given)", options)
|
85
|
+
validate("Macro '#{@name}' takes no parameters (#{@node.params.length} given)", options) do
|
86
|
+
case @node.params.length
|
87
|
+
when 0 then
|
88
|
+
true
|
89
|
+
when 1 then
|
90
|
+
result = true
|
91
|
+
@node.param(0).children.each do |p|
|
92
|
+
result = p.is_a?(Glyph::TextNode) && p[:value].blank?
|
93
|
+
break unless result
|
94
|
+
end
|
95
|
+
result
|
96
|
+
else
|
97
|
+
false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Raises a macro error if Glyph is running in safe mode.
|
103
|
+
# @raise [Glyph::MacroError] the macro cannot be used allowed in safe mode
|
104
|
+
# @since 0.3.0
|
105
|
+
def safety_check
|
106
|
+
macro_error "Macro '#@name' cannot be used in safe mode" if Glyph.safe?
|
43
107
|
end
|
44
108
|
|
109
|
+
# Ensure that no mutual inclusion occurs within the specified parameter or attribute
|
110
|
+
# @param [Fixnum, Symbol] the parameter index or attribute name to check
|
111
|
+
# @raise [Glyph::MacroError] mutual inclusion was detected
|
112
|
+
# @since 0.3.0
|
113
|
+
def no_mutual_inclusion_in(arg)
|
114
|
+
check_type = arg.is_a?(Symbol) ? :attribute : :parameter
|
115
|
+
check_value = nil
|
116
|
+
found = @node.find_parent do |n|
|
117
|
+
if n.is_a?(Glyph::MacroNode) && Glyph::MACROS[n[:name]] == Glyph::MACROS[@name] then
|
118
|
+
case check_type
|
119
|
+
when :attribute then
|
120
|
+
check_value = n.children.select do |node|
|
121
|
+
node.is_a?(Glyph::AttributeNode) && node[:name] == arg
|
122
|
+
end[0][:value] rescue nil
|
123
|
+
check_value == attr(arg)
|
124
|
+
when :parameter then
|
125
|
+
check_value = n.children.select do |node|
|
126
|
+
node.is_a?(Glyph::ParameterNode) && node[:name] == :"#{arg}"
|
127
|
+
end[0][:value] rescue nil
|
128
|
+
check_value == param(arg)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
if found then
|
133
|
+
macro_error "Mutual Inclusion in #{check_type}(#{arg}): '#{check_value}'", Glyph::MutualInclusionError
|
134
|
+
end
|
135
|
+
end
|
45
136
|
end
|
46
137
|
|
47
138
|
end
|
139
|
+
|
48
140
|
end
|
data/lib/glyph/node.rb
CHANGED
@@ -135,4 +135,28 @@ class Node < Hash
|
|
135
135
|
ascend(parent) {|e| return e unless e.parent }
|
136
136
|
end
|
137
137
|
|
138
|
+
# Converts self to a hash
|
139
|
+
# @return [Hash] the converted hash
|
140
|
+
# @since 0.3.0
|
141
|
+
def to_hash
|
142
|
+
{}.merge(self)
|
143
|
+
end
|
144
|
+
|
145
|
+
# @return [String] a textual representation of self
|
146
|
+
# @since 0.3.0
|
147
|
+
def inspect
|
148
|
+
string = ""
|
149
|
+
descend do |e, level|
|
150
|
+
string << " "*level+e.to_hash.inspect+"\n"
|
151
|
+
end
|
152
|
+
string.chomp
|
153
|
+
end
|
154
|
+
|
155
|
+
# @return (Boolean) true if the nodes are equal
|
156
|
+
# @since 0.3.0
|
157
|
+
def ==(node)
|
158
|
+
return false unless node.is_a? Node
|
159
|
+
self.to_hash == node.to_hash && self.children == node.children
|
160
|
+
end
|
161
|
+
|
138
162
|
end
|
data/lib/glyph/parser.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'strscan'
|
3
|
+
|
4
|
+
module Glyph
|
5
|
+
|
6
|
+
# The Glyph::Parser class can parse a string of text containing Glyph macros and produce the
|
7
|
+
# corresponding syntax tree.
|
8
|
+
# @since 0.3.0
|
9
|
+
class Parser
|
10
|
+
|
11
|
+
# Initializes the parser.
|
12
|
+
# @param [String] text the text to parse
|
13
|
+
# @param [String] source_name the name of the source file (stored in the root node)
|
14
|
+
# @since 0.3.0
|
15
|
+
def initialize(text, source_name="--")
|
16
|
+
@source_name = source_name || "--"
|
17
|
+
@input = StringScanner.new text
|
18
|
+
@output = create_node DocumentNode, :name => @source_name.to_sym
|
19
|
+
@current_macro = nil
|
20
|
+
@current_attribute = nil
|
21
|
+
end
|
22
|
+
|
23
|
+
# Parses the string of text provided during initialization
|
24
|
+
# @return [Glyph::SyntaxNode] the Abstract Syntax Tree corresponding to the string
|
25
|
+
# @since 0.3.0
|
26
|
+
def parse
|
27
|
+
count = 0
|
28
|
+
while result = parse_contents(@output) do
|
29
|
+
@output << result
|
30
|
+
count +=1
|
31
|
+
end
|
32
|
+
if @input.pos < @input.string.length then
|
33
|
+
current_char = @input.string[@input.pos].chr
|
34
|
+
illegal_delimiter = current_char.match(/\]|\[/) rescue nil
|
35
|
+
error "Macro delimiter '#{current_char}' not escaped" if illegal_delimiter
|
36
|
+
end
|
37
|
+
@output
|
38
|
+
end
|
39
|
+
|
40
|
+
protected
|
41
|
+
|
42
|
+
def parse_contents(current)
|
43
|
+
escape_sequence(current) ||
|
44
|
+
parameter_delimiter(current) ||
|
45
|
+
escaping_attribute(current) ||
|
46
|
+
escaping_macro(current) ||
|
47
|
+
attribute(current) ||
|
48
|
+
macro(current) ||
|
49
|
+
text(current)
|
50
|
+
end
|
51
|
+
|
52
|
+
def parse_escaped_contents(current)
|
53
|
+
escape_sequence(current) || parameter_delimiter(current) || escaped_text(current)
|
54
|
+
end
|
55
|
+
|
56
|
+
def escaping_macro(current)
|
57
|
+
if @input.scan(/[^\[\]\|\\\s]+\[\=/) then
|
58
|
+
name = @input.matched
|
59
|
+
name.chop!
|
60
|
+
name.chop!
|
61
|
+
error "#{name}[...] - A macro cannot start with '@' or a digit." if name.match(/^[0-1@]/)
|
62
|
+
node = create_node(MacroNode, {
|
63
|
+
:name => name.to_sym,
|
64
|
+
:escape => true,
|
65
|
+
:attributes => [],
|
66
|
+
:parameters => []
|
67
|
+
})
|
68
|
+
while contents = parse_escaped_contents(node) do
|
69
|
+
node << contents unless contents.is_a?(AttributeNode)
|
70
|
+
end
|
71
|
+
@input.scan(/\=\]/) or error "Escaping macro '#{name}' not closed"
|
72
|
+
organize_children_for node
|
73
|
+
node
|
74
|
+
else
|
75
|
+
nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def escaping_attribute(current)
|
80
|
+
if @input.scan(/@[^\[\]\|\\\s]+\[\=/) then
|
81
|
+
error "Attributes cannot be nested" if @current_attribute
|
82
|
+
name = @input.matched[1..@input.matched.length-3]
|
83
|
+
node = create_node(AttributeNode, {
|
84
|
+
:escape => true,
|
85
|
+
:name => name.to_sym
|
86
|
+
})
|
87
|
+
while contents = parse_escaped_contents(node) do
|
88
|
+
node << contents
|
89
|
+
end
|
90
|
+
current[:attributes] << node
|
91
|
+
@input.scan(/\=\]/) or error "Attribute @#{name} not closed"
|
92
|
+
node
|
93
|
+
else
|
94
|
+
nil
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def macro(current)
|
99
|
+
if @input.scan(/[^\[\]\|\\\s]+\[/) then
|
100
|
+
name = @input.matched
|
101
|
+
name.chop!
|
102
|
+
error "#{name}[...] - A macro cannot start with '@' or a digit." if name.match(/^[0-1@]/)
|
103
|
+
node = create_node(MacroNode, {
|
104
|
+
:escape => false,
|
105
|
+
:name => name.to_sym,
|
106
|
+
:attributes => [],
|
107
|
+
:parameters => []
|
108
|
+
})
|
109
|
+
while contents = parse_contents(node) do
|
110
|
+
node << contents unless contents.is_a?(AttributeNode)
|
111
|
+
end
|
112
|
+
@input.scan(/\]/) or error "Macro '#{name}' not closed"
|
113
|
+
organize_children_for node
|
114
|
+
node
|
115
|
+
else
|
116
|
+
nil
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def attribute(current)
|
121
|
+
if @input.scan(/@[^\[\]\|\\\s]+\[/) then
|
122
|
+
error "Attributes cannot be nested" if current.is_a?(AttributeNode)
|
123
|
+
name = @input.matched[1..@input.matched.length-2]
|
124
|
+
node = create_node(AttributeNode, {
|
125
|
+
:escape => false,
|
126
|
+
:name => name.to_sym
|
127
|
+
})
|
128
|
+
while contents = parse_contents(node) do
|
129
|
+
node << contents
|
130
|
+
end
|
131
|
+
current[:attributes] << node
|
132
|
+
@input.scan(/\]/) or error "Attribute @#{name} not closed"
|
133
|
+
node
|
134
|
+
else
|
135
|
+
nil
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
def text(current)
|
140
|
+
start_p = @input.pos
|
141
|
+
res = @input.scan_until /(\\.)|(\A(\]|\|)|[^\\](\]|\|)|[^\[\]\|\\\s]+\[|\Z)/
|
142
|
+
offset = @input.matched.match(/^[^\\](\]|\|)$/) ? 1 : @input.matched.length
|
143
|
+
@input.pos = @input.pos - offset rescue @input.pos
|
144
|
+
return nil if @input.pos == start_p
|
145
|
+
match = @input.string[start_p..@input.pos-1]
|
146
|
+
illegal_macro_delimiter? start_p, match
|
147
|
+
if match.length > 0 then
|
148
|
+
create_node TextNode, :value => match
|
149
|
+
else
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
def escaped_text(current)
|
155
|
+
start_p = @input.pos
|
156
|
+
res = @input.scan_until /(\\.)|(\A(\=\]|\|)|[^\\](\=\]|\|)|\Z)/
|
157
|
+
case
|
158
|
+
when @input.matched.match(/^[^\\]\=\]$/) then
|
159
|
+
offset = 2
|
160
|
+
when @input.matched.match(/^[^\\]\|$/) then
|
161
|
+
offset = 1
|
162
|
+
else
|
163
|
+
offset = @input.matched.length
|
164
|
+
end
|
165
|
+
@input.pos = @input.pos - offset rescue @input.pos
|
166
|
+
return nil if @input.pos == start_p
|
167
|
+
match = @input.string[start_p..@input.pos-1]
|
168
|
+
illegal_nesting = match.match(/([^\[\]\|\\\s]+)\[\=/)[1] rescue nil
|
169
|
+
if illegal_nesting then
|
170
|
+
error "Cannot nest escaping macro '#{illegal_nesting}' within escaping macro '#{current[:name]}'"
|
171
|
+
end
|
172
|
+
if match.length > 0 then
|
173
|
+
create_node TextNode, :value => match, :escaped => true
|
174
|
+
else
|
175
|
+
nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
def parameter_delimiter(current)
|
180
|
+
if @input.scan(/\|/) then
|
181
|
+
# Parameters are not allowed outside macros or inside attributes
|
182
|
+
if current.is_a?(DocumentNode) || current.is_a?(AttributeNode) then
|
183
|
+
@input.pos = @input.pos-1
|
184
|
+
error "Parameter delimiter '|' not allowed here"
|
185
|
+
end
|
186
|
+
create_node SyntaxNode, :parameter => true
|
187
|
+
else
|
188
|
+
nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def escape_sequence(current)
|
193
|
+
if @input.scan(/\\./) then
|
194
|
+
create_node EscapeNode, :value => @input.matched, :escaped => true
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
private
|
199
|
+
|
200
|
+
def aggregate_parameters_for(node)
|
201
|
+
indices = []
|
202
|
+
count = 0
|
203
|
+
node.children.each do |n|
|
204
|
+
indices << count if n[:parameter]
|
205
|
+
count += 1
|
206
|
+
end
|
207
|
+
# No parameter found
|
208
|
+
if indices == [] then
|
209
|
+
node[:parameters][0] = create_node ParameterNode, :name => :"0"
|
210
|
+
node.children.each do |c|
|
211
|
+
node[:parameters][0] << c
|
212
|
+
end
|
213
|
+
else
|
214
|
+
# Parameters found
|
215
|
+
current_index = 0
|
216
|
+
total_parameters = 0
|
217
|
+
save_parameter = lambda do |max_index|
|
218
|
+
parameter = create_node ParameterNode, :name => "#{total_parameters}".to_sym
|
219
|
+
total_parameters +=1
|
220
|
+
current_index.upto(max_index) do |index|
|
221
|
+
parameter << (node & index)
|
222
|
+
end
|
223
|
+
node[:parameters] << parameter
|
224
|
+
end
|
225
|
+
indices.each do |i|
|
226
|
+
save_parameter.call(i-1)
|
227
|
+
current_index = i+1
|
228
|
+
end
|
229
|
+
save_parameter.call(node.children.length-1)
|
230
|
+
end
|
231
|
+
node[:parameters]
|
232
|
+
end
|
233
|
+
|
234
|
+
def organize_children_for(node)
|
235
|
+
aggregate_parameters_for node
|
236
|
+
node.children.clear
|
237
|
+
node[:parameters].each do |p|
|
238
|
+
node << p
|
239
|
+
end
|
240
|
+
empty_parameter =
|
241
|
+
node.children.length == 1 &&
|
242
|
+
((node&0).children.length == 0 ||
|
243
|
+
(node&0).children.length == 0 &&
|
244
|
+
(node&0&0).is_a?(TextNode) &&
|
245
|
+
(node&0&0)[:value].blank?)
|
246
|
+
node.children.clear if empty_parameter
|
247
|
+
node.delete(:parameters)
|
248
|
+
node[:attributes].each do |a|
|
249
|
+
node << a
|
250
|
+
end
|
251
|
+
node.delete(:attributes)
|
252
|
+
end
|
253
|
+
|
254
|
+
def illegal_macro_delimiter?(start_p, string)
|
255
|
+
string.match(/\A(\[|\])|[^\\](\[|\])/)
|
256
|
+
illegal_delimiter = $1 || $2
|
257
|
+
if illegal_delimiter then
|
258
|
+
@input.pos = start_p + string.index(illegal_delimiter)
|
259
|
+
error "Macro delimiter '#{illegal_delimiter}' not escaped"
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def error(msg)
|
264
|
+
lines = @input.string[0..@input.pos].split(/\n/)
|
265
|
+
line = lines.length
|
266
|
+
column = lines.last.length
|
267
|
+
raise Glyph::SyntaxError.new("#{@source_name} [#{line}, #{column}] "+msg)
|
268
|
+
end
|
269
|
+
|
270
|
+
def create_node(klass, hash={})
|
271
|
+
klass.new.from hash
|
272
|
+
end
|
273
|
+
|
274
|
+
end
|
275
|
+
|
276
|
+
end
|
277
|
+
|
278
|
+
|