hobo 0.5.3
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/LICENSE.txt +22 -0
- data/README.txt +18 -0
- data/bin/hobo +81 -0
- data/hobo_files/plugin/CHANGES.txt +963 -0
- data/hobo_files/plugin/LICENSE.txt +22 -0
- data/hobo_files/plugin/README +4 -0
- data/hobo_files/plugin/Rakefile +11 -0
- data/hobo_files/plugin/generators/hobo/hobo_generator.rb +37 -0
- data/hobo_files/plugin/generators/hobo/templates/application.dryml +2 -0
- data/hobo_files/plugin/generators/hobo/templates/guest.rb +31 -0
- data/hobo_files/plugin/generators/hobo_front_controller/USAGE +11 -0
- data/hobo_files/plugin/generators/hobo_front_controller/hobo_front_controller_generator.rb +90 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/controller.rb +51 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/functional_test.rb +18 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/index.dryml +43 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/login.dryml +44 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/search.dryml +18 -0
- data/hobo_files/plugin/generators/hobo_front_controller/templates/signup.dryml +45 -0
- data/hobo_files/plugin/generators/hobo_model/USAGE +26 -0
- data/hobo_files/plugin/generators/hobo_model/hobo_model_generator.rb +38 -0
- data/hobo_files/plugin/generators/hobo_model/templates/fixtures.yml +11 -0
- data/hobo_files/plugin/generators/hobo_model/templates/migration.rb +13 -0
- data/hobo_files/plugin/generators/hobo_model/templates/model.rb +24 -0
- data/hobo_files/plugin/generators/hobo_model/templates/unit_test.rb +10 -0
- data/hobo_files/plugin/generators/hobo_model_controller/USAGE +30 -0
- data/hobo_files/plugin/generators/hobo_model_controller/hobo_model_controller_generator.rb +43 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/controller.rb +5 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/functional_test.rb +18 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/helper.rb +2 -0
- data/hobo_files/plugin/generators/hobo_model_controller/templates/view.rhtml +2 -0
- data/hobo_files/plugin/generators/hobo_rapid/hobo_rapid_generator.rb +51 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/hobo_rapid.js +436 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/default_mapping.rb +11 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/banner.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_bodytop.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_01.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_02.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_03.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_corner_04.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_bottom.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_left.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_right.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/bkg_shadow_top.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_blue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_dblue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_green.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_purple.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/header_red.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/logo.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/spinner.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_dblue.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_green.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_purple.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/txt_list_img_red.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_01.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_02.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_03.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_corner_04.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_bottom.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_left.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_right.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/images/window_shadow_top.gif +0 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/public/stylesheets/application.css +390 -0
- data/hobo_files/plugin/generators/hobo_rapid/templates/themes/default/views/application.dryml +104 -0
- data/hobo_files/plugin/generators/hobo_user_model/USAGE +26 -0
- data/hobo_files/plugin/generators/hobo_user_model/hobo_user_model_generator.rb +38 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/fixtures.yml +11 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/migration.rb +15 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/model.rb +58 -0
- data/hobo_files/plugin/generators/hobo_user_model/templates/unit_test.rb +10 -0
- data/hobo_files/plugin/init.rb +44 -0
- data/hobo_files/plugin/lib/action_view_extensions/base.rb +14 -0
- data/hobo_files/plugin/lib/active_record/has_many_association.rb +54 -0
- data/hobo_files/plugin/lib/active_record/has_many_through_association.rb +22 -0
- data/hobo_files/plugin/lib/active_record/table_definition.rb +34 -0
- data/hobo_files/plugin/lib/extensions.rb +245 -0
- data/hobo_files/plugin/lib/extensions/test_case.rb +130 -0
- data/hobo_files/plugin/lib/hobo.rb +353 -0
- data/hobo_files/plugin/lib/hobo/HtmlString +3 -0
- data/hobo_files/plugin/lib/hobo/authenticated_user.rb +106 -0
- data/hobo_files/plugin/lib/hobo/authentication_support.rb +108 -0
- data/hobo_files/plugin/lib/hobo/composite_model.rb +66 -0
- data/hobo_files/plugin/lib/hobo/controller.rb +134 -0
- data/hobo_files/plugin/lib/hobo/controller_helpers.rb +135 -0
- data/hobo_files/plugin/lib/hobo/core.rb +475 -0
- data/hobo_files/plugin/lib/hobo/define_tags.rb +56 -0
- data/hobo_files/plugin/lib/hobo/dryml.rb +161 -0
- data/hobo_files/plugin/lib/hobo/dryml/dryml_builder.rb +126 -0
- data/hobo_files/plugin/lib/hobo/dryml/tag_module.rb +9 -0
- data/hobo_files/plugin/lib/hobo/dryml/taglib.rb +57 -0
- data/hobo_files/plugin/lib/hobo/dryml/template.rb +586 -0
- data/hobo_files/plugin/lib/hobo/dryml/template_environment.rb +302 -0
- data/hobo_files/plugin/lib/hobo/dryml/template_handler.rb +19 -0
- data/hobo_files/plugin/lib/hobo/generator.rb +25 -0
- data/hobo_files/plugin/lib/hobo/html_string.rb +3 -0
- data/hobo_files/plugin/lib/hobo/lazy_hash.rb +28 -0
- data/hobo_files/plugin/lib/hobo/mapping_tags.rb +262 -0
- data/hobo_files/plugin/lib/hobo/markdown_string.rb +7 -0
- data/hobo_files/plugin/lib/hobo/model.rb +391 -0
- data/hobo_files/plugin/lib/hobo/model_controller.rb +676 -0
- data/hobo_files/plugin/lib/hobo/model_queries.rb +92 -0
- data/hobo_files/plugin/lib/hobo/model_support.rb +44 -0
- data/hobo_files/plugin/lib/hobo/password_string.rb +3 -0
- data/hobo_files/plugin/lib/hobo/predicate_dispatch.rb +78 -0
- data/hobo_files/plugin/lib/hobo/proc_binding.rb +32 -0
- data/hobo_files/plugin/lib/hobo/rapid.rb +447 -0
- data/hobo_files/plugin/lib/hobo/static_tags +92 -0
- data/hobo_files/plugin/lib/hobo/text.rb +3 -0
- data/hobo_files/plugin/lib/hobo/textile_string.rb +13 -0
- data/hobo_files/plugin/lib/hobo/undefined.rb +41 -0
- data/hobo_files/plugin/lib/hobo/undefined_access_error.rb +5 -0
- data/hobo_files/plugin/lib/hobo/where_fragment.rb +23 -0
- data/hobo_files/plugin/lib/rexml.rb +345 -0
- data/hobo_files/plugin/tags/core.dryml +6 -0
- data/hobo_files/plugin/tags/rapid.dryml +177 -0
- data/hobo_files/plugin/tags/rapid_editing.dryml +168 -0
- data/hobo_files/plugin/tags/rapid_navigation.dryml +95 -0
- data/hobo_files/plugin/tags/rapid_pages.dryml +175 -0
- data/hobo_files/plugin/tasks/environments.rake +19 -0
- data/hobo_files/plugin/tasks/hobo_tasks.rake +4 -0
- data/hobo_files/plugin/test/hobo_dryml_template_test.rb +7 -0
- data/hobo_files/plugin/test/hobo_test.rb +7 -0
- data/hobo_files/plugin/uninstall.rb +1 -0
- metadata +206 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Hobo
|
|
2
|
+
|
|
3
|
+
module DefineTags
|
|
4
|
+
|
|
5
|
+
def self.included(base)
|
|
6
|
+
base.extend(ClassMethods)
|
|
7
|
+
base.extend(PredicateDispatch::ClassMethods)
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
module ClassMethods
|
|
11
|
+
|
|
12
|
+
attr_reader :hobo_tag_blocks
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def def_tag(name, *attrs_and_pred, &tagdef_block)
|
|
16
|
+
pred, attrs = if attrs_and_pred.first.is_a? Proc
|
|
17
|
+
[attrs_and_pred.first, attrs_and_pred[1..-1]]
|
|
18
|
+
else
|
|
19
|
+
[nil, attrs_and_pred]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
name = name.to_s
|
|
23
|
+
@hobo_tag_blocks ||= HashWithIndifferentAccess.new
|
|
24
|
+
@hobo_tag_blocks[name] = tagdef_block
|
|
25
|
+
@hobo_tag_blocks["#{name}_predicate"] = pred if pred
|
|
26
|
+
|
|
27
|
+
safe_name = Dryml.unreserve(name)
|
|
28
|
+
locals = attrs.map{|a| Hobo::Dryml.unreserve(a)} + ["options"]
|
|
29
|
+
|
|
30
|
+
def_line = if pred
|
|
31
|
+
"defp :#{safe_name}, @hobo_tag_blocks['#{name}_predicate'] do |options, block|"
|
|
32
|
+
elsif predicate_method?(safe_name)
|
|
33
|
+
# be sure not to overwrite the predicate dispatch method
|
|
34
|
+
"defp :#{safe_name} do |options, block|"
|
|
35
|
+
else
|
|
36
|
+
"def #{safe_name}(options={}, &block)"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
src = <<-END
|
|
40
|
+
#{def_line}
|
|
41
|
+
_tag_context(options, block) do |tagbody|
|
|
42
|
+
locals = _tag_locals(options, #{attrs.inspect}, [])
|
|
43
|
+
locals_hash = { :tagbody => tagbody };
|
|
44
|
+
#{locals.inspect}.each_with_index{|a, i| locals_hash[a] = locals[i]}
|
|
45
|
+
Hobo::ProcBinding.new(self, locals_hash).instance_eval(&#{self.name}.hobo_tag_blocks['#{name}'])
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
END
|
|
49
|
+
class_eval src, __FILE__, __LINE__
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
module Hobo::Dryml
|
|
2
|
+
|
|
3
|
+
class DrymlException < Exception; end
|
|
4
|
+
|
|
5
|
+
class AttributeExtensionString < String;
|
|
6
|
+
def drop_prefix; self[2..-1]; end
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
TagDef = Struct.new "TagDef", :name, :attrs, :proc
|
|
10
|
+
|
|
11
|
+
RESERVED_WORDS = %w{if for while do class else elsif unless case when module in}
|
|
12
|
+
|
|
13
|
+
EMPTY_PAGE = "[tag-page]"
|
|
14
|
+
|
|
15
|
+
@renderer_classes = {}
|
|
16
|
+
|
|
17
|
+
class << self
|
|
18
|
+
|
|
19
|
+
attr_accessor :last_if
|
|
20
|
+
|
|
21
|
+
def clear_cache
|
|
22
|
+
@renderer_classes = {}
|
|
23
|
+
@tag_page_renderer_class = nil
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render_tag(view, tag, options={})
|
|
27
|
+
renderer = empty_page_renderer(view)
|
|
28
|
+
renderer.render_tag(tag, options)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def empty_page_renderer(view)
|
|
33
|
+
page_renderer(view, [], EMPTY_PAGE)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def page_renderer(view, local_names=[], page=nil)
|
|
38
|
+
if RAILS_ENV == "development"
|
|
39
|
+
clear_cache
|
|
40
|
+
Taglib.clear_cache
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
prepare_view!(view)
|
|
44
|
+
if page == EMPTY_PAGE
|
|
45
|
+
@tag_page_renderer_class = make_renderer_class("", EMPTY_PAGE, local_names, [ApplicationHelper]) if
|
|
46
|
+
@tag_page_renderer_class.nil?
|
|
47
|
+
@tag_page_renderer_class.new(page, view)
|
|
48
|
+
else
|
|
49
|
+
page ||= view.instance_variable_get('@hobo_template_path')
|
|
50
|
+
template_path = "app/views/" + page + ".dryml"
|
|
51
|
+
src_file = File.new(File.join(RAILS_ROOT, template_path))
|
|
52
|
+
renderer_class = @renderer_classes[page]
|
|
53
|
+
|
|
54
|
+
# do we need to recompile?
|
|
55
|
+
if (!renderer_class or # nothing cached?
|
|
56
|
+
(local_names - renderer_class.compiled_local_names).any? or # any new local names?
|
|
57
|
+
renderer_class.load_time < src_file.mtime) # cache out of date?
|
|
58
|
+
renderer_class = make_renderer_class(src_file.read, template_path, local_names,
|
|
59
|
+
default_imports_for_view(view))
|
|
60
|
+
renderer_class.load_time = src_file.mtime
|
|
61
|
+
@renderer_classes[page] = renderer_class
|
|
62
|
+
end
|
|
63
|
+
renderer_class.new(page, view)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def prepare_view!(view)
|
|
69
|
+
# Not sure why this isn't done for me...
|
|
70
|
+
# There's probably a button to press round here somewhere
|
|
71
|
+
for var in %w(@flash @cookies @action_name @_session @_request @request_origin
|
|
72
|
+
@template @request @ignore_missing_templates @_headers @variables_added
|
|
73
|
+
@_flash @response @template_class
|
|
74
|
+
@_cookies @before_filter_chain_aborted @url
|
|
75
|
+
@_response @template_root @headers @_params @params @session)
|
|
76
|
+
unless @view.instance_variables.include?(var)
|
|
77
|
+
view.instance_variable_set(var, view.controller.instance_variable_get(var))
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def default_imports_for_view(view)
|
|
85
|
+
[ApplicationHelper,
|
|
86
|
+
view.controller.class.name.sub(/Controller$/, "Helper").constantize]
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def make_renderer_class(template_src, template_path, locals, imports)
|
|
91
|
+
renderer_class = Class.new(TemplateEnvironment)
|
|
92
|
+
compile_renderer_class(renderer_class, template_src, template_path, locals, imports)
|
|
93
|
+
renderer_class
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def compile_renderer_class(renderer_class, template_src, template_path, locals, imports)
|
|
98
|
+
template = Template.new(template_src, renderer_class, template_path)
|
|
99
|
+
imports.each {|m| template.import_module(m)}
|
|
100
|
+
|
|
101
|
+
# the sum of all the names we've seen so far - eventually we'll be ready for all of 'em
|
|
102
|
+
all_local_names = renderer_class.compiled_local_names | locals
|
|
103
|
+
|
|
104
|
+
template.compile(all_local_names)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def unreserve(word)
|
|
109
|
+
word = word.to_s
|
|
110
|
+
if RESERVED_WORDS.include?(word)
|
|
111
|
+
word + "_"
|
|
112
|
+
else
|
|
113
|
+
word
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def merge_tag_options(to, from)
|
|
119
|
+
res = to.merge({})
|
|
120
|
+
from.each_pair do |option, value|
|
|
121
|
+
res[option] = if value.is_a?(AttributeExtensionString) and to.has_key?(option)
|
|
122
|
+
"#{to[option]} #{value.drop_prefix}"
|
|
123
|
+
else
|
|
124
|
+
value
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
res
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def hashify_options(options, merge_into=nil)
|
|
132
|
+
result = merge_into || options
|
|
133
|
+
|
|
134
|
+
options.each_pair do |key, val|
|
|
135
|
+
if key.is_a? Array
|
|
136
|
+
result.delete(key)
|
|
137
|
+
k = key.first
|
|
138
|
+
|
|
139
|
+
if key.length == 2 and key.last.is_a? Fixnum
|
|
140
|
+
hashify_options(val) if val.is_a?(Hash)
|
|
141
|
+
result[k] ||= []
|
|
142
|
+
result[k][key.last] = val
|
|
143
|
+
else
|
|
144
|
+
existing = options[k]
|
|
145
|
+
v = if key.length == 1
|
|
146
|
+
val.is_a?(Hash) ? hashify_options(val, existing) : val
|
|
147
|
+
else
|
|
148
|
+
hashify_options({key[1..-1] => val}, existing)
|
|
149
|
+
end
|
|
150
|
+
result[k] = v
|
|
151
|
+
end
|
|
152
|
+
else
|
|
153
|
+
hashify_options(val) if val.is_a?(Hash)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
result
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
module Hobo::Dryml
|
|
2
|
+
|
|
3
|
+
class DRYMLBuilder
|
|
4
|
+
|
|
5
|
+
APPLICATION_TAGLIB = "hobolib/application"
|
|
6
|
+
CORE_TAGLIB = "plugins/hobo/tags/core"
|
|
7
|
+
|
|
8
|
+
def initialize(template_path)
|
|
9
|
+
@build_instructions = Array.new
|
|
10
|
+
@template_path = template_path
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def set_environment(environment)
|
|
15
|
+
@environment = environment
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def ready?(mtime)
|
|
20
|
+
!(@build_instructions.empty? || @last_build_time.nil? || mtime > @last_build_time)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def clear_instructions
|
|
25
|
+
@build_instructions.clear
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def add_build_instruction(params)
|
|
30
|
+
@build_instructions << params
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def <<(params)
|
|
35
|
+
@build_instructions << params
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def get_render_page_source(src, local_names)
|
|
40
|
+
locals = local_names.map{|l| "#{l} = __local_assigns__[:#{l}];"}.join(' ')
|
|
41
|
+
|
|
42
|
+
("def render_page(__page_this__, __local_assigns__); " +
|
|
43
|
+
"#{locals} new_object_context(__page_this__) do " +
|
|
44
|
+
src +
|
|
45
|
+
"; end + part_contexts_js; end")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build(local_names, auto_taglibs)
|
|
50
|
+
if auto_taglibs
|
|
51
|
+
import_taglib(CORE_TAGLIB)
|
|
52
|
+
import_taglib(APPLICATION_TAGLIB)
|
|
53
|
+
Hobo::MappingTags.apply_standard_mappings(@environment)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
@build_instructions.each do |build_instruction|
|
|
57
|
+
case build_instruction[:type]
|
|
58
|
+
when :def, :part
|
|
59
|
+
@environment.class_eval(build_instruction[:src], @template_path, build_instruction[:line_num])
|
|
60
|
+
|
|
61
|
+
when :render_page
|
|
62
|
+
method_src = get_render_page_source(build_instruction[:src], local_names)
|
|
63
|
+
@environment.compiled_local_names = local_names
|
|
64
|
+
@environment.class_eval(method_src, @template_path, build_instruction[:line_num])
|
|
65
|
+
|
|
66
|
+
when :taglib
|
|
67
|
+
import_taglib(build_instruction[:name], build_instruction[:as])
|
|
68
|
+
|
|
69
|
+
when :module
|
|
70
|
+
import_module(build_instruction[:name].constantize, build_instruction[:as])
|
|
71
|
+
|
|
72
|
+
when :set_theme
|
|
73
|
+
set_theme(build_instruction[:name])
|
|
74
|
+
|
|
75
|
+
when :alias_method
|
|
76
|
+
@environment.send(:alias_method, build_instruction[:new], build_instruction[:old])
|
|
77
|
+
|
|
78
|
+
else
|
|
79
|
+
raise HoboError.new("DRYML: Unknown build instruction type found when building #{@template_path}")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
@last_build_time = Time.now
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def expand_template_path(path)
|
|
87
|
+
base = if path.starts_with? "plugins"
|
|
88
|
+
"vendor/" + path
|
|
89
|
+
elsif path.include?("/")
|
|
90
|
+
"app/views/#{path}"
|
|
91
|
+
else
|
|
92
|
+
template_dir = File.dirname(@template_path)
|
|
93
|
+
"#{template_dir}/#{path}"
|
|
94
|
+
end
|
|
95
|
+
base + ".dryml"
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def import_taglib(src_path, as=nil)
|
|
100
|
+
path = expand_template_path(src_path)
|
|
101
|
+
unless @template_path == path
|
|
102
|
+
taglib = Taglib.get(RAILS_ROOT + (path.starts_with?("/") ? path : "/" + path))
|
|
103
|
+
taglib.import_into(@environment, as)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def import_module(mod, as=nil)
|
|
109
|
+
raise NotImplementedError.new if as
|
|
110
|
+
@environment.send(:include, mod)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def set_theme(name)
|
|
115
|
+
if Hobo.current_theme.nil? or Hobo.current_theme == name
|
|
116
|
+
Hobo.current_theme = name
|
|
117
|
+
import_taglib("hobolib/themes/#{name}/application")
|
|
118
|
+
mapping_module = "#{name}_mapping"
|
|
119
|
+
if File.exists?(path = RAILS_ROOT + "/app/views/hobolib/themes/#{mapping_module}.rb")
|
|
120
|
+
load(path)
|
|
121
|
+
Hobo::MappingTags.apply_mappings(@environment)
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
module Hobo::Dryml
|
|
2
|
+
|
|
3
|
+
class Taglib
|
|
4
|
+
|
|
5
|
+
@cache = {}
|
|
6
|
+
|
|
7
|
+
class << self
|
|
8
|
+
|
|
9
|
+
def get(path)
|
|
10
|
+
raise DrymlException, "No such taglib: #{path}" unless File.exists?(path)
|
|
11
|
+
file = File.new(path)
|
|
12
|
+
|
|
13
|
+
taglib = @cache[file.path]
|
|
14
|
+
if taglib
|
|
15
|
+
taglib.reload
|
|
16
|
+
else
|
|
17
|
+
taglib = Taglib.new(file)
|
|
18
|
+
@cache[file.path] = taglib
|
|
19
|
+
end
|
|
20
|
+
taglib
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def clear_cache
|
|
24
|
+
@cache = {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def initialize(file)
|
|
30
|
+
@file = file
|
|
31
|
+
load
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def reload
|
|
35
|
+
load if @file.mtime > @last_load_time
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def load
|
|
39
|
+
@module = Module.new
|
|
40
|
+
@file.rewind
|
|
41
|
+
template = Template.new(@file.read, @module, @file.path)
|
|
42
|
+
template.compile([], false)
|
|
43
|
+
@last_load_time = @file.mtime
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def import_into(class_or_module, as)
|
|
47
|
+
if as
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
as_class = Class.new(TemplateEnvironment) { include @module }
|
|
50
|
+
class_or_module.send(:define_method, as) { @module }
|
|
51
|
+
else
|
|
52
|
+
class_or_module.send(:include, @module)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,586 @@
|
|
|
1
|
+
require 'rexml/document'
|
|
2
|
+
|
|
3
|
+
module Hobo::Dryml
|
|
4
|
+
|
|
5
|
+
class Template
|
|
6
|
+
|
|
7
|
+
DRYML_NAME = "[a-zA-Z_][a-zA-Z0-9_]*"
|
|
8
|
+
|
|
9
|
+
@build_cache = {}
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
attr_reader :build_cache
|
|
13
|
+
|
|
14
|
+
def clear_build_cache
|
|
15
|
+
@build_cache.clear()
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def initialize(src, environment, template_path)
|
|
20
|
+
@src = src
|
|
21
|
+
|
|
22
|
+
@environment = environment # a class or a module
|
|
23
|
+
@environment.send(:include, Hobo::PredicateDispatch)
|
|
24
|
+
|
|
25
|
+
@template_path = template_path.sub(/^#{Regexp.escape(RAILS_ROOT)}/, "")
|
|
26
|
+
|
|
27
|
+
@builder = Template.build_cache[@template_path] || DRYMLBuilder.new(@template_path)
|
|
28
|
+
@builder.set_environment(environment)
|
|
29
|
+
|
|
30
|
+
@last_element = nil
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
attr_reader :tags, :template_path
|
|
34
|
+
|
|
35
|
+
def compile(local_names=[], auto_taglibs=true)
|
|
36
|
+
now = Time.now
|
|
37
|
+
|
|
38
|
+
unless @template_path == EMPTY_PAGE
|
|
39
|
+
filename = RAILS_ROOT + (@template_path.starts_with?("/") ? @template_path : "/" + @template_path)
|
|
40
|
+
mtime = File.stat(filename).mtime
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if mtime.nil? || !@builder.ready?(mtime)
|
|
44
|
+
@builder.clear_instructions
|
|
45
|
+
parsed = true
|
|
46
|
+
# parse the DRYML file creating a list of build instructions
|
|
47
|
+
if is_taglib?
|
|
48
|
+
process_src
|
|
49
|
+
else
|
|
50
|
+
create_render_page_method
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# store build instructions in the cache
|
|
54
|
+
Template.build_cache[@template_path] = @builder
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# compile the build instructions
|
|
58
|
+
@builder.build(local_names, auto_taglibs)
|
|
59
|
+
|
|
60
|
+
from_cache = (parsed ? '' : ' (from cache)')
|
|
61
|
+
logger.info(" DRYML: Compiled#{from_cache} #{template_path} in %.2fs" % (Time.now - now))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def create_render_page_method
|
|
65
|
+
erb_src = process_src
|
|
66
|
+
|
|
67
|
+
src = ERB.new(erb_src).src[("_erbout = '';").length..-1]
|
|
68
|
+
|
|
69
|
+
@builder.add_build_instruction(:type => :render_page, :src => src, :line_num => 1)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def is_taglib?
|
|
74
|
+
@environment.class == Module
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def process_src
|
|
79
|
+
# Replace <%...%> scriptlets with xml-safe references into a hash of scriptlets
|
|
80
|
+
@scriptlets = {}
|
|
81
|
+
src = @src.gsub(/<%(.*?)%>/m) do
|
|
82
|
+
_, scriptlet = *Regexp.last_match
|
|
83
|
+
id = @scriptlets.size + 1
|
|
84
|
+
@scriptlets[id] = scriptlet
|
|
85
|
+
newlines = "\n" * scriptlet.count("\n")
|
|
86
|
+
"[![HOBO-ERB#{id}#{newlines}]!]"
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@xmlsrc = "<dryml_page>" + src + "</dryml_page>"
|
|
90
|
+
|
|
91
|
+
@doc = REXML::Document.new(RexSource.new(@xmlsrc))
|
|
92
|
+
|
|
93
|
+
erb_src = restore_erb_scriptlets(children_to_erb(@doc.root))
|
|
94
|
+
|
|
95
|
+
erb_src
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def restore_erb_scriptlets(src)
|
|
100
|
+
src.gsub(/\[!\[HOBO-ERB(\d+)\s*\]!\]/m) { "<%#{@scriptlets[Regexp.last_match[1].to_i]}%>" }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def erb_process(src)
|
|
105
|
+
ERB.new(restore_erb_scriptlets(src)).src
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def children_to_erb(nodes)
|
|
110
|
+
nodes.map{|x| node_to_erb(x)}.join
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def node_to_erb(node)
|
|
115
|
+
case node
|
|
116
|
+
|
|
117
|
+
# v important this comes before REXML::Text, as REXML::CData < REXML::Text
|
|
118
|
+
when REXML::CData
|
|
119
|
+
REXML::CData::START + node.to_s + REXML::CData::STOP
|
|
120
|
+
|
|
121
|
+
when REXML::Comment
|
|
122
|
+
REXML::Comment::START + node.to_s + REXML::Comment::STOP
|
|
123
|
+
|
|
124
|
+
when REXML::Text
|
|
125
|
+
node.to_s
|
|
126
|
+
|
|
127
|
+
when REXML::Element
|
|
128
|
+
element_to_erb(node)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def element_to_erb(el)
|
|
134
|
+
dryml_exception("badly placed parameter tag <#{el.name}>", el) if
|
|
135
|
+
el.name.starts_with?(":")
|
|
136
|
+
|
|
137
|
+
@last_element = el
|
|
138
|
+
case el.name
|
|
139
|
+
|
|
140
|
+
when "taglib"
|
|
141
|
+
taglib_element(el)
|
|
142
|
+
# return nothing - the import has no presence in the erb source
|
|
143
|
+
tag_newlines(el)
|
|
144
|
+
|
|
145
|
+
when "set_theme"
|
|
146
|
+
require_attribute(el, "name", /^#{DRYML_NAME}$/)
|
|
147
|
+
@builder.add_build_instruction(:type => :set_theme, :name => el.attributes['name'])
|
|
148
|
+
|
|
149
|
+
# return nothing - set_theme has no presence in the erb source
|
|
150
|
+
tag_newlines(el)
|
|
151
|
+
|
|
152
|
+
when "def"
|
|
153
|
+
def_element(el)
|
|
154
|
+
|
|
155
|
+
when "tagbody"
|
|
156
|
+
tagbody_element(el)
|
|
157
|
+
|
|
158
|
+
else
|
|
159
|
+
if el.name.not_in?(Hobo.static_tags) or
|
|
160
|
+
el.attributes['replace_option'] or el.attributes['content_option']
|
|
161
|
+
tag_call(el)
|
|
162
|
+
else
|
|
163
|
+
html_element_to_erb(el)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def taglib_element(el)
|
|
170
|
+
require_toplevel(el)
|
|
171
|
+
require_attribute(el, "as", /^#{DRYML_NAME}$/, true)
|
|
172
|
+
if el.attributes["src"]
|
|
173
|
+
@builder.add_build_instruction(:type => :taglib,
|
|
174
|
+
:name => el.attributes["src"],
|
|
175
|
+
:as => el.attributes["as"])
|
|
176
|
+
elsif el.attributes["module"]
|
|
177
|
+
@builder.add_build_instruction(:type => :module,
|
|
178
|
+
:name => el.attributes["module"],
|
|
179
|
+
:as => el.attributes["as"])
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def import_module(mod, as=nil)
|
|
185
|
+
@builder.import_module(mod, as)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def def_element(el)
|
|
190
|
+
require_toplevel(el)
|
|
191
|
+
require_attribute(el, "tag", /^#{DRYML_NAME}$/)
|
|
192
|
+
require_attribute(el, "attrs", /^\s*#{DRYML_NAME}(\s*,\s*#{DRYML_NAME})*\s*$/, true)
|
|
193
|
+
require_attribute(el, "alias_of", /^#{DRYML_NAME}$/, true)
|
|
194
|
+
require_attribute(el, "alias_current", /^#{DRYML_NAME}$/, true)
|
|
195
|
+
require_attribute(el, "if", /^#{DRYML_NAME}$/, true)
|
|
196
|
+
|
|
197
|
+
name = el.attributes["tag"]
|
|
198
|
+
|
|
199
|
+
alias_of = el.attributes['alias_of']
|
|
200
|
+
alias_current = el.attributes['alias_current']
|
|
201
|
+
|
|
202
|
+
dryml_exception("def cannot have both alias_of and alias_current", el) if alias_of && alias_current
|
|
203
|
+
dryml_exception("def with alias_of must be empty", el) if alias_of and el.size > 0
|
|
204
|
+
|
|
205
|
+
if alias_of || alias_current
|
|
206
|
+
old_name = alias_current ? name : alias_of
|
|
207
|
+
new_name = alias_current ? alias_current : name
|
|
208
|
+
|
|
209
|
+
@builder.add_build_instruction(:type => :alias_method, :new => new_name.to_sym, :old => old_name.to_sym)
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if alias_of
|
|
213
|
+
"<% #{tag_newlines(el)} %>"
|
|
214
|
+
else
|
|
215
|
+
attrspec = el.attributes["attrs"]
|
|
216
|
+
attr_names = attrspec ? attrspec.split(/\s*,\s*/) : []
|
|
217
|
+
|
|
218
|
+
invalids = attr_names & %w{obj attr this}
|
|
219
|
+
dryml_exception("invalid attrs in def: #{invalids * ', '}", el) unless invalids.empty?
|
|
220
|
+
|
|
221
|
+
create_tag_method(el, name.to_sym, attr_names.omap{to_sym})
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def create_tag_method(el, name, attrs)
|
|
227
|
+
name = Hobo::Dryml.unreserve(name)
|
|
228
|
+
|
|
229
|
+
inner_tags = find_inner_tags(el)
|
|
230
|
+
|
|
231
|
+
# A statement to assign values to local variables named after the tag's attrs.
|
|
232
|
+
setup_locals = ( (attrs.map{|a| "#{Hobo::Dryml.unreserve(a)}, "} + ['options, inner_tag_options']).join +
|
|
233
|
+
" = _tag_locals(__options__, #{attrs.inspect}, #{inner_tags.inspect})" )
|
|
234
|
+
|
|
235
|
+
start = "_tag_context(__options__, __block__) do |tagbody| #{setup_locals}"
|
|
236
|
+
|
|
237
|
+
pred = el.attributes["if"]
|
|
238
|
+
pred = pred[1..-1] if pred && pred.starts_with?('#')
|
|
239
|
+
|
|
240
|
+
def_line = if pred
|
|
241
|
+
"defp :#{name}, (proc {|options| #{pred}}) do |__options__, __block__|"
|
|
242
|
+
elsif @environment.predicate_method?(name)
|
|
243
|
+
# be sure not to overwrite the predicate dispatch method
|
|
244
|
+
"defp :#{name} do |__options__, __block__|"
|
|
245
|
+
else
|
|
246
|
+
"def #{name}(__options__={}, &__block__)"
|
|
247
|
+
end
|
|
248
|
+
|
|
249
|
+
method_src = ( "<% #{def_line}; #{start} " +
|
|
250
|
+
# reproduce any line breaks in the start-tag so that line numbers are preserved
|
|
251
|
+
tag_newlines(el) + "%>" +
|
|
252
|
+
children_to_erb(el) +
|
|
253
|
+
"<% @output; end; end %>" )
|
|
254
|
+
|
|
255
|
+
logger.debug(restore_erb_scriptlets(method_src)) if el.attributes["hobo_debug_source"]
|
|
256
|
+
|
|
257
|
+
@builder.add_build_instruction(:type => :def, :src => erb_process(method_src), :line_num => element_line_num(el))
|
|
258
|
+
|
|
259
|
+
# keep line numbers matching up
|
|
260
|
+
"<% #{"\n" * method_src.count("\n")} %>"
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def find_inner_tags(el)
|
|
265
|
+
el.map do |e|
|
|
266
|
+
if e.is_a?(REXML::Element)
|
|
267
|
+
name = e.attributes["content_option"] || e.attributes["replace_option"]
|
|
268
|
+
[(name if name && !is_code_attribute?(name))] + find_inner_tags(e)
|
|
269
|
+
else
|
|
270
|
+
[]
|
|
271
|
+
end
|
|
272
|
+
end.flatten.compact
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
def tagbody_element(el)
|
|
277
|
+
dryml_exception("tagbody can only appear inside a <def>", el) unless
|
|
278
|
+
find_ancestor(el) {|e| e.name == 'def'}
|
|
279
|
+
dryml_exception("tagbody cannot appear inside a part", el) if
|
|
280
|
+
find_ancestor(el) {|e| e.attributes['part_id']}
|
|
281
|
+
tagbody_call(el)
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def tagbody_call(el)
|
|
286
|
+
options = []
|
|
287
|
+
obj = el.attributes['obj']
|
|
288
|
+
attr = el.attributes['attr']
|
|
289
|
+
options << ":obj => #{attribute_to_ruby(obj)}" if obj
|
|
290
|
+
options << ":attr => #{attribute_to_ruby(attr)}" if attr
|
|
291
|
+
else_ = attribute_to_ruby(el.attributes['else'])
|
|
292
|
+
"<%= tagbody ? tagbody.call({ #{options * ', '} }) : #{else_} %>"
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def part_element(el, content)
|
|
297
|
+
require_attribute(el, "part_id", /^#{DRYML_NAME}$/)
|
|
298
|
+
part_name = el.attributes['part_id']
|
|
299
|
+
dom_id = el.attributes['id'] || part_name
|
|
300
|
+
|
|
301
|
+
dryml_exception("dupplicate part name: #{part_name}", el) if
|
|
302
|
+
(part_name + "_part").in?(@environment.instance_methods)
|
|
303
|
+
|
|
304
|
+
part_src = "<% def #{part_name}_part #{tag_newlines(el)}; new_context do %>" +
|
|
305
|
+
content +
|
|
306
|
+
"<% end; end %>"
|
|
307
|
+
create_part(part_src, element_line_num(el))
|
|
308
|
+
|
|
309
|
+
newlines = "\n" * part_src.count("\n")
|
|
310
|
+
res = "<%= call_part(#{attribute_to_ruby(dom_id)}, :#{part_name}) #{newlines} %>"
|
|
311
|
+
res
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def create_part(erb_src, line_num)
|
|
316
|
+
# Add a method to the part module for this template
|
|
317
|
+
|
|
318
|
+
@builder.add_build_instruction(:type => :part, :src => erb_process(erb_src), :line_num => line_num)
|
|
319
|
+
end
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
def tag_call(el)
|
|
323
|
+
require_attribute(el, "content_option", /#{DRYML_NAME}/, true)
|
|
324
|
+
require_attribute(el, "replace_option", /#{DRYML_NAME}/, true)
|
|
325
|
+
|
|
326
|
+
# find out if it's empty before removing any <:param_tags>
|
|
327
|
+
empty_el = el.size == 0
|
|
328
|
+
|
|
329
|
+
# gather <:param_tags>, and remove them from the dom
|
|
330
|
+
compiled_param_tags = compile_parameter_tags(el)
|
|
331
|
+
|
|
332
|
+
name = Hobo::Dryml.unreserve(el.name)
|
|
333
|
+
options = tag_options(el, compiled_param_tags)
|
|
334
|
+
newlines = tag_newlines(el)
|
|
335
|
+
replace_option = el.attributes["replace_option"]
|
|
336
|
+
content_option = el.attributes["content_option"]
|
|
337
|
+
dryml_exception("both replace_option and content_option given") if replace_option && content_option
|
|
338
|
+
call = if replace_option
|
|
339
|
+
replace_option = attribute_to_ruby(replace_option)
|
|
340
|
+
"call_replaceable_tag(:#{name}, #{options}, inner_tag_options[#{replace_option}.to_sym])"
|
|
341
|
+
elsif content_option
|
|
342
|
+
content_option = attribute_to_ruby(content_option)
|
|
343
|
+
"call_replaceable_content_tag(:#{name}, #{options}, inner_tag_options[#{content_option}.to_sym])"
|
|
344
|
+
else
|
|
345
|
+
"#{name}(#{options})"
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
part_id = el.attributes['part_id']
|
|
349
|
+
if empty_el
|
|
350
|
+
if part_id
|
|
351
|
+
"<span id='#{part_id}'>" + part_element(el, "<%= #{call} %>") + "</span>"
|
|
352
|
+
else
|
|
353
|
+
"<%= #{call} #{newlines}%>"
|
|
354
|
+
end
|
|
355
|
+
else
|
|
356
|
+
children = children_to_erb(el)
|
|
357
|
+
if part_id
|
|
358
|
+
id = el.attributes['id'] || part_id
|
|
359
|
+
"<span id='<%= #{attribute_to_ruby(id)} %>'>" +
|
|
360
|
+
part_element(el, "<% _erbout.concat(#{call} do %>#{children}<% end) %>") +
|
|
361
|
+
"</span>"
|
|
362
|
+
else
|
|
363
|
+
"<% _erbout.concat(#{call} do #{newlines}%>#{children}<% end) %>"
|
|
364
|
+
end
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
def compile_parameter_tags(el)
|
|
370
|
+
# The implementation of parameter tags is greatly complicated
|
|
371
|
+
# by the need to maintain line-number parity between the dryml source
|
|
372
|
+
# and generated erb source
|
|
373
|
+
|
|
374
|
+
last = el.children.reverse.ofind{is_a?(REXML::Element) && name.starts_with?(":")}
|
|
375
|
+
return "" if last.nil?
|
|
376
|
+
param_section = el.children[0..el.index(last)]
|
|
377
|
+
|
|
378
|
+
dryml_exception("invalid content before parameter tag", el) unless param_section.all? do |e|
|
|
379
|
+
(e.is_a?(REXML::Element) && e.name.starts_with?(":")) ||
|
|
380
|
+
(e.is_a?(REXML::Text) && e.to_s.blank?) ||
|
|
381
|
+
e.is_a?(REXML::Comment)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
last = param_section.last
|
|
385
|
+
compiled = param_section.map do |e|
|
|
386
|
+
# REXML Bug - don't use el.remove (also removes other children that are == )
|
|
387
|
+
el.delete_at(el.index(e))
|
|
388
|
+
case e
|
|
389
|
+
when REXML::Element
|
|
390
|
+
array_index = begin
|
|
391
|
+
# If there are other param-tags with this
|
|
392
|
+
# name, the index of this one, else nil
|
|
393
|
+
same_name_params = param_section.select {|x| x.is_a?(REXML::Element) and x.name == e.name}
|
|
394
|
+
same_name_params.length > 1 && same_name_params.index(e)
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
param_name = attr_name_to_option_key(e.name[1..-1], array_index)
|
|
398
|
+
|
|
399
|
+
dryml_exception("duplicate attribute/parameter-tag #{param}", el) if
|
|
400
|
+
param_name.in?(el.attributes.keys)
|
|
401
|
+
|
|
402
|
+
param_value =
|
|
403
|
+
if e.has_attributes?
|
|
404
|
+
pairs = e.attributes.map do |n,v|
|
|
405
|
+
"#{attr_name_to_option_key(n)} => " + "#{attribute_to_ruby(v)}"
|
|
406
|
+
end
|
|
407
|
+
# If there is content to, that goes in the hash under the key :content
|
|
408
|
+
if e.size > 0
|
|
409
|
+
pairs << "#{tag_newlines(el)}:content => (capture do %>#{children_to_erb(e)}<%; end)"
|
|
410
|
+
end
|
|
411
|
+
"{" + pairs.join(",") + "}"
|
|
412
|
+
elsif e.size > 0
|
|
413
|
+
"#{tag_newlines(el)}(capture do %>#{children_to_erb(e)}<%; end)"
|
|
414
|
+
else
|
|
415
|
+
"''"
|
|
416
|
+
end
|
|
417
|
+
pair = "#{param_name} => #{param_value}"
|
|
418
|
+
pair << ", " unless e == last
|
|
419
|
+
pair
|
|
420
|
+
|
|
421
|
+
when REXML::Text
|
|
422
|
+
e.to_s
|
|
423
|
+
|
|
424
|
+
when REXML::Comment
|
|
425
|
+
REXML::Comment::START + node.to_s + REXML::Comment::STOP
|
|
426
|
+
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
compiled.join
|
|
431
|
+
end
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def attr_name_to_option_key(name, array_index=nil)
|
|
435
|
+
parts = name.split(".").map { |p| ":#{p}" }
|
|
436
|
+
parts << array_index if array_index
|
|
437
|
+
if parts.length == 1
|
|
438
|
+
parts.first
|
|
439
|
+
else
|
|
440
|
+
"[" + parts.join(', ') + "]"
|
|
441
|
+
end
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
|
|
445
|
+
def tag_options(el, param_tags_compiled)
|
|
446
|
+
attributes = el.attributes
|
|
447
|
+
|
|
448
|
+
options = attributes.map do |n,v|
|
|
449
|
+
param_name = attr_name_to_option_key(n)
|
|
450
|
+
param_value = attribute_to_ruby(v)
|
|
451
|
+
"#{param_name} => #{param_value}"
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
options << param_tags_compiled unless param_tags_compiled.blank?
|
|
455
|
+
all = options.join(', ')
|
|
456
|
+
|
|
457
|
+
xattrs = attributes['xattrs']
|
|
458
|
+
if xattrs
|
|
459
|
+
extra_options = if xattrs.blank?
|
|
460
|
+
"options"
|
|
461
|
+
elsif xattrs.starts_with?("#")
|
|
462
|
+
xattrs[1..-1]
|
|
463
|
+
else
|
|
464
|
+
dryml_exception("invalid xattrs", el)
|
|
465
|
+
end
|
|
466
|
+
"{#{all}}.merge((#{extra_options}) || {})"
|
|
467
|
+
else
|
|
468
|
+
"{#{all}}"
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def html_element_to_erb(el)
|
|
474
|
+
start_tag_src = el.instance_variable_get("@start_tag_source").
|
|
475
|
+
gsub(REXML::CData::START, "").gsub(REXML::CData::STOP, "")
|
|
476
|
+
|
|
477
|
+
xattrs = el.attributes["xattrs"]
|
|
478
|
+
if xattrs
|
|
479
|
+
attr_args = if xattrs.starts_with?('#')
|
|
480
|
+
xattrs[1..-1]
|
|
481
|
+
elsif xattrs.blank?
|
|
482
|
+
"options"
|
|
483
|
+
else
|
|
484
|
+
dryml_exception("invalid xattrs", el)
|
|
485
|
+
end
|
|
486
|
+
class_attr = el.attributes["class"]
|
|
487
|
+
if class_attr
|
|
488
|
+
raise HoboError.new("invalid class attribute with xattrs: '#{class_attr}'") if
|
|
489
|
+
class_attr =~ /'|\[!\[HOBO-ERB/
|
|
490
|
+
|
|
491
|
+
attr_args.concat(", '#{class_attr}'")
|
|
492
|
+
start_tag_src.sub!(/\s*class\s*=\s*('[^']*?'|"[^"]*?")/, "")
|
|
493
|
+
end
|
|
494
|
+
start_tag_src.sub!(/\s*xattrs\s*=\s*('[^']*?'|"[^"]*?")/, " <%= xattrs(#{attr_args}) %>")
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
if start_tag_src.ends_with?("/>")
|
|
498
|
+
start_tag_src
|
|
499
|
+
else
|
|
500
|
+
if el.attributes['part_id']
|
|
501
|
+
body = part_element(el, children_to_erb(el))
|
|
502
|
+
if el.attributes["id"]
|
|
503
|
+
# remove part_id, and eval the id attribute with an erb scriptlet
|
|
504
|
+
start_tag_src.sub!(/\s*part_id\s*=\s*('[^']*?'|"[^"]*?")/, "")
|
|
505
|
+
id_expr = attribute_to_ruby(el.attributes['id'])
|
|
506
|
+
start_tag_src.sub!(/id\s*=\s*('[^']*?'|"[^"]*?")/, "id='<%= #{id_expr} %>'")
|
|
507
|
+
else
|
|
508
|
+
# rename part_id to id
|
|
509
|
+
start_tag_src.sub!(/part_id\s*=\s*('[^']*?'|"[^"]*?")/, "id=\\1")
|
|
510
|
+
end
|
|
511
|
+
dryml_exception("multiple part ids", el) if start_tag_src.index("part_id=")
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
start_tag_src + body + "</#{el.name}>"
|
|
515
|
+
else
|
|
516
|
+
start_tag_src + children_to_erb(el) + "</#{el.name}>"
|
|
517
|
+
end
|
|
518
|
+
end
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
def attribute_to_ruby(attr)
|
|
522
|
+
dryml_exception("erb scriptlet in attribute of defined tag") if attr && attr.index("[![HOBO-ERB")
|
|
523
|
+
if attr.nil?
|
|
524
|
+
"nil"
|
|
525
|
+
elsif is_code_attribute?(attr)
|
|
526
|
+
"(#{attr[1..-1]})"
|
|
527
|
+
else
|
|
528
|
+
str = if not attr =~ /"/
|
|
529
|
+
'"' + attr + '"'
|
|
530
|
+
elsif not attr =~ /'/
|
|
531
|
+
"'#{attr}'"
|
|
532
|
+
else
|
|
533
|
+
dryml_exception("invalid quote(s) in attribute value")
|
|
534
|
+
end
|
|
535
|
+
attr.starts_with?("++") ? "attr_extension(#{str})" : str
|
|
536
|
+
end
|
|
537
|
+
end
|
|
538
|
+
|
|
539
|
+
def find_ancestor(el)
|
|
540
|
+
e = el.parent
|
|
541
|
+
until e.is_a? REXML::Document
|
|
542
|
+
return e if yield(e)
|
|
543
|
+
e = e.parent
|
|
544
|
+
end
|
|
545
|
+
return nil
|
|
546
|
+
end
|
|
547
|
+
|
|
548
|
+
def require_toplevel(el)
|
|
549
|
+
dryml_exception("<#{el.name}> can only be at the top level", el) if el.parent != @doc.root
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
def require_attribute(el, name, rx=nil, optional=false)
|
|
553
|
+
val = el.attributes[name]
|
|
554
|
+
if val
|
|
555
|
+
dryml_exception("invalid #{name}=\"#{val}\" attribute on <#{el.name}>", el) unless val =~ rx
|
|
556
|
+
else
|
|
557
|
+
dryml_exception("missing #{name} attribute on <#{el.name}>", el) unless optional
|
|
558
|
+
end
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def dryml_exception(message, el=nil)
|
|
562
|
+
el ||= @last_element
|
|
563
|
+
raise DrymlException.new(message + " -- at #{template_path}:#{element_line_num(el)}")
|
|
564
|
+
end
|
|
565
|
+
|
|
566
|
+
def element_line_num(el)
|
|
567
|
+
offset = el.instance_variable_get("@source_offset")
|
|
568
|
+
line_no = @xmlsrc[0..offset].count("\n") + 1
|
|
569
|
+
end
|
|
570
|
+
|
|
571
|
+
def tag_newlines(el)
|
|
572
|
+
src = el.instance_variable_get("@start_tag_source")
|
|
573
|
+
"\n" * src.count("\n")
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def is_code_attribute?(attr_value)
|
|
577
|
+
attr_value.starts_with?("#")
|
|
578
|
+
end
|
|
579
|
+
|
|
580
|
+
def logger
|
|
581
|
+
ActionController::Base.logger rescue nil
|
|
582
|
+
end
|
|
583
|
+
|
|
584
|
+
end
|
|
585
|
+
|
|
586
|
+
end
|