dryml 1.1.0.pre0
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/CHANGES.txt +9 -0
- data/LICENSE.txt +22 -0
- data/README +36 -0
- data/Rakefile +47 -0
- data/TODO.txt +6 -0
- data/lib/dryml/dryml_builder.rb +140 -0
- data/lib/dryml/dryml_doc.rb +155 -0
- data/lib/dryml/dryml_generator.rb +271 -0
- data/lib/dryml/dryml_support_controller.rb +13 -0
- data/lib/dryml/helper.rb +69 -0
- data/lib/dryml/parser/attribute.rb +41 -0
- data/lib/dryml/parser/base_parser.rb +254 -0
- data/lib/dryml/parser/document.rb +57 -0
- data/lib/dryml/parser/element.rb +27 -0
- data/lib/dryml/parser/elements.rb +21 -0
- data/lib/dryml/parser/source.rb +58 -0
- data/lib/dryml/parser/text.rb +13 -0
- data/lib/dryml/parser/tree_parser.rb +67 -0
- data/lib/dryml/parser.rb +3 -0
- data/lib/dryml/part_context.rb +133 -0
- data/lib/dryml/scoped_variables.rb +42 -0
- data/lib/dryml/static_tags +98 -0
- data/lib/dryml/tag_parameters.rb +32 -0
- data/lib/dryml/taglib.rb +124 -0
- data/lib/dryml/template.rb +1021 -0
- data/lib/dryml/template_environment.rb +613 -0
- data/lib/dryml/template_handler.rb +187 -0
- data/lib/dryml.rb +291 -0
- data/taglibs/core.dryml +136 -0
- data/test/dryml.rdoctest +68 -0
- metadata +131 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
module Dryml
|
2
|
+
|
3
|
+
class TemplateHandler < ActionView::TemplateHandler
|
4
|
+
|
5
|
+
def compile(*args)
|
6
|
+
# Ignore - we handle compilation ourselves
|
7
|
+
end
|
8
|
+
|
9
|
+
# Pre Rails 2.2
|
10
|
+
def render(template)
|
11
|
+
renderer = Dryml.page_renderer_for_template(@view, template.locals.keys, template)
|
12
|
+
this = @view.instance_variable_set("@this", @view.controller.send(:dryml_context) || template.locals[:this])
|
13
|
+
s = renderer.render_page(this, template.locals)
|
14
|
+
# Important to strip whitespace, or the browser hangs around for ages (FF2)
|
15
|
+
s.strip
|
16
|
+
end
|
17
|
+
|
18
|
+
def render_for_rails22(template, view, local_assigns)
|
19
|
+
renderer = Dryml.page_renderer_for_template(view, local_assigns.keys, template)
|
20
|
+
this = view.controller.send(:dryml_context) || local_assigns[:this]
|
21
|
+
@view._?.instance_variable_set("@this", this)
|
22
|
+
s = renderer.render_page(this, local_assigns)
|
23
|
+
|
24
|
+
# Important to strip whitespace, or the browser hangs around for ages (FF2)
|
25
|
+
s.strip
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
module ActionController
|
33
|
+
|
34
|
+
class Base
|
35
|
+
|
36
|
+
def dryml_context
|
37
|
+
@this
|
38
|
+
end
|
39
|
+
|
40
|
+
def dryml_fallback_tag(tag_name)
|
41
|
+
@dryml_fallback_tag = tag_name
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def call_dryml_tag(tag, options={})
|
46
|
+
@template.send(:_evaluate_assigns_and_ivars)
|
47
|
+
|
48
|
+
# TODO: Figure out what this bit is all about :-)
|
49
|
+
if options[:with]
|
50
|
+
@this = options[:with] unless options[:field]
|
51
|
+
else
|
52
|
+
options[:with] = dryml_context
|
53
|
+
end
|
54
|
+
|
55
|
+
Dryml.render_tag(@template, tag, options)
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
# TODO: This is namespace polution, should be called render_dryml_tag
|
60
|
+
def render_tag(tag, attributes={}, options={})
|
61
|
+
text = call_dryml_tag(tag, attributes)
|
62
|
+
text && render({:text => text, :layout => false }.merge(options))
|
63
|
+
end
|
64
|
+
|
65
|
+
# DRYML fallback tags -- monkey patch this method to attempt to render a tag if there's no template
|
66
|
+
def render_for_file_with_dryml(template, status = nil, layout = nil, locals = {})
|
67
|
+
# in rails 2.2, "template" is actually "template_path"
|
68
|
+
|
69
|
+
# if we're passed a MissingTemplateWrapper, see if there's a
|
70
|
+
# dryml tag that will render the page
|
71
|
+
if template.respond_to? :original_template_path
|
72
|
+
# this is the Rails 2.3 path
|
73
|
+
tag_name = @dryml_fallback_tag || "#{File.basename(template.original_template_path).dasherize}-page"
|
74
|
+
|
75
|
+
text = call_dryml_tag(tag_name)
|
76
|
+
if text
|
77
|
+
return render_for_text(text, status)
|
78
|
+
else
|
79
|
+
template.raise_wrapped_exception
|
80
|
+
end
|
81
|
+
else
|
82
|
+
begin
|
83
|
+
result = render_for_file_without_dryml(template, status, layout, locals)
|
84
|
+
rescue ActionView::MissingTemplate => ex
|
85
|
+
# this is the Rails 2.2 path
|
86
|
+
tag_name = @dryml_fallback_tag || "#{File.basename(template).dasherize}-page"
|
87
|
+
|
88
|
+
text = call_dryml_tag(tag_name)
|
89
|
+
if text
|
90
|
+
return render_for_text(text, status)
|
91
|
+
else
|
92
|
+
raise ex
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
alias_method_chain :render_for_file, :dryml
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
class ActionView::Template
|
103
|
+
|
104
|
+
def render_with_dryml(view, local_assigns = {})
|
105
|
+
if handler == Dryml::TemplateHandler
|
106
|
+
render_dryml(view, local_assigns)
|
107
|
+
else
|
108
|
+
render_without_dryml(view, local_assigns)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
alias_method_chain :render, :dryml
|
112
|
+
|
113
|
+
# We've had to copy a bunch of logic from Renderable#render, because we need to prevent Rails
|
114
|
+
# from trying to compile our template. DRYML templates are each compiled as a class, not just a method,
|
115
|
+
# so the support for compiling templates that Rails provides is innadequate.
|
116
|
+
def render_dryml(view, local_assigns = {})
|
117
|
+
if view.instance_variable_defined?(:@_render_stack)
|
118
|
+
# Rails 2.2
|
119
|
+
stack = view.instance_variable_get(:@_render_stack)
|
120
|
+
stack.push(self)
|
121
|
+
|
122
|
+
# This is only used for TestResponse to set rendered_template
|
123
|
+
unless is_a?(ActionView::InlineTemplate) || view.instance_variable_get(:@_first_render)
|
124
|
+
view.instance_variable_set(:@_first_render, self)
|
125
|
+
end
|
126
|
+
|
127
|
+
view.send(:_evaluate_assigns_and_ivars)
|
128
|
+
view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
|
129
|
+
|
130
|
+
result = Dryml::TemplateHandler.new.render_for_rails22(self, view, local_assigns)
|
131
|
+
|
132
|
+
stack.pop
|
133
|
+
result
|
134
|
+
else
|
135
|
+
# Rails 2.3
|
136
|
+
compile(local_assigns)
|
137
|
+
|
138
|
+
view.with_template self do
|
139
|
+
view.send(:_evaluate_assigns_and_ivars)
|
140
|
+
view.send(:_set_controller_content_type, mime_type) if respond_to?(:mime_type)
|
141
|
+
|
142
|
+
Dryml::TemplateHandler.new.render_for_rails22(self, view, local_assigns)
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
# this is only used in Rails 2.3
|
150
|
+
class MissingTemplateWrapper
|
151
|
+
attr_reader :original_template_path
|
152
|
+
|
153
|
+
def initialize(exception, path)
|
154
|
+
@exception = exception
|
155
|
+
@original_template_path = path
|
156
|
+
end
|
157
|
+
|
158
|
+
def method_missing(*args)
|
159
|
+
raise @exception
|
160
|
+
end
|
161
|
+
|
162
|
+
def render
|
163
|
+
raise @exception
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
module ActionView
|
169
|
+
class PathSet < Array
|
170
|
+
# this is only used by Rails 2.3
|
171
|
+
def find_template_with_dryml(original_template_path, format = nil, html_fallback = true)
|
172
|
+
begin
|
173
|
+
find_template_without_dryml(original_template_path, format, html_fallback)
|
174
|
+
rescue ActionView::MissingTemplate => ex
|
175
|
+
# instead of throwing the exception right away, hand back a
|
176
|
+
# time bomb instead. It'll blow if mishandled...
|
177
|
+
return MissingTemplateWrapper.new(ex, original_template_path)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
if method_defined? "find_template"
|
182
|
+
# only rails 2.3 has this function
|
183
|
+
alias_method_chain :find_template, :dryml
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
data/lib/dryml.rb
ADDED
@@ -0,0 +1,291 @@
|
|
1
|
+
# The Don't Repeat Yourself Markup Language
|
2
|
+
#
|
3
|
+
# Author:: Tom Locke (tom@tomlocke.com)
|
4
|
+
# Copyright:: Copyright (c) 2008
|
5
|
+
# License:: Distributes under the same terms as Ruby
|
6
|
+
|
7
|
+
|
8
|
+
|
9
|
+
# gem dependencies
|
10
|
+
require 'hobosupport'
|
11
|
+
require 'action_pack'
|
12
|
+
require 'active_record' if ActionPack::VERSION::MAJOR==2 && ActionPack::VERSION::MINOR==2
|
13
|
+
|
14
|
+
ActiveSupport::Dependencies.load_paths |= [ File.dirname(__FILE__)] if ActiveSupport.const_defined? :Dependencies
|
15
|
+
|
16
|
+
# Hobo can be installed in /vendor/hobo, /vendor/plugins/hobo, vendor/plugins/hobo/hobo, etc.
|
17
|
+
::DRYML_ROOT = File.expand_path(File.dirname(__FILE__) + "/..")
|
18
|
+
|
19
|
+
# The Don't Repeat Yourself Markup Language
|
20
|
+
module Dryml
|
21
|
+
|
22
|
+
VERSION = "1.1.0.pre0"
|
23
|
+
|
24
|
+
class DrymlSyntaxError < RuntimeError; end
|
25
|
+
|
26
|
+
class DrymlException < Exception
|
27
|
+
def initialize(message, path=nil, line_num=nil)
|
28
|
+
if path && line_num
|
29
|
+
super(message + " -- at #{path}:#{line_num}")
|
30
|
+
else
|
31
|
+
super(message)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
TagDef = Struct.new "TagDef", :name, :attrs, :proc
|
37
|
+
|
38
|
+
RESERVED_WORDS = %w{if for while do class else elsif unless case when module in}
|
39
|
+
|
40
|
+
EMPTY_PAGE = "[tag-page]"
|
41
|
+
|
42
|
+
APPLICATION_TAGLIB = { :src => "taglibs/application" }
|
43
|
+
CORE_TAGLIB = { :src => "core", :plugin => "dryml" }
|
44
|
+
|
45
|
+
DEFAULT_IMPORTS = defined?(ApplicationHelper) ? [ApplicationHelper] : []
|
46
|
+
|
47
|
+
@renderer_classes = {}
|
48
|
+
@tag_page_renderer_classes = {}
|
49
|
+
|
50
|
+
extend self
|
51
|
+
|
52
|
+
attr_accessor :last_if
|
53
|
+
|
54
|
+
def enable(generator_directories=[], output_directory=".")
|
55
|
+
ActionView::Template.register_template_handler("dryml", Dryml::TemplateHandler)
|
56
|
+
if ActionView::Template.respond_to? :exempt_from_layout
|
57
|
+
ActionView::Template.exempt_from_layout('dryml')
|
58
|
+
elsif
|
59
|
+
ActionView::Base.exempt_from_layout('dryml')
|
60
|
+
end
|
61
|
+
DrymlGenerator.enable(generator_directories, output_directory)
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def precompile_taglibs
|
66
|
+
Dir.chdir(RAILS_ROOT) do
|
67
|
+
taglibs = Dir["vendor/plugins/**/taglibs/**/*.dryml"] + Dir["app/views/taglibs/**/*.dryml"]
|
68
|
+
taglibs.each do |f|
|
69
|
+
Dryml::Taglib.get(:template_dir => File.dirname(f), :src => File.basename(f).remove(".dryml"))
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
|
75
|
+
def clear_cache
|
76
|
+
@renderer_classes = {}
|
77
|
+
@tag_page_renderer_classes = {}
|
78
|
+
end
|
79
|
+
|
80
|
+
def render_tag(view, tag, options={})
|
81
|
+
renderer = empty_page_renderer(view)
|
82
|
+
renderer.render_tag(tag, options)
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
def empty_page_renderer(view)
|
87
|
+
controller_name = view.controller.class.name.underscore.sub(/_controller$/, "")
|
88
|
+
page_renderer(view, [], "#{controller_name}/#{EMPTY_PAGE}")
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
def page_renderer_for_template(view, local_names, template)
|
93
|
+
page_renderer(view, local_names, template.path_without_extension, template.filename)
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def page_renderer(view, local_names=[], page=nil, filename=nil)
|
98
|
+
if RAILS_ENV == "development"
|
99
|
+
clear_cache
|
100
|
+
Taglib.clear_cache
|
101
|
+
end
|
102
|
+
|
103
|
+
prepare_view!(view)
|
104
|
+
included_taglibs = ([APPLICATION_TAGLIB, subsite_taglib(page)] + controller_taglibs(view.controller.class)).compact
|
105
|
+
|
106
|
+
if page.ends_with?(EMPTY_PAGE)
|
107
|
+
# DELETE ME: controller_class = controller_class_for(page)
|
108
|
+
controller_class = view.controller.class
|
109
|
+
@tag_page_renderer_classes[controller_class.name] ||=
|
110
|
+
make_renderer_class("", page, local_names, DEFAULT_IMPORTS, included_taglibs)
|
111
|
+
@tag_page_renderer_classes[controller_class.name].new(page, view)
|
112
|
+
else
|
113
|
+
filename ||= if view.view_paths.respond_to? :find_template
|
114
|
+
# Rails 2.3
|
115
|
+
view.view_paths.find_template(page + ".dryml").filename
|
116
|
+
else
|
117
|
+
# Rails 2.2
|
118
|
+
view._pick_template(page + ".dryml").filename
|
119
|
+
end
|
120
|
+
mtime = File.mtime(filename)
|
121
|
+
renderer_class = @renderer_classes[page]
|
122
|
+
|
123
|
+
# do we need to recompile?
|
124
|
+
if (!renderer_class || # nothing cached?
|
125
|
+
(local_names - renderer_class.compiled_local_names).any? || # any new local names?
|
126
|
+
renderer_class.load_time < mtime) # cache out of date?
|
127
|
+
renderer_class = make_renderer_class(File.read(filename), filename, local_names,
|
128
|
+
DEFAULT_IMPORTS, included_taglibs)
|
129
|
+
renderer_class.load_time = mtime
|
130
|
+
@renderer_classes[page] = renderer_class
|
131
|
+
end
|
132
|
+
renderer_class.new(page, view)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
|
137
|
+
# TODO: Delete this - not needed (use view.controller.class)
|
138
|
+
def controller_class_for(page)
|
139
|
+
controller, view = Controller.controller_and_view_for(page)
|
140
|
+
"#{controller.camelize}Controller".constantize
|
141
|
+
end
|
142
|
+
|
143
|
+
|
144
|
+
def controller_taglibs(controller_class)
|
145
|
+
controller_class.try.included_taglibs || []
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
def subsite_taglib(page)
|
150
|
+
parts = page.split("/")
|
151
|
+
subsite = parts.length >= 3 ? parts[0..-3].join('_') : "front"
|
152
|
+
src = "taglibs/#{subsite}_site"
|
153
|
+
{ :src => src } if Object.const_defined?(:RAILS_ROOT) && File.exists?("#{RAILS_ROOT}/app/views/#{src}.dryml")
|
154
|
+
end
|
155
|
+
|
156
|
+
def get_field(object, field)
|
157
|
+
return nil if object.nil?
|
158
|
+
field_str = field.to_s
|
159
|
+
begin
|
160
|
+
return object.send(field_str)
|
161
|
+
rescue NoMethodError => ex
|
162
|
+
if field_str =~ /^\d+$/
|
163
|
+
return object[field.to_i]
|
164
|
+
else
|
165
|
+
return object[field]
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
def get_field_path(object, path)
|
172
|
+
path = if path.is_a? String
|
173
|
+
path.split('.')
|
174
|
+
else
|
175
|
+
Array(path)
|
176
|
+
end
|
177
|
+
|
178
|
+
parent = nil
|
179
|
+
path.each do |field|
|
180
|
+
return nil if object.nil?
|
181
|
+
parent = object
|
182
|
+
object = get_field(parent, field)
|
183
|
+
end
|
184
|
+
[parent, path.last, object]
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
def prepare_view!(view)
|
189
|
+
# Not sure why this isn't done for me...
|
190
|
+
# There's probably a button to press round here somewhere
|
191
|
+
for var in %w(@flash @cookies @action_name @_session @_request @request_origin
|
192
|
+
@template @request @ignore_missing_templates @_headers @variables_added
|
193
|
+
@_flash @response @template_class
|
194
|
+
@_cookies @before_filter_chain_aborted @url
|
195
|
+
@_response @template_root @headers @_params @params @session)
|
196
|
+
unless @view.instance_variables.include?(var)
|
197
|
+
view.instance_variable_set(var, view.controller.instance_variable_get(var))
|
198
|
+
end
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
# create and compile a renderer class (AKA Dryml::Template::Environment)
|
203
|
+
#
|
204
|
+
# template_src:: the DRYML source
|
205
|
+
# template_path:: the filename of the source. This is used for
|
206
|
+
# caching
|
207
|
+
# locals:: local variables.
|
208
|
+
# imports:: A list of helper modules to import. For example, Hobo
|
209
|
+
# uses [Hobo::HoboHelper, Hobo::Translations,
|
210
|
+
# ApplicationHelper]
|
211
|
+
# included_taglibs:: A list of Taglibs to include. { :src =>
|
212
|
+
# "core", :plugin => "dryml" } is automatically
|
213
|
+
# added to this list.
|
214
|
+
#
|
215
|
+
def make_renderer_class(template_src, template_path, locals=[], imports=[], included_taglibs=[])
|
216
|
+
renderer_class = Class.new(TemplateEnvironment)
|
217
|
+
compile_renderer_class(renderer_class, template_src, template_path, locals, imports, included_taglibs)
|
218
|
+
renderer_class
|
219
|
+
end
|
220
|
+
|
221
|
+
|
222
|
+
def compile_renderer_class(renderer_class, template_src, template_path, locals, imports, included_taglibs=[])
|
223
|
+
template = Dryml::Template.new(template_src, renderer_class, template_path)
|
224
|
+
imports.each {|m| template.import_module(m)}
|
225
|
+
|
226
|
+
taglibs = [CORE_TAGLIB] + included_taglibs
|
227
|
+
|
228
|
+
# the sum of all the names we've seen so far - eventually we'll be ready for all of 'em
|
229
|
+
all_local_names = renderer_class.compiled_local_names | locals
|
230
|
+
|
231
|
+
template.compile(all_local_names, taglibs)
|
232
|
+
end
|
233
|
+
|
234
|
+
|
235
|
+
def unreserve(word)
|
236
|
+
word = word.to_s
|
237
|
+
if RESERVED_WORDS.include?(word)
|
238
|
+
word + "_"
|
239
|
+
else
|
240
|
+
word
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
|
245
|
+
def static_tags
|
246
|
+
@static_tags ||= begin
|
247
|
+
path = if Object.const_defined?(:RAILS_ROOT) && FileTest.exists?("#{RAILS_ROOT}/config/dryml_static_tags.txt")
|
248
|
+
"#{RAILS_ROOT}/config/dryml_static_tags.txt"
|
249
|
+
else
|
250
|
+
File.join(File.dirname(__FILE__), "dryml/static_tags")
|
251
|
+
end
|
252
|
+
File.readlines(path).*.chop
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
attr_writer :static_tags
|
257
|
+
|
258
|
+
# Helper function for use outside Hobo/Rails
|
259
|
+
#
|
260
|
+
# Pass the template context in locals[:this]
|
261
|
+
#
|
262
|
+
# This function caches. If the mtime of template_path is older
|
263
|
+
# than the last compilation time, the cached version will be
|
264
|
+
# used. If no template_path is given, template_src is used as the
|
265
|
+
# key to the cache.
|
266
|
+
#
|
267
|
+
# If a local variable is not present when the template is
|
268
|
+
# compiled, it will be ignored when the template is used. In
|
269
|
+
# other words, the variable values may change, but the names may
|
270
|
+
# not.
|
271
|
+
#
|
272
|
+
# included_taglibs is only used during template compilation.
|
273
|
+
#
|
274
|
+
# @param [String] template_src the DRYML source
|
275
|
+
# @param [Hash] locals local variables.
|
276
|
+
# @param [String, nil] template_path the filename of the source.
|
277
|
+
# @param [Array] included_taglibs A list of Taglibs to include. { :src =>
|
278
|
+
# "core", :plugin => "dryml" } is automatically
|
279
|
+
# added to this list.
|
280
|
+
# @param [ActionView::Base] view an ActionView instance
|
281
|
+
def render(template_src, locals={}, template_path=nil, included_taglibs=[], view=nil)
|
282
|
+
template_path ||= template_src
|
283
|
+
view ||= ActionView::Base.new(ActionController::Base.view_paths, {})
|
284
|
+
this = locals.delete(:this) || nil
|
285
|
+
|
286
|
+
renderer_class = Dryml::Template.build_cache[template_path]._?.environment ||
|
287
|
+
Dryml.make_renderer_class(template_src, template_path, locals.keys)
|
288
|
+
renderer_class.new(template_path, view).render_page(this, locals)
|
289
|
+
end
|
290
|
+
|
291
|
+
end
|
data/taglibs/core.dryml
ADDED
@@ -0,0 +1,136 @@
|
|
1
|
+
<!-- Core DRYML tags. These are included implicitly and are always available. Contains mainly control-flow tags. -->
|
2
|
+
|
3
|
+
<!-- Call the tag given by the `tag` attribute. This lets you call tags dynamically based on some runtime value.
|
4
|
+
It's the DRYML equivalent of Ruby's `send` method.
|
5
|
+
-->
|
6
|
+
<def tag="call-tag" attrs="tag">
|
7
|
+
<%= send(tag.gsub('-', '_'), attributes, parameters) %>
|
8
|
+
</def>
|
9
|
+
|
10
|
+
|
11
|
+
<!-- Wrap the body in the tag specified by the `tag` attribute, iff `when` is true.
|
12
|
+
|
13
|
+
Using regular DRYML conditional logic it is rather akward to conditionally wrap some tag in another tag. This tag makes it easy to do that.
|
14
|
+
|
15
|
+
### Usage
|
16
|
+
|
17
|
+
For example, you might want to wrap an `<img>` tag in an `<a>` tag but only under certain conditions. Say the current context has an `href` attribute that may or may not be nil. We want to wrap the img in `<a>` if `href` is not nil:
|
18
|
+
|
19
|
+
<wrap when="&this.href.present?" tag="a" href="&this.href"><img src="&this.img_filename"/></wrap>
|
20
|
+
{: .dryml}
|
21
|
+
-->
|
22
|
+
<def tag="wrap" attrs="tag, when, parameter">
|
23
|
+
<% parameter ||= :default %>
|
24
|
+
<%= when_ ? send(tag, attributes, { parameter.to_sym => parameters[:default] }) : parameters.default %>
|
25
|
+
</def>
|
26
|
+
|
27
|
+
|
28
|
+
<!-- DRYML version of `render(:partial => 'my_partial')`
|
29
|
+
|
30
|
+
### Usage
|
31
|
+
|
32
|
+
<partial name="my-partial" locals="&{:x => 10, :y => 20}"/>
|
33
|
+
-->
|
34
|
+
<def tag="partial" attrs="name, locals"><%=
|
35
|
+
locals ||= {}
|
36
|
+
render(:partial => name, :locals => locals.merge(:this => this))
|
37
|
+
%></def>
|
38
|
+
|
39
|
+
|
40
|
+
<!-- Repeat a section of mark-up. The context should be a collection (anything that responds to `each`). The content of the call to `<repeat>` will be repeated for each item in the collection, and the context will be set to each item in turn.
|
41
|
+
|
42
|
+
### Attributes
|
43
|
+
|
44
|
+
- join: The value of this attribute, if given, will be inserted between each of the items (e.g. `join=", "` is very common).
|
45
|
+
-->
|
46
|
+
<def tag="repeat" attrs="join"><if><%=
|
47
|
+
raise ArgumentError, "Cannot <repeat> on #{this.inspect}" unless this.respond_to? :each
|
48
|
+
context_map do
|
49
|
+
parameters.default
|
50
|
+
end.join(join)
|
51
|
+
%></if></def>
|
52
|
+
|
53
|
+
|
54
|
+
<!-- The 'do nothing' tag. Used to add parameters or change context without adding any markup -->
|
55
|
+
<def tag="do"><%= parameters.default %></def>
|
56
|
+
|
57
|
+
<!-- Alias of `do` -->
|
58
|
+
<def tag="with" alias-of="do"/>
|
59
|
+
|
60
|
+
<!-- DRYML's 'if' test
|
61
|
+
|
62
|
+
### Usage
|
63
|
+
|
64
|
+
<if test="¤t_user.administrtator?">Logged in as administrator</if>
|
65
|
+
<else>Logged in as normal user</else>
|
66
|
+
|
67
|
+
**IMPORTANT NOTE**: `<if>` tests for non-blank vs. blank (as defined by ActiveSuport), not true vs. false.
|
68
|
+
|
69
|
+
If you do not give the `test` attribute, uses the current context instead. This allows a nice trick like this:
|
70
|
+
|
71
|
+
<if:comments>...</if>
|
72
|
+
|
73
|
+
This has the double effect of changing the context to the `this.comments`, and only evaluating the body if there are comments (because an empty
|
74
|
+
collection is considered blank)
|
75
|
+
-->
|
76
|
+
<def tag="if" attrs="test"><%=
|
77
|
+
test = all_attributes.fetch(:test, this)
|
78
|
+
res = (cond = !test.blank?) ? parameters.default : ""
|
79
|
+
Dryml.last_if = cond
|
80
|
+
res
|
81
|
+
%></def>
|
82
|
+
|
83
|
+
<!-- General purpose `else` clause.
|
84
|
+
|
85
|
+
`<else>` works with various tags such as `<if>` and `<repeat>` (the else clause will be output if the collection was empty). It simply outputs its content if `Dryml.last_if` is false. This is pretty much a crazy hack which violates many good principles of language design, but it's very useful : )
|
86
|
+
-->
|
87
|
+
<def tag="else"><%= parameters.default unless Dryml.last_if %></def>
|
88
|
+
|
89
|
+
|
90
|
+
<!-- Same behaviour as `<if>`, except the test is negated. -->
|
91
|
+
<def tag="unless" attrs="test"><%=
|
92
|
+
test = all_attributes.fetch(:test, this)
|
93
|
+
res = (cond = test.blank?) ? parameters.default : ""
|
94
|
+
Dryml.last_if = cond
|
95
|
+
res
|
96
|
+
%></def>
|
97
|
+
|
98
|
+
|
99
|
+
<!-- nodoc. -->
|
100
|
+
<def tag="fake-field-context" attrs="fake-field, context"><%=
|
101
|
+
res = ""
|
102
|
+
new_field_context(fake_field, context) { res << parameters.default }
|
103
|
+
res
|
104
|
+
%></def>
|
105
|
+
|
106
|
+
|
107
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
108
|
+
<def tag="html"><%= "<html#{tag_options(attributes)}>" -%><do param="default"/><%= "</html>" -%></def>
|
109
|
+
|
110
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
111
|
+
<def tag="table"><%= "<table#{tag_options(attributes)}>" -%><do param="default"/><%= "</table>" -%></def>
|
112
|
+
|
113
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
114
|
+
<def tag="a"><%= "<a#{tag_options(attributes)}>" -%><do param="default"/><%= "</a>" -%></def>
|
115
|
+
|
116
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
117
|
+
<def tag="section"><%= "<section#{tag_options(attributes)}>" -%><do param="default"/><%= "</section>" -%></def>
|
118
|
+
|
119
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
120
|
+
<def tag="header"><%= "<header#{tag_options(attributes)}>" -%><do param="default"/><%= "</header>" -%></def>
|
121
|
+
|
122
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
123
|
+
<def tag="footer"><%= "<footer#{tag_options(attributes)}>" -%><do param="default"/><%= "</footer>" -%></def>
|
124
|
+
|
125
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
126
|
+
<def tag="form"><%= "<form#{tag_options(attributes)}>" -%><do param="default"/><%= "</form>" -%></def>
|
127
|
+
|
128
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
129
|
+
<def tag="submit"><%= "<submit#{tag_options(attributes)}>" -%><do param="default"/><%= "</submit>" -%></def>
|
130
|
+
|
131
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
132
|
+
<def tag="input"><%= "<input#{tag_options(attributes)}>" -%><do param="default"/><%= "</input>" -%></def>
|
133
|
+
|
134
|
+
<!-- nodoc. Define core HTML tags defined in Rapid so that DRYML can be used without Rapid. -->
|
135
|
+
<def tag="link"><%= "<link#{tag_options(attributes)}>" -%><do param="default"/><%= "</link>" -%></def>
|
136
|
+
|
data/test/dryml.rdoctest
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
|
2
|
+
>> require 'rubygems'
|
3
|
+
>> require 'active_support'
|
4
|
+
>> require 'action_view'
|
5
|
+
>> require 'action_controller'
|
6
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../hobosupport/lib')
|
7
|
+
>> $:.unshift File.join(File.expand_path(File.dirname(__FILE__)), '../../dryml/lib')
|
8
|
+
>> require 'hobosupport'
|
9
|
+
>> require 'dryml'
|
10
|
+
>> require 'dryml/template_handler'
|
11
|
+
>> Dryml.enable
|
12
|
+
|
13
|
+
{.hidden}
|
14
|
+
|
15
|
+
>> Dryml.render("hi")
|
16
|
+
=> "hi"
|
17
|
+
|
18
|
+
>> Dryml.render("<%= this %>", {:this => "hello"})
|
19
|
+
=> "hello"
|
20
|
+
|
21
|
+
>> Dryml.render(%q{<if test="&true">Hi</if><else>Bye</else>})
|
22
|
+
=> "Hi"
|
23
|
+
|
24
|
+
>> Dryml.render(%q{<repeat with="&[1,2,3]"><%= this %> </repeat>})
|
25
|
+
=> "1 2 3 "
|
26
|
+
|
27
|
+
>> Dryml.render(%q{<def tag="myp"><p param="default"/></def><myp>Hi</myp>})
|
28
|
+
=> "<p>Hi</p>"
|
29
|
+
|
30
|
+
>> Dryml.render(%q{<def tag="myp"><p param="default"/></def><call-tag tag="myp">Hi</call-tag>}).strip
|
31
|
+
=> "<p>Hi</p>"
|
32
|
+
|
33
|
+
>> Dryml.render(%q{<def tag="myp"><p param="default"/></def><wrap tag="myp" when="&true">img</wrap>}).strip
|
34
|
+
=> "<p>img</p>"
|
35
|
+
|
36
|
+
This triggers bug #452, so disabled. FIXME.
|
37
|
+
|
38
|
+
#>>
|
39
|
+
Dryml.render(%q{<def tag="myp">
|
40
|
+
<p param="default"/>
|
41
|
+
</def>
|
42
|
+
<extend tag="myp">
|
43
|
+
<old-myp merge>
|
44
|
+
<default: replace>Hello <default: restore/></default:>
|
45
|
+
</old-myp>
|
46
|
+
</extend>
|
47
|
+
<myp>World</myp>}).strip
|
48
|
+
#=> "<p>Hello World</p>"
|
49
|
+
|
50
|
+
Test caching
|
51
|
+
|
52
|
+
>> template = %q{<if test="&x">Hi</if><else>Bye</else>}
|
53
|
+
>> t1 = Time.now
|
54
|
+
>> Dryml.render(template, {:x => true})
|
55
|
+
=> "Hi"
|
56
|
+
>> t2 = Time.now
|
57
|
+
>> Dryml.render(template, {:x => false})
|
58
|
+
=> "Bye"
|
59
|
+
>> t3 = Time.now
|
60
|
+
|
61
|
+
>> (t3-t2)*1.5 < (t2-t1)
|
62
|
+
=> true
|
63
|
+
|
64
|
+
Test descendent searching
|
65
|
+
|
66
|
+
>> doc = REXML::Document.new '<a><b><c><d a="b">sean</d></c></b></a>'
|
67
|
+
>> Dryml::Template.descendent_select(doc.root) { |el| el.attribute 'a' }.size
|
68
|
+
=> 1
|