zena 1.2.1 → 1.2.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.
- data/History.txt +38 -1
- data/app/controllers/documents_controller.rb +7 -5
- data/app/controllers/nodes_controller.rb +47 -6
- data/app/controllers/user_sessions_controller.rb +12 -3
- data/app/controllers/virtual_classes_controller.rb +8 -2
- data/app/models/acl.rb +5 -2
- data/app/models/cached_page.rb +5 -5
- data/app/models/column.rb +27 -4
- data/app/models/group.rb +1 -1
- data/app/models/node.rb +106 -24
- data/app/models/note.rb +2 -1
- data/app/models/relation.rb +9 -4
- data/app/models/relation_proxy.rb +2 -2
- data/app/models/role.rb +12 -5
- data/app/models/site.rb +10 -9
- data/app/models/skin.rb +8 -0
- data/app/models/string_hash.rb +65 -0
- data/app/models/text_document.rb +1 -1
- data/app/models/user.rb +2 -0
- data/app/models/virtual_class.rb +43 -10
- data/app/views/comments/create.rjs +1 -32
- data/app/views/comments/edit.rjs +1 -1
- data/app/views/comments/update.rjs +1 -1
- data/app/views/documents/show.rhtml +1 -1
- data/app/views/groups/_form.rhtml +7 -0
- data/app/views/groups/_li.rhtml +1 -1
- data/app/views/nodes/500.html +2 -1
- data/app/views/nodes/destroy.rjs +2 -0
- data/app/views/sites/jobs.erb +2 -3
- data/app/views/templates/document_create_tabs/_file.rhtml +1 -1
- data/app/views/templates/document_create_tabs/_import.rhtml +4 -1
- data/app/views/templates/document_create_tabs/_template.rhtml +3 -0
- data/app/views/templates/document_create_tabs/_text_document.rhtml +3 -0
- data/app/views/versions/custom_tab.rhtml +1 -1
- data/app/views/versions/edit.rhtml +1 -1
- data/bricks/acls/lib/bricks/acls.rb +3 -3
- data/bricks/acls/zena/test/unit/acl_test.rb +15 -0
- data/bricks/fs_skin/lib/bricks/fs_skin.rb +190 -0
- data/bricks/fs_skin/zena/init.rb +1 -0
- data/bricks/fs_skin/zena/migrate/20110702010330_add_fs_skin_to_idx_templates.rb +12 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Image-edit.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Image.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Node-+index.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Node-+notFound.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Node-+search.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Node.zafu +1 -1
- data/bricks/{static → fs_skin}/zena/skins/blog/Post.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Project--kml.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/Project.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/comments.zafu +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/dict.yml +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/dateBg.jpg +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/header.png +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/mapPin.png +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/menu.gif +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/menuover.gif +0 -0
- data/bricks/{static → fs_skin}/zena/skins/blog/img/style.css +0 -0
- data/bricks/fs_skin/zena/tasks.rb +26 -0
- data/bricks/{static/zena/test/integration/static_integration_test.rb → fs_skin/zena/test/integration/fs_skin_integration_test.rb} +6 -6
- data/bricks/fs_skin/zena/test/unit/fs_skin_test.rb +33 -0
- data/bricks/grid/lib/bricks/grid.rb +4 -3
- data/bricks/tags/lib/bricks/tags.rb +1 -7
- data/bricks/zena/zena/migrate/20120605091558_add_ssl_login_to_site.rb +7 -0
- data/bricks/zena/zena/migrate/20120630123551_add_auto_publish_to_group.rb +9 -0
- data/config/bricks.yml +3 -3
- data/config/gems.yml +2 -3
- data/lib/tasks/zena.rake +7 -3
- data/lib/zafu.rb +7 -0
- data/lib/zafu/all.rb +21 -0
- data/lib/zafu/compiler.rb +7 -0
- data/lib/zafu/controller_methods.rb +58 -0
- data/lib/zafu/handler.rb +57 -0
- data/lib/zafu/info.rb +4 -0
- data/lib/zafu/markup.rb +309 -0
- data/lib/zafu/mock_helper.rb +42 -0
- data/lib/zafu/node_context.rb +203 -0
- data/lib/zafu/ordered_hash.rb +53 -0
- data/lib/zafu/parser.rb +676 -0
- data/lib/zafu/parsing_rules.rb +382 -0
- data/lib/zafu/process/ajax.rb +530 -0
- data/lib/zafu/process/conditional.rb +92 -0
- data/lib/zafu/process/context.rb +186 -0
- data/lib/zafu/process/forms.rb +143 -0
- data/lib/zafu/process/html.rb +186 -0
- data/lib/zafu/process/ruby_less_processing.rb +321 -0
- data/lib/zafu/security.rb +15 -0
- data/lib/zafu/template.rb +25 -0
- data/lib/zafu/test_helper.rb +19 -0
- data/lib/zafu/view_methods.rb +6 -0
- data/lib/zena.rb +1 -1
- data/lib/zena/acts/enrollable.rb +1 -1
- data/lib/zena/app.rb +4 -17
- data/lib/zena/console.rb +18 -1
- data/lib/zena/core_ext/file_utils.rb +13 -1
- data/lib/zena/core_ext/fixnum.rb +4 -0
- data/lib/zena/core_ext/float.rb +7 -0
- data/lib/zena/deploy.rb +4 -2
- data/lib/zena/deploy/app_init.rhtml +2 -1
- data/lib/zena/deploy/database.rhtml +1 -1
- data/lib/zena/info.rb +1 -1
- data/lib/zena/parser/zazen_rules.rb +4 -4
- data/lib/zena/routes.rb +1 -1
- data/lib/zena/test_controller.rb +1 -1
- data/lib/zena/use.rb +14 -1
- data/lib/zena/use/action.rb +4 -2
- data/lib/zena/use/ajax.rb +86 -38
- data/lib/zena/use/authlogic.rb +16 -1
- data/lib/zena/use/calendar.rb +37 -17
- data/lib/zena/use/conditional.rb +2 -2
- data/lib/zena/use/context.rb +30 -9
- data/lib/zena/use/dates.rb +39 -3
- data/lib/zena/use/display.rb +6 -19
- data/lib/zena/use/forms.rb +100 -79
- data/lib/zena/use/i18n.rb +40 -16
- data/lib/zena/use/query_builder.rb +0 -6
- data/lib/zena/use/query_node.rb +17 -4
- data/lib/zena/use/relations.rb +1 -3
- data/lib/zena/use/rendering.rb +10 -8
- data/lib/zena/use/scope_index.rb +5 -1
- data/lib/zena/use/search.rb +2 -1
- data/lib/zena/use/urls.rb +82 -77
- data/lib/zena/use/workflow.rb +12 -4
- data/lib/zena/use/zafu_safe_definitions.rb +37 -9
- data/lib/zena/use/zafu_templates.rb +49 -20
- data/lib/zena/use/zazen.rb +6 -2
- data/locale/it/LC_MESSAGES/zena.mo +0 -0
- data/locale/it/zena.mo +0 -0
- data/locale/it/zena.po +1982 -0
- data/public/images/arrow_back.png +0 -0
- data/public/images/remove_tag.png +0 -0
- data/public/javascripts/grid.js +800 -199
- data/public/javascripts/window.js +1 -1
- data/public/javascripts/zena.js +130 -21
- data/public/stylesheets/grid.css +11 -2
- data/public/stylesheets/zena.css +2 -1
- data/test/custom_queries/complex.host.yml +5 -0
- data/test/fixtures/files/TestNode.zafu +36 -0
- data/test/functional/nodes_controller_test.rb +18 -1
- data/test/integration/zafu_compiler/action.yml +2 -2
- data/test/integration/zafu_compiler/ajax.yml +44 -26
- data/test/integration/zafu_compiler/asset.yml +12 -2
- data/test/integration/zafu_compiler/basic.yml +0 -16
- data/test/integration/zafu_compiler/calendar.yml +6 -6
- data/test/integration/zafu_compiler/complex_ok.yml +23 -1
- data/test/integration/zafu_compiler/conditional.yml +5 -5
- data/test/integration/zafu_compiler/context.yml +6 -5
- data/test/integration/zafu_compiler/dates.yml +23 -2
- data/test/integration/zafu_compiler/display.yml +46 -2
- data/test/integration/zafu_compiler/errors.yml +2 -2
- data/test/integration/zafu_compiler/eval.yml +35 -7
- data/test/integration/zafu_compiler/forms.yml +47 -13
- data/test/integration/zafu_compiler/i18n.yml +2 -2
- data/test/integration/zafu_compiler/meta.yml +35 -1
- data/test/integration/zafu_compiler/query.yml +23 -4
- data/test/integration/zafu_compiler/relations.yml +10 -6
- data/test/integration/zafu_compiler/roles.yml +4 -4
- data/test/integration/zafu_compiler/rubyless.yml +11 -1
- data/test/integration/zafu_compiler/safe_definitions.yml +23 -5
- data/test/integration/zafu_compiler/security.yml +10 -6
- data/test/integration/zafu_compiler/urls.yml +23 -6
- data/test/integration/zafu_compiler/zafu_attributes.yml +1 -1
- data/test/integration/zafu_compiler/zazen.yml +14 -0
- data/test/selenium/Add/add3.rsel +8 -8
- data/test/selenium/Destroy/0setup.rsel +12 -0
- data/test/selenium/Destroy/destroy1.rsel +16 -0
- data/test/selenium/Edit/edit2.rsel +9 -9
- data/test/selenium/Edit/edit5.rsel +9 -9
- data/test/selenium/Edit/edit6.rsel +9 -9
- data/test/selenium/Form/form4.rsel +17 -0
- data/test/selenium/Toggle/toggle1.rsel +2 -0
- data/test/selenium/Toggle/toggle2.rsel +18 -0
- data/test/sites/zena/columns.yml +3 -0
- data/test/sites/zena/versions.yml +7 -0
- data/test/unit/cached_page_test.rb +13 -13
- data/test/unit/column_test.rb +26 -0
- data/test/unit/node_test.rb +16 -1
- data/test/unit/project_test.rb +6 -1
- data/test/unit/relation_test.rb +1 -1
- data/test/unit/role_test.rb +1 -1
- data/test/unit/string_hash_test.rb +30 -0
- data/test/unit/virtual_class_test.rb +31 -17
- data/test/unit/zafu_markup_test.rb +414 -0
- data/test/unit/zafu_node_context_test.rb +375 -0
- data/test/unit/zafu_ordered_hash_test.rb +69 -0
- data/test/unit/zena/acts/enrollable_test.rb +1 -1
- data/test/unit/zena/parser/zafu_asset.yml +0 -10
- data/test/unit/zena/parser/zazen.yml +1 -1
- data/test/unit/zena/parser_test.rb +1 -72
- data/test/unit/zena/use/dates_test.rb +1 -1
- data/test/unit/zena/use/rendering_test.rb +24 -7
- data/test/unit/zena/use/scope_index_test.rb +17 -0
- data/test/unit/zena/use/zazen_test.rb +2 -1
- data/zena.gemspec +71 -37
- metadata +104 -83
- data/app/views/nodes/destroy.erb +0 -0
- data/bricks/static/lib/bricks/static.rb +0 -151
- data/bricks/static/zena/init.rb +0 -1
- data/bricks/static/zena/migrate/20110702010330_add_static_to_idx_templates.rb +0 -12
- data/bricks/static/zena/test/unit/static_test.rb +0 -33
- data/lib/zena/parser/zafu_rules.rb +0 -244
- data/lib/zena/parser/zafu_tags.rb +0 -198
- data/lib/zena/parser/zena_rules.rb +0 -23
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Zafu
|
|
2
|
+
# This is the 'src_helper' used when none is provided. Its main purpose is to provide some information
|
|
3
|
+
# during testing.
|
|
4
|
+
class MockHelper
|
|
5
|
+
def initialize(strings = {})
|
|
6
|
+
@strings = strings
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def get_template_text(opts)
|
|
10
|
+
src = opts[:src]
|
|
11
|
+
folder = (opts[:base_path] && opts[:base_path] != '') ? opts[:base_path][1..-1].split('/') : []
|
|
12
|
+
src = src[1..-1] if src[0..0] == '/' # just ignore the 'relative' or 'absolute' tricks.
|
|
13
|
+
url = (folder + src.split('/')).join('_')
|
|
14
|
+
if test = @strings[url]
|
|
15
|
+
return [test['src'], url.split('_').join('/')]
|
|
16
|
+
else
|
|
17
|
+
nil
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def template_url_for_asset(opts)
|
|
22
|
+
"/test_#{opts[:type]}/#{opts[:src]}"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def method_missing(sym, *args)
|
|
26
|
+
arguments = args.map do |arg|
|
|
27
|
+
if arg.kind_of?(Hash)
|
|
28
|
+
res = []
|
|
29
|
+
arg.each do |k,v|
|
|
30
|
+
unless v.nil?
|
|
31
|
+
res << "#{k}:#{v.inspect.gsub(/'|"/, "|")}"
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
res.sort.join(' ')
|
|
35
|
+
else
|
|
36
|
+
arg.inspect.gsub(/'|"/, "|")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
res = "[#{sym} #{arguments.join(' ')}]"
|
|
40
|
+
end
|
|
41
|
+
end # DummyHelper
|
|
42
|
+
end
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
module Zafu
|
|
2
|
+
class NodeContext
|
|
3
|
+
# The name of the variable halding the current object or list ("@node", "var1")
|
|
4
|
+
attr_reader :name
|
|
5
|
+
|
|
6
|
+
# The previous NodeContext
|
|
7
|
+
attr_reader :up
|
|
8
|
+
|
|
9
|
+
# The type of object contained in the current context (Node, Page, Image)
|
|
10
|
+
attr_reader :klass
|
|
11
|
+
|
|
12
|
+
# The current DOM prefix to use when building DOM ids. This is set by the parser when
|
|
13
|
+
# it has a name or dom id defined ('main', 'related', 'list', etc).
|
|
14
|
+
attr_writer :dom_prefix
|
|
15
|
+
|
|
16
|
+
# This is used to force a given dom_id (in saved templates for example).
|
|
17
|
+
attr_accessor :saved_dom_id
|
|
18
|
+
|
|
19
|
+
# Any kind of information that the compiler might need to use (QueryBuilder query used
|
|
20
|
+
# to fetch the node for example).
|
|
21
|
+
attr_reader :opts
|
|
22
|
+
|
|
23
|
+
def initialize(name, klass, up = nil, opts = {})
|
|
24
|
+
@name, @klass, @up, @opts = name, klass, up, opts
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def move_to(name, klass, opts={})
|
|
28
|
+
self.class.new(name, klass, self, opts)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Since the idiom to write the node context name is the main purpose of this class, it
|
|
32
|
+
# deserves this shortcut.
|
|
33
|
+
def to_s
|
|
34
|
+
name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def single_class
|
|
38
|
+
@single_class ||= Array(klass).flatten.first
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Return true if the NodeContext represents an element of the given type. We use 'will_be' because
|
|
42
|
+
# it is equivalent to 'is_a', but for future objects (during rendering).
|
|
43
|
+
def will_be?(type)
|
|
44
|
+
single_class <= type
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Return a new node context that corresponds to the current object when rendered alone (in an ajax response or
|
|
48
|
+
# from a direct 'show' in a controller). The returned node context has no parent (up is nil).
|
|
49
|
+
# The convention is to use the class of the current object to build this name.
|
|
50
|
+
# You can also use an 'after_class' parameter to move up in the current object's class hierarchy to get
|
|
51
|
+
# ivar name (see #master_class).
|
|
52
|
+
def as_main(after_class = nil)
|
|
53
|
+
klass = after_class ? master_class(after_class) : single_class
|
|
54
|
+
res = self.class.new("@#{klass.to_s.underscore}", single_class, nil)
|
|
55
|
+
res.propagate_dom_scope! if @dom_scope
|
|
56
|
+
res.dom_prefix = self.dom_prefix
|
|
57
|
+
res
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Find the class just afer 'after_class' in the class hierarchy.
|
|
61
|
+
# For example if we have Dog < Mamal < Animal < Creature,
|
|
62
|
+
# master_class(Creature) would return Animal
|
|
63
|
+
def master_class(after_class)
|
|
64
|
+
klass = single_class
|
|
65
|
+
begin
|
|
66
|
+
up = klass.superclass
|
|
67
|
+
return klass if up == after_class
|
|
68
|
+
end while klass = up
|
|
69
|
+
return self.klass
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Generate a unique DOM id for this element based on dom_scopes defined in parent contexts.
|
|
73
|
+
# :code option returns ruby
|
|
74
|
+
# :erb option returns either string content or "<%= ... %>"
|
|
75
|
+
# default returns something to insert in interpolated string such as '#{xxx}'
|
|
76
|
+
def dom_id(opts = {})
|
|
77
|
+
dom_prefix = opts[:dom_prefix] || self.dom_prefix
|
|
78
|
+
options = {:list => true, :erb => true}.merge(opts)
|
|
79
|
+
|
|
80
|
+
if options[:erb] || options[:code]
|
|
81
|
+
dom = dom_id(options.merge(:erb => false, :code => false))
|
|
82
|
+
|
|
83
|
+
if dom =~ /^#\{([^\{]+)\}$/
|
|
84
|
+
code = $1
|
|
85
|
+
elsif dom =~ /#\{/
|
|
86
|
+
code = "%Q{#{dom}}"
|
|
87
|
+
else
|
|
88
|
+
str = dom
|
|
89
|
+
code = dom.inspect
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
if options[:code]
|
|
93
|
+
code
|
|
94
|
+
else
|
|
95
|
+
str || "<%= #{code} %>"
|
|
96
|
+
end
|
|
97
|
+
else
|
|
98
|
+
@saved_dom_id || if options[:list]
|
|
99
|
+
scopes = dom_scopes
|
|
100
|
+
scopes = [dom_prefix] if scopes.empty?
|
|
101
|
+
scopes + [make_scope_id]
|
|
102
|
+
else
|
|
103
|
+
scopes = dom_scopes
|
|
104
|
+
scopes + ((@up || scopes.empty?) ? [dom_prefix] : [])
|
|
105
|
+
end.compact.uniq.join('_')
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# This holds the current context's unique name if it has it's own or one from the hierarchy. If
|
|
110
|
+
# none is found, it builds one.
|
|
111
|
+
def dom_prefix
|
|
112
|
+
@dom_prefix || (@up ? @up.dom_prefix : nil)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Return dom_prefix without looking up.
|
|
116
|
+
def raw_dom_prefix
|
|
117
|
+
@dom_prefix
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Mark the current context as being a looping element (each) whose DOM id needs to be propagated to sub-nodes
|
|
121
|
+
# in order to ensure uniqueness of the dom_id (loops in loops problem).
|
|
122
|
+
def propagate_dom_scope!
|
|
123
|
+
@dom_scope = true
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns the first occurence of the klass up in the hierachy
|
|
127
|
+
# This does not resolve [Node] as [Node].first.
|
|
128
|
+
def get(klass)
|
|
129
|
+
if list_context?
|
|
130
|
+
return @up ? @up.get(klass) : nil
|
|
131
|
+
end
|
|
132
|
+
if self.klass <= klass
|
|
133
|
+
self
|
|
134
|
+
# return self unless list_context?
|
|
135
|
+
#
|
|
136
|
+
# res_class = self.klass
|
|
137
|
+
# method = self.name
|
|
138
|
+
# while res_class.kind_of?(Array)
|
|
139
|
+
# method = "#{method}.first"
|
|
140
|
+
# res_class = res_class.first
|
|
141
|
+
# end
|
|
142
|
+
# move_to(method, res_class)
|
|
143
|
+
elsif @up
|
|
144
|
+
@up.get(klass)
|
|
145
|
+
else
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def up(klass = nil)
|
|
151
|
+
klass ? @up.get(klass) : @up
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
# Return true if the current klass is an Array.
|
|
155
|
+
def list_context?
|
|
156
|
+
klass.kind_of?(Array)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Return the name of the current class with underscores like 'sub_page'.
|
|
160
|
+
def underscore
|
|
161
|
+
class_name.to_s.underscore
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# Return the 'real' class name or the superclass name if the current class is an anonymous class.
|
|
165
|
+
def class_name
|
|
166
|
+
klass = single_class
|
|
167
|
+
while klass.name == ''
|
|
168
|
+
klass = klass.superclass
|
|
169
|
+
end
|
|
170
|
+
if list_context?
|
|
171
|
+
"[#{klass}]"
|
|
172
|
+
else
|
|
173
|
+
klass.name
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
# Return the name to use for input fields
|
|
178
|
+
def form_name
|
|
179
|
+
@form_name ||= master_class(ActiveRecord::Base).name.underscore
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
protected
|
|
183
|
+
# List of scopes defined in ancestry (used to generate dom_id).
|
|
184
|
+
def dom_scopes
|
|
185
|
+
return [@saved_dom_id] if @dom_scope && @saved_dom_id
|
|
186
|
+
if @up
|
|
187
|
+
scopes = @up.dom_scopes
|
|
188
|
+
if @dom_scope
|
|
189
|
+
(scopes.empty? ? [dom_prefix] : scopes) + [make_scope_id]
|
|
190
|
+
else
|
|
191
|
+
scopes
|
|
192
|
+
end
|
|
193
|
+
else
|
|
194
|
+
@dom_scope ? [dom_prefix, make_scope_id] : []
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
private
|
|
199
|
+
def make_scope_id
|
|
200
|
+
"\#{#{@name}.zip}"
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module Zafu
|
|
2
|
+
|
|
3
|
+
if RUBY_VERSION.split('.')[0..1].join('.').to_f > 1.8
|
|
4
|
+
OrderedHash = Hash
|
|
5
|
+
else
|
|
6
|
+
class OrderedHash < Hash
|
|
7
|
+
|
|
8
|
+
def []=(k, v)
|
|
9
|
+
get_keys << k unless get_keys.include?(k)
|
|
10
|
+
super
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def merge!(hash)
|
|
14
|
+
hash.keys.each do |k|
|
|
15
|
+
get_keys << k unless get_keys.include?(k)
|
|
16
|
+
end
|
|
17
|
+
super
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def merge(hash)
|
|
21
|
+
res = dup
|
|
22
|
+
res.merge!(hash)
|
|
23
|
+
res
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
alias o_keys keys
|
|
27
|
+
def get_keys
|
|
28
|
+
@keys ||= o_keys
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def keys
|
|
32
|
+
get_keys.dup
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def each
|
|
36
|
+
keys.each do |k|
|
|
37
|
+
yield(k, self[k])
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def delete(k)
|
|
42
|
+
get_keys.delete(k)
|
|
43
|
+
super
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def dup
|
|
47
|
+
copy = super
|
|
48
|
+
copy.instance_variable_set(:@keys, keys)
|
|
49
|
+
copy
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
data/lib/zafu/parser.rb
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
module Zafu
|
|
2
|
+
|
|
3
|
+
def self.parser_with_rules(*modules)
|
|
4
|
+
parser = Class.new(Parser)
|
|
5
|
+
modules.flatten.each do |mod|
|
|
6
|
+
parser.send(:include, mod)
|
|
7
|
+
end
|
|
8
|
+
parser
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
class Parser
|
|
12
|
+
# If you wonder what the difference is between 'after_wrap' and 'after_process' here it is:
|
|
13
|
+
# 'after_wrap' is called by the 'wrap' method from within the method handler, 'after_process' is called
|
|
14
|
+
# at the very end. Example:
|
|
15
|
+
#
|
|
16
|
+
# <% if var = Node.all %> | <---
|
|
17
|
+
# <li>...</li> <--- content for after_wrap | <--- content for after_process
|
|
18
|
+
# <% end %> | <---
|
|
19
|
+
#
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
TEXT_CALLBACKS = %w{before_parse after_parse before_wrap wrap after_wrap after_process}
|
|
23
|
+
PROCESS_CALLBACKS = %w{before_process expander process_unknown}
|
|
24
|
+
CALLBACKS = TEXT_CALLBACKS + PROCESS_CALLBACKS
|
|
25
|
+
|
|
26
|
+
@@callbacks = {}
|
|
27
|
+
attr_accessor :text, :name, :method, :pass, :options, :blocks, :ids, :defined_ids, :parent, :errors
|
|
28
|
+
|
|
29
|
+
# Method parameters "<r:show attr='name'/>" (params contains {'attr' => 'name'}).
|
|
30
|
+
attr_accessor :params
|
|
31
|
+
|
|
32
|
+
class << self
|
|
33
|
+
def new_with_url(path, opts={})
|
|
34
|
+
helper = opts[:helper] || Zafu::MockHelper.new
|
|
35
|
+
text, fullpath, base_path = self.get_template_text(path, helper)
|
|
36
|
+
return parser_error("template '#{path}' not found", 'include') unless text
|
|
37
|
+
self.new(text, :helper => helper, :base_path => base_path, :included_history => [fullpath], :root => path, :master_template => opts[:master_template])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Retrieve the template text in the current folder or as an absolute path.
|
|
41
|
+
# This method is used when 'including' text
|
|
42
|
+
def get_template_text(path, helper, base_path=nil, opts={})
|
|
43
|
+
cache = (visitor.zafu_cache ||= {})
|
|
44
|
+
unless res = cache[path]
|
|
45
|
+
res = helper.send(:get_template_text, path, base_path, opts)
|
|
46
|
+
res = [parser_error("template '#{path}' not found", 'include'), nil, nil] unless res
|
|
47
|
+
cache[path] = res
|
|
48
|
+
end
|
|
49
|
+
res
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def parser_error(message, method)
|
|
53
|
+
"<span class='parser_error'><span class='method'>#{erb_safe method}</span> <span class='message'>#{erb_safe message}</span></span>"
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def erb_safe(text)
|
|
57
|
+
text.gsub('<%', '<%').gsub('%>', '%>')
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
CALLBACKS.each do |clbk|
|
|
61
|
+
eval %Q{
|
|
62
|
+
attr_accessor :#{clbk}_callbacks
|
|
63
|
+
|
|
64
|
+
def #{clbk}_callbacks
|
|
65
|
+
@#{clbk}_callbacks ||= superclass.respond_to?(:#{clbk}_callbacks) ? superclass.#{clbk}_callbacks : []
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def #{clbk}(*args)
|
|
69
|
+
self.#{clbk}_callbacks += args
|
|
70
|
+
end
|
|
71
|
+
}
|
|
72
|
+
end
|
|
73
|
+
end # class << self
|
|
74
|
+
|
|
75
|
+
PROCESS_CALLBACKS.each do |clbk|
|
|
76
|
+
eval %Q{
|
|
77
|
+
def #{clbk}
|
|
78
|
+
self.class.#{clbk}_callbacks.each do |callback|
|
|
79
|
+
send(callback)
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def expander
|
|
86
|
+
self.class.expander_callbacks.reverse_each do |callback|
|
|
87
|
+
if res = send(callback)
|
|
88
|
+
if res.kind_of?(String)
|
|
89
|
+
@result << res
|
|
90
|
+
end
|
|
91
|
+
return @result
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
nil
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
TEXT_CALLBACKS.each do |clbk|
|
|
98
|
+
eval %Q{
|
|
99
|
+
def #{clbk}(text)
|
|
100
|
+
self.class.#{clbk}_callbacks.each do |callback|
|
|
101
|
+
text = send(callback, text)
|
|
102
|
+
end
|
|
103
|
+
text
|
|
104
|
+
end
|
|
105
|
+
}
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
alias wrap_callbacks wrap
|
|
109
|
+
|
|
110
|
+
def wrap(text)
|
|
111
|
+
after_wrap(
|
|
112
|
+
wrap_callbacks(
|
|
113
|
+
before_wrap(text) + @out_post
|
|
114
|
+
)
|
|
115
|
+
# @text contains unparsed data (white space)
|
|
116
|
+
) + @text
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# This method is called at the very beginning of the processing chain and is
|
|
120
|
+
# used to store state to make 'process' reintrant...
|
|
121
|
+
def save_state
|
|
122
|
+
{
|
|
123
|
+
:@context => @context, # <== we need this when rendering twice the same part
|
|
124
|
+
:@result => @result,
|
|
125
|
+
:@out_post => @out_post,
|
|
126
|
+
:@params => @params.dup,
|
|
127
|
+
:@method => @method,
|
|
128
|
+
:@var => @var,
|
|
129
|
+
}
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Restore state from a hash
|
|
133
|
+
def restore_state(saved)
|
|
134
|
+
saved.each do |key, value|
|
|
135
|
+
instance_variable_set(key, value)
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def parser_error(message, method = @method, halt = true)
|
|
140
|
+
if halt
|
|
141
|
+
self.class.parser_error(message, method)
|
|
142
|
+
else
|
|
143
|
+
@errors << self.class.parser_error(message, method)
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def parser_continue(message, method = @method)
|
|
149
|
+
parser_error(message, method, false)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def process_unknown
|
|
153
|
+
self.class.process_unknown_callbacks.each do |callback|
|
|
154
|
+
if res = send(callback)
|
|
155
|
+
return res
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
@errors.empty? ? default_unknown : show_errors
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def show_errors
|
|
162
|
+
@errors.join(' ')
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def initialize(text, opts={})
|
|
166
|
+
@stack = []
|
|
167
|
+
@ok = true
|
|
168
|
+
@blocks = []
|
|
169
|
+
@errors = []
|
|
170
|
+
|
|
171
|
+
@options = {:mode=>:void, :method=>'void'}.merge(opts)
|
|
172
|
+
@params = @options.delete(:params) || {}
|
|
173
|
+
@method = @options.delete(:method)
|
|
174
|
+
@ids = @options[:ids] ||= {}
|
|
175
|
+
original_ids = @ids.dup
|
|
176
|
+
@defined_ids = {} # ids defined in this node or this node's sub blocks
|
|
177
|
+
mode = @options.delete(:mode)
|
|
178
|
+
@parent = @options.delete(:parent)
|
|
179
|
+
|
|
180
|
+
if opts[:sub]
|
|
181
|
+
@text = text
|
|
182
|
+
else
|
|
183
|
+
@text = before_parse(text)
|
|
184
|
+
if part = opts[:part]
|
|
185
|
+
# Optimization: try to find part from id
|
|
186
|
+
if @text =~ /^(.*?)<[^<>]+id='#{part}'/m
|
|
187
|
+
eat $1
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
start(mode)
|
|
193
|
+
|
|
194
|
+
# set name
|
|
195
|
+
@name ||= extract_name
|
|
196
|
+
@options[:ids][@name] = self if @name
|
|
197
|
+
|
|
198
|
+
unless opts[:sub]
|
|
199
|
+
@text = after_parse(@text)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
@ids.keys.each do |k|
|
|
203
|
+
if original_ids[k] != @ids[k]
|
|
204
|
+
@defined_ids[k] = @ids[k]
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
@ok
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def extract_name
|
|
211
|
+
@options[:name] || @params[:id]
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def to_erb(context)
|
|
215
|
+
context[:helper] ||= @options[:helper]
|
|
216
|
+
process(context)
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def start(mode)
|
|
220
|
+
enter(mode)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Pass some contextual information to siblings
|
|
224
|
+
def pass(elems = nil)
|
|
225
|
+
return @pass unless elems
|
|
226
|
+
(@pass ||= {}).merge!(elems)
|
|
227
|
+
@pass
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
# Hook called when replacing part of an included template with '<r:with part='main'>...</r:with>'
|
|
231
|
+
# This replaces the current object 'self' which is in the original included template, with the custom version 'obj'.
|
|
232
|
+
def replace_with(obj)
|
|
233
|
+
# keep @method (obj's method is always 'with')
|
|
234
|
+
@blocks = obj.blocks.empty? ? @blocks : obj.blocks
|
|
235
|
+
obj.params.delete(:part)
|
|
236
|
+
@params.merge!(obj.params)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
# Hook called when including a part "<r:include template='layout' part='title'/>"
|
|
240
|
+
def include_part(obj)
|
|
241
|
+
[obj]
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def empty?
|
|
245
|
+
@blocks == [] && (@params == {} || @params == {:part => @params[:part]})
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
def process(context={})
|
|
249
|
+
return '' if @method == 'ignore' || @method.blank?
|
|
250
|
+
|
|
251
|
+
saved = save_state
|
|
252
|
+
|
|
253
|
+
if @name
|
|
254
|
+
# we pass the name as 'context' in the children tags
|
|
255
|
+
@context = context.merge(:name => @name)
|
|
256
|
+
else
|
|
257
|
+
@context = context
|
|
258
|
+
end
|
|
259
|
+
# FIXME: replace with array and join (faster)
|
|
260
|
+
@result = ""
|
|
261
|
+
@out_post = ""
|
|
262
|
+
@pass = nil
|
|
263
|
+
|
|
264
|
+
before_process
|
|
265
|
+
|
|
266
|
+
res = wrap(expander || default_expander)
|
|
267
|
+
|
|
268
|
+
res = after_process(res)
|
|
269
|
+
|
|
270
|
+
# restore state
|
|
271
|
+
restore_state(saved)
|
|
272
|
+
|
|
273
|
+
res
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
# Default processing
|
|
277
|
+
def default_expander
|
|
278
|
+
if respond_to?("r_#{@method}".to_sym)
|
|
279
|
+
do_method("r_#{@method}".to_sym)
|
|
280
|
+
else
|
|
281
|
+
do_method(:process_unknown)
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
def do_method(sym)
|
|
286
|
+
res = self.send(sym)
|
|
287
|
+
if res.kind_of?(String)
|
|
288
|
+
@result << res
|
|
289
|
+
elsif @result.blank?
|
|
290
|
+
@result << (@errors.empty? ? '' : show_errors)
|
|
291
|
+
end
|
|
292
|
+
@result
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def r_void
|
|
296
|
+
expand_with
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
alias to_s r_void
|
|
300
|
+
|
|
301
|
+
def r_inspect
|
|
302
|
+
expand_with(:preflight=>true)
|
|
303
|
+
@blocks = []
|
|
304
|
+
self.inspect
|
|
305
|
+
end
|
|
306
|
+
|
|
307
|
+
# basic rule to display errors
|
|
308
|
+
def default_unknown
|
|
309
|
+
sp = ""
|
|
310
|
+
@params.each do |k,v|
|
|
311
|
+
sp += " #{k}=#{v.inspect.gsub("'","TMPQUOTE").gsub('"',"'").gsub("TMPQUOTE",'"')}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
res = "<span class='parser_unknown'><r:#{@method}#{sp}"
|
|
315
|
+
inner = expand_with
|
|
316
|
+
if inner != ''
|
|
317
|
+
res + "></span>#{inner}<span class='parser_unknown'><r:/#{@method}></span>"
|
|
318
|
+
else
|
|
319
|
+
res + "/></span>"
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
# Set context with variables (unsafe) from template.
|
|
324
|
+
def r_expand_with
|
|
325
|
+
hash = {}
|
|
326
|
+
@params.each do |k,v|
|
|
327
|
+
hash["exp_#{k}"] = v.inspect
|
|
328
|
+
end
|
|
329
|
+
expand_with(hash)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def r_ignore
|
|
333
|
+
''
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def include_template
|
|
337
|
+
return parser_error("missing 'template' attribute") unless @params[:template]
|
|
338
|
+
if @options[:part] && @options[:part] == @params[:part]
|
|
339
|
+
# fetching only a part, do not open this element (same as original caller) as it is useless and will make us loop the loop.
|
|
340
|
+
@method = 'ignore'
|
|
341
|
+
enter(:void)
|
|
342
|
+
return
|
|
343
|
+
end
|
|
344
|
+
@method = 'void'
|
|
345
|
+
|
|
346
|
+
# fetch text
|
|
347
|
+
@options[:included_history] ||= []
|
|
348
|
+
|
|
349
|
+
included_text, absolute_url, base_path = self.class.get_template_text(@params[:template], @options[:helper], @options[:base_path])
|
|
350
|
+
|
|
351
|
+
if absolute_url
|
|
352
|
+
absolute_url += "::#{@params[:part].gsub('/','_')}" if @params[:part]
|
|
353
|
+
absolute_url += "??#{@options[:part].gsub('/','_')}" if @options[:part]
|
|
354
|
+
if @options[:included_history].include?(absolute_url)
|
|
355
|
+
included_text = parser_error("infinity loop: #{(@options[:included_history] + [absolute_url]).join(' --> ')}", 'include')
|
|
356
|
+
else
|
|
357
|
+
included_history = @options[:included_history] + [absolute_url]
|
|
358
|
+
end
|
|
359
|
+
else
|
|
360
|
+
# Error: included_text contains the error meessage
|
|
361
|
+
@blocks = [included_text]
|
|
362
|
+
return
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
res = self.class.new(included_text, :helper => @options[:helper], :base_path => base_path, :included_history => included_history, :part => @params[:part], :parent => self) # we set :part to avoid loop failure when doing self inclusion
|
|
366
|
+
|
|
367
|
+
if @params[:part]
|
|
368
|
+
if iblock = res.ids[@params[:part]]
|
|
369
|
+
included_blocks = include_part(iblock)
|
|
370
|
+
# get all ids from inside the included part:
|
|
371
|
+
@ids.merge! iblock.defined_ids
|
|
372
|
+
else
|
|
373
|
+
included_blocks = [parser_error("'#{@params[:part]}' not found in template '#{@params[:template]}'", 'include')]
|
|
374
|
+
end
|
|
375
|
+
else
|
|
376
|
+
included_blocks = res.blocks
|
|
377
|
+
@ids.merge! res.ids
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
enter(:void) # normal scan on content
|
|
381
|
+
# replace 'with'
|
|
382
|
+
|
|
383
|
+
@blocks.each do |b|
|
|
384
|
+
next if b.kind_of?(String) || b.method != 'with'
|
|
385
|
+
if target = res.ids[b.params[:part]]
|
|
386
|
+
if target.kind_of?(String)
|
|
387
|
+
# error
|
|
388
|
+
elsif b.empty?
|
|
389
|
+
target.method = 'ignore'
|
|
390
|
+
else
|
|
391
|
+
target.replace_with(b)
|
|
392
|
+
end
|
|
393
|
+
else
|
|
394
|
+
# part not found
|
|
395
|
+
parser_error("'#{b.params[:part]}' not found in template '#{@params[:template]}'", 'with')
|
|
396
|
+
end
|
|
397
|
+
end
|
|
398
|
+
@blocks = included_blocks
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# Return a hash of all descendants. Find a specific descendant with descendant['form'] for example.
|
|
402
|
+
def all_descendants
|
|
403
|
+
@all_descendants ||= begin
|
|
404
|
+
d = {}
|
|
405
|
+
@blocks.each do |b|
|
|
406
|
+
next if b.kind_of?(String)
|
|
407
|
+
b.public_descendants.each do |k,v|
|
|
408
|
+
d[k] ||= []
|
|
409
|
+
d[k] += v
|
|
410
|
+
end
|
|
411
|
+
# latest is used first: use direct children before grandchildren.
|
|
412
|
+
d[b.method] ||= []
|
|
413
|
+
d[b.method] << b
|
|
414
|
+
end
|
|
415
|
+
d
|
|
416
|
+
end
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
# Find a direct child with +child[method]+.
|
|
420
|
+
def child
|
|
421
|
+
Hash[*@blocks.map do |b|
|
|
422
|
+
b.kind_of?(String) ? nil : [b.method, b]
|
|
423
|
+
end.compact.flatten]
|
|
424
|
+
end
|
|
425
|
+
|
|
426
|
+
def dynamic_blocks?
|
|
427
|
+
@blocks.detect { |b| !b.kind_of?(String) }
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def descendants(key)
|
|
431
|
+
all_descendants[key] || []
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def ancestors
|
|
435
|
+
@ancestors ||= begin
|
|
436
|
+
if parent
|
|
437
|
+
parent.ancestors + [parent]
|
|
438
|
+
else
|
|
439
|
+
[]
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
alias public_descendants all_descendants
|
|
445
|
+
|
|
446
|
+
# Return the last defined parent for the given keys.
|
|
447
|
+
def ancestor(keys)
|
|
448
|
+
keys = Array(keys)
|
|
449
|
+
ancestors.reverse_each do |a|
|
|
450
|
+
if keys.include?(a.method)
|
|
451
|
+
return a
|
|
452
|
+
end
|
|
453
|
+
end
|
|
454
|
+
nil
|
|
455
|
+
end
|
|
456
|
+
|
|
457
|
+
# Return the last defined descendant for the given key.
|
|
458
|
+
def descendant(key)
|
|
459
|
+
descendants(key).last
|
|
460
|
+
end
|
|
461
|
+
|
|
462
|
+
# Return the root block (the one opened first).
|
|
463
|
+
def root
|
|
464
|
+
@root ||= parent ? parent.root : self
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def success?
|
|
468
|
+
return @ok
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def flush(str=@text)
|
|
472
|
+
return if str == ''
|
|
473
|
+
if @blocks.last.kind_of?(String)
|
|
474
|
+
@blocks[-1] << str
|
|
475
|
+
else
|
|
476
|
+
@blocks << str
|
|
477
|
+
end
|
|
478
|
+
@text = @text[str.length..-1]
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
# Build blocks
|
|
482
|
+
def store(obj)
|
|
483
|
+
if obj.kind_of?(String) && @blocks.last.kind_of?(String)
|
|
484
|
+
@blocks[-1] << obj
|
|
485
|
+
elsif obj != ''
|
|
486
|
+
@blocks << obj
|
|
487
|
+
end
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
# Output ERB code during ast processing.
|
|
491
|
+
def out(str)
|
|
492
|
+
@result << str
|
|
493
|
+
# Avoid double entry when this is the last call in a render method.
|
|
494
|
+
true
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
# Output ERB code that will be inserted after @result.
|
|
498
|
+
def out_post(str)
|
|
499
|
+
@out_post << str
|
|
500
|
+
# Avoid double entry when this is the last call in a render method.
|
|
501
|
+
true
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
# Advance parser.
|
|
505
|
+
def eat(arg)
|
|
506
|
+
if arg.kind_of?(String)
|
|
507
|
+
len = arg.length
|
|
508
|
+
elsif arg.kind_of?(Fixnum)
|
|
509
|
+
len = arg
|
|
510
|
+
else
|
|
511
|
+
raise
|
|
512
|
+
end
|
|
513
|
+
@text = @text[len..-1]
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def enter(mode)
|
|
517
|
+
@stack << mode
|
|
518
|
+
# puts "ENTER(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
|
519
|
+
if mode == :void
|
|
520
|
+
sym = :scan
|
|
521
|
+
else
|
|
522
|
+
sym = "scan_#{mode}".to_sym
|
|
523
|
+
end
|
|
524
|
+
while (@text != '' && @stack[-1] == mode)
|
|
525
|
+
# puts "CONTINUE(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
|
526
|
+
self.send(sym)
|
|
527
|
+
end
|
|
528
|
+
# puts "LEAVE(#{@method},:#{mode}) [#{@text}] #{@zafu_tag_count.inspect}"
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
def make(mode, opts={})
|
|
532
|
+
if opts[:text]
|
|
533
|
+
custom_text = opts.delete(:text)
|
|
534
|
+
end
|
|
535
|
+
text = custom_text || @text
|
|
536
|
+
opts = @options.merge(opts).merge(:sub => true, :mode => mode, :parent => self)
|
|
537
|
+
new_obj = self.class.new(text, opts)
|
|
538
|
+
if new_obj.success?
|
|
539
|
+
@text = new_obj.text unless custom_text
|
|
540
|
+
new_obj.text = ""
|
|
541
|
+
store new_obj
|
|
542
|
+
else
|
|
543
|
+
flush @text[0..(new_obj.text.length - @text.length)] unless custom_text
|
|
544
|
+
end
|
|
545
|
+
# puts "MADE #{new_obj.inspect}"
|
|
546
|
+
# puts "TEXT #{@text.inspect}"
|
|
547
|
+
new_obj
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def leave(mode=nil)
|
|
551
|
+
if mode.nil?
|
|
552
|
+
@stack = []
|
|
553
|
+
return
|
|
554
|
+
end
|
|
555
|
+
pop = true
|
|
556
|
+
while @stack != [] && pop
|
|
557
|
+
pop = @stack.pop
|
|
558
|
+
break if pop == mode
|
|
559
|
+
end
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def fail
|
|
563
|
+
@ok = false
|
|
564
|
+
@stack = []
|
|
565
|
+
end
|
|
566
|
+
|
|
567
|
+
def check_params(*args)
|
|
568
|
+
missing = []
|
|
569
|
+
if args[0].kind_of?(Array)
|
|
570
|
+
# or groups
|
|
571
|
+
ok = false
|
|
572
|
+
args.each_index do |i|
|
|
573
|
+
unless args[i].kind_of?(Array)
|
|
574
|
+
missing[i] = [args[i]]
|
|
575
|
+
next
|
|
576
|
+
end
|
|
577
|
+
missing[i] = []
|
|
578
|
+
args[i].each do |arg|
|
|
579
|
+
missing[i] << arg.to_s unless @params[arg]
|
|
580
|
+
end
|
|
581
|
+
if missing[i] == []
|
|
582
|
+
ok = true
|
|
583
|
+
break
|
|
584
|
+
end
|
|
585
|
+
end
|
|
586
|
+
if ok
|
|
587
|
+
return true
|
|
588
|
+
else
|
|
589
|
+
out "[#{@method} parameter(s) missing:#{missing[0].sort.join(', ')}]"
|
|
590
|
+
return false
|
|
591
|
+
end
|
|
592
|
+
else
|
|
593
|
+
args.each do |arg|
|
|
594
|
+
missing << arg.to_s unless @params[arg]
|
|
595
|
+
end
|
|
596
|
+
end
|
|
597
|
+
if missing != []
|
|
598
|
+
out "[#{@method} parameter(s) missing:#{missing.sort.join(', ')}]"
|
|
599
|
+
return false
|
|
600
|
+
end
|
|
601
|
+
true
|
|
602
|
+
end
|
|
603
|
+
|
|
604
|
+
def expand_block(block, new_context={})
|
|
605
|
+
block.process(@context.merge(new_context))
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
def expand_with(acontext={})
|
|
609
|
+
|
|
610
|
+
blocks = acontext.delete(:blocks) || @blocks
|
|
611
|
+
res = ""
|
|
612
|
+
|
|
613
|
+
only = acontext[:only]
|
|
614
|
+
new_context = @context.merge(acontext)
|
|
615
|
+
|
|
616
|
+
if acontext[:ignore]
|
|
617
|
+
new_context[:ignore] = (@context[:ignore] || []) + (acontext[:ignore] || []).uniq
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
if acontext[:no_ignore]
|
|
621
|
+
new_context[:ignore] = (new_context[:ignore] || []) - acontext[:no_ignore]
|
|
622
|
+
end
|
|
623
|
+
|
|
624
|
+
ignore = new_context[:ignore]
|
|
625
|
+
|
|
626
|
+
blocks.each do |b|
|
|
627
|
+
if b.kind_of?(String)
|
|
628
|
+
if (!only || (only.kind_of?(Array) && only.include?(:string))) && (!ignore || !ignore.include?(:string))
|
|
629
|
+
res << b
|
|
630
|
+
end
|
|
631
|
+
elsif (!only || (only.kind_of?(Array) && only.include?(b.method)) || only =~ b.method) && (!ignore || !ignore.include?(b.method))
|
|
632
|
+
res << b.process(new_context.dup)
|
|
633
|
+
if pass = b.pass
|
|
634
|
+
new_context.merge!(pass)
|
|
635
|
+
end
|
|
636
|
+
end
|
|
637
|
+
end
|
|
638
|
+
res
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def inspect
|
|
642
|
+
attributes = []
|
|
643
|
+
params = []
|
|
644
|
+
(@params || {}).each do |k,v|
|
|
645
|
+
unless v.nil?
|
|
646
|
+
params << "#{k.inspect.gsub('"', "'")}=>'#{v}'"
|
|
647
|
+
end
|
|
648
|
+
end
|
|
649
|
+
attributes << " {= #{params.sort.join(', ')}}" unless params == []
|
|
650
|
+
|
|
651
|
+
context = []
|
|
652
|
+
(@context || {}).each do |k,v|
|
|
653
|
+
unless v.nil?
|
|
654
|
+
context << "#{k.inspect.gsub('"', "'")}=>'#{v}'"
|
|
655
|
+
end
|
|
656
|
+
end
|
|
657
|
+
attributes << " {> #{context.sort.join(', ')}}" unless context == []
|
|
658
|
+
|
|
659
|
+
res = []
|
|
660
|
+
@blocks.each do |b|
|
|
661
|
+
if b.kind_of?(String)
|
|
662
|
+
res << b
|
|
663
|
+
else
|
|
664
|
+
res << b.inspect
|
|
665
|
+
end
|
|
666
|
+
end
|
|
667
|
+
result = "[#{@method}#{attributes.join('')}"
|
|
668
|
+
if res != []
|
|
669
|
+
result += "]#{res}[/#{@method}]"
|
|
670
|
+
else
|
|
671
|
+
result += "/]"
|
|
672
|
+
end
|
|
673
|
+
result + @text
|
|
674
|
+
end
|
|
675
|
+
end # Parser
|
|
676
|
+
end # Zafu
|